Merge ab/7061308 into stage.

Bug: 180401296
Merged-In: I1512f0aee1f960e809a42448b9aafed0de3dbc2a
Change-Id: I12dddc8066b2477f7dce5778ab3dd96d6e43afc4
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 6740bb5..2116e21 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,6 +4,7 @@
 [Builtin Hooks]
 bpfmt = true
 clang_format = true
+aidl_format = true
 
 [Hook Scripts]
 aosp_hook_confirmationui = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} confirmationui
diff --git a/audio/5.0/config/api/current.txt b/audio/5.0/config/api/current.txt
index a1d8e1e..dbb5d3b 100644
--- a/audio/5.0/config/api/current.txt
+++ b/audio/5.0/config/api/current.txt
@@ -133,6 +133,7 @@
     enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_LDAC;
     enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_LHDC;
     enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_LHDC_LL;
+    enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_MAT;
     enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_MAT_1_0;
     enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_MAT_2_0;
     enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_MAT_2_1;
@@ -198,7 +199,7 @@
   public static class DevicePorts.DevicePort {
     ctor public DevicePorts.DevicePort();
     method public String getAddress();
-    method public java.util.List<audio.policy.configuration.V5_0.AudioFormat> getEncodedFormats();
+    method public java.util.List<java.lang.String> getEncodedFormats();
     method public audio.policy.configuration.V5_0.Gains getGains();
     method public java.util.List<audio.policy.configuration.V5_0.Profile> getProfile();
     method public audio.policy.configuration.V5_0.Role getRole();
@@ -206,7 +207,7 @@
     method public String getType();
     method public boolean get_default();
     method public void setAddress(String);
-    method public void setEncodedFormats(java.util.List<audio.policy.configuration.V5_0.AudioFormat>);
+    method public void setEncodedFormats(java.util.List<java.lang.String>);
     method public void setGains(audio.policy.configuration.V5_0.Gains);
     method public void setRole(audio.policy.configuration.V5_0.Role);
     method public void setTagName(String);
@@ -379,10 +380,10 @@
 
   public static class SurroundFormats.Format {
     ctor public SurroundFormats.Format();
-    method public audio.policy.configuration.V5_0.AudioFormat getName();
-    method public java.util.List<audio.policy.configuration.V5_0.AudioFormat> getSubformats();
-    method public void setName(audio.policy.configuration.V5_0.AudioFormat);
-    method public void setSubformats(java.util.List<audio.policy.configuration.V5_0.AudioFormat>);
+    method public String getName();
+    method public java.util.List<java.lang.String> getSubformats();
+    method public void setName(String);
+    method public void setSubformats(java.util.List<java.lang.String>);
   }
 
   public class SurroundSound {
diff --git a/audio/5.0/config/audio_policy_configuration.xsd b/audio/5.0/config/audio_policy_configuration.xsd
index 284d2e2..f92136c 100644
--- a/audio/5.0/config/audio_policy_configuration.xsd
+++ b/audio/5.0/config/audio_policy_configuration.xsd
@@ -361,6 +361,7 @@
             <xs:enumeration value="AUDIO_FORMAT_AC4"/>
             <xs:enumeration value="AUDIO_FORMAT_LDAC"/>
             <xs:enumeration value="AUDIO_FORMAT_E_AC3_JOC"/>
+            <xs:enumeration value="AUDIO_FORMAT_MAT"/>
             <xs:enumeration value="AUDIO_FORMAT_MAT_1_0"/>
             <xs:enumeration value="AUDIO_FORMAT_MAT_2_0"/>
             <xs:enumeration value="AUDIO_FORMAT_MAT_2_1"/>
@@ -610,13 +611,13 @@
         </xs:sequence>
     </xs:complexType>
     <xs:simpleType name="audioFormatsList">
-        <xs:list itemType="audioFormat" />
+        <xs:list itemType="extendableAudioFormat" />
     </xs:simpleType>
     <xs:complexType name="surroundFormats">
         <xs:sequence>
             <xs:element name="format" minOccurs="0" maxOccurs="unbounded">
                 <xs:complexType>
-                    <xs:attribute name="name" type="audioFormat" use="required"/>
+                    <xs:attribute name="name" type="extendableAudioFormat" use="required"/>
                     <xs:attribute name="subformats" type="audioFormatsList" />
                 </xs:complexType>
             </xs:element>
diff --git a/audio/6.0/config/api/current.txt b/audio/6.0/config/api/current.txt
index 6b49e5e..01db90e 100644
--- a/audio/6.0/config/api/current.txt
+++ b/audio/6.0/config/api/current.txt
@@ -133,6 +133,7 @@
     enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LDAC;
     enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LHDC;
     enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LHDC_LL;
+    enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT;
     enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_1_0;
     enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_2_0;
     enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_2_1;
@@ -198,7 +199,7 @@
   public static class DevicePorts.DevicePort {
     ctor public DevicePorts.DevicePort();
     method public String getAddress();
-    method public java.util.List<audio.policy.configuration.V6_0.AudioFormat> getEncodedFormats();
+    method public java.util.List<java.lang.String> getEncodedFormats();
     method public audio.policy.configuration.V6_0.Gains getGains();
     method public java.util.List<audio.policy.configuration.V6_0.Profile> getProfile();
     method public audio.policy.configuration.V6_0.Role getRole();
@@ -206,7 +207,7 @@
     method public String getType();
     method public boolean get_default();
     method public void setAddress(String);
-    method public void setEncodedFormats(java.util.List<audio.policy.configuration.V6_0.AudioFormat>);
+    method public void setEncodedFormats(java.util.List<java.lang.String>);
     method public void setGains(audio.policy.configuration.V6_0.Gains);
     method public void setRole(audio.policy.configuration.V6_0.Role);
     method public void setTagName(String);
@@ -390,10 +391,10 @@
 
   public static class SurroundFormats.Format {
     ctor public SurroundFormats.Format();
-    method public audio.policy.configuration.V6_0.AudioFormat getName();
-    method public java.util.List<audio.policy.configuration.V6_0.AudioFormat> getSubformats();
-    method public void setName(audio.policy.configuration.V6_0.AudioFormat);
-    method public void setSubformats(java.util.List<audio.policy.configuration.V6_0.AudioFormat>);
+    method public String getName();
+    method public java.util.List<java.lang.String> getSubformats();
+    method public void setName(String);
+    method public void setSubformats(java.util.List<java.lang.String>);
   }
 
   public class SurroundSound {
diff --git a/audio/6.0/config/audio_policy_configuration.xsd b/audio/6.0/config/audio_policy_configuration.xsd
index 341c6b3..c2b8c5d 100644
--- a/audio/6.0/config/audio_policy_configuration.xsd
+++ b/audio/6.0/config/audio_policy_configuration.xsd
@@ -363,6 +363,7 @@
             <xs:enumeration value="AUDIO_FORMAT_AC4"/>
             <xs:enumeration value="AUDIO_FORMAT_LDAC"/>
             <xs:enumeration value="AUDIO_FORMAT_E_AC3_JOC"/>
+            <xs:enumeration value="AUDIO_FORMAT_MAT"/>
             <xs:enumeration value="AUDIO_FORMAT_MAT_1_0"/>
             <xs:enumeration value="AUDIO_FORMAT_MAT_2_0"/>
             <xs:enumeration value="AUDIO_FORMAT_MAT_2_1"/>
@@ -613,13 +614,13 @@
         </xs:sequence>
     </xs:complexType>
     <xs:simpleType name="audioFormatsList">
-        <xs:list itemType="audioFormat" />
+        <xs:list itemType="extendableAudioFormat" />
     </xs:simpleType>
     <xs:complexType name="surroundFormats">
         <xs:sequence>
             <xs:element name="format" minOccurs="0" maxOccurs="unbounded">
                 <xs:complexType>
-                    <xs:attribute name="name" type="audioFormat" use="required"/>
+                    <xs:attribute name="name" type="extendableAudioFormat" use="required"/>
                     <xs:attribute name="subformats" type="audioFormatsList" />
                 </xs:complexType>
             </xs:element>
diff --git a/audio/7.0/IDevice.hal b/audio/7.0/IDevice.hal
index e30e545..e423f29 100644
--- a/audio/7.0/IDevice.hal
+++ b/audio/7.0/IDevice.hal
@@ -103,6 +103,11 @@
      * If the stream can not be opened with the proposed audio config,
      * HAL must provide suggested values for the audio config.
      *
+     * Note: INVALID_ARGUMENTS is returned both in the case when the
+     * HAL can not use the provided config and in the case when
+     * the value of any argument is invalid. In the latter case the
+     * HAL must provide a default initialized suggested config.
+     *
      * @param ioHandle handle assigned by AudioFlinger.
      * @param device device type and (if needed) address.
      * @param config stream configuration.
@@ -111,7 +116,8 @@
                              May be used by implementations to configure hardware effects.
      * @return retval operation completion status.
      * @return outStream created output stream.
-     * @return suggestedConfig in case of invalid parameters, suggested config.
+     * @return suggestedConfig in the case of rejection of the proposed config,
+     *                         a config suggested by the HAL.
      */
     openOutputStream(
             AudioIoHandle ioHandle,
@@ -128,6 +134,11 @@
      * If the stream can not be opened with the proposed audio config,
      * HAL must provide suggested values for the audio config.
      *
+     * Note: INVALID_ARGUMENTS is returned both in the case when the
+     * HAL can not use the provided config and in the case when
+     * the value of any argument is invalid. In the latter case the
+     * HAL must provide a default initialized suggested config.
+     *
      * @param ioHandle handle assigned by AudioFlinger.
      * @param device device type and (if needed) address.
      * @param config stream configuration.
@@ -136,7 +147,8 @@
      *                     May be used by implementations to configure processing effects.
      * @return retval operation completion status.
      * @return inStream in case of success, created input stream.
-     * @return suggestedConfig in case of invalid parameters, suggested config.
+     * @return suggestedConfig in the case of rejection of the proposed config,
+     *                         a config suggested by the HAL.
      */
     openInputStream(
             AudioIoHandle ioHandle,
@@ -162,6 +174,9 @@
      * Creates an audio patch between several source and sink ports.  The handle
      * is allocated by the HAL and must be unique for this audio HAL module.
      *
+     * Optional method. HAL must support it if 'supportsAudioPatches' returns
+     * 'true'.
+     *
      * @param sources patch sources.
      * @param sinks patch sinks.
      * @return retval operation completion status.
@@ -177,6 +192,9 @@
      * as the HAL module can figure out a way of switching the route without
      * causing audio disruption.
      *
+     * Optional method. HAL must support it if 'supportsAudioPatches' returns
+     * 'true'.
+     *
      * @param previousPatch handle of the previous patch to update.
      * @param sources new patch sources.
      * @param sinks new patch sinks.
@@ -192,6 +210,9 @@
     /**
      * Release an audio patch.
      *
+     * Optional method. HAL must support it if 'supportsAudioPatches' returns
+     * 'true'.
+     *
      * @param patch patch handle.
      * @return retval operation completion status.
      */
diff --git a/audio/7.0/IStream.hal b/audio/7.0/IStream.hal
index ab9aa7d..393e38f 100644
--- a/audio/7.0/IStream.hal
+++ b/audio/7.0/IStream.hal
@@ -73,14 +73,14 @@
 
     /**
      * Sets stream parameters. Only sets parameters that are specified.
-     * See the description of AudioConfigBase for the details.
      *
      * Optional method. If implemented, only called on a stopped stream.
      *
      * @param config basic stream configuration.
      * @return retval operation completion status.
      */
-    setAudioProperties(AudioConfigBase config) generates (Result retval);
+    setAudioProperties(AudioConfigBaseOptional config)
+            generates (Result retval);
 
     /**
      * Applies audio effect to the stream.
diff --git a/audio/7.0/IStreamIn.hal b/audio/7.0/IStreamIn.hal
index 0a3f24b..bf9ae52 100644
--- a/audio/7.0/IStreamIn.hal
+++ b/audio/7.0/IStreamIn.hal
@@ -41,6 +41,18 @@
     setGain(float gain) generates (Result retval);
 
     /**
+     * Called when the metadata of the stream's sink has been changed.
+     * Optional method
+     *
+     * @param sinkMetadata Description of the audio that is suggested by the clients.
+     * @return retval operation completion status.
+     *        If any of the metadata fields contains an invalid value,
+     *        returns INVALID_ARGUMENTS.
+     *        If method isn't supported by the HAL returns NOT_SUPPORTED.
+     */
+    updateSinkMetadata(SinkMetadata sinkMetadata) generates (Result retval);
+
+    /**
      * Commands that can be executed on the driver reader thread.
      */
     enum ReadCommand : int32_t {
@@ -82,12 +94,6 @@
     };
 
     /**
-     * Called when the metadata of the stream's sink has been changed.
-     * @param sinkMetadata Description of the audio that is suggested by the clients.
-     */
-    updateSinkMetadata(SinkMetadata sinkMetadata);
-
-    /**
      * Set up required transports for receiving audio buffers from the driver.
      *
      * The transport consists of three message queues:
diff --git a/audio/7.0/IStreamOut.hal b/audio/7.0/IStreamOut.hal
index 38d750f..4daab26 100644
--- a/audio/7.0/IStreamOut.hal
+++ b/audio/7.0/IStreamOut.hal
@@ -45,6 +45,18 @@
     setVolume(float left, float right) generates (Result retval);
 
     /**
+     * Called when the metadata of the stream's source has been changed.
+     * Optional method
+     *
+     * @param sourceMetadata Description of the audio that is played by the clients.
+     * @return retval operation completion status.
+     *        If any of the metadata fields contains an invalid value,
+     *        returns INVALID_ARGUMENTS.
+     *        If method isn't supported by the HAL returns NOT_SUPPORTED.
+     */
+    updateSourceMetadata(SourceMetadata sourceMetadata) generates (Result retval);
+
+    /**
      * Commands that can be executed on the driver writer thread.
      */
     enum WriteCommand : int32_t {
@@ -77,12 +89,6 @@
     };
 
     /**
-     * Called when the metadata of the stream's source has been changed.
-     * @param sourceMetadata Description of the audio that is played by the clients.
-     */
-    updateSourceMetadata(SourceMetadata sourceMetadata);
-
-    /**
      * Set up required transports for passing audio buffers to the driver.
      *
      * The transport consists of three message queues:
diff --git a/audio/7.0/config/api/current.txt b/audio/7.0/config/api/current.txt
index 1da8b09..48093c5 100644
--- a/audio/7.0/config/api/current.txt
+++ b/audio/7.0/config/api/current.txt
@@ -198,6 +198,7 @@
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_APTX_HD;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_APTX_TWSP;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_CELT;
+    enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_DEFAULT;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_DOLBY_TRUEHD;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_DSD;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_DTS;
@@ -211,6 +212,7 @@
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_FLAC;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_HE_AAC_V1;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_HE_AAC_V2;
+    enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_IEC60958;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_IEC61937;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_LC3;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_LDAC;
@@ -222,6 +224,10 @@
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MAT_2_1;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MP2;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MP3;
+    enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MPEGH_BL_L3;
+    enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MPEGH_BL_L4;
+    enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MPEGH_LC_L3;
+    enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MPEGH_LC_L4;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_OPUS;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_PCM_16_BIT;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_PCM_24_BIT_PACKED;
@@ -250,7 +256,6 @@
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_HW_AV_SYNC;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_HW_HOTWORD;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_MMAP_NOIRQ;
-    enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_NONE;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_RAW;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_SYNC;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_VOIP_TX;
@@ -263,7 +268,6 @@
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_IEC958_NONAUDIO;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_INCALL_MUSIC;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_MMAP_NOIRQ;
-    enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_NONE;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_NON_BLOCKING;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_PRIMARY;
     enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_RAW;
@@ -541,7 +545,7 @@
 
   public enum Version {
     method @NonNull public String getRawName();
-    enum_constant public static final android.audio.policy.configuration.V7_0.Version _1_0;
+    enum_constant public static final android.audio.policy.configuration.V7_0.Version _7_0;
   }
 
   public class Volume {
diff --git a/audio/7.0/config/audio_policy_configuration.xsd b/audio/7.0/config/audio_policy_configuration.xsd
index 56b3a27..531572b 100644
--- a/audio/7.0/config/audio_policy_configuration.xsd
+++ b/audio/7.0/config/audio_policy_configuration.xsd
@@ -20,7 +20,7 @@
     <!-- List the config versions supported by audio policy. -->
     <xs:simpleType name="version">
         <xs:restriction base="xs:decimal">
-            <xs:enumeration value="1.0"/>
+            <xs:enumeration value="7.0"/>
         </xs:restriction>
     </xs:simpleType>
     <xs:simpleType name="halVersion">
@@ -159,13 +159,9 @@
         <xs:annotation>
             <xs:documentation xml:lang="en">
               The flags indicate suggested stream attributes supported by the profile.
-              Use of AUDIO_{INPUT|OUTPUT}_FLAG_NONE in the XML file isn't required
-              as empty flag lists are allowed. However these constants are useful for
-              representing an empty enum value.
             </xs:documentation>
         </xs:annotation>
         <xs:restriction base="xs:string">
-            <xs:enumeration value="AUDIO_OUTPUT_FLAG_NONE" />
             <xs:enumeration value="AUDIO_OUTPUT_FLAG_DIRECT" />
             <xs:enumeration value="AUDIO_OUTPUT_FLAG_PRIMARY" />
             <xs:enumeration value="AUDIO_OUTPUT_FLAG_FAST" />
@@ -181,7 +177,6 @@
             <xs:enumeration value="AUDIO_OUTPUT_FLAG_MMAP_NOIRQ" />
             <xs:enumeration value="AUDIO_OUTPUT_FLAG_VOIP_RX" />
             <xs:enumeration value="AUDIO_OUTPUT_FLAG_INCALL_MUSIC" />
-            <xs:enumeration value="AUDIO_INPUT_FLAG_NONE" />
             <xs:enumeration value="AUDIO_INPUT_FLAG_FAST" />
             <xs:enumeration value="AUDIO_INPUT_FLAG_HW_HOTWORD" />
             <xs:enumeration value="AUDIO_INPUT_FLAG_RAW" />
@@ -329,6 +324,7 @@
     </xs:simpleType>
     <xs:simpleType name="audioFormat">
         <xs:restriction base="xs:string">
+            <xs:enumeration value="AUDIO_FORMAT_DEFAULT" />
             <xs:enumeration value="AUDIO_FORMAT_PCM_16_BIT" />
             <xs:enumeration value="AUDIO_FORMAT_PCM_8_BIT"/>
             <xs:enumeration value="AUDIO_FORMAT_PCM_32_BIT"/>
@@ -406,6 +402,11 @@
             <xs:enumeration value="AUDIO_FORMAT_LHDC_LL"/>
             <xs:enumeration value="AUDIO_FORMAT_APTX_TWSP"/>
             <xs:enumeration value="AUDIO_FORMAT_LC3"/>
+            <xs:enumeration value="AUDIO_FORMAT_MPEGH_BL_L3"/>
+            <xs:enumeration value="AUDIO_FORMAT_MPEGH_BL_L4"/>
+            <xs:enumeration value="AUDIO_FORMAT_MPEGH_LC_L3"/>
+            <xs:enumeration value="AUDIO_FORMAT_MPEGH_LC_L4"/>
+            <xs:enumeration value="AUDIO_FORMAT_IEC60958"/>
         </xs:restriction>
     </xs:simpleType>
     <xs:simpleType name="extendableAudioFormat">
diff --git a/audio/7.0/config/update_audio_policy_config.sh b/audio/7.0/config/update_audio_policy_config.sh
index 051a0df..159fa35 100755
--- a/audio/7.0/config/update_audio_policy_config.sh
+++ b/audio/7.0/config/update_audio_policy_config.sh
@@ -113,6 +113,9 @@
 echo "Press Ctrl-C to cancel, Enter to continue"
 read
 
+# Update 'audioPolicyConfiguration version="1.0"' -> 7.0 in the main file
+sed -i -r -e 's/(audioPolicyConfiguration version=")1.0/\17.0/' ${SOURCE_CONFIG}
+
 updateFile() {
     FILE=$1
     ATTR=$2
diff --git a/audio/README.md b/audio/README.md
index b77b9ba..1938ad4 100644
--- a/audio/README.md
+++ b/audio/README.md
@@ -7,47 +7,49 @@
 
 ## Directory Structure
 
-* `2.0` -- version 2.0 of the core HIDL API. Note that `.hal` files
+* `2.0` — version 2.0 of the core HIDL API. Note that `.hal` files
   can not be moved into the `core` directory because that would change
   its namespace and include path.
-   - `config` -- the XSD schema for the Audio Policy Manager
+   - `config` — the XSD schema for the Audio Policy Manager
      configuration file.
-* `4.0` -- version 4.0 of the core HIDL API.
+* `4.0` — version 4.0 of the core HIDL API.
 * ...
-* `common` -- common types for audio core and effect HIDL API.
-   - `2.0` -- version 2.0 of the common types HIDL API.
-   - `4.0` -- version 4.0.
+* `common` — common types for audio core and effect HIDL API.
+   - `2.0` — version 2.0 of the common types HIDL API.
+   - `4.0` — version 4.0.
    - ...
-   - `7.0` -- version 7.0.
-      - `example` -- example implementation of the core and effect
+   - `7.0` — version 7.0.
+      - `example` — example implementation of the core and effect
         V7.0 API. It represents a "fake" audio HAL that doesn't
         actually communicate with hardware.
-   - `all-versions` -- code common to all version of both core and effect API.
-      - `default` -- shared code of the default implementation.
-         - `service` -- vendor HAL service for hosting the default
+   - `all-versions` — code common to all version of both core and effect API.
+      - `default` — shared code of the default implementation.
+         - `service` — vendor HAL service for hosting the default
            implementation.
-      - `test` -- utilities used by tests.
-      - `util` -- utilities used by both implementation and tests.
-* `core` -- VTS tests and the default implementation of the core API
+      - `test` — utilities used by tests.
+      - `util` — utilities used by both implementation and tests.
+* `core` — VTS tests and the default implementation of the core API
   (not HIDL API, it's in `audio/N.M`).
-   - `7.0` -- code specific to version V7.0 of the core HIDL API
-   - `all-versions` -- the code is common between all versions,
+   - `7.0` — code specific to version V7.0 of the core HIDL API
+   - `all-versions` — the code is common between all versions,
      version-specific parts are enclosed into conditional directives
      of preprocessor or reside in dedicated files.
-       - `default` -- code that wraps the legacy API (from
+       - `default` — code that wraps the legacy API (from
          `hardware/libhardware`).
+         - `util` — utilities for the default implementation.
        - `vts` VTS tests for the core HIDL API.
-* `effect` -- same for the effect HIDL API.
+* `effect` — same for the effect HIDL API.
    - `2.0`
-      - `config` -- the XSD schema for the Audio Effects configuration
-        file.
+      - `config` — the XSD schema for the Audio Effects configuration file.
    - `4.0`
    - ...
    - `all-versions`
-      - `default`
-      - `vts`
-* `policy` -- Configurable Audio Policy schemes.
-   - `1.0` -- note that versions of CAP are not linked to the versions
+       - `default` — code that wraps the legacy API (from
+         `hardware/libhardware`).
+         - `util` — utilities for the default implementation.
+       - `vts` VTS tests for the effect HIDL API.
+* `policy` — Configurable Audio Policy schemes.
+   - `1.0` — note that versions of CAP are not linked to the versions
      of audio HAL.
-      - `vts` -- VTS tests for validating actual configuration files.
-      - `xml` -- XSD schemas for CAP configuration files.
+      - `vts` — VTS tests for validating actual configuration files.
+      - `xml` — XSD schemas for CAP configuration files.
diff --git a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
index b7c1cc9..b427f3a 100644
--- a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
+++ b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
@@ -212,12 +212,17 @@
     return isOutputDevice(stringToAudioDevice(device));
 }
 
-static inline bool isVendorExtension(const std::string& device) {
+static inline bool maybeVendorExtension(const std::string& s) {
+    // Only checks whether the string starts with the "vendor prefix".
+    static const std::string vendorPrefix = "VX_";
+    return s.size() > vendorPrefix.size() && s.substr(0, vendorPrefix.size()) == vendorPrefix;
+}
+
+static inline bool isVendorExtension(const std::string& s) {
     // Must match the "vendorExtension" rule from the XSD file.
     static const std::string vendorPrefix = "VX_";
-    return device.size() > vendorPrefix.size() &&
-           device.substr(0, vendorPrefix.size()) == vendorPrefix &&
-           std::all_of(device.begin() + vendorPrefix.size(), device.end(),
+    return maybeVendorExtension(s) &&
+           std::all_of(s.begin() + vendorPrefix.size(), s.end(),
                        [](unsigned char c) { return c == '_' || std::isalnum(c); });
 }
 
diff --git a/audio/common/7.0/example/Effect.cpp b/audio/common/7.0/example/Effect.cpp
index 27f28c6..5788811 100644
--- a/audio/common/7.0/example/Effect.cpp
+++ b/audio/common/7.0/example/Effect.cpp
@@ -107,14 +107,13 @@
 }
 
 Return<void> Effect::getConfig(getConfig_cb _hidl_cb) {
-    const EffectConfig config = {
-            {} /* inputCfg */,
-            // outputCfg
-            {{} /* buffer */,
-             {toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT), 48000 /* samplingRateHz */,
-              toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO)}, /* base */
-             EffectBufferAccess::ACCESS_ACCUMULATE,
-             0 /* mask */}};
+    EffectConfig config;
+    // inputCfg left unspecified.
+    config.outputCfg.base.format.value(toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT));
+    config.outputCfg.base.sampleRateHz.value(48000);
+    config.outputCfg.base.channelMask.value(
+            toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO));
+    config.outputCfg.accessMode.value(EffectBufferAccess::ACCESS_ACCUMULATE);
     _hidl_cb(Result::OK, config);
     return Void();
 }
diff --git a/audio/common/7.0/types.hal b/audio/common/7.0/types.hal
index ed56c73..bea0705 100644
--- a/audio/common/7.0/types.hal
+++ b/audio/common/7.0/types.hal
@@ -61,6 +61,8 @@
  * Audio stream type describing the intended use case of a stream.
  * See 'audioStreamType' in audio_policy_configuration.xsd for the
  * list of allowed values.
+ *
+ * An empty string is used to specify the "default" stream type.
  */
 typedef string AudioStreamType;
 
@@ -118,9 +120,28 @@
  * Base configuration attributes applicable to any stream of audio.
  */
 struct AudioConfigBase {
-    AudioFormat format;                 // empty means 'unspecified'
-    uint32_t sampleRateHz;              // 0 means 'unspecified'
-    AudioChannelMask channelMask;       // empty means 'unspecified'
+    AudioFormat format;
+    uint32_t sampleRateHz;
+    AudioChannelMask channelMask;
+};
+
+/**
+ * Base configuration attributes applicable to any stream of audio.
+ * Any attribute may be left unspecified.
+ */
+struct AudioConfigBaseOptional {
+    safe_union Format {
+        Monostate unspecified;
+        AudioFormat value;
+    } format;
+    safe_union SampleRate {
+        Monostate unspecified;
+        uint32_t value;
+    } sampleRateHz;
+    safe_union ChannelMask {
+        Monostate unspecified;
+        AudioChannelMask value;
+    } channelMask;
 };
 
 /**
@@ -270,10 +291,22 @@
  */
 struct AudioConfig {
     AudioConfigBase base;
-    AudioOffloadInfo offloadInfo;
+    safe_union OffloadInfo {
+        Monostate unspecified;
+        AudioOffloadInfo info;
+    } offloadInfo;
     uint64_t frameCount;
 };
 
+/**
+ * AudioTag 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. See 'vendorExtension' in audio_policy_configuration.xsd
+ * for a formal definition.
+ */
+typedef string AudioTag;
+
 /** Metadata of a playback track for a StreamOut. */
 struct PlaybackTrackMetadata {
     AudioUsage usage;
@@ -284,6 +317,9 @@
      * Must not be negative.
      */
     float gain;
+    AudioChannelMask channelMask;
+    /** Tags from AudioTrack audio atttributes */
+    vec<AudioTag> tags;
 };
 
 /** Metadatas of the source of a StreamOut. */
@@ -307,6 +343,9 @@
         Monostate unspecified;
         DeviceAddress device;
     } destination;
+    AudioChannelMask channelMask;
+    /** Tags from AudioTrack audio atttributes */
+    vec<AudioTag> tags;
 };
 
 /** Metadatas of the sink of a StreamIn. */
@@ -421,11 +460,9 @@
      */
     AudioPortHandle id;
     /**
-     * Basic parameters: sampling rate, format, channel mask. Only some of the
-     * parameters (or none) may be set. See the documentation of the
-     * AudioConfigBase struct.
+     * Basic parameters: sampling rate, format, channel mask.
      */
-    AudioConfigBase base;
+    AudioConfigBaseOptional base;
     /** Associated gain control. */
     safe_union OptionalGain {
         Monostate unspecified;
diff --git a/audio/common/all-versions/default/7.0/HidlUtils.cpp b/audio/common/all-versions/default/7.0/HidlUtils.cpp
index c985a70..2949fac 100644
--- a/audio/common/all-versions/default/7.0/HidlUtils.cpp
+++ b/audio/common/all-versions/default/7.0/HidlUtils.cpp
@@ -16,11 +16,13 @@
 
 #include <stdio.h>
 #include <string.h>
+#include <algorithm>
 
 #define LOG_TAG "HidlUtils"
 #include <log/log.h>
 
 #include <android_audio_policy_configuration_V7_0-enums.h>
+#include <common/all-versions/HidlSupport.h>
 #include <common/all-versions/VersionUtils.h>
 
 #include "HidlUtils.h"
@@ -146,6 +148,59 @@
     return result;
 }
 
+status_t HidlUtils::audioConfigBaseOptionalFromHal(const audio_config_base_t& halConfigBase,
+                                                   bool isInput, bool formatSpecified,
+                                                   bool sampleRateSpecified,
+                                                   bool channelMaskSpecified,
+                                                   AudioConfigBaseOptional* configBase) {
+    status_t result = NO_ERROR;
+    if (formatSpecified) {
+        AudioFormat value;
+        CONVERT_CHECKED(audioFormatFromHal(halConfigBase.format, &value), result);
+        configBase->format.value(std::move(value));
+    } else {
+        configBase->format.unspecified({});
+    }
+    if (sampleRateSpecified) {
+        configBase->sampleRateHz.value(halConfigBase.sample_rate);
+    } else {
+        configBase->sampleRateHz.unspecified({});
+    }
+    if (channelMaskSpecified) {
+        AudioChannelMask value;
+        CONVERT_CHECKED(audioChannelMaskFromHal(halConfigBase.channel_mask, isInput, &value),
+                        result);
+        configBase->channelMask.value(std::move(value));
+    }
+    return result;
+}
+
+status_t HidlUtils::audioConfigBaseOptionalToHal(const AudioConfigBaseOptional& configBase,
+                                                 audio_config_base_t* halConfigBase,
+                                                 bool* formatSpecified, bool* sampleRateSpecified,
+                                                 bool* channelMaskSpecified) {
+    status_t result = NO_ERROR;
+    *formatSpecified = configBase.format.getDiscriminator() ==
+                       AudioConfigBaseOptional::Format::hidl_discriminator::value;
+    if (*formatSpecified) {
+        CONVERT_CHECKED(audioFormatToHal(configBase.format.value(), &halConfigBase->format),
+                        result);
+    }
+    *sampleRateSpecified = configBase.sampleRateHz.getDiscriminator() ==
+                           AudioConfigBaseOptional::SampleRate::hidl_discriminator::value;
+    if (*sampleRateSpecified) {
+        halConfigBase->sample_rate = configBase.sampleRateHz.value();
+    }
+    *channelMaskSpecified = configBase.channelMask.getDiscriminator() ==
+                            AudioConfigBaseOptional::ChannelMask::hidl_discriminator::value;
+    if (*channelMaskSpecified) {
+        CONVERT_CHECKED(
+                audioChannelMaskToHal(configBase.channelMask.value(), &halConfigBase->channel_mask),
+                result);
+    }
+    return result;
+}
+
 status_t HidlUtils::audioContentTypeFromHal(const audio_content_type_t halContentType,
                                             AudioContentType* contentType) {
     *contentType = audio_content_type_to_string(halContentType);
@@ -227,7 +282,7 @@
                                              hidl_vec<AudioGainMode>* gainModeMask) {
     status_t status = NO_ERROR;
     std::vector<AudioGainMode> result;
-    for (uint32_t bit = 0; bit < sizeof(audio_gain_mode_t) * 8; ++bit) {
+    for (uint32_t bit = 0; halGainModeMask != 0 && bit < sizeof(audio_gain_mode_t) * 8; ++bit) {
         audio_gain_mode_t flag = static_cast<audio_gain_mode_t>(1u << bit);
         if ((flag & halGainModeMask) == flag) {
             AudioGainMode flagStr = audio_gain_mode_to_string(flag);
@@ -237,6 +292,7 @@
                 ALOGE("Unknown audio gain mode value 0x%X", flag);
                 status = BAD_VALUE;
             }
+            halGainModeMask = static_cast<audio_gain_mode_t>(halGainModeMask & ~flag);
         }
     }
     *gainModeMask = result;
@@ -279,25 +335,35 @@
     return BAD_VALUE;
 }
 
+// The "default" value of audio_stream_type_t is represented by an empty string.
 status_t HidlUtils::audioStreamTypeFromHal(audio_stream_type_t halStreamType,
                                            AudioStreamType* streamType) {
-    *streamType = audio_stream_type_to_string(halStreamType);
-    if (!streamType->empty() && !xsd::isUnknownAudioStreamType(*streamType)) {
+    if (halStreamType != AUDIO_STREAM_DEFAULT) {
+        *streamType = audio_stream_type_to_string(halStreamType);
+        if (!streamType->empty() && !xsd::isUnknownAudioStreamType(*streamType)) {
+            return NO_ERROR;
+        }
+        ALOGE("Unknown audio stream type value 0x%X", halStreamType);
+        return BAD_VALUE;
+    } else {
+        *streamType = "";
         return NO_ERROR;
     }
-    ALOGE("Unknown audio stream type value 0x%X", halStreamType);
-    return BAD_VALUE;
 }
 
 status_t HidlUtils::audioStreamTypeToHal(const AudioStreamType& streamType,
                                          audio_stream_type_t* halStreamType) {
-    if (!xsd::isUnknownAudioStreamType(streamType) &&
-        audio_stream_type_from_string(streamType.c_str(), halStreamType)) {
+    if (!streamType.empty()) {
+        if (!xsd::isUnknownAudioStreamType(streamType) &&
+            audio_stream_type_from_string(streamType.c_str(), halStreamType)) {
+            return NO_ERROR;
+        }
+        ALOGE("Unknown audio stream type \"%s\"", streamType.c_str());
+        return BAD_VALUE;
+    } else {
+        *halStreamType = AUDIO_STREAM_DEFAULT;
         return NO_ERROR;
     }
-    ALOGE("Unknown audio stream type \"%s\"", streamType.c_str());
-    *halStreamType = AUDIO_STREAM_DEFAULT;
-    return BAD_VALUE;
 }
 
 status_t HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, bool isInput,
@@ -306,7 +372,12 @@
     audio_config_base_t halConfigBase = {halConfig.sample_rate, halConfig.channel_mask,
                                          halConfig.format};
     CONVERT_CHECKED(audioConfigBaseFromHal(halConfigBase, isInput, &config->base), result);
-    CONVERT_CHECKED(audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo), result);
+    if (halConfig.offload_info.sample_rate != 0) {
+        config->offloadInfo.info({});
+        CONVERT_CHECKED(
+                audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo.info()),
+                result);
+    }
     config->frameCount = halConfig.frame_count;
     return result;
 }
@@ -319,7 +390,11 @@
     halConfig->sample_rate = halConfigBase.sample_rate;
     halConfig->channel_mask = halConfigBase.channel_mask;
     halConfig->format = halConfigBase.format;
-    CONVERT_CHECKED(audioOffloadInfoToHal(config.offloadInfo, &halConfig->offload_info), result);
+    if (config.offloadInfo.getDiscriminator() ==
+        AudioConfig::OffloadInfo::hidl_discriminator::info) {
+        CONVERT_CHECKED(audioOffloadInfoToHal(config.offloadInfo.info(), &halConfig->offload_info),
+                        result);
+    }
     halConfig->frame_count = config.frameCount;
     return result;
 }
@@ -498,23 +573,14 @@
               audio_port_config_has_input_direction(&halConfig), isInput);
         result = BAD_VALUE;
     }
-    if (halConfig.config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) {
-        config->base.sampleRateHz = halConfig.sample_rate;
-    } else {
-        config->base.sampleRateHz = {};
-    }
-    if (halConfig.config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) {
-        CONVERT_CHECKED(
-                audioChannelMaskFromHal(halConfig.channel_mask, isInput, &config->base.channelMask),
-                result);
-    } else {
-        config->base.channelMask = {};
-    }
-    if (halConfig.config_mask & AUDIO_PORT_CONFIG_FORMAT) {
-        CONVERT_CHECKED(audioFormatFromHal(halConfig.format, &config->base.format), result);
-    } else {
-        config->base.format = {};
-    }
+    audio_config_base_t halConfigBase = {halConfig.sample_rate, halConfig.channel_mask,
+                                         halConfig.format};
+    CONVERT_CHECKED(
+            audioConfigBaseOptionalFromHal(
+                    halConfigBase, isInput, halConfig.config_mask & AUDIO_PORT_CONFIG_FORMAT,
+                    halConfig.config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE,
+                    halConfig.config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK, &config->base),
+            result);
     if (halConfig.config_mask & AUDIO_PORT_CONFIG_GAIN) {
         config->gain.config({});
         CONVERT_CHECKED(audioGainConfigFromHal(halConfig.gain, isInput, &config->gain.config()),
@@ -530,19 +596,23 @@
     status_t result = NO_ERROR;
     memset(halConfig, 0, sizeof(audio_port_config));
     halConfig->id = config.id;
-    halConfig->config_mask = {};
-    if (config.base.sampleRateHz != 0) {
+    halConfig->config_mask = 0;
+    audio_config_base_t halConfigBase = AUDIO_CONFIG_BASE_INITIALIZER;
+    bool formatSpecified = false, sRateSpecified = false, channelMaskSpecified = false;
+    CONVERT_CHECKED(audioConfigBaseOptionalToHal(config.base, &halConfigBase, &formatSpecified,
+                                                 &sRateSpecified, &channelMaskSpecified),
+                    result);
+    if (sRateSpecified) {
         halConfig->config_mask |= AUDIO_PORT_CONFIG_SAMPLE_RATE;
-        halConfig->sample_rate = config.base.sampleRateHz;
+        halConfig->sample_rate = halConfigBase.sample_rate;
     }
-    if (!config.base.channelMask.empty()) {
+    if (channelMaskSpecified) {
         halConfig->config_mask |= AUDIO_PORT_CONFIG_CHANNEL_MASK;
-        CONVERT_CHECKED(audioChannelMaskToHal(config.base.channelMask, &halConfig->channel_mask),
-                        result);
+        halConfig->channel_mask = halConfigBase.channel_mask;
     }
-    if (!config.base.format.empty()) {
+    if (formatSpecified) {
         halConfig->config_mask |= AUDIO_PORT_CONFIG_FORMAT;
-        CONVERT_CHECKED(audioFormatToHal(config.base.format, &halConfig->format), result);
+        halConfig->format = halConfigBase.format;
     }
     if (config.gain.getDiscriminator() ==
         AudioPortConfig::OptionalGain::hidl_discriminator::config) {
@@ -800,6 +870,75 @@
     return result;
 }
 
+status_t HidlUtils::audioTagsFromHal(const std::vector<std::string>& strTags,
+                                     hidl_vec<AudioTag>* tags) {
+    status_t result = NO_ERROR;
+    tags->resize(strTags.size());
+    size_t to = 0;
+    for (size_t from = 0; from < strTags.size(); ++from) {
+        const auto& tag = strTags[from];
+        if (xsd::isVendorExtension(tag)) {
+            (*tags)[to++] = tag;
+        } else {
+            ALOGE("Vendor extension tag is ill-formed: \"%s\"", tag.c_str());
+            result = BAD_VALUE;
+        }
+    }
+    if (to != strTags.size()) {
+        tags->resize(to);
+    }
+    return result;
+}
+
+status_t HidlUtils::audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags) {
+    memset(halTags, 0, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE);
+    status_t result = NO_ERROR;
+    std::ostringstream halTagsBuffer;
+    bool hasValue = false;
+    for (const auto& tag : tags) {
+        if (hasValue) {
+            halTagsBuffer << sAudioTagSeparator;
+        }
+        if (xsd::isVendorExtension(tag) && strchr(tag.c_str(), sAudioTagSeparator) == nullptr) {
+            halTagsBuffer << tag;
+            hasValue = true;
+        } else {
+            ALOGE("Vendor extension tag is ill-formed: \"%s\"", tag.c_str());
+            result = BAD_VALUE;
+        }
+    }
+    std::string fullHalTags{std::move(halTagsBuffer.str())};
+    strncpy(halTags, fullHalTags.c_str(), AUDIO_ATTRIBUTES_TAGS_MAX_SIZE);
+    CONVERT_CHECKED(fullHalTags.length() <= AUDIO_ATTRIBUTES_TAGS_MAX_SIZE ? NO_ERROR : BAD_VALUE,
+                    result);
+    return result;
+}
+
+hidl_vec<AudioTag> HidlUtils::filterOutNonVendorTags(const hidl_vec<AudioTag>& tags) {
+    hidl_vec<AudioTag> result;
+    result.resize(tags.size());
+    size_t resultIdx = 0;
+    for (const auto& tag : tags) {
+        if (xsd::maybeVendorExtension(tag)) {
+            result[resultIdx++] = tag;
+        }
+    }
+    if (resultIdx != result.size()) {
+        result.resize(resultIdx);
+    }
+    return result;
+}
+
+std::vector<std::string> HidlUtils::filterOutNonVendorTags(const std::vector<std::string>& tags) {
+    std::vector<std::string> result;
+    std::copy_if(tags.begin(), tags.end(), std::back_inserter(result), xsd::maybeVendorExtension);
+    return result;
+}
+
+std::vector<std::string> HidlUtils::splitAudioTags(const char* halTags) {
+    return utils::splitString(halTags, sAudioTagSeparator);
+}
+
 status_t HidlUtils::deviceAddressFromHal(audio_devices_t halDeviceType,
                                          const char* halDeviceAddress, DeviceAddress* device) {
     status_t result = NO_ERROR;
diff --git a/audio/common/all-versions/default/Android.bp b/audio/common/all-versions/default/Android.bp
index 45f0b8f..29a3d6f 100644
--- a/audio/common/all-versions/default/Android.bp
+++ b/audio/common/all-versions/default/Android.bp
@@ -114,7 +114,7 @@
     ],
 }
 
-cc_library_shared {
+cc_library {
     name: "android.hardware.audio.common@6.0-util",
     defaults: ["android.hardware.audio.common-util_default"],
     srcs: [":android.hardware.audio.common-util@2-6"],
@@ -152,6 +152,32 @@
 // Note: this isn't a VTS test, but rather a unit test
 // to verify correctness of conversion utilities.
 cc_test {
+    name: "android.hardware.audio.common@6.0-util_tests",
+    defaults: ["android.hardware.audio.common-util_default"],
+
+    srcs: ["tests/hidlutils6_tests.cpp"],
+
+    // Use static linking to allow running in presubmit on
+    // targets that don't have HAL V6.
+    static_libs: [
+        "android.hardware.audio.common@6.0",
+        "android.hardware.audio.common@6.0-util",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-DMAJOR_VERSION=6",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+
+    test_suites: ["device-tests"],
+}
+
+// Note: this isn't a VTS test, but rather a unit test
+// to verify correctness of conversion utilities.
+cc_test {
     name: "android.hardware.audio.common@7.0-util_tests",
     defaults: ["android.hardware.audio.common-util_default"],
 
diff --git a/audio/common/all-versions/default/HidlUtils.h b/audio/common/all-versions/default/HidlUtils.h
index a0bd1bc..dd4ca4d 100644
--- a/audio/common/all-versions/default/HidlUtils.h
+++ b/audio/common/all-versions/default/HidlUtils.h
@@ -20,6 +20,8 @@
 #include PATH(android/hardware/audio/common/FILE_VERSION/types.h)
 
 #include <memory>
+#include <string>
+#include <vector>
 
 #include <system/audio.h>
 
@@ -77,6 +79,8 @@
 #endif
 
 #if MAJOR_VERSION >= 7
+    static constexpr char sAudioTagSeparator = ';';
+
     static status_t audioChannelMaskFromHal(audio_channel_mask_t halChannelMask, bool isInput,
                                             AudioChannelMask* channelMask);
     static status_t audioChannelMasksFromHal(const std::vector<std::string>& halChannelMasks,
@@ -87,6 +91,15 @@
                                            AudioConfigBase* configBase);
     static status_t audioConfigBaseToHal(const AudioConfigBase& configBase,
                                          audio_config_base_t* halConfigBase);
+    static status_t audioConfigBaseOptionalFromHal(const audio_config_base_t& halConfigBase,
+                                                   bool isInput, bool formatSpecified,
+                                                   bool sampleRateSpecified,
+                                                   bool channelMaskSpecified,
+                                                   AudioConfigBaseOptional* configBase);
+    static status_t audioConfigBaseOptionalToHal(const AudioConfigBaseOptional& configBase,
+                                                 audio_config_base_t* halConfigBase,
+                                                 bool* formatSpecified, bool* sampleRateSpecified,
+                                                 bool* channelMaskSpecified);
     static status_t audioDeviceTypeFromHal(audio_devices_t halDevice, AudioDevice* device);
     static status_t audioDeviceTypeToHal(const AudioDevice& device, audio_devices_t* halDevice);
     static status_t audioFormatFromHal(audio_format_t halFormat, AudioFormat* format);
@@ -107,6 +120,12 @@
                                            AudioStreamType* streamType);
     static status_t audioStreamTypeToHal(const AudioStreamType& streamType,
                                          audio_stream_type_t* halStreamType);
+    static status_t audioTagsFromHal(const std::vector<std::string>& strTags,
+                                     hidl_vec<AudioTag>* tags);
+    static status_t audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags);
+    static hidl_vec<AudioTag> filterOutNonVendorTags(const hidl_vec<AudioTag>& tags);
+    static std::vector<std::string> filterOutNonVendorTags(const std::vector<std::string>& tags);
+    static std::vector<std::string> splitAudioTags(const char* halTags);
 
   private:
     static status_t audioIndexChannelMaskFromHal(audio_channel_mask_t halChannelMask,
@@ -126,6 +145,7 @@
                                                struct audio_port_config_device_ext* device,
                                                struct audio_port_config_mix_ext* mix,
                                                struct audio_port_config_session_ext* session);
+
 #endif  // MAJOR_VERSION >= 7
 
     // V4 and below have DeviceAddress defined in the 'core' interface.
@@ -190,6 +210,9 @@
                *halDeviceType == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
         snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%s",
                  device.rSubmixAddress.c_str());
+    } else {
+        // Fall back to bus address for other device types, e.g. for microphones.
+        snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%s", device.busAddress.c_str());
     }
     return NO_ERROR;
 }
@@ -229,6 +252,7 @@
         device->rSubmixAddress = halDeviceAddress;
         return OK;
     }
+    // Fall back to bus address for other device types, e.g. for microphones.
     device->busAddress = halDeviceAddress;
     return NO_ERROR;
 }
diff --git a/audio/common/all-versions/default/TEST_MAPPING b/audio/common/all-versions/default/TEST_MAPPING
index 4316ccf..c965113 100644
--- a/audio/common/all-versions/default/TEST_MAPPING
+++ b/audio/common/all-versions/default/TEST_MAPPING
@@ -1,6 +1,9 @@
 {
   "presubmit": [
     {
+      "name": "android.hardware.audio.common@6.0-util_tests"
+    },
+    {
       "name": "android.hardware.audio.common@7.0-util_tests"
     }
   ]
diff --git a/audio/common/all-versions/default/UuidUtils.cpp b/audio/common/all-versions/default/UuidUtils.cpp
index 85edc7b..6c4c94d 100644
--- a/audio/common/all-versions/default/UuidUtils.cpp
+++ b/audio/common/all-versions/default/UuidUtils.cpp
@@ -42,6 +42,14 @@
     memcpy(halUuid->node, uuid.node.data(), uuid.node.size());
 }
 
+std::string UuidUtils::uuidToString(const audio_uuid_t& halUuid) {
+    char str[64];
+    snprintf(str, sizeof(str), "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", halUuid.timeLow,
+             halUuid.timeMid, halUuid.timeHiAndVersion, halUuid.clockSeq, halUuid.node[0],
+             halUuid.node[1], halUuid.node[2], halUuid.node[3], halUuid.node[4], halUuid.node[5]);
+    return str;
+}
+
 }  // namespace implementation
 }  // namespace CPP_VERSION
 }  // namespace common
diff --git a/audio/common/all-versions/default/UuidUtils.h b/audio/common/all-versions/default/UuidUtils.h
index 38db48a..cd04fb0 100644
--- a/audio/common/all-versions/default/UuidUtils.h
+++ b/audio/common/all-versions/default/UuidUtils.h
@@ -17,14 +17,14 @@
 #ifndef android_hardware_audio_Uuid_Utils_H_
 #define android_hardware_audio_Uuid_Utils_H_
 
+#include <string>
+
 // clang-format off
 #include PATH(android/hardware/audio/common/FILE_VERSION/types.h)
 // clang-format on
 
 #include <system/audio.h>
 
-using ::android::hardware::hidl_vec;
-
 namespace android {
 namespace hardware {
 namespace audio {
@@ -38,6 +38,7 @@
   public:
     static void uuidFromHal(const audio_uuid_t& halUuid, Uuid* uuid);
     static void uuidToHal(const Uuid& uuid, audio_uuid_t* halUuid);
+    static std::string uuidToString(const audio_uuid_t& halUuid);
 };
 
 }  // namespace implementation
diff --git a/audio/common/all-versions/default/service/service.cpp b/audio/common/all-versions/default/service/service.cpp
index 710ddce..bbc14ad 100644
--- a/audio/common/all-versions/default/service/service.cpp
+++ b/audio/common/all-versions/default/service/service.cpp
@@ -63,17 +63,17 @@
     const std::vector<InterfacesList> mandatoryInterfaces = {
         {
             "Audio Core API",
+            "android.hardware.audio@7.0::IDevicesFactory",
             "android.hardware.audio@6.0::IDevicesFactory",
             "android.hardware.audio@5.0::IDevicesFactory",
             "android.hardware.audio@4.0::IDevicesFactory",
-            "android.hardware.audio@2.0::IDevicesFactory"
         },
         {
             "Audio Effect API",
+            "android.hardware.audio.effect@7.0::IEffectsFactory",
             "android.hardware.audio.effect@6.0::IEffectsFactory",
             "android.hardware.audio.effect@5.0::IEffectsFactory",
             "android.hardware.audio.effect@4.0::IEffectsFactory",
-            "android.hardware.audio.effect@2.0::IEffectsFactory",
         }
     };
 
diff --git a/audio/common/all-versions/default/tests/hidlutils6_tests.cpp b/audio/common/all-versions/default/tests/hidlutils6_tests.cpp
new file mode 100644
index 0000000..3a24e75
--- /dev/null
+++ b/audio/common/all-versions/default/tests/hidlutils6_tests.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#define LOG_TAG "HidlUtils_Test"
+#include <log/log.h>
+
+#include <HidlUtils.h>
+#include <system/audio.h>
+
+using namespace android;
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+
+// Not generated automatically because DeviceAddress contains
+// an union.
+//
+// operator== must be defined in the same namespace as the data type.
+namespace android::hardware::audio::common::CPP_VERSION {
+
+inline bool operator==(const DeviceAddress& lhs, const DeviceAddress& rhs) {
+    if (lhs.device != rhs.device) return false;
+    audio_devices_t halDeviceType = static_cast<audio_devices_t>(lhs.device);
+    if (audio_is_a2dp_out_device(halDeviceType) || audio_is_a2dp_in_device(halDeviceType)) {
+        return lhs.address.mac == rhs.address.mac;
+    } else if (halDeviceType == AUDIO_DEVICE_OUT_IP || halDeviceType == AUDIO_DEVICE_IN_IP) {
+        return lhs.address.ipv4 == rhs.address.ipv4;
+    } else if (audio_is_usb_out_device(halDeviceType) || audio_is_usb_in_device(halDeviceType)) {
+        return lhs.address.alsa == rhs.address.alsa;
+    } else if (halDeviceType == AUDIO_DEVICE_OUT_REMOTE_SUBMIX ||
+               halDeviceType == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
+        return lhs.rSubmixAddress == rhs.rSubmixAddress;
+    }
+    // busAddress field can be used for types other than bus, e.g. for microphones.
+    return lhs.busAddress == rhs.busAddress;
+}
+
+}  // namespace android::hardware::audio::common::CPP_VERSION
+
+static void ConvertDeviceAddress(const DeviceAddress& device) {
+    audio_devices_t halDeviceType;
+    char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN] = {};
+    EXPECT_EQ(NO_ERROR, HidlUtils::deviceAddressToHal(device, &halDeviceType, halDeviceAddress));
+    DeviceAddress deviceBack;
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::deviceAddressFromHal(halDeviceType, halDeviceAddress, &deviceBack));
+    EXPECT_EQ(device, deviceBack);
+}
+
+TEST(HidlUtils6, ConvertUniqueDeviceAddress) {
+    DeviceAddress speaker;
+    speaker.device = AudioDevice::OUT_SPEAKER;
+    ConvertDeviceAddress(speaker);
+
+    DeviceAddress micWithAddress;
+    micWithAddress.device = AudioDevice::IN_BUILTIN_MIC;
+    micWithAddress.busAddress = "bottom";
+    ConvertDeviceAddress(micWithAddress);
+}
+
+TEST(HidlUtils6, ConvertA2dpDeviceAddress) {
+    DeviceAddress a2dpSpeaker;
+    a2dpSpeaker.device = AudioDevice::OUT_BLUETOOTH_A2DP_SPEAKER;
+    a2dpSpeaker.address.mac = std::array<uint8_t, 6>{1, 2, 3, 4, 5, 6};
+    ConvertDeviceAddress(a2dpSpeaker);
+}
+
+TEST(HidlUtils6, ConvertIpv4DeviceAddress) {
+    DeviceAddress ipv4;
+    ipv4.device = AudioDevice::OUT_IP;
+    ipv4.address.ipv4 = std::array<uint8_t, 4>{1, 2, 3, 4};
+    ConvertDeviceAddress(ipv4);
+}
+
+TEST(HidlUtils6, ConvertUsbDeviceAddress) {
+    DeviceAddress usbHeadset;
+    usbHeadset.device = AudioDevice::OUT_USB_HEADSET;
+    usbHeadset.address.alsa = {1, 2};
+    ConvertDeviceAddress(usbHeadset);
+}
+
+TEST(HidlUtils6, ConvertBusDeviceAddress) {
+    DeviceAddress bus;
+    bus.device = AudioDevice::OUT_BUS;
+    bus.busAddress = "bus_device";
+    ConvertDeviceAddress(bus);
+}
+
+TEST(HidlUtils6, ConvertRSubmixDeviceAddress) {
+    DeviceAddress rSubmix;
+    rSubmix.device = AudioDevice::OUT_REMOTE_SUBMIX;
+    rSubmix.rSubmixAddress = AUDIO_REMOTE_SUBMIX_DEVICE_ADDRESS;
+    ConvertDeviceAddress(rSubmix);
+}
diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
index 22571c0..ec6bdf3 100644
--- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp
+++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
@@ -35,30 +35,27 @@
 using namespace ::android::audio::policy::configuration::V7_0;
 }
 
-static constexpr audio_channel_mask_t kInvalidHalChannelMask =
-        static_cast<audio_channel_mask_t>(0xFFFFFFFFU);
+static constexpr audio_channel_mask_t kInvalidHalChannelMask = AUDIO_CHANNEL_INVALID;
 static constexpr audio_content_type_t kInvalidHalContentType =
         static_cast<audio_content_type_t>(0xFFFFFFFFU);
 static constexpr audio_devices_t kInvalidHalDevice = static_cast<audio_devices_t>(0xFFFFFFFFU);
-static constexpr audio_format_t kInvalidHalFormat = static_cast<audio_format_t>(0xFFFFFFFFU);
+static constexpr audio_format_t kInvalidHalFormat = AUDIO_FORMAT_INVALID;
 static constexpr audio_gain_mode_t kInvalidHalGainMode =
         static_cast<audio_gain_mode_t>(0xFFFFFFFFU);
-static constexpr audio_source_t kInvalidHalSource = static_cast<audio_source_t>(0xFFFFFFFFU);
-static constexpr audio_stream_type_t kInvalidHalStreamType =
-        static_cast<audio_stream_type_t>(0xFFFFFFFFU);
+// AUDIO_SOURCE_INVALID is framework-only.
+static constexpr audio_source_t kInvalidHalSource = static_cast<audio_source_t>(-1);
+// AUDIO_STREAM_DEFAULT is framework-only
+static constexpr audio_stream_type_t kInvalidHalStreamType = static_cast<audio_stream_type_t>(-2);
 static constexpr audio_usage_t kInvalidHalUsage = static_cast<audio_usage_t>(0xFFFFFFFFU);
 
 TEST(HidlUtils, ConvertInvalidChannelMask) {
     AudioChannelMask invalid;
-    EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskFromHal(AUDIO_CHANNEL_INVALID,
-                                                            false /*isInput*/, &invalid));
-    EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskFromHal(AUDIO_CHANNEL_INVALID, true /*isInput*/,
-                                                            &invalid));
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskFromHal(kInvalidHalChannelMask,
                                                             false /*isInput*/, &invalid));
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskFromHal(kInvalidHalChannelMask,
                                                             true /*isInput*/, &invalid));
     audio_channel_mask_t halInvalid;
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskToHal("", &halInvalid));
     // INVALID channel mask is not in XSD thus it's not allowed for transfer over HIDL.
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskToHal("AUDIO_CHANNEL_INVALID", &halInvalid));
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskToHal("random string", &halInvalid));
@@ -148,40 +145,241 @@
     }
 }
 
+static AudioConfigBase generateValidConfigBase(bool isInput) {
+    AudioConfigBase configBase;
+    configBase.sampleRateHz = 44100;
+    configBase.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
+    configBase.channelMask = isInput ? toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_STEREO)
+                                     : toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
+    return configBase;
+}
+
 TEST(HidlUtils, ConvertInvalidConfigBase) {
     AudioConfigBase invalid;
-    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseFromHal({.sample_rate = 0,
-                                                            .channel_mask = kInvalidHalChannelMask,
-                                                            .format = kInvalidHalFormat},
-                                                           false /*isInput*/, &invalid));
-    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseFromHal({.sample_rate = 0,
-                                                            .channel_mask = kInvalidHalChannelMask,
-                                                            .format = kInvalidHalFormat},
-                                                           true /*isInput*/, &invalid));
+    audio_config_base_t halInvalidChannelMask = AUDIO_CONFIG_BASE_INITIALIZER;
+    halInvalidChannelMask.channel_mask = kInvalidHalChannelMask;
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseFromHal(halInvalidChannelMask, false /*isInput*/,
+                                                           &invalid));
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigBaseFromHal(halInvalidChannelMask, true /*isInput*/, &invalid));
+    audio_config_base_t halInvalidFormat = AUDIO_CONFIG_BASE_INITIALIZER;
+    halInvalidFormat.format = kInvalidHalFormat;
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigBaseFromHal(halInvalidFormat, false /*isInput*/, &invalid));
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigBaseFromHal(halInvalidFormat, true /*isInput*/, &invalid));
+
     audio_config_base_t halInvalid;
-    invalid.sampleRateHz = 0;
-    invalid.channelMask = "random string";
-    invalid.format = "random string";
-    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseToHal(invalid, &halInvalid));
+    AudioConfigBase invalidChannelMask = generateValidConfigBase(false /*isInput*/);
+    invalidChannelMask.channelMask = "random string";
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseToHal(invalidChannelMask, &halInvalid));
+    AudioConfigBase invalidFormat = generateValidConfigBase(false /*isInput*/);
+    invalidFormat.format = "random string";
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseToHal(invalidFormat, &halInvalid));
+}
+
+TEST(HidlUtils, ConvertConfigBaseDefault) {
+    audio_config_base_t halBaseDefault = AUDIO_CONFIG_BASE_INITIALIZER;
+    AudioConfigBase baseDefaultOut, baseDefaultIn;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseFromHal(halBaseDefault, false /*isInput*/,
+                                                          &baseDefaultOut));
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigBaseFromHal(halBaseDefault, true /*isInput*/, &baseDefaultIn));
+    EXPECT_EQ(baseDefaultOut, baseDefaultIn);
+    audio_config_base_t halBaseDefaultBack;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseToHal(baseDefaultOut, &halBaseDefaultBack));
+    EXPECT_EQ(halBaseDefault.sample_rate, halBaseDefaultBack.sample_rate);
+    EXPECT_EQ(halBaseDefault.channel_mask, halBaseDefaultBack.channel_mask);
+    EXPECT_EQ(halBaseDefault.format, halBaseDefaultBack.format);
 }
 
 TEST(HidlUtils, ConvertConfigBase) {
-    AudioConfigBase configBase;
-    configBase.sampleRateHz = 44100;
-    configBase.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
-    configBase.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
-    audio_config_base_t halConfigBase;
-    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseToHal(configBase, &halConfigBase));
-    AudioConfigBase configBaseBack;
+    AudioConfigBase configBaseOut = generateValidConfigBase(false /*isInput*/);
+    audio_config_base_t halConfigBaseOut;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseToHal(configBaseOut, &halConfigBaseOut));
+    AudioConfigBase configBaseOutBack;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseFromHal(halConfigBaseOut, false /*isInput*/,
+                                                          &configBaseOutBack));
+    EXPECT_EQ(configBaseOut, configBaseOutBack);
+
+    AudioConfigBase configBaseIn = generateValidConfigBase(true /*isInput*/);
+    audio_config_base_t halConfigBaseIn;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseToHal(configBaseIn, &halConfigBaseIn));
+    AudioConfigBase configBaseInBack;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseFromHal(halConfigBaseIn, true /*isInput*/,
+                                                          &configBaseInBack));
+    EXPECT_EQ(configBaseIn, configBaseInBack);
+}
+
+TEST(HidlUtils, ConvertInvalidConfigBaseOptional) {
+    AudioConfigBaseOptional invalid;
+    audio_config_base_t halInvalidChannelMask = AUDIO_CONFIG_BASE_INITIALIZER;
+    halInvalidChannelMask.channel_mask = kInvalidHalChannelMask;
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigBaseOptionalFromHal(
+                      halInvalidChannelMask, false /*isInput*/, false /*formatSpecified*/,
+                      false /*sampleRateSpecified*/, true /*channelMaskSpecified*/, &invalid));
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigBaseOptionalFromHal(
+                      halInvalidChannelMask, true /*isInput*/, false /*formatSpecified*/,
+                      false /*sampleRateSpecified*/, true /*channelMaskSpecified*/, &invalid));
+    // Unspecified invalid values are ignored
+    AudioConfigBaseOptional unspecified;
     EXPECT_EQ(NO_ERROR,
-              HidlUtils::audioConfigBaseFromHal(halConfigBase, false /*isInput*/, &configBaseBack));
-    EXPECT_EQ(configBase, configBaseBack);
+              HidlUtils::audioConfigBaseOptionalFromHal(
+                      halInvalidChannelMask, false /*isInput*/, false /*formatSpecified*/,
+                      false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &unspecified));
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigBaseOptionalFromHal(
+                      halInvalidChannelMask, true /*isInput*/, false /*formatSpecified*/,
+                      false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &unspecified));
+    audio_config_base_t halInvalidFormat = AUDIO_CONFIG_BASE_INITIALIZER;
+    halInvalidFormat.format = kInvalidHalFormat;
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigBaseOptionalFromHal(
+                      halInvalidFormat, false /*isInput*/, true /*formatSpecified*/,
+                      false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &invalid));
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigBaseOptionalFromHal(
+                      halInvalidFormat, true /*isInput*/, true /*formatSpecified*/,
+                      false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &invalid));
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigBaseOptionalFromHal(
+                      halInvalidFormat, false /*isInput*/, false /*formatSpecified*/,
+                      false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &unspecified));
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigBaseOptionalFromHal(
+                      halInvalidFormat, true /*isInput*/, false /*formatSpecified*/,
+                      false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &unspecified));
+
+    audio_config_base_t halInvalid;
+    AudioConfigBaseOptional invalidChannelMask;
+    bool formatSpecified, sampleRateSpecified, channelMaskSpecified;
+    invalidChannelMask.channelMask.value("random string");
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseOptionalToHal(
+                                 invalidChannelMask, &halInvalid, &formatSpecified,
+                                 &sampleRateSpecified, &channelMaskSpecified));
+    AudioConfigBaseOptional invalidFormat;
+    invalidFormat.format.value("random string");
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigBaseOptionalToHal(invalidFormat, &halInvalid, &formatSpecified,
+                                                      &sampleRateSpecified, &channelMaskSpecified));
+}
+
+TEST(HidlUtils, ConvertConfigBaseOptionalDefault) {
+    audio_config_base_t halBaseDefault = AUDIO_CONFIG_BASE_INITIALIZER;
+    AudioConfigBaseOptional baseDefaultUnspecOut, baseDefaultUnspecIn;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+                                halBaseDefault, false /*isInput*/, false /*formatSpecified*/,
+                                false /*sampleRateSpecified*/, false /*channelMaskSpecified*/,
+                                &baseDefaultUnspecOut));
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+                                halBaseDefault, true /*isInput*/, false /*formatSpecified*/,
+                                false /*sampleRateSpecified*/, false /*channelMaskSpecified*/,
+                                &baseDefaultUnspecIn));
+    EXPECT_EQ(baseDefaultUnspecOut, baseDefaultUnspecIn);
+    audio_config_base_t halBaseDefaultUnspecBack = AUDIO_CONFIG_BASE_INITIALIZER;
+    bool formatSpecified, sampleRateSpecified, channelMaskSpecified;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalToHal(
+                                baseDefaultUnspecOut, &halBaseDefaultUnspecBack, &formatSpecified,
+                                &sampleRateSpecified, &channelMaskSpecified));
+    EXPECT_FALSE(formatSpecified);
+    EXPECT_FALSE(sampleRateSpecified);
+    EXPECT_FALSE(channelMaskSpecified);
+    EXPECT_EQ(halBaseDefault.sample_rate, halBaseDefaultUnspecBack.sample_rate);
+    EXPECT_EQ(halBaseDefault.channel_mask, halBaseDefaultUnspecBack.channel_mask);
+    EXPECT_EQ(halBaseDefault.format, halBaseDefaultUnspecBack.format);
+
+    AudioConfigBaseOptional baseDefaultSpecOut, baseDefaultSpecIn;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+                                halBaseDefault, false /*isInput*/, true /*formatSpecified*/,
+                                true /*sampleRateSpecified*/, true /*channelMaskSpecified*/,
+                                &baseDefaultSpecOut));
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+                                halBaseDefault, true /*isInput*/, true /*formatSpecified*/,
+                                true /*sampleRateSpecified*/, true /*channelMaskSpecified*/,
+                                &baseDefaultSpecIn));
+    EXPECT_EQ(baseDefaultSpecOut, baseDefaultSpecIn);
+    audio_config_base_t halBaseDefaultSpecBack;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalToHal(
+                                baseDefaultSpecOut, &halBaseDefaultSpecBack, &formatSpecified,
+                                &sampleRateSpecified, &channelMaskSpecified));
+    EXPECT_TRUE(formatSpecified);
+    EXPECT_TRUE(sampleRateSpecified);
+    EXPECT_TRUE(channelMaskSpecified);
+    EXPECT_EQ(halBaseDefault.sample_rate, halBaseDefaultSpecBack.sample_rate);
+    EXPECT_EQ(halBaseDefault.channel_mask, halBaseDefaultSpecBack.channel_mask);
+    EXPECT_EQ(halBaseDefault.format, halBaseDefaultSpecBack.format);
+}
+
+TEST(HidlUtils, ConvertConfigBaseOptionalEmpty) {
+    AudioConfigBaseOptional empty;
+    bool formatSpecified, sampleRateSpecified, channelMaskSpecified;
+    audio_config_base_t halEmpty = AUDIO_CONFIG_BASE_INITIALIZER;
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigBaseOptionalToHal(empty, &halEmpty, &formatSpecified,
+                                                      &sampleRateSpecified, &channelMaskSpecified));
+    EXPECT_FALSE(formatSpecified);
+    EXPECT_FALSE(sampleRateSpecified);
+    EXPECT_FALSE(channelMaskSpecified);
+    AudioConfigBaseOptional emptyOutBack, emptyInBack;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+                                halEmpty, false /*isInput*/, formatSpecified, sampleRateSpecified,
+                                channelMaskSpecified, &emptyOutBack));
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+                                halEmpty, true /*isInput*/, formatSpecified, sampleRateSpecified,
+                                channelMaskSpecified, &emptyInBack));
+    EXPECT_EQ(emptyOutBack, emptyInBack);
+    EXPECT_EQ(empty, emptyOutBack);
+}
+
+TEST(HidlUtils, ConvertConfigBaseOptional) {
+    AudioConfigBase validBaseOut = generateValidConfigBase(false /*isInput*/);
+    AudioConfigBaseOptional configBaseOut;
+    configBaseOut.format.value(validBaseOut.format);
+    configBaseOut.sampleRateHz.value(validBaseOut.sampleRateHz);
+    configBaseOut.channelMask.value(validBaseOut.channelMask);
+    audio_config_base_t halConfigBaseOut;
+    bool formatSpecified, sampleRateSpecified, channelMaskSpecified;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalToHal(
+                                configBaseOut, &halConfigBaseOut, &formatSpecified,
+                                &sampleRateSpecified, &channelMaskSpecified));
+    EXPECT_TRUE(formatSpecified);
+    EXPECT_TRUE(sampleRateSpecified);
+    EXPECT_TRUE(channelMaskSpecified);
+    AudioConfigBaseOptional configBaseOutBack;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+                                halConfigBaseOut, false /*isInput*/, formatSpecified,
+                                sampleRateSpecified, channelMaskSpecified, &configBaseOutBack));
+    EXPECT_EQ(configBaseOut, configBaseOutBack);
+
+    AudioConfigBase validBaseIn = generateValidConfigBase(true /*isInput*/);
+    AudioConfigBaseOptional configBaseIn;
+    configBaseIn.format.value(validBaseIn.format);
+    configBaseIn.sampleRateHz.value(validBaseIn.sampleRateHz);
+    configBaseIn.channelMask.value(validBaseIn.channelMask);
+    audio_config_base_t halConfigBaseIn;
+    formatSpecified = false;
+    sampleRateSpecified = false;
+    channelMaskSpecified = false;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalToHal(
+                                configBaseIn, &halConfigBaseIn, &formatSpecified,
+                                &sampleRateSpecified, &channelMaskSpecified));
+    EXPECT_TRUE(formatSpecified);
+    EXPECT_TRUE(sampleRateSpecified);
+    EXPECT_TRUE(channelMaskSpecified);
+    AudioConfigBaseOptional configBaseInBack;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+                                halConfigBaseIn, true /*isInput*/, formatSpecified,
+                                sampleRateSpecified, channelMaskSpecified, &configBaseInBack));
+    EXPECT_EQ(configBaseIn, configBaseInBack);
 }
 
 TEST(HidlUtils, ConvertInvalidContentType) {
     AudioContentType invalid;
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioContentTypeFromHal(kInvalidHalContentType, &invalid));
     audio_content_type_t halInvalid;
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioContentTypeToHal("", &halInvalid));
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioContentTypeToHal("random string", &halInvalid));
 }
 
@@ -202,6 +400,7 @@
     AudioDevice invalid;
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioDeviceTypeFromHal(kInvalidHalDevice, &invalid));
     audio_devices_t halInvalid;
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioDeviceTypeToHal("", &halInvalid));
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioDeviceTypeToHal("random string", &halInvalid));
 }
 
@@ -233,6 +432,7 @@
 // The enums module is too small to have unit tests on its own.
 TEST(HidlUtils, VendorExtension) {
     EXPECT_TRUE(xsd::isVendorExtension("VX_GOOGLE_VR_42"));
+    EXPECT_FALSE(xsd::isVendorExtension(""));
     EXPECT_FALSE(xsd::isVendorExtension("random string"));
     EXPECT_FALSE(xsd::isVendorExtension("VX_"));
     EXPECT_FALSE(xsd::isVendorExtension("VX_GOOGLE_$$"));
@@ -276,6 +476,11 @@
     DeviceAddress speaker;
     speaker.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_SPEAKER);
     ConvertDeviceAddress(speaker);
+
+    DeviceAddress micWithAddress;
+    micWithAddress.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_BUILTIN_MIC);
+    micWithAddress.address.id("bottom");
+    ConvertDeviceAddress(micWithAddress);
 }
 
 TEST(HidlUtils, ConvertA2dpDeviceAddress) {
@@ -347,6 +552,9 @@
     AudioFormat invalid;
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatFromHal(kInvalidHalFormat, &invalid));
     audio_format_t halInvalid;
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatToHal("", &halInvalid));
+    // INVALID format is not in XSD thus it's not allowed for transfer over HIDL.
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatToHal("AUDIO_FORMAT_INVALID", &halInvalid));
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatToHal("random string", &halInvalid));
 }
 
@@ -357,8 +565,9 @@
         AudioFormat formatBack;
         EXPECT_EQ(NO_ERROR, HidlUtils::audioFormatToHal(format, &halFormat))
                 << "Conversion of \"" << format << "\" failed";
-        EXPECT_TRUE(audio_is_valid_format(halFormat))
-                << "Converted format \"" << format << "\" is invalid";
+        EXPECT_EQ(enumVal != xsd::AudioFormat::AUDIO_FORMAT_DEFAULT,
+                  audio_is_valid_format(halFormat))
+                << "Validity of \"" << format << "\" is not as expected";
         EXPECT_EQ(NO_ERROR, HidlUtils::audioFormatFromHal(halFormat, &formatBack))
                 << "Conversion of format " << halFormat << " failed";
         EXPECT_EQ(format, formatBack);
@@ -430,6 +639,9 @@
     AudioSource invalid;
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioSourceFromHal(kInvalidHalSource, &invalid));
     audio_source_t halInvalid;
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioSourceToHal("", &halInvalid));
+    // INVALID source is not in XSD thus it's not allowed for transfer over HIDL.
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioSourceToHal("AUDIO_SOURCE_INVALID", &halInvalid));
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioSourceToHal("random string", &halInvalid));
 }
 
@@ -456,6 +668,15 @@
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioStreamTypeToHal("random string", &halInvalid));
 }
 
+TEST(HidlUtils, ConvertDefaultStreamType) {
+    AudioStreamType streamDefault = "";
+    audio_stream_type_t halStreamDefault;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioStreamTypeToHal(streamDefault, &halStreamDefault));
+    AudioStreamType streamDefaultBack;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioStreamTypeFromHal(halStreamDefault, &streamDefaultBack));
+    EXPECT_EQ(streamDefault, streamDefaultBack);
+}
+
 TEST(HidlUtils, ConvertStreamType) {
     for (const auto enumVal : xsdc_enum_range<xsd::AudioStreamType>{}) {
         const AudioStreamType streamType = toString(enumVal);
@@ -524,6 +745,7 @@
     AudioUsage invalid;
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioUsageFromHal(kInvalidHalUsage, &invalid));
     audio_usage_t halInvalid;
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioUsageToHal("", &halInvalid));
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioUsageToHal("random string", &halInvalid));
 }
 
@@ -543,7 +765,7 @@
 TEST(HidlUtils, ConvertInvalidOffloadInfo) {
     AudioOffloadInfo invalid;
     audio_offload_info_t halInvalid = AUDIO_INFO_INITIALIZER;
-    halInvalid.channel_mask = AUDIO_CHANNEL_INVALID;
+    halInvalid.channel_mask = kInvalidHalChannelMask;
     halInvalid.format = kInvalidHalFormat;
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioOffloadInfoFromHal(halInvalid, &invalid));
     invalid.base.channelMask = "random string";
@@ -553,9 +775,7 @@
 
 TEST(HidlUtils, ConvertOffloadInfo) {
     AudioOffloadInfo offloadInfo = {};
-    offloadInfo.base.sampleRateHz = 44100;
-    offloadInfo.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
-    offloadInfo.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
+    offloadInfo.base = generateValidConfigBase(false /*isInput*/);
     offloadInfo.streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC);
     offloadInfo.bitRatePerSecond = 320;
     offloadInfo.durationMicroseconds = -1;
@@ -574,31 +794,87 @@
 
 TEST(HidlUtils, ConvertInvalidConfig) {
     AudioConfig invalid;
-    audio_config_t halInvalid = AUDIO_CONFIG_INITIALIZER;
-    halInvalid.channel_mask = AUDIO_CHANNEL_INVALID;
-    halInvalid.format = kInvalidHalFormat;
-    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigFromHal(halInvalid, false /*isInput*/, &invalid));
-    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigFromHal(halInvalid, true /*isInput*/, &invalid));
-    invalid.base.channelMask = "random string";
-    invalid.base.format = "random string";
-    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigToHal(invalid, &halInvalid));
+    audio_config_t halInvalidChannelMask = AUDIO_CONFIG_INITIALIZER;
+    halInvalidChannelMask.channel_mask = kInvalidHalChannelMask;
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigFromHal(halInvalidChannelMask, false /*isInput*/, &invalid));
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigFromHal(halInvalidChannelMask, true /*isInput*/, &invalid));
+    audio_config_t halInvalidFormat = AUDIO_CONFIG_INITIALIZER;
+    halInvalidFormat.format = kInvalidHalFormat;
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigFromHal(halInvalidFormat, false /*isInput*/, &invalid));
+    EXPECT_EQ(BAD_VALUE,
+              HidlUtils::audioConfigFromHal(halInvalidFormat, true /*isInput*/, &invalid));
+
+    AudioConfig invalidChannelMask;
+    audio_config_t halInvalid;
+    invalidChannelMask.base.channelMask = "random string";
+    invalidChannelMask.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_DEFAULT);
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigToHal(invalidChannelMask, &halInvalid));
+    AudioConfig invalidFormat;
+    invalidFormat.base.format = "random string";
+    invalidFormat.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigToHal(invalidFormat, &halInvalid));
+}
+
+TEST(HidlUtils, ConvertConfigDefault) {
+    audio_config_t halDefault = AUDIO_CONFIG_INITIALIZER;
+    AudioConfig defaultOut, defaultIn;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigFromHal(halDefault, false /*isInput*/, &defaultOut));
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigFromHal(halDefault, true /*isInput*/, &defaultIn));
+    EXPECT_EQ(defaultOut, defaultIn);
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(defaultOut, &halDefault));
+
+    // Note: empty channel mask and config are not valid values.
+    AudioConfig defaultCfg{};
+    defaultCfg.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+    defaultCfg.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_DEFAULT);
+    audio_config_t halDefaultCfg;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(defaultCfg, &halDefaultCfg));
+    AudioConfig defaultCfgBackOut, defaultCfgBackIn;
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigFromHal(halDefaultCfg, false /*isInput*/, &defaultCfgBackOut));
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigFromHal(halDefaultCfg, true /*isInput*/, &defaultCfgBackIn));
+    EXPECT_EQ(defaultCfgBackOut, defaultCfgBackIn);
+    EXPECT_EQ(defaultCfg, defaultCfgBackOut);
 }
 
 TEST(HidlUtils, ConvertConfig) {
+    AudioConfig configOut{};
+    configOut.base = generateValidConfigBase(false /*isInput*/);
+    audio_config_t halConfigOut;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(configOut, &halConfigOut));
+    AudioConfig configOutBack;
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigFromHal(halConfigOut, false /*isInput*/, &configOutBack));
+    EXPECT_EQ(configOut, configOutBack);
+
+    AudioConfig configIn{};
+    configIn.base = generateValidConfigBase(true /*isInput*/);
+    audio_config_t halConfigIn;
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(configIn, &halConfigIn));
+    AudioConfig configInBack;
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioConfigFromHal(halConfigIn, true /*isInput*/, &configInBack));
+    EXPECT_EQ(configIn, configInBack);
+}
+
+TEST(HidlUtils, ConvertConfigWithOffloadInfo) {
     AudioConfig config = {};
-    config.base.sampleRateHz = 44100;
-    config.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
-    config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
-    config.offloadInfo.base = config.base;
-    config.offloadInfo.streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC);
-    config.offloadInfo.bitRatePerSecond = 320;
-    config.offloadInfo.durationMicroseconds = -1;
-    config.offloadInfo.bitWidth = 16;
-    config.offloadInfo.bufferSize = 1024;
-    config.offloadInfo.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA);
-    config.offloadInfo.encapsulationMode = AudioEncapsulationMode::ELEMENTARY_STREAM;
-    config.offloadInfo.contentId = 42;
-    config.offloadInfo.syncId = 13;
+    config.base = generateValidConfigBase(false /*isInput*/);
+    config.offloadInfo.info(
+            AudioOffloadInfo{.base = config.base,
+                             .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC),
+                             .bitRatePerSecond = 320,
+                             .durationMicroseconds = -1,
+                             .bitWidth = 16,
+                             .bufferSize = 1024,
+                             .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+                             .encapsulationMode = AudioEncapsulationMode::ELEMENTARY_STREAM,
+                             .contentId = 42,
+                             .syncId = 13});
     audio_config_t halConfig;
     EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(config, &halConfig));
     AudioConfig configBack;
@@ -642,18 +918,18 @@
     halInvalid.type = AUDIO_PORT_TYPE_MIX;
     halInvalid.role = AUDIO_PORT_ROLE_NONE;  // note: this is valid.
     halInvalid.config_mask = AUDIO_PORT_CONFIG_CHANNEL_MASK;
-    halInvalid.channel_mask = AUDIO_CHANNEL_INVALID;
+    halInvalid.channel_mask = kInvalidHalChannelMask;
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioPortConfigFromHal(halInvalid, &invalid));
-    invalid.base.channelMask = "random string";
+    invalid.base.channelMask.value("random string");
     EXPECT_EQ(BAD_VALUE, HidlUtils::audioPortConfigToHal(invalid, &halInvalid));
 }
 
 TEST(HidlUtils, ConvertAudioPortConfig) {
     AudioPortConfig config = {};
     config.id = 42;
-    config.base.sampleRateHz = 44100;
-    config.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
-    config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
+    config.base.sampleRateHz.value(44100);
+    config.base.channelMask.value(toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO));
+    config.base.format.value(toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT));
     config.gain.config({});
     config.gain.config().channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
     config.ext.device({});
@@ -707,3 +983,74 @@
     EXPECT_EQ(NO_ERROR, HidlUtils::audioPortToHal(portBack, &halPortBack));
     EXPECT_TRUE(audio_ports_v7_are_equal(&halPort, &halPortBack));
 }
+
+TEST(HidlUtils, ConvertInvalidAudioTags) {
+    char halTag[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+
+    hidl_vec<AudioTag> emptyTag = {{""}};
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(emptyTag, halTag));
+
+    hidl_vec<AudioTag> longTag = {{std::string(AUDIO_ATTRIBUTES_TAGS_MAX_SIZE + 1, 'A')}};
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(longTag, halTag));
+
+    hidl_vec<AudioTag> tagSeparator = {
+            {std::string(AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1, HidlUtils::sAudioTagSeparator)}};
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(tagSeparator, halTag));
+
+    hidl_vec<AudioTag> notExtensions = {{"", "random string", "VX_", "VX_GOOGLE_$$"}};
+    EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(notExtensions, halTag));
+}
+
+TEST(HidlUtils, ConvertAudioTags) {
+    hidl_vec<AudioTag> emptyTags;
+    char halEmptyTags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(emptyTags, halEmptyTags));
+    hidl_vec<AudioTag> emptyTagsBack;
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioTagsFromHal(HidlUtils::splitAudioTags(halEmptyTags), &emptyTagsBack));
+    EXPECT_EQ(emptyTags, emptyTagsBack);
+
+    hidl_vec<AudioTag> oneTag = {{"VX_GOOGLE_VR"}};
+    char halOneTag[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(oneTag, halOneTag));
+    hidl_vec<AudioTag> oneTagBack;
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioTagsFromHal(HidlUtils::splitAudioTags(halOneTag), &oneTagBack));
+    EXPECT_EQ(oneTag, oneTagBack);
+
+    hidl_vec<AudioTag> twoTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
+    char halTwoTags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+    EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(twoTags, halTwoTags));
+    hidl_vec<AudioTag> twoTagsBack;
+    EXPECT_EQ(NO_ERROR,
+              HidlUtils::audioTagsFromHal(HidlUtils::splitAudioTags(halTwoTags), &twoTagsBack));
+    EXPECT_EQ(twoTags, twoTagsBack);
+}
+
+template <typename T>
+class FilterTest : public ::testing::Test {};
+using FilterTestTypeParams = ::testing::Types<hidl_vec<AudioTag>, std::vector<std::string>>;
+TYPED_TEST_SUITE(FilterTest, FilterTestTypeParams);
+
+TYPED_TEST(FilterTest, FilterOutNonVendorTags) {
+    TypeParam emptyTags;
+    EXPECT_EQ(emptyTags, HidlUtils::filterOutNonVendorTags(emptyTags));
+
+    TypeParam allVendorTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
+    EXPECT_EQ(allVendorTags, HidlUtils::filterOutNonVendorTags(allVendorTags));
+
+    TypeParam oneVendorTag = {{"", "VX_GOOGLE_VR", "random_string"}};
+    TypeParam oneVendorTagOnly = HidlUtils::filterOutNonVendorTags(oneVendorTag);
+    EXPECT_EQ(1, oneVendorTagOnly.size());
+    EXPECT_EQ(oneVendorTag[1], oneVendorTagOnly[0]);
+
+    // The vendor extension isn't valid, however it must not be filtered out
+    // so the converter can detect the issue.
+    TypeParam oneMaybeVendorTag = {{"", "random string", "VX_GOOGLE_$$"}};
+    TypeParam oneMaybeVendorTagOnly = HidlUtils::filterOutNonVendorTags(oneMaybeVendorTag);
+    EXPECT_EQ(1, oneMaybeVendorTagOnly.size());
+    EXPECT_EQ(oneMaybeVendorTag[2], oneMaybeVendorTagOnly[0]);
+
+    TypeParam noVendorTags = {{"", "random string", "V_"}};
+    EXPECT_EQ(emptyTags, HidlUtils::filterOutNonVendorTags(noVendorTags));
+}
diff --git a/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h b/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
index b514a43..d7802cf 100644
--- a/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
+++ b/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
@@ -20,6 +20,9 @@
 
 #include <hidl/HidlSupport.h>
 #include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
 
 namespace android::hardware::audio::common::utils {
 
@@ -29,6 +32,16 @@
     return std::find(values.begin(), values.end(), e) != values.end();
 }
 
+static inline std::vector<std::string> splitString(const std::string& s, char separator) {
+    std::istringstream iss(s);
+    std::string t;
+    std::vector<std::string> result;
+    while (std::getline(iss, t, separator)) {
+        result.push_back(std::move(t));
+    }
+    return result;
+}
+
 } // namespace android::hardware::audio::common::utils
 
 #endif  // android_hardware_audio_common_HidlSupport_H_
diff --git a/audio/core/all-versions/default/Android.bp b/audio/core/all-versions/default/Android.bp
index e0f0860..dcff425 100644
--- a/audio/core/all-versions/default/Android.bp
+++ b/audio/core/all-versions/default/Android.bp
@@ -1,7 +1,6 @@
 filegroup {
     name: "android.hardware.audio-impl_srcs",
     srcs: [
-        "Conversions.cpp",
         "Device.cpp",
         "DevicesFactory.cpp",
         "ParametersUtil.cpp",
@@ -63,6 +62,7 @@
     defaults: ["android.hardware.audio-impl_default"],
     shared_libs: [
         "android.hardware.audio@2.0",
+        "android.hardware.audio@2.0-util",
         "android.hardware.audio.common@2.0",
         "android.hardware.audio.common@2.0-util",
     ],
@@ -79,6 +79,7 @@
 
     shared_libs: [
         "android.hardware.audio@4.0",
+        "android.hardware.audio@4.0-util",
         "android.hardware.audio.common@4.0",
         "android.hardware.audio.common@4.0-util",
     ],
@@ -94,6 +95,7 @@
     defaults: ["android.hardware.audio-impl_default"],
     shared_libs: [
         "android.hardware.audio@5.0",
+        "android.hardware.audio@5.0-util",
         "android.hardware.audio.common@5.0",
         "android.hardware.audio.common@5.0-util",
     ],
@@ -109,6 +111,7 @@
     defaults: ["android.hardware.audio-impl_default"],
     shared_libs: [
         "android.hardware.audio@6.0",
+        "android.hardware.audio@6.0-util",
         "android.hardware.audio.common@6.0",
         "android.hardware.audio.common@6.0-util",
     ],
@@ -129,6 +132,7 @@
     defaults: ["android.hardware.audio-impl_default"],
     shared_libs: [
         "android.hardware.audio@7.0",
+        "android.hardware.audio@7.0-util",
         "android.hardware.audio.common@7.0",
         "android.hardware.audio.common@7.0-enums",
         "android.hardware.audio.common@7.0-util",
diff --git a/audio/core/all-versions/default/Conversions.cpp b/audio/core/all-versions/default/Conversions.cpp
deleted file mode 100644
index 8e0a140..0000000
--- a/audio/core/all-versions/default/Conversions.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#include "core/default/Conversions.h"
-
-#include <stdio.h>
-
-#if MAJOR_VERSION >= 7
-#include <android_audio_policy_configuration_V7_0-enums.h>
-#endif
-#include <HidlUtils.h>
-#include <log/log.h>
-
-namespace android {
-namespace hardware {
-namespace audio {
-namespace CPP_VERSION {
-namespace implementation {
-
-using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
-
-#if MAJOR_VERSION <= 6
-std::string deviceAddressToHal(const DeviceAddress& address) {
-    audio_devices_t halDevice;
-    char halAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
-    (void)deviceAddressToHal(address, &halDevice, halAddress);
-    return halAddress;
-}
-#endif
-
-status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
-                            char* halDeviceAddress) {
-#if MAJOR_VERSION >= 5
-    return HidlUtils::deviceAddressToHal(device, halDeviceType, halDeviceAddress);
-#else
-    return HidlUtils::deviceAddressToHalImpl(device, halDeviceType, halDeviceAddress);
-#endif
-}
-
-status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
-                              DeviceAddress* device) {
-#if MAJOR_VERSION >= 5
-    return HidlUtils::deviceAddressFromHal(halDeviceType, halDeviceAddress, device);
-#else
-    return HidlUtils::deviceAddressFromHalImpl(halDeviceType, halDeviceAddress, device);
-#endif
-}
-
-#if MAJOR_VERSION >= 4
-bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst,
-                                    const struct audio_microphone_characteristic_t& src) {
-    bool status = false;
-    if (pDst != NULL) {
-        pDst->deviceId = src.device_id;
-
-        if (deviceAddressFromHal(src.device, src.address, &pDst->deviceAddress) != OK) {
-            return false;
-        }
-        pDst->channelMapping.resize(AUDIO_CHANNEL_COUNT_MAX);
-        for (size_t ch = 0; ch < pDst->channelMapping.size(); ch++) {
-            pDst->channelMapping[ch] = AudioMicrophoneChannelMapping(src.channel_mapping[ch]);
-        }
-        pDst->location = AudioMicrophoneLocation(src.location);
-        pDst->group = (AudioMicrophoneGroup)src.group;
-        pDst->indexInTheGroup = (uint32_t)src.index_in_the_group;
-        pDst->sensitivity = src.sensitivity;
-        pDst->maxSpl = src.max_spl;
-        pDst->minSpl = src.min_spl;
-        pDst->directionality = AudioMicrophoneDirectionality(src.directionality);
-        pDst->frequencyResponse.resize(src.num_frequency_responses);
-        for (size_t k = 0; k < src.num_frequency_responses; k++) {
-            pDst->frequencyResponse[k].frequency = src.frequency_responses[0][k];
-            pDst->frequencyResponse[k].level = src.frequency_responses[1][k];
-        }
-        pDst->position.x = src.geometric_location.x;
-        pDst->position.y = src.geometric_location.y;
-        pDst->position.z = src.geometric_location.z;
-
-        pDst->orientation.x = src.orientation.x;
-        pDst->orientation.y = src.orientation.y;
-        pDst->orientation.z = src.orientation.z;
-
-        status = true;
-    }
-    return status;
-}
-#endif  // MAJOR_VERSION >= 4
-
-#if MAJOR_VERSION >= 7
-namespace xsd {
-using namespace ::android::audio::policy::configuration::V7_0;
-}
-
-bool audioInputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_input_flags_t* halFlags) {
-    bool success = true;
-    *halFlags = {};
-    for (const auto& flag : flags) {
-        audio_input_flags_t halFlag;
-        if (!xsd::isUnknownAudioInOutFlag(flag) &&
-            audio_input_flag_from_string(flag.c_str(), &halFlag)) {
-            *halFlags = static_cast<audio_input_flags_t>(*halFlags | halFlag);
-        } else {
-            ALOGE("Unknown audio input flag \"%s\"", flag.c_str());
-            success = false;
-        }
-    }
-    return success;
-}
-
-bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_flags_t* halFlags) {
-    bool success = true;
-    *halFlags = {};
-    for (const auto& flag : flags) {
-        audio_output_flags_t halFlag;
-        if (!xsd::isUnknownAudioInOutFlag(flag) &&
-            audio_output_flag_from_string(flag.c_str(), &halFlag)) {
-            *halFlags = static_cast<audio_output_flags_t>(*halFlags | halFlag);
-        } else {
-            ALOGE("Unknown audio output flag \"%s\"", flag.c_str());
-            success = false;
-        }
-    }
-    return success;
-}
-#endif
-
-}  // namespace implementation
-}  // namespace CPP_VERSION
-}  // namespace audio
-}  // namespace hardware
-}  // namespace android
diff --git a/audio/core/all-versions/default/Device.cpp b/audio/core/all-versions/default/Device.cpp
index bb69f0b..70a1a4d 100644
--- a/audio/core/all-versions/default/Device.cpp
+++ b/audio/core/all-versions/default/Device.cpp
@@ -17,9 +17,7 @@
 #define LOG_TAG "DeviceHAL"
 
 #include "core/default/Device.h"
-#include <HidlUtils.h>
 #include "common/all-versions/default/EffectMap.h"
-#include "core/default/Conversions.h"
 #include "core/default/StreamIn.h"
 #include "core/default/StreamOut.h"
 #include "core/default/Util.h"
@@ -33,6 +31,8 @@
 
 #include <android/log.h>
 
+#include <HidlUtils.h>
+
 namespace android {
 namespace hardware {
 namespace audio {
@@ -135,13 +135,14 @@
 
 Return<void> Device::getInputBufferSize(const AudioConfig& config, getInputBufferSize_cb _hidl_cb) {
     audio_config_t halConfig;
-    HidlUtils::audioConfigToHal(config, &halConfig);
-    size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig);
     Result retval(Result::INVALID_ARGUMENTS);
     uint64_t bufferSize = 0;
-    if (halBufferSize != 0) {
-        retval = Result::OK;
-        bufferSize = halBufferSize;
+    if (HidlUtils::audioConfigToHal(config, &halConfig) == NO_ERROR) {
+        size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig);
+        if (halBufferSize != 0) {
+            retval = Result::OK;
+            bufferSize = halBufferSize;
+        }
     }
     _hidl_cb(retval, bufferSize);
     return Void();
@@ -153,15 +154,17 @@
                                                                 const AudioOutputFlags& flags,
                                                                 AudioConfig* suggestedConfig) {
     audio_config_t halConfig;
-    HidlUtils::audioConfigToHal(config, &halConfig);
+    if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) {
+        return {Result::INVALID_ARGUMENTS, nullptr};
+    }
     audio_stream_out_t* halStream;
     audio_devices_t halDevice;
     char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
-    if (deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
+    if (CoreUtils::deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
         return {Result::INVALID_ARGUMENTS, nullptr};
     }
     audio_output_flags_t halFlags;
-    if (!audioOutputFlagsToHal(flags, &halFlags)) {
+    if (CoreUtils::audioOutputFlagsToHal(flags, &halFlags) != NO_ERROR) {
         return {Result::INVALID_ARGUMENTS, nullptr};
     }
     ALOGV("open_output_stream handle: %d devices: %x flags: %#x "
@@ -186,16 +189,18 @@
         int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config,
         const AudioInputFlags& flags, AudioSource source, AudioConfig* suggestedConfig) {
     audio_config_t halConfig;
-    HidlUtils::audioConfigToHal(config, &halConfig);
+    if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) {
+        return {Result::INVALID_ARGUMENTS, nullptr};
+    }
     audio_stream_in_t* halStream;
     audio_devices_t halDevice;
     char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
-    if (deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
+    if (CoreUtils::deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
         return {Result::INVALID_ARGUMENTS, nullptr};
     }
     audio_input_flags_t halFlags;
     audio_source_t halSource;
-    if (!audioInputFlagsToHal(flags, &halFlags) ||
+    if (CoreUtils::audioInputFlagsToHal(flags, &halFlags) != NO_ERROR ||
         HidlUtils::audioSourceToHal(source, &halSource) != NO_ERROR) {
         return {Result::INVALID_ARGUMENTS, nullptr};
     }
@@ -248,6 +253,17 @@
 #endif
                                       const SourceMetadata& sourceMetadata,
                                       openOutputStream_cb _hidl_cb) {
+#if MAJOR_VERSION <= 6
+    if (status_t status = CoreUtils::sourceMetadataToHal(sourceMetadata, nullptr);
+        status != NO_ERROR) {
+#else
+    if (status_t status = CoreUtils::sourceMetadataToHalV7(sourceMetadata,
+                                                           false /*ignoreNonVendorTags*/, nullptr);
+        status != NO_ERROR) {
+#endif
+        _hidl_cb(analyzeStatus("sourceMetadataToHal", status), nullptr, AudioConfig{});
+        return Void();
+    }
     AudioConfig suggestedConfig;
     auto [result, streamOut] =
         openOutputStreamImpl(ioHandle, device, config, flags, &suggestedConfig);
@@ -271,7 +287,17 @@
         // This should never happen, the framework must not create as stream
         // if there is no client
         ALOGE("openInputStream called without tracks connected");
-        _hidl_cb(Result::INVALID_ARGUMENTS, nullptr, AudioConfig());
+        _hidl_cb(Result::INVALID_ARGUMENTS, nullptr, AudioConfig{});
+        return Void();
+    }
+#if MAJOR_VERSION <= 6
+    if (status_t status = CoreUtils::sinkMetadataToHal(sinkMetadata, nullptr); status != NO_ERROR) {
+#else
+    if (status_t status = CoreUtils::sinkMetadataToHalV7(sinkMetadata,
+                                                         false /*ignoreNonVendorTags*/, nullptr);
+        status != NO_ERROR) {
+#endif
+        _hidl_cb(analyzeStatus("sinkMetadataToHal", status), nullptr, AudioConfig{});
         return Void();
     }
     // Pick the first one as the main.
@@ -304,11 +330,17 @@
         const hidl_vec<AudioPortConfig>& sinks) {
     Result retval(Result::NOT_SUPPORTED);
     if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
-        std::unique_ptr<audio_port_config[]> halSources;
-        HidlUtils::audioPortConfigsToHal(sources, &halSources);
-        std::unique_ptr<audio_port_config[]> halSinks;
-        HidlUtils::audioPortConfigsToHal(sinks, &halSinks);
         audio_patch_handle_t halPatch = static_cast<audio_patch_handle_t>(patch);
+        std::unique_ptr<audio_port_config[]> halSources;
+        if (status_t status = HidlUtils::audioPortConfigsToHal(sources, &halSources);
+            status != NO_ERROR) {
+            return {analyzeStatus("audioPortConfigsToHal;sources", status), patch};
+        }
+        std::unique_ptr<audio_port_config[]> halSinks;
+        if (status_t status = HidlUtils::audioPortConfigsToHal(sinks, &halSinks);
+            status != NO_ERROR) {
+            return {analyzeStatus("audioPortConfigsToHal;sinks", status), patch};
+        }
         retval = analyzeStatus("create_audio_patch",
                                mDevice->create_audio_patch(mDevice, sources.size(), &halSources[0],
                                                            sinks.size(), &halSinks[0], &halPatch));
@@ -343,7 +375,10 @@
 Return<Result> Device::setAudioPortConfig(const AudioPortConfig& config) {
     if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
         struct audio_port_config halPortConfig;
-        HidlUtils::audioPortConfigToHal(config, &halPortConfig);
+        if (status_t status = HidlUtils::audioPortConfigToHal(config, &halPortConfig);
+            status != NO_ERROR) {
+            return analyzeStatus("audioPortConfigToHal", status);
+        }
         return analyzeStatus("set_audio_port_config",
                              mDevice->set_audio_port_config(mDevice, &halPortConfig));
     }
@@ -414,7 +449,7 @@
         mDevice->get_microphones(mDevice, &mic_array[0], &actual_mics) == 0) {
         microphones.resize(actual_mics);
         for (size_t i = 0; i < actual_mics; ++i) {
-            halToMicrophoneCharacteristics(&microphones[i], mic_array[i]);
+            (void)CoreUtils::microphoneInfoFromHal(mic_array[i], &microphones[i]);
         }
         retval = Result::OK;
     }
diff --git a/audio/core/all-versions/default/ParametersUtil.cpp b/audio/core/all-versions/default/ParametersUtil.cpp
index 694eb73..4d53645 100644
--- a/audio/core/all-versions/default/ParametersUtil.cpp
+++ b/audio/core/all-versions/default/ParametersUtil.cpp
@@ -15,11 +15,12 @@
  */
 
 #include "core/default/ParametersUtil.h"
-#include "core/default/Conversions.h"
 #include "core/default/Util.h"
 
 #include <system/audio.h>
 
+#include <util/CoreUtils.h>
+
 namespace android {
 namespace hardware {
 namespace audio {
@@ -153,7 +154,7 @@
 Result ParametersUtil::setParam(const char* name, const DeviceAddress& address) {
     audio_devices_t halDeviceType;
     char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
-    if (deviceAddressToHal(address, &halDeviceType, halDeviceAddress) != NO_ERROR) {
+    if (CoreUtils::deviceAddressToHal(address, &halDeviceType, halDeviceAddress) != NO_ERROR) {
         return Result::INVALID_ARGUMENTS;
     }
     AudioParameter params{String8(halDeviceAddress)};
diff --git a/audio/core/all-versions/default/Stream.cpp b/audio/core/all-versions/default/Stream.cpp
index c74079d..7e32573 100644
--- a/audio/core/all-versions/default/Stream.cpp
+++ b/audio/core/all-versions/default/Stream.cpp
@@ -17,8 +17,8 @@
 #define LOG_TAG "StreamHAL"
 
 #include "core/default/Stream.h"
+#include "common/all-versions/HidlSupport.h"
 #include "common/all-versions/default/EffectMap.h"
-#include "core/default/Conversions.h"
 #include "core/default/Util.h"
 
 #include <inttypes.h>
@@ -29,6 +29,7 @@
 #include <hardware/audio_effect.h>
 #include <media/AudioContainers.h>
 #include <media/TypeConverter.h>
+#include <util/CoreUtils.h>
 
 namespace android {
 namespace hardware {
@@ -37,6 +38,7 @@
 namespace implementation {
 
 using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+using ::android::hardware::audio::common::utils::splitString;
 
 Stream::Stream(bool isInput, audio_stream_t* stream) : mIsInput(isInput), mStream(stream) {
     (void)mIsInput;  // prevent 'unused field' warnings in pre-V7 versions.
@@ -220,7 +222,7 @@
     // Ensure that the separator is one character, despite that it's defined as a C string.
     static_assert(sizeof(AUDIO_PARAMETER_VALUE_LIST_SEPARATOR) == 2);
     std::vector<std::string> halFormats =
-            util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+            splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
     hidl_vec<AudioFormat> formats;
     (void)HidlUtils::audioFormatsFromHal(halFormats, &formats);
     std::vector<AudioProfile> tempProfiles;
@@ -235,7 +237,7 @@
         result = getParam(AudioParameter::keyStreamSupportedSamplingRates, &halListValue, context);
         if (result != Result::OK) break;
         std::vector<std::string> halSampleRates =
-                util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+                splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
         hidl_vec<uint32_t> sampleRates;
         sampleRates.resize(halSampleRates.size());
         for (size_t i = 0; i < sampleRates.size(); ++i) {
@@ -245,7 +247,7 @@
         result = getParam(AudioParameter::keyStreamSupportedChannels, &halListValue, context);
         if (result != Result::OK) break;
         std::vector<std::string> halChannelMasks =
-                util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+                splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
         hidl_vec<AudioChannelMask> channelMasks;
         (void)HidlUtils::audioChannelMasksFromHal(halChannelMasks, &channelMasks);
         // Create a profile.
@@ -276,23 +278,36 @@
     return Void();
 }
 
-Return<Result> Stream::setAudioProperties(const AudioConfigBase& config) {
-    audio_config_base_t halConfigBase = {};
-    status_t status = HidlUtils::audioConfigBaseToHal(config, &halConfigBase);
+Return<Result> Stream::setAudioProperties(const AudioConfigBaseOptional& config) {
+    audio_config_base_t halConfigBase = AUDIO_CONFIG_BASE_INITIALIZER;
+    bool formatSpecified, sRateSpecified, channelMaskSpecified;
+    status_t status = HidlUtils::audioConfigBaseOptionalToHal(
+            config, &halConfigBase, &formatSpecified, &sRateSpecified, &channelMaskSpecified);
     if (status != NO_ERROR) {
         return Stream::analyzeStatus("set_audio_properties", status);
     }
-    if (Result result = setParam(AudioParameter::keySamplingRate,
-                                 static_cast<int>(halConfigBase.sample_rate));
-        result != Result::OK) {
-        return result;
+    if (sRateSpecified) {
+        if (Result result = setParam(AudioParameter::keySamplingRate,
+                                     static_cast<int>(halConfigBase.sample_rate));
+            result != Result::OK) {
+            return result;
+        }
     }
-    if (Result result =
-                setParam(AudioParameter::keyChannels, static_cast<int>(halConfigBase.channel_mask));
-        result != Result::OK) {
-        return result;
+    if (channelMaskSpecified) {
+        if (Result result = setParam(AudioParameter::keyChannels,
+                                     static_cast<int>(halConfigBase.channel_mask));
+            result != Result::OK) {
+            return result;
+        }
     }
-    return setParam(AudioParameter::keyFormat, static_cast<int>(halConfigBase.format));
+    if (formatSpecified) {
+        if (Result result =
+                    setParam(AudioParameter::keyFormat, static_cast<int>(halConfigBase.format));
+            result != Result::OK) {
+            return result;
+        }
+    }
+    return Result::OK;
 }
 
 #endif  // MAJOR_VERSION <= 6
@@ -358,9 +373,10 @@
     hidl_vec<DeviceAddress> devices;
     if (retval == Result::OK) {
         devices.resize(1);
-        retval = Stream::analyzeStatus("get_devices",
-                                       deviceAddressFromHal(static_cast<audio_devices_t>(halDevice),
-                                                            nullptr, &devices[0]));
+        retval = Stream::analyzeStatus(
+                "get_devices",
+                CoreUtils::deviceAddressFromHal(static_cast<audio_devices_t>(halDevice), nullptr,
+                                                &devices[0]));
     }
     _hidl_cb(retval, devices);
     return Void();
diff --git a/audio/core/all-versions/default/StreamIn.cpp b/audio/core/all-versions/default/StreamIn.cpp
index ead7204..599f3c3 100644
--- a/audio/core/all-versions/default/StreamIn.cpp
+++ b/audio/core/all-versions/default/StreamIn.cpp
@@ -17,7 +17,6 @@
 #define LOG_TAG "StreamInHAL"
 
 #include "core/default/StreamIn.h"
-#include "core/default/Conversions.h"
 #include "core/default/Util.h"
 #include "common/all-versions/HidlSupport.h"
 
@@ -27,6 +26,7 @@
 #include <HidlUtils.h>
 #include <android/log.h>
 #include <hardware/audio.h>
+#include <util/CoreUtils.h>
 #include <utils/Trace.h>
 #include <cmath>
 #include <memory>
@@ -233,7 +233,7 @@
     return mStreamCommon->getSupportedProfiles(_hidl_cb);
 }
 
-Return<Result> StreamIn::setAudioProperties(const AudioConfigBase& config) {
+Return<Result> StreamIn::setAudioProperties(const AudioConfigBaseOptional& config) {
     return mStreamCommon->setAudioProperties(config);
 }
 
@@ -478,31 +478,74 @@
 }
 
 #if MAJOR_VERSION >= 4
-Return<void> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
-    if (mStream->update_sink_metadata == nullptr) {
-        return Void();  // not supported by the HAL
-    }
+Result StreamIn::doUpdateSinkMetadata(const SinkMetadata& sinkMetadata) {
     std::vector<record_track_metadata> halTracks;
-    halTracks.reserve(sinkMetadata.tracks.size());
-    for (auto& metadata : sinkMetadata.tracks) {
-        record_track_metadata halTrackMetadata = {.gain = metadata.gain};
-        (void)HidlUtils::audioSourceToHal(metadata.source, &halTrackMetadata.source);
-#if MAJOR_VERSION >= 5
-        if (metadata.destination.getDiscriminator() ==
-            RecordTrackMetadata::Destination::hidl_discriminator::device) {
-            (void)deviceAddressToHal(metadata.destination.device(), &halTrackMetadata.dest_device,
-                                     halTrackMetadata.dest_device_address);
+#if MAJOR_VERSION <= 6
+    (void)CoreUtils::sinkMetadataToHal(sinkMetadata, &halTracks);
+#else
+    // Validate whether a conversion to V7 is possible. This is needed
+    // to have a consistent behavior of the HAL regardless of the API
+    // version of the legacy HAL (and also to be consistent with openInputStream).
+    std::vector<record_track_metadata_v7> halTracksV7;
+    if (status_t status = CoreUtils::sinkMetadataToHalV7(
+                sinkMetadata, false /*ignoreNonVendorTags*/, &halTracksV7);
+        status == NO_ERROR) {
+        halTracks.reserve(halTracksV7.size());
+        for (auto metadata_v7 : halTracksV7) {
+            halTracks.push_back(std::move(metadata_v7.base));
         }
-#endif
-        halTracks.push_back(halTrackMetadata);
+    } else {
+        return Stream::analyzeStatus("sinkMetadataToHal", status);
     }
+#endif  // MAJOR_VERSION <= 6
     const sink_metadata_t halMetadata = {
         .track_count = halTracks.size(),
         .tracks = halTracks.data(),
     };
     mStream->update_sink_metadata(mStream, &halMetadata);
+    return Result::OK;
+}
+
+#if MAJOR_VERSION >= 7
+Result StreamIn::doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata) {
+    std::vector<record_track_metadata_v7> halTracks;
+    if (status_t status = CoreUtils::sinkMetadataToHalV7(sinkMetadata,
+                                                         false /*ignoreNonVendorTags*/, &halTracks);
+        status != NO_ERROR) {
+        return Stream::analyzeStatus("sinkMetadataToHal", status);
+    }
+    const sink_metadata_v7_t halMetadata = {
+            .track_count = halTracks.size(),
+            .tracks = halTracks.data(),
+    };
+    mStream->update_sink_metadata_v7(mStream, &halMetadata);
+    return Result::OK;
+}
+#endif  //  MAJOR_VERSION >= 7
+
+#if MAJOR_VERSION <= 6
+Return<void> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
+    if (mStream->update_sink_metadata == nullptr) {
+        return Void();  // not supported by the HAL
+    }
+    (void)doUpdateSinkMetadata(sinkMetadata);
     return Void();
 }
+#elif MAJOR_VERSION >= 7
+Return<Result> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
+    if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) {
+        if (mStream->update_sink_metadata == nullptr) {
+            return Result::NOT_SUPPORTED;
+        }
+        return doUpdateSinkMetadata(sinkMetadata);
+    } else {
+        if (mStream->update_sink_metadata_v7 == nullptr) {
+            return Result::NOT_SUPPORTED;
+        }
+        return doUpdateSinkMetadataV7(sinkMetadata);
+    }
+}
+#endif
 
 Return<void> StreamIn::getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) {
     Result retval = Result::NOT_SUPPORTED;
@@ -514,7 +557,7 @@
         mStream->get_active_microphones(mStream, &mic_array[0], &actual_mics) == 0) {
         microphones.resize(actual_mics);
         for (size_t i = 0; i < actual_mics; ++i) {
-            halToMicrophoneCharacteristics(&microphones[i], mic_array[i]);
+            (void)CoreUtils::microphoneInfoFromHal(mic_array[i], &microphones[i]);
         }
         retval = Result::OK;
     }
diff --git a/audio/core/all-versions/default/StreamOut.cpp b/audio/core/all-versions/default/StreamOut.cpp
index 5633cbb..a089f6b 100644
--- a/audio/core/all-versions/default/StreamOut.cpp
+++ b/audio/core/all-versions/default/StreamOut.cpp
@@ -29,6 +29,7 @@
 #include <HidlUtils.h>
 #include <android/log.h>
 #include <hardware/audio.h>
+#include <util/CoreUtils.h>
 #include <utils/Trace.h>
 
 namespace android {
@@ -238,7 +239,7 @@
     return mStreamCommon->getSupportedProfiles(_hidl_cb);
 }
 
-Return<Result> StreamOut::setAudioProperties(const AudioConfigBase& config) {
+Return<Result> StreamOut::setAudioProperties(const AudioConfigBaseOptional& config) {
     return mStreamCommon->setAudioProperties(config);
 }
 
@@ -585,26 +586,75 @@
 }
 
 #if MAJOR_VERSION >= 4
-Return<void> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
-    if (mStream->update_source_metadata == nullptr) {
-        return Void();  // not supported by the HAL
-    }
+Result StreamOut::doUpdateSourceMetadata(const SourceMetadata& sourceMetadata) {
     std::vector<playback_track_metadata_t> halTracks;
-    halTracks.reserve(sourceMetadata.tracks.size());
-    for (auto& metadata : sourceMetadata.tracks) {
-        playback_track_metadata_t halTrackMetadata = {.gain = metadata.gain};
-        (void)HidlUtils::audioUsageToHal(metadata.usage, &halTrackMetadata.usage);
-        (void)HidlUtils::audioContentTypeToHal(metadata.contentType,
-                                               &halTrackMetadata.content_type);
-        halTracks.push_back(std::move(halTrackMetadata));
+#if MAJOR_VERSION <= 6
+    (void)CoreUtils::sourceMetadataToHal(sourceMetadata, &halTracks);
+#else
+    // Validate whether a conversion to V7 is possible. This is needed
+    // to have a consistent behavior of the HAL regardless of the API
+    // version of the legacy HAL (and also to be consistent with openOutputStream).
+    std::vector<playback_track_metadata_v7> halTracksV7;
+    if (status_t status = CoreUtils::sourceMetadataToHalV7(
+                sourceMetadata, false /*ignoreNonVendorTags*/, &halTracksV7);
+        status == NO_ERROR) {
+        halTracks.reserve(halTracksV7.size());
+        for (auto metadata_v7 : halTracksV7) {
+            halTracks.push_back(std::move(metadata_v7.base));
+        }
+    } else {
+        return Stream::analyzeStatus("sourceMetadataToHal", status);
     }
+#endif  // MAJOR_VERSION <= 6
     const source_metadata_t halMetadata = {
         .track_count = halTracks.size(),
         .tracks = halTracks.data(),
     };
     mStream->update_source_metadata(mStream, &halMetadata);
+    return Result::OK;
+}
+
+#if MAJOR_VERSION >= 7
+Result StreamOut::doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata) {
+    std::vector<playback_track_metadata_v7> halTracks;
+    if (status_t status = CoreUtils::sourceMetadataToHalV7(
+                sourceMetadata, false /*ignoreNonVendorTags*/, &halTracks);
+        status != NO_ERROR) {
+        return Stream::analyzeStatus("sourceMetadataToHal", status);
+    }
+    const source_metadata_v7_t halMetadata = {
+            .track_count = halTracks.size(),
+            .tracks = halTracks.data(),
+    };
+    mStream->update_source_metadata_v7(mStream, &halMetadata);
+    return Result::OK;
+}
+#endif  //  MAJOR_VERSION >= 7
+
+#if MAJOR_VERSION <= 6
+Return<void> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
+    if (mStream->update_source_metadata == nullptr) {
+        return Void();  // not supported by the HAL
+    }
+    (void)doUpdateSourceMetadata(sourceMetadata);
     return Void();
 }
+#elif MAJOR_VERSION >= 7
+Return<Result> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
+    if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) {
+        if (mStream->update_source_metadata == nullptr) {
+            return Result::NOT_SUPPORTED;
+        }
+        return doUpdateSourceMetadata(sourceMetadata);
+    } else {
+        if (mStream->update_source_metadata_v7 == nullptr) {
+            return Result::NOT_SUPPORTED;
+        }
+        return doUpdateSourceMetadataV7(sourceMetadata);
+    }
+}
+#endif
+
 Return<Result> StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t /*programId*/) {
     return Result::NOT_SUPPORTED;  // TODO: propagate to legacy
 }
@@ -612,32 +662,65 @@
 
 #if MAJOR_VERSION >= 6
 Return<void> StreamOut::getDualMonoMode(getDualMonoMode_cb _hidl_cb) {
-    _hidl_cb(Result::NOT_SUPPORTED, DualMonoMode::OFF);
+    audio_dual_mono_mode_t mode = AUDIO_DUAL_MONO_MODE_OFF;
+    Result retval = mStream->get_dual_mono_mode != nullptr
+                            ? Stream::analyzeStatus("get_dual_mono_mode",
+                                                    mStream->get_dual_mono_mode(mStream, &mode))
+                            : Result::NOT_SUPPORTED;
+    _hidl_cb(retval, DualMonoMode(mode));
     return Void();
 }
 
-Return<Result> StreamOut::setDualMonoMode(DualMonoMode /*mode*/) {
-    return Result::NOT_SUPPORTED;
+Return<Result> StreamOut::setDualMonoMode(DualMonoMode mode) {
+    return mStream->set_dual_mono_mode != nullptr
+                   ? Stream::analyzeStatus(
+                             "set_dual_mono_mode",
+                             mStream->set_dual_mono_mode(mStream,
+                                                         static_cast<audio_dual_mono_mode_t>(mode)))
+                   : Result::NOT_SUPPORTED;
 }
 
 Return<void> StreamOut::getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) {
-    _hidl_cb(Result::NOT_SUPPORTED, -std::numeric_limits<float>::infinity());
+    float leveldB = -std::numeric_limits<float>::infinity();
+    Result retval = mStream->get_audio_description_mix_level != nullptr
+                            ? Stream::analyzeStatus(
+                                      "get_audio_description_mix_level",
+                                      mStream->get_audio_description_mix_level(mStream, &leveldB))
+                            : Result::NOT_SUPPORTED;
+    _hidl_cb(retval, leveldB);
     return Void();
 }
 
-Return<Result> StreamOut::setAudioDescriptionMixLevel(float /*leveldB*/) {
-    return Result::NOT_SUPPORTED;
+Return<Result> StreamOut::setAudioDescriptionMixLevel(float leveldB) {
+    return mStream->set_audio_description_mix_level != nullptr
+                   ? Stream::analyzeStatus(
+                             "set_audio_description_mix_level",
+                             mStream->set_audio_description_mix_level(mStream, leveldB))
+                   : Result::NOT_SUPPORTED;
 }
 
 Return<void> StreamOut::getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) {
-    _hidl_cb(Result::NOT_SUPPORTED,
-             // Same as AUDIO_PLAYBACK_RATE_INITIALIZER
-             PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::FAIL});
+    audio_playback_rate_t rate = AUDIO_PLAYBACK_RATE_INITIALIZER;
+    Result retval =
+            mStream->get_playback_rate_parameters != nullptr
+                    ? Stream::analyzeStatus("get_playback_rate_parameters",
+                                            mStream->get_playback_rate_parameters(mStream, &rate))
+                    : Result::NOT_SUPPORTED;
+    _hidl_cb(retval,
+             PlaybackRate{rate.mSpeed, rate.mPitch, static_cast<TimestretchMode>(rate.mStretchMode),
+                          static_cast<TimestretchFallbackMode>(rate.mFallbackMode)});
     return Void();
 }
 
-Return<Result> StreamOut::setPlaybackRateParameters(const PlaybackRate& /*playbackRate*/) {
-    return Result::NOT_SUPPORTED;
+Return<Result> StreamOut::setPlaybackRateParameters(const PlaybackRate& playbackRate) {
+    audio_playback_rate_t rate = {
+            playbackRate.speed, playbackRate.pitch,
+            static_cast<audio_timestretch_stretch_mode_t>(playbackRate.timestretchMode),
+            static_cast<audio_timestretch_fallback_mode_t>(playbackRate.fallbackMode)};
+    return mStream->set_playback_rate_parameters != nullptr
+                   ? Stream::analyzeStatus("set_playback_rate_parameters",
+                                           mStream->set_playback_rate_parameters(mStream, &rate))
+                   : Result::NOT_SUPPORTED;
 }
 
 Return<Result> StreamOut::setEventCallback(const sp<IStreamOutEventCallback>& callback) {
diff --git a/audio/core/all-versions/default/TEST_MAPPING b/audio/core/all-versions/default/TEST_MAPPING
new file mode 100644
index 0000000..d53c97a
--- /dev/null
+++ b/audio/core/all-versions/default/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "android.hardware.audio@7.0-util_tests"
+    }
+  ]
+}
diff --git a/audio/core/all-versions/default/include/core/default/Conversions.h b/audio/core/all-versions/default/include/core/default/Conversions.h
deleted file mode 100644
index 2372771..0000000
--- a/audio/core/all-versions/default/include/core/default/Conversions.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#ifndef ANDROID_HARDWARE_AUDIO_CONVERSIONS_H_
-#define ANDROID_HARDWARE_AUDIO_CONVERSIONS_H_
-
-#include PATH(android/hardware/audio/FILE_VERSION/types.h)
-
-#include <string>
-
-#include <system/audio.h>
-
-#include <VersionUtils.h>
-
-namespace android {
-namespace hardware {
-namespace audio {
-namespace CPP_VERSION {
-namespace implementation {
-
-using ::android::hardware::hidl_vec;
-using namespace ::android::hardware::audio::common::CPP_VERSION;
-using namespace ::android::hardware::audio::CPP_VERSION;
-
-#if MAJOR_VERSION <= 6
-// Temporary version for compatibility with forks of the default implementation.
-// Will be removed, do not use!
-std::string deviceAddressToHal(const DeviceAddress& address);
-#endif
-
-status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
-                            char* halDeviceAddress);
-status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
-                              DeviceAddress* device);
-
-#if MAJOR_VERSION >= 4
-bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst,
-                                    const struct audio_microphone_characteristic_t& src);
-#endif
-
-#if MAJOR_VERSION <= 6
-using AudioInputFlags =
-        ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
-using AudioOutputFlags =
-        ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
-
-inline bool audioInputFlagsToHal(AudioInputFlags flags, audio_input_flags_t* halFlags) {
-    *halFlags = static_cast<audio_input_flags_t>(flags);
-    return true;
-}
-
-inline bool audioOutputFlagsToHal(AudioOutputFlags flags, audio_output_flags_t* halFlags) {
-    *halFlags = static_cast<audio_output_flags_t>(flags);
-    return true;
-}
-#else
-bool audioInputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_input_flags_t* halFlags);
-bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_flags_t* halFlags);
-#endif
-
-}  // namespace implementation
-}  // namespace CPP_VERSION
-}  // namespace audio
-}  // namespace hardware
-}  // namespace android
-
-#endif  // ANDROID_HARDWARE_AUDIO_CONVERSIONS_H_
diff --git a/audio/core/all-versions/default/include/core/default/Device.h b/audio/core/all-versions/default/include/core/default/Device.h
index 461c253..5851fc9 100644
--- a/audio/core/all-versions/default/include/core/default/Device.h
+++ b/audio/core/all-versions/default/include/core/default/Device.h
@@ -31,6 +31,7 @@
 #include <hidl/MQDescriptor.h>
 
 #include <VersionUtils.h>
+#include <util/CoreUtils.h>
 
 namespace android {
 namespace hardware {
@@ -43,17 +44,10 @@
 using ::android::hardware::hidl_vec;
 using ::android::hardware::Return;
 using ::android::hardware::Void;
-#if MAJOR_VERSION <= 6
-using AudioInputFlags =
-        ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
-using AudioOutputFlags =
-        ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
-#else
-using AudioInputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
-using AudioOutputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
-#endif
 using namespace ::android::hardware::audio::common::CPP_VERSION;
 using namespace ::android::hardware::audio::CPP_VERSION;
+using AudioInputFlags = CoreUtils::AudioInputFlags;
+using AudioOutputFlags = CoreUtils::AudioOutputFlags;
 
 struct Device : public IDevice, public ParametersUtil {
     explicit Device(audio_hw_device_t* device);
@@ -146,6 +140,8 @@
     void closeOutputStream(audio_stream_out_t* stream);
     audio_hw_device_t* device() const { return mDevice; }
 
+    uint32_t version() const { return mDevice->common.version; }
+
   private:
     bool mIsClosed;
     audio_hw_device_t* mDevice;
@@ -161,8 +157,6 @@
     // Methods from ParametersUtil.
     char* halGetParameters(const char* keys) override;
     int halSetParameters(const char* keysAndValues) override;
-
-    uint32_t version() const { return mDevice->common.version; }
 };
 
 }  // namespace implementation
diff --git a/audio/core/all-versions/default/include/core/default/Stream.h b/audio/core/all-versions/default/include/core/default/Stream.h
index 0865992..66d60e3 100644
--- a/audio/core/all-versions/default/include/core/default/Stream.h
+++ b/audio/core/all-versions/default/include/core/default/Stream.h
@@ -77,7 +77,7 @@
     Return<Result> setFormat(AudioFormat format) override;
 #else
     Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
-    Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+    Return<Result> setAudioProperties(const AudioConfigBaseOptional& config) override;
 #endif  // MAJOR_VERSION <= 6
     Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
     Return<Result> addEffect(uint64_t effectId) override;
diff --git a/audio/core/all-versions/default/include/core/default/StreamIn.h b/audio/core/all-versions/default/include/core/default/StreamIn.h
index b861c6c..a980f3f 100644
--- a/audio/core/all-versions/default/include/core/default/StreamIn.h
+++ b/audio/core/all-versions/default/include/core/default/StreamIn.h
@@ -72,7 +72,7 @@
     Return<Result> setFormat(AudioFormat format) override;
 #else
     Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
-    Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+    Return<Result> setAudioProperties(const AudioConfigBaseOptional& config) override;
 #endif  // MAJOR_VERSION <= 6
     Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
     Return<Result> addEffect(uint64_t effectId) override;
@@ -114,9 +114,13 @@
     Return<void> createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override;
     Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override;
 #if MAJOR_VERSION >= 4
+#if MAJOR_VERSION <= 6
     Return<void> updateSinkMetadata(const SinkMetadata& sinkMetadata) override;
-    Return<void> getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override;
+#else
+    Return<Result> updateSinkMetadata(const SinkMetadata& sinkMetadata) override;
 #endif
+    Return<void> getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override;
+#endif  // MAJOR_VERSION >= 4
 #if MAJOR_VERSION >= 5
     Return<Result> setMicrophoneDirection(MicrophoneDirection direction) override;
     Return<Result> setMicrophoneFieldDimension(float zoom) override;
@@ -124,7 +128,14 @@
     static Result getCapturePositionImpl(audio_stream_in_t* stream, uint64_t* frames,
                                          uint64_t* time);
 
-   private:
+  private:
+#if MAJOR_VERSION >= 4
+    Result doUpdateSinkMetadata(const SinkMetadata& sinkMetadata);
+#if MAJOR_VERSION >= 7
+    Result doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata);
+#endif
+#endif  // MAJOR_VERSION >= 4
+
     const sp<Device> mDevice;
     audio_stream_in_t* mStream;
     const sp<Stream> mStreamCommon;
diff --git a/audio/core/all-versions/default/include/core/default/StreamOut.h b/audio/core/all-versions/default/include/core/default/StreamOut.h
index 9f64e3e..ccc1c1a 100644
--- a/audio/core/all-versions/default/include/core/default/StreamOut.h
+++ b/audio/core/all-versions/default/include/core/default/StreamOut.h
@@ -72,7 +72,7 @@
     Return<Result> setFormat(AudioFormat format) override;
 #else
     Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
-    Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+    Return<Result> setAudioProperties(const AudioConfigBaseOptional& config) override;
 #endif  // MAJOR_VERSION <= 6
     Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
     Return<Result> addEffect(uint64_t effectId) override;
@@ -123,9 +123,13 @@
     Return<void> createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override;
     Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override;
 #if MAJOR_VERSION >= 4
-    Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
     Return<Result> selectPresentation(int32_t presentationId, int32_t programId) override;
+#if MAJOR_VERSION <= 6
+    Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
+#else
+    Return<Result> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
 #endif
+#endif  // MAJOR_VERSION >= 4
 #if MAJOR_VERSION >= 6
     Return<void> getDualMonoMode(getDualMonoMode_cb _hidl_cb) override;
     Return<Result> setDualMonoMode(DualMonoMode mode) override;
@@ -143,6 +147,13 @@
 #endif
 
   private:
+#if MAJOR_VERSION >= 4
+    Result doUpdateSourceMetadata(const SourceMetadata& sourceMetadata);
+#if MAJOR_VERSION >= 7
+    Result doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata);
+#endif
+#endif  // MAJOR_VERSION >= 4
+
     const sp<Device> mDevice;
     audio_stream_out_t* mStream;
     const sp<Stream> mStreamCommon;
diff --git a/audio/core/all-versions/default/include/core/default/Util.h b/audio/core/all-versions/default/include/core/default/Util.h
index 3d629cd..78ae03e 100644
--- a/audio/core/all-versions/default/include/core/default/Util.h
+++ b/audio/core/all-versions/default/include/core/default/Util.h
@@ -20,8 +20,6 @@
 #include PATH(android/hardware/audio/FILE_VERSION/types.h)
 
 #include <algorithm>
-#include <sstream>
-#include <string>
 #include <vector>
 
 #include <system/audio.h>
@@ -72,16 +70,6 @@
     return analyzeStatus(status);
 }
 
-static inline std::vector<std::string> splitString(const std::string& s, char separator) {
-    std::istringstream iss(s);
-    std::string t;
-    std::vector<std::string> result;
-    while (std::getline(iss, t, separator)) {
-        result.push_back(std::move(t));
-    }
-    return result;
-}
-
 }  // namespace util
 }  // namespace implementation
 }  // namespace CPP_VERSION
diff --git a/audio/core/all-versions/default/util/Android.bp b/audio/core/all-versions/default/util/Android.bp
new file mode 100644
index 0000000..447184b
--- /dev/null
+++ b/audio/core/all-versions/default/util/Android.bp
@@ -0,0 +1,139 @@
+cc_defaults {
+    name: "android.hardware.audio-util_default",
+    defaults: ["hidl_defaults"],
+
+    vendor_available: true,
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "CoreUtils.cpp",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libhidlbase",
+        "android.hardware.audio.common-util",
+    ],
+    export_shared_lib_headers: [
+        "android.hardware.audio.common-util",
+    ],
+
+    header_libs: [
+        "libaudio_system_headers",
+        "libhardware_headers",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.audio@2.0-util",
+    defaults: ["android.hardware.audio-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@2.0",
+        "android.hardware.audio.common@2.0-util",
+        "android.hardware.audio@2.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=2",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.audio@4.0-util",
+    defaults: ["android.hardware.audio-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@4.0",
+        "android.hardware.audio.common@4.0-util",
+        "android.hardware.audio@4.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=4",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.audio@5.0-util",
+    defaults: ["android.hardware.audio-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@5.0",
+        "android.hardware.audio.common@5.0-util",
+        "android.hardware.audio@5.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=5",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.audio@6.0-util",
+    defaults: ["android.hardware.audio-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@6.0",
+        "android.hardware.audio.common@6.0-util",
+        "android.hardware.audio@6.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=6",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+cc_library {
+    name: "android.hardware.audio@7.0-util",
+    defaults: ["android.hardware.audio-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@7.0",
+        "android.hardware.audio.common@7.0-enums",
+        "android.hardware.audio.common@7.0-util",
+        "android.hardware.audio@7.0",
+        "libbase",
+        "libxml2",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=7",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+// Note: this isn't a VTS test, but rather a unit test
+// to verify correctness of conversion utilities.
+cc_test {
+    name: "android.hardware.audio@7.0-util_tests",
+    defaults: ["android.hardware.audio-util_default"],
+
+    srcs: ["tests/coreutils_tests.cpp"],
+
+    // Use static linking to allow running in presubmit on
+    // targets that don't have HAL V7.
+    static_libs: [
+        "android.hardware.audio.common@7.0",
+        "android.hardware.audio.common@7.0-enums",
+        "android.hardware.audio.common@7.0-util",
+        "android.hardware.audio@7.0",
+        "android.hardware.audio@7.0-util",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libxml2",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-DMAJOR_VERSION=7",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+
+    test_suites: ["device-tests"],
+}
diff --git a/audio/core/all-versions/default/util/CoreUtils.cpp b/audio/core/all-versions/default/util/CoreUtils.cpp
new file mode 100644
index 0000000..14f76f3
--- /dev/null
+++ b/audio/core/all-versions/default/util/CoreUtils.cpp
@@ -0,0 +1,476 @@
+/*
+ * 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.
+ */
+
+#if MAJOR_VERSION >= 7
+#include <android_audio_policy_configuration_V7_0-enums.h>
+#endif
+#include <HidlUtils.h>
+#include <log/log.h>
+
+#include "util/CoreUtils.h"
+
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+#if MAJOR_VERSION >= 7
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+#endif
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace CPP_VERSION {
+namespace implementation {
+
+#define CONVERT_CHECKED(expr, result)                   \
+    if (status_t status = (expr); status != NO_ERROR) { \
+        result = status;                                \
+    }
+
+status_t CoreUtils::deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
+                                       char* halDeviceAddress) {
+#if MAJOR_VERSION >= 5
+    return HidlUtils::deviceAddressToHal(device, halDeviceType, halDeviceAddress);
+#else
+    return HidlUtils::deviceAddressToHalImpl(device, halDeviceType, halDeviceAddress);
+#endif
+}
+
+status_t CoreUtils::deviceAddressFromHal(audio_devices_t halDeviceType,
+                                         const char* halDeviceAddress, DeviceAddress* device) {
+#if MAJOR_VERSION >= 5
+    return HidlUtils::deviceAddressFromHal(halDeviceType, halDeviceAddress, device);
+#else
+    return HidlUtils::deviceAddressFromHalImpl(halDeviceType, halDeviceAddress, device);
+#endif
+}
+
+#if MAJOR_VERSION >= 4
+status_t CoreUtils::microphoneInfoFromHal(
+        const struct audio_microphone_characteristic_t& halMicInfo, MicrophoneInfo* micInfo) {
+    status_t result = NO_ERROR;
+    micInfo->deviceId = halMicInfo.device_id;
+    CONVERT_CHECKED(
+            deviceAddressFromHal(halMicInfo.device, halMicInfo.address, &micInfo->deviceAddress),
+            result);
+    size_t chCount;
+    for (chCount = 0; chCount < AUDIO_CHANNEL_COUNT_MAX; ++chCount) {
+        if (halMicInfo.channel_mapping[chCount] == AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED) {
+            break;
+        }
+    }
+    micInfo->channelMapping.resize(chCount);
+    for (size_t ch = 0; ch < micInfo->channelMapping.size(); ch++) {
+        micInfo->channelMapping[ch] = AudioMicrophoneChannelMapping(halMicInfo.channel_mapping[ch]);
+    }
+    micInfo->location = AudioMicrophoneLocation(halMicInfo.location);
+    micInfo->group = AudioMicrophoneGroup(halMicInfo.group);
+    micInfo->indexInTheGroup = static_cast<uint32_t>(halMicInfo.index_in_the_group);
+    micInfo->sensitivity = halMicInfo.sensitivity;
+    micInfo->maxSpl = halMicInfo.max_spl;
+    micInfo->minSpl = halMicInfo.min_spl;
+    micInfo->directionality = AudioMicrophoneDirectionality(halMicInfo.directionality);
+    micInfo->frequencyResponse.resize(halMicInfo.num_frequency_responses);
+    for (size_t k = 0; k < halMicInfo.num_frequency_responses; k++) {
+        micInfo->frequencyResponse[k].frequency = halMicInfo.frequency_responses[0][k];
+        micInfo->frequencyResponse[k].level = halMicInfo.frequency_responses[1][k];
+    }
+    micInfo->position.x = halMicInfo.geometric_location.x;
+    micInfo->position.y = halMicInfo.geometric_location.y;
+    micInfo->position.z = halMicInfo.geometric_location.z;
+    micInfo->orientation.x = halMicInfo.orientation.x;
+    micInfo->orientation.y = halMicInfo.orientation.y;
+    micInfo->orientation.z = halMicInfo.orientation.z;
+    return result;
+}
+
+status_t CoreUtils::microphoneInfoToHal(const MicrophoneInfo& micInfo,
+                                        audio_microphone_characteristic_t* halMicInfo) {
+    status_t result = NO_ERROR;
+    strncpy(halMicInfo->device_id, micInfo.deviceId.c_str(), AUDIO_MICROPHONE_ID_MAX_LEN);
+    halMicInfo->device_id[AUDIO_MICROPHONE_ID_MAX_LEN - 1] = '\0';
+    if (micInfo.deviceId.size() >= AUDIO_MICROPHONE_ID_MAX_LEN) {
+        ALOGE("HIDL MicrophoneInfo device ID is too long: %zu", micInfo.deviceId.size());
+        result = BAD_VALUE;
+    }
+    CONVERT_CHECKED(
+            deviceAddressToHal(micInfo.deviceAddress, &halMicInfo->device, halMicInfo->address),
+            result);
+    if (micInfo.channelMapping.size() > AUDIO_CHANNEL_COUNT_MAX) {
+        ALOGE("HIDL MicrophoneInfo has too many channelMapping elements: %zu",
+              micInfo.channelMapping.size());
+        result = BAD_VALUE;
+    }
+    size_t ch;
+    for (ch = 0; ch < micInfo.channelMapping.size() && ch < AUDIO_CHANNEL_COUNT_MAX; ch++) {
+        halMicInfo->channel_mapping[ch] =
+                static_cast<audio_microphone_channel_mapping_t>(micInfo.channelMapping[ch]);
+    }
+    for (; ch < AUDIO_CHANNEL_COUNT_MAX; ch++) {
+        halMicInfo->channel_mapping[ch] = AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED;
+    }
+    halMicInfo->location = static_cast<audio_microphone_location_t>(micInfo.location);
+    halMicInfo->group = static_cast<audio_microphone_group_t>(micInfo.group);
+    halMicInfo->index_in_the_group = static_cast<unsigned int>(micInfo.indexInTheGroup);
+    halMicInfo->sensitivity = micInfo.sensitivity;
+    halMicInfo->max_spl = micInfo.maxSpl;
+    halMicInfo->min_spl = micInfo.minSpl;
+    halMicInfo->directionality =
+            static_cast<audio_microphone_directionality_t>(micInfo.directionality);
+    halMicInfo->num_frequency_responses =
+            static_cast<unsigned int>(micInfo.frequencyResponse.size());
+    if (halMicInfo->num_frequency_responses > AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES) {
+        ALOGE("HIDL MicrophoneInfo has too many frequency responses: %u",
+              halMicInfo->num_frequency_responses);
+        halMicInfo->num_frequency_responses = AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES;
+        result = BAD_VALUE;
+    }
+    for (size_t k = 0; k < halMicInfo->num_frequency_responses; k++) {
+        halMicInfo->frequency_responses[0][k] = micInfo.frequencyResponse[k].frequency;
+        halMicInfo->frequency_responses[1][k] = micInfo.frequencyResponse[k].level;
+    }
+    halMicInfo->geometric_location.x = micInfo.position.x;
+    halMicInfo->geometric_location.y = micInfo.position.y;
+    halMicInfo->geometric_location.z = micInfo.position.z;
+    halMicInfo->orientation.x = micInfo.orientation.x;
+    halMicInfo->orientation.y = micInfo.orientation.y;
+    halMicInfo->orientation.z = micInfo.orientation.z;
+    return result;
+}
+
+status_t CoreUtils::sinkMetadataFromHal(const std::vector<record_track_metadata_t>& halTracks,
+                                        SinkMetadata* sinkMetadata) {
+    status_t result = NO_ERROR;
+    sinkMetadata->tracks.resize(halTracks.size());
+    for (size_t i = 0; i < sinkMetadata->tracks.size(); ++i) {
+        const auto& halTrackMetadata = halTracks[i];
+        RecordTrackMetadata trackMetadata{};
+        CONVERT_CHECKED(
+                HidlUtils::audioSourceFromHal(halTrackMetadata.source, &trackMetadata.source),
+                result);
+        trackMetadata.gain = halTrackMetadata.gain;
+#if MAJOR_VERSION >= 5
+        if (halTrackMetadata.dest_device != AUDIO_DEVICE_NONE) {
+            DeviceAddress address;
+            if (status_t status =
+                        deviceAddressFromHal(halTrackMetadata.dest_device,
+                                             halTrackMetadata.dest_device_address, &address);
+                status == NO_ERROR) {
+                trackMetadata.destination.device(std::move(address));
+            } else {
+                result = status;
+            }
+        }
+#if MAJOR_VERSION >= 7
+        trackMetadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+#endif
+#endif  // MAJOR_VERSION >= 5
+        sinkMetadata->tracks[i] = std::move(trackMetadata);
+    }
+    return result;
+}
+
+status_t CoreUtils::sinkMetadataFromHalV7(const std::vector<record_track_metadata_v7_t>& halTracks,
+                                          bool ignoreNonVendorTags, SinkMetadata* sinkMetadata) {
+    std::vector<record_track_metadata_t> bases;
+    bases.reserve(halTracks.size());
+    std::transform(halTracks.begin(), halTracks.end(), std::back_inserter(bases),
+                   [](const record_track_metadata_v7_t& src) -> record_track_metadata_t {
+                       record_track_metadata_t result;
+                       record_track_metadata_from_v7(&result, &src);
+                       return result;
+                   });
+    status_t result = sinkMetadataFromHal(bases, sinkMetadata);
+#if MAJOR_VERSION >= 7
+    for (size_t i = 0; i < halTracks.size(); ++i) {
+        auto& trackMetadata = sinkMetadata->tracks[i];
+        const auto& halTrackMetadata = halTracks[i];
+        CONVERT_CHECKED(
+                HidlUtils::audioChannelMaskFromHal(halTrackMetadata.channel_mask, true /*isInput*/,
+                                                   &trackMetadata.channelMask),
+                result);
+        std::vector<std::string> strTags = HidlUtils::splitAudioTags(halTrackMetadata.tags);
+        if (ignoreNonVendorTags) {
+            strTags = HidlUtils::filterOutNonVendorTags(strTags);
+        }
+        CONVERT_CHECKED(HidlUtils::audioTagsFromHal(strTags, &trackMetadata.tags), result);
+    }
+#else
+    (void)ignoreNonVendorTags;
+#endif
+    return result;
+}
+
+status_t CoreUtils::sinkMetadataToHal(const SinkMetadata& sinkMetadata,
+                                      std::vector<record_track_metadata_t>* halTracks) {
+    status_t result = NO_ERROR;
+    if (halTracks != nullptr) {
+        halTracks->reserve(sinkMetadata.tracks.size());
+    }
+    for (auto& trackMetadata : sinkMetadata.tracks) {
+        record_track_metadata halTrackMetadata{.gain = trackMetadata.gain};
+        CONVERT_CHECKED(HidlUtils::audioSourceToHal(trackMetadata.source, &halTrackMetadata.source),
+                        result);
+#if MAJOR_VERSION >= 5
+        if (trackMetadata.destination.getDiscriminator() ==
+            RecordTrackMetadata::Destination::hidl_discriminator::device) {
+            CONVERT_CHECKED(deviceAddressToHal(trackMetadata.destination.device(),
+                                               &halTrackMetadata.dest_device,
+                                               halTrackMetadata.dest_device_address),
+                            result);
+        }
+#endif
+        if (halTracks != nullptr) {
+            halTracks->push_back(std::move(halTrackMetadata));
+        }
+    }
+    return result;
+}
+
+status_t CoreUtils::sinkMetadataToHalV7(const SinkMetadata& sinkMetadata, bool ignoreNonVendorTags,
+                                        std::vector<record_track_metadata_v7_t>* halTracks) {
+    std::vector<record_track_metadata> bases;
+    status_t result = sinkMetadataToHal(sinkMetadata, halTracks != nullptr ? &bases : nullptr);
+    if (halTracks != nullptr) {
+        halTracks->reserve(sinkMetadata.tracks.size());
+    }
+    for (size_t i = 0; i < sinkMetadata.tracks.size(); ++i) {
+        record_track_metadata_v7_t halTrackMetadata;
+        if (halTracks != nullptr) {
+            record_track_metadata_to_v7(&halTrackMetadata, &bases[i]);
+        }
+#if MAJOR_VERSION >= 7
+        const auto& trackMetadata = sinkMetadata.tracks[i];
+        CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(trackMetadata.channelMask,
+                                                         &halTrackMetadata.channel_mask),
+                        result);
+        if (ignoreNonVendorTags) {
+            CONVERT_CHECKED(
+                    HidlUtils::audioTagsToHal(HidlUtils::filterOutNonVendorTags(trackMetadata.tags),
+                                              halTrackMetadata.tags),
+                    result);
+        } else {
+            CONVERT_CHECKED(HidlUtils::audioTagsToHal(trackMetadata.tags, halTrackMetadata.tags),
+                            result);
+        }
+#else
+        (void)ignoreNonVendorTags;
+#endif
+        if (halTracks != nullptr) {
+            halTracks->push_back(std::move(halTrackMetadata));
+        }
+    }
+    return result;
+}
+
+status_t CoreUtils::sourceMetadataFromHal(const std::vector<playback_track_metadata_t>& halTracks,
+                                          SourceMetadata* sourceMetadata) {
+    status_t result = NO_ERROR;
+    sourceMetadata->tracks.resize(halTracks.size());
+    for (size_t i = 0; i < sourceMetadata->tracks.size(); ++i) {
+        const auto& halTrackMetadata = halTracks[i];
+        PlaybackTrackMetadata trackMetadata{};
+        CONVERT_CHECKED(HidlUtils::audioUsageFromHal(halTrackMetadata.usage, &trackMetadata.usage),
+                        result);
+        CONVERT_CHECKED(HidlUtils::audioContentTypeFromHal(halTrackMetadata.content_type,
+                                                           &trackMetadata.contentType),
+                        result);
+        trackMetadata.gain = halTrackMetadata.gain;
+#if MAJOR_VERSION >= 7
+        trackMetadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+#endif
+        sourceMetadata->tracks[i] = std::move(trackMetadata);
+    }
+    return result;
+}
+
+status_t CoreUtils::sourceMetadataFromHalV7(
+        const std::vector<playback_track_metadata_v7_t>& halTracks, bool ignoreNonVendorTags,
+        SourceMetadata* sourceMetadata) {
+    std::vector<playback_track_metadata_t> bases;
+    bases.reserve(halTracks.size());
+    std::transform(halTracks.begin(), halTracks.end(), std::back_inserter(bases),
+                   [](const playback_track_metadata_v7_t& src) -> playback_track_metadata_t {
+                       playback_track_metadata_t result;
+                       playback_track_metadata_from_v7(&result, &src);
+                       return result;
+                   });
+    status_t result = sourceMetadataFromHal(bases, sourceMetadata);
+#if MAJOR_VERSION >= 7
+    for (size_t i = 0; i < halTracks.size(); ++i) {
+        auto& trackMetadata = sourceMetadata->tracks[i];
+        const auto& halTrackMetadata = halTracks[i];
+        CONVERT_CHECKED(
+                HidlUtils::audioChannelMaskFromHal(halTrackMetadata.channel_mask, false /*isInput*/,
+                                                   &trackMetadata.channelMask),
+                result);
+        std::vector<std::string> strTags = HidlUtils::splitAudioTags(halTrackMetadata.tags);
+        if (ignoreNonVendorTags) {
+            strTags = HidlUtils::filterOutNonVendorTags(strTags);
+        }
+        CONVERT_CHECKED(HidlUtils::audioTagsFromHal(strTags, &trackMetadata.tags), result);
+    }
+#else
+    (void)ignoreNonVendorTags;
+#endif
+    return result;
+}
+
+status_t CoreUtils::sourceMetadataToHal(const SourceMetadata& sourceMetadata,
+                                        std::vector<playback_track_metadata_t>* halTracks) {
+    status_t result = NO_ERROR;
+    if (halTracks != nullptr) {
+        halTracks->reserve(sourceMetadata.tracks.size());
+    }
+    for (auto& trackMetadata : sourceMetadata.tracks) {
+        playback_track_metadata_t halTrackMetadata{.gain = trackMetadata.gain};
+        CONVERT_CHECKED(HidlUtils::audioUsageToHal(trackMetadata.usage, &halTrackMetadata.usage),
+                        result);
+        CONVERT_CHECKED(HidlUtils::audioContentTypeToHal(trackMetadata.contentType,
+                                                         &halTrackMetadata.content_type),
+                        result);
+        if (halTracks != nullptr) {
+            halTracks->push_back(std::move(halTrackMetadata));
+        }
+    }
+    return result;
+}
+
+status_t CoreUtils::sourceMetadataToHalV7(const SourceMetadata& sourceMetadata,
+                                          bool ignoreNonVendorTags,
+                                          std::vector<playback_track_metadata_v7_t>* halTracks) {
+    std::vector<playback_track_metadata_t> bases;
+    status_t result = sourceMetadataToHal(sourceMetadata, halTracks != nullptr ? &bases : nullptr);
+    if (halTracks != nullptr) {
+        halTracks->reserve(sourceMetadata.tracks.size());
+    }
+    for (size_t i = 0; i < sourceMetadata.tracks.size(); ++i) {
+        playback_track_metadata_v7_t halTrackMetadata;
+        if (halTracks != nullptr) {
+            playback_track_metadata_to_v7(&halTrackMetadata, &bases[i]);
+        }
+#if MAJOR_VERSION >= 7
+        const auto& trackMetadata = sourceMetadata.tracks[i];
+        CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(trackMetadata.channelMask,
+                                                         &halTrackMetadata.channel_mask),
+                        result);
+        if (ignoreNonVendorTags) {
+            CONVERT_CHECKED(
+                    HidlUtils::audioTagsToHal(HidlUtils::filterOutNonVendorTags(trackMetadata.tags),
+                                              halTrackMetadata.tags),
+                    result);
+        } else {
+            CONVERT_CHECKED(HidlUtils::audioTagsToHal(trackMetadata.tags, halTrackMetadata.tags),
+                            result);
+        }
+#else
+        (void)ignoreNonVendorTags;
+#endif
+        if (halTracks != nullptr) {
+            halTracks->push_back(std::move(halTrackMetadata));
+        }
+    }
+    return result;
+}
+#endif  // MAJOR_VERSION >= 4
+
+#if MAJOR_VERSION >= 7
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+
+status_t CoreUtils::audioInputFlagsFromHal(audio_input_flags_t halFlagMask,
+                                           AudioInputFlags* flags) {
+    status_t status = NO_ERROR;
+    std::vector<AudioInOutFlag> result;
+    for (uint32_t bit = 0; halFlagMask != 0 && bit < sizeof(audio_input_flags_t) * 8; ++bit) {
+        audio_input_flags_t flag = static_cast<audio_input_flags_t>(1u << bit);
+        if ((flag & halFlagMask) == flag) {
+            AudioInOutFlag flagStr = audio_input_flag_to_string(flag);
+            if (!flagStr.empty() && !xsd::isUnknownAudioInOutFlag(flagStr)) {
+                result.push_back(flagStr);
+            } else {
+                ALOGE("Unknown audio input flag value 0x%X", flag);
+                status = BAD_VALUE;
+            }
+            halFlagMask = static_cast<audio_input_flags_t>(halFlagMask & ~flag);
+        }
+    }
+    *flags = result;
+    return status;
+}
+
+status_t CoreUtils::audioInputFlagsToHal(const AudioInputFlags& flags,
+                                         audio_input_flags_t* halFlagMask) {
+    status_t status = NO_ERROR;
+    *halFlagMask = {};
+    for (const auto& flag : flags) {
+        audio_input_flags_t halFlag;
+        if (!xsd::isUnknownAudioInOutFlag(flag) &&
+            audio_input_flag_from_string(flag.c_str(), &halFlag)) {
+            *halFlagMask = static_cast<audio_input_flags_t>(*halFlagMask | halFlag);
+        } else {
+            ALOGE("Unknown audio input flag \"%s\"", flag.c_str());
+            status = BAD_VALUE;
+        }
+    }
+    return status;
+}
+
+status_t CoreUtils::audioOutputFlagsFromHal(audio_output_flags_t halFlagMask,
+                                            AudioOutputFlags* flags) {
+    status_t status = NO_ERROR;
+    std::vector<AudioInOutFlag> result;
+    for (uint32_t bit = 0; halFlagMask != 0 && bit < sizeof(audio_output_flags_t) * 8; ++bit) {
+        audio_output_flags_t flag = static_cast<audio_output_flags_t>(1u << bit);
+        if ((flag & halFlagMask) == flag) {
+            AudioInOutFlag flagStr = audio_output_flag_to_string(flag);
+            if (!flagStr.empty() && !xsd::isUnknownAudioInOutFlag(flagStr)) {
+                result.push_back(flagStr);
+            } else {
+                ALOGE("Unknown audio output flag value 0x%X", flag);
+                status = BAD_VALUE;
+            }
+            halFlagMask = static_cast<audio_output_flags_t>(halFlagMask & ~flag);
+        }
+    }
+    *flags = result;
+    return status;
+}
+
+status_t CoreUtils::audioOutputFlagsToHal(const AudioOutputFlags& flags,
+                                          audio_output_flags_t* halFlagMask) {
+    status_t status = NO_ERROR;
+    *halFlagMask = {};
+    for (const auto& flag : flags) {
+        audio_output_flags_t halFlag;
+        if (!xsd::isUnknownAudioInOutFlag(flag) &&
+            audio_output_flag_from_string(flag.c_str(), &halFlag)) {
+            *halFlagMask = static_cast<audio_output_flags_t>(*halFlagMask | halFlag);
+        } else {
+            ALOGE("Unknown audio output flag \"%s\"", flag.c_str());
+            status = BAD_VALUE;
+        }
+    }
+    return status;
+}
+#endif
+
+}  // namespace implementation
+}  // namespace CPP_VERSION
+}  // namespace audio
+}  // namespace hardware
+}  // namespace android
diff --git a/audio/core/all-versions/default/util/include/util/CoreUtils.h b/audio/core/all-versions/default/util/include/util/CoreUtils.h
new file mode 100644
index 0000000..1e5272a
--- /dev/null
+++ b/audio/core/all-versions/default/util/include/util/CoreUtils.h
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+// clang-format off
+#include PATH(android/hardware/audio/FILE_VERSION/types.h)
+// clang-format off
+
+#include <vector>
+
+#include <system/audio.h>
+
+#include <common/all-versions/VersionUtils.h>
+#include <VersionUtils.h>
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace CPP_VERSION {
+namespace implementation {
+
+using ::android::hardware::audio::common::utils::EnumBitfield;
+using ::android::hardware::hidl_vec;
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using namespace ::android::hardware::audio::CPP_VERSION;
+
+struct CoreUtils {
+    // Note: the converters for DeviceAddress have to be in CoreUtils for HAL V4
+    // because DeviceAddress used to be defined in the core HAL. For V5 and above
+    // these functions simply delegate to HidlUtils.
+    static status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
+            char* halDeviceAddress);
+    static status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
+            DeviceAddress* device);
+#if MAJOR_VERSION >= 4
+    static status_t microphoneInfoFromHal(const struct audio_microphone_characteristic_t& halMicInfo,
+            MicrophoneInfo* micInfo);
+    static status_t microphoneInfoToHal(const MicrophoneInfo& micInfo, audio_microphone_characteristic_t* halMicInfo);
+    // Note: {Sink|Source}Metadata types are defined in 'common' (since V5), so they can be used
+    // by the BT HAL. However, the converters are defined here, not in HidlUtils to avoid adding
+    // conditionals to handle V4. The converters are only used by 'core' HAL anyways.
+    static status_t sinkMetadataFromHal(const std::vector<record_track_metadata_t>& halTracks,
+            SinkMetadata* sinkMetadata);
+    static status_t sinkMetadataFromHalV7(const std::vector<record_track_metadata_v7_t>& halTracks,
+            bool ignoreNonVendorTags, SinkMetadata* sinkMetadata);
+    static status_t sinkMetadataToHal(const SinkMetadata& sinkMetadata,
+            std::vector<record_track_metadata_t>* halTracks);
+    static status_t sinkMetadataToHalV7(const SinkMetadata& sinkMetadata, bool ignoreNonVendorTags,
+            std::vector<record_track_metadata_v7_t>* halTracks);
+    static status_t sourceMetadataFromHal(const std::vector<playback_track_metadata_t>& halTracks,
+            SourceMetadata* sourceMetadata);
+    static status_t sourceMetadataFromHalV7(const std::vector<playback_track_metadata_v7_t>& halTracks,
+            bool ignoreNonVendorTags, SourceMetadata* sourceMetadata);
+    static status_t sourceMetadataToHal(const SourceMetadata& sourceMetadata,
+            std::vector<playback_track_metadata_t>* halTracks);
+    static status_t sourceMetadataToHalV7(const SourceMetadata& sourceMetadata, bool ignoreNonVendorTags,
+            std::vector<playback_track_metadata_v7_t>* halTracks);
+#endif
+
+#if MAJOR_VERSION <= 6
+    using AudioInputFlags =
+            ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
+    using AudioOutputFlags =
+            ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
+    static inline status_t audioInputFlagsFromHal(audio_input_flags_t halFlagMask, AudioInputFlags* flags) {
+        *flags = EnumBitfield<AudioInputFlag>(halFlagMask);
+        return NO_ERROR;
+    }
+    static inline status_t audioInputFlagsToHal(AudioInputFlags flags, audio_input_flags_t* halFlagMask) {
+        *halFlagMask = static_cast<audio_input_flags_t>(flags);
+        return NO_ERROR;
+    }
+    static inline status_t audioOutputFlagsFromHal(audio_output_flags_t halFlagMask, AudioOutputFlags* flags) {
+        *flags = EnumBitfield<AudioOutputFlag>(halFlagMask);
+        return NO_ERROR;
+    }
+    static inline status_t audioOutputFlagsToHal(AudioOutputFlags flags, audio_output_flags_t* halFlagMask) {
+        *halFlagMask = static_cast<audio_output_flags_t>(flags);
+        return NO_ERROR;
+    }
+#else
+    using AudioInputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
+    using AudioOutputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
+    static status_t audioInputFlagsFromHal(audio_input_flags_t halFlagMask, AudioInputFlags* flags);
+    static status_t audioInputFlagsToHal(const AudioInputFlags& flags, audio_input_flags_t* halFlagMask);
+    static status_t audioOutputFlagsFromHal(audio_output_flags_t halFlagMask, AudioOutputFlags* flags);
+    static status_t audioOutputFlagsToHal(const AudioOutputFlags& flags, audio_output_flags_t* halFlagMask);
+#endif
+
+};
+
+}  // namespace implementation
+}  // namespace CPP_VERSION
+}  // namespace audio
+}  // namespace hardware
+}  // namespace android
diff --git a/audio/core/all-versions/default/util/tests/coreutils_tests.cpp b/audio/core/all-versions/default/util/tests/coreutils_tests.cpp
new file mode 100644
index 0000000..0c18482
--- /dev/null
+++ b/audio/core/all-versions/default/util/tests/coreutils_tests.cpp
@@ -0,0 +1,525 @@
+/*
+ * 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 <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#define LOG_TAG "CoreUtils_Test"
+#include <log/log.h>
+
+#include <android_audio_policy_configuration_V7_0-enums.h>
+#include <system/audio.h>
+#include <util/CoreUtils.h>
+#include <xsdc/XsdcSupport.h>
+
+using namespace android;
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using namespace ::android::hardware::audio::CPP_VERSION;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::audio::CPP_VERSION::implementation::CoreUtils;
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+
+static constexpr audio_channel_mask_t kInvalidHalChannelMask = AUDIO_CHANNEL_INVALID;
+static constexpr audio_content_type_t kInvalidHalContentType =
+        static_cast<audio_content_type_t>(0xFFFFFFFFU);
+static constexpr audio_devices_t kInvalidHalDevice = static_cast<audio_devices_t>(0xFFFFFFFFU);
+static constexpr audio_input_flags_t kInvalidInputFlags =
+        static_cast<audio_input_flags_t>(0xFFFFFFFFU);
+static constexpr audio_output_flags_t kInvalidOutputFlags =
+        static_cast<audio_output_flags_t>(0xFFFFFFFFU);
+// AUDIO_SOURCE_INVALID is framework-only.
+static constexpr audio_source_t kInvalidHalSource = static_cast<audio_source_t>(-1);
+static constexpr audio_usage_t kInvalidHalUsage = static_cast<audio_usage_t>(0xFFFFFFFFU);
+
+static bool isInputFlag(xsd::AudioInOutFlag flag) {
+    return toString(flag).find("_INPUT_FLAG_") != std::string::npos;
+}
+
+static bool isOutputFlag(xsd::AudioInOutFlag flag) {
+    return toString(flag).find("_OUTPUT_FLAG_") != std::string::npos;
+}
+
+TEST(CoreUtils, ConvertInvalidInputFlagMask) {
+    CoreUtils::AudioInputFlags invalid;
+    EXPECT_EQ(BAD_VALUE, CoreUtils::audioInputFlagsFromHal(kInvalidInputFlags, &invalid));
+    audio_input_flags_t halInvalid;
+    invalid.resize(1);
+    invalid[0] = "random string";
+    EXPECT_EQ(BAD_VALUE, CoreUtils::audioInputFlagsToHal(invalid, &halInvalid));
+}
+
+TEST(CoreUtils, ConvertInputFlagMask) {
+    CoreUtils::AudioInputFlags emptyInputFlags;
+    audio_input_flags_t halEmptyInputFlags;
+    EXPECT_EQ(NO_ERROR, CoreUtils::audioInputFlagsToHal(emptyInputFlags, &halEmptyInputFlags));
+    EXPECT_EQ(AUDIO_INPUT_FLAG_NONE, halEmptyInputFlags);
+    CoreUtils::AudioInputFlags emptyInputFlagsBack;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::audioInputFlagsFromHal(halEmptyInputFlags, &emptyInputFlagsBack));
+    EXPECT_EQ(emptyInputFlags, emptyInputFlagsBack);
+    CoreUtils::AudioInputFlags emptyInputFlagsFromNone;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::audioInputFlagsFromHal(AUDIO_INPUT_FLAG_NONE, &emptyInputFlagsFromNone));
+    EXPECT_EQ(emptyInputFlags, emptyInputFlagsFromNone);
+
+    std::vector<std::string> allEnumValues;
+    for (const auto enumVal : xsdc_enum_range<xsd::AudioInOutFlag>{}) {
+        if (isInputFlag(enumVal)) {
+            allEnumValues.push_back(toString(enumVal));
+        }
+    }
+    CoreUtils::AudioInputFlags allInputFlags;
+    allInputFlags.resize(allEnumValues.size());
+    for (size_t i = 0; i < allEnumValues.size(); ++i) {
+        allInputFlags[i] = allEnumValues[i];
+    }
+    audio_input_flags_t halAllInputFlags;
+    EXPECT_EQ(NO_ERROR, CoreUtils::audioInputFlagsToHal(allInputFlags, &halAllInputFlags));
+    CoreUtils::AudioInputFlags allInputFlagsBack;
+    EXPECT_EQ(NO_ERROR, CoreUtils::audioInputFlagsFromHal(halAllInputFlags, &allInputFlagsBack));
+    EXPECT_EQ(allInputFlags, allInputFlagsBack);
+}
+
+TEST(CoreUtils, ConvertInvalidOutputFlagMask) {
+    CoreUtils::AudioOutputFlags invalid;
+    EXPECT_EQ(BAD_VALUE, CoreUtils::audioOutputFlagsFromHal(kInvalidOutputFlags, &invalid));
+    audio_output_flags_t halInvalid;
+    invalid.resize(1);
+    invalid[0] = "random string";
+    EXPECT_EQ(BAD_VALUE, CoreUtils::audioOutputFlagsToHal(invalid, &halInvalid));
+}
+
+TEST(CoreUtils, ConvertOutputFlagMask) {
+    CoreUtils::AudioOutputFlags emptyOutputFlags;
+    audio_output_flags_t halEmptyOutputFlags;
+    EXPECT_EQ(NO_ERROR, CoreUtils::audioOutputFlagsToHal(emptyOutputFlags, &halEmptyOutputFlags));
+    EXPECT_EQ(AUDIO_OUTPUT_FLAG_NONE, halEmptyOutputFlags);
+    CoreUtils::AudioOutputFlags emptyOutputFlagsBack;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::audioOutputFlagsFromHal(halEmptyOutputFlags, &emptyOutputFlagsBack));
+    EXPECT_EQ(emptyOutputFlags, emptyOutputFlagsBack);
+    CoreUtils::AudioOutputFlags emptyOutputFlagsFromNone;
+    EXPECT_EQ(NO_ERROR, CoreUtils::audioOutputFlagsFromHal(AUDIO_OUTPUT_FLAG_NONE,
+                                                           &emptyOutputFlagsFromNone));
+    EXPECT_EQ(emptyOutputFlags, emptyOutputFlagsFromNone);
+
+    std::vector<std::string> allEnumValues;
+    for (const auto enumVal : xsdc_enum_range<xsd::AudioInOutFlag>{}) {
+        if (isOutputFlag(enumVal)) {
+            allEnumValues.push_back(toString(enumVal));
+        }
+    }
+    CoreUtils::AudioOutputFlags allOutputFlags;
+    allOutputFlags.resize(allEnumValues.size());
+    for (size_t i = 0; i < allEnumValues.size(); ++i) {
+        allOutputFlags[i] = allEnumValues[i];
+    }
+    audio_output_flags_t halAllOutputFlags;
+    EXPECT_EQ(NO_ERROR, CoreUtils::audioOutputFlagsToHal(allOutputFlags, &halAllOutputFlags));
+    CoreUtils::AudioOutputFlags allOutputFlagsBack;
+    EXPECT_EQ(NO_ERROR, CoreUtils::audioOutputFlagsFromHal(halAllOutputFlags, &allOutputFlagsBack));
+    EXPECT_EQ(allOutputFlags, allOutputFlagsBack);
+}
+
+static MicrophoneInfo generateValidMicrophoneInfo() {
+    MicrophoneInfo micInfo{};
+    micInfo.deviceAddress.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_BUILTIN_MIC);
+    micInfo.channelMapping.resize(1);
+    micInfo.channelMapping[0] = AudioMicrophoneChannelMapping::DIRECT;
+    micInfo.location = AudioMicrophoneLocation::MAINBODY_MOVABLE;
+    micInfo.group = 42;
+    micInfo.indexInTheGroup = 13;
+    micInfo.sensitivity = 65.5;
+    micInfo.maxSpl = 100.5;
+    micInfo.minSpl = 36.6;
+    micInfo.directionality = AudioMicrophoneDirectionality::HYPER_CARDIOID;
+    micInfo.frequencyResponse.resize(1);
+    micInfo.frequencyResponse[0].frequency = 1000;
+    micInfo.frequencyResponse[0].level = 85;
+    micInfo.position.x = 0;
+    micInfo.position.y = 1;
+    micInfo.position.z = 0;
+    micInfo.orientation.x = 0;
+    micInfo.orientation.y = 0;
+    micInfo.orientation.z = 1;
+    return micInfo;
+}
+
+TEST(CoreUtils, ConvertInvalidMicrophoneInfo) {
+    MicrophoneInfo invalid;
+    audio_microphone_characteristic_t halInvalid{};
+    halInvalid.device = kInvalidHalDevice;
+    EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoFromHal(halInvalid, &invalid));
+
+    MicrophoneInfo oversizeDeviceId = generateValidMicrophoneInfo();
+    oversizeDeviceId.deviceId = std::string(AUDIO_MICROPHONE_ID_MAX_LEN + 1, 'A');
+    EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoToHal(oversizeDeviceId, &halInvalid));
+    MicrophoneInfo invalidDeviceType = generateValidMicrophoneInfo();
+    invalidDeviceType.deviceAddress.deviceType = "random string";
+    EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoToHal(invalidDeviceType, &halInvalid));
+    MicrophoneInfo oversizeChannelMapping = generateValidMicrophoneInfo();
+    oversizeChannelMapping.channelMapping.resize(AUDIO_CHANNEL_COUNT_MAX + 1);
+    EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoToHal(oversizeChannelMapping, &halInvalid));
+    MicrophoneInfo oversizeFrequencyResponses = generateValidMicrophoneInfo();
+    oversizeFrequencyResponses.frequencyResponse.resize(AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES +
+                                                        1);
+    EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoToHal(oversizeFrequencyResponses, &halInvalid));
+}
+
+TEST(CoreUtils, ConvertMicrophoneInfo) {
+    MicrophoneInfo micInfo = generateValidMicrophoneInfo();
+    audio_microphone_characteristic_t halMicInfo;
+    EXPECT_EQ(NO_ERROR, CoreUtils::microphoneInfoToHal(micInfo, &halMicInfo));
+    MicrophoneInfo micInfoBack;
+    EXPECT_EQ(NO_ERROR, CoreUtils::microphoneInfoFromHal(halMicInfo, &micInfoBack));
+    EXPECT_EQ(micInfo, micInfoBack);
+}
+
+static RecordTrackMetadata generateMinimalRecordTrackMetadata() {
+    RecordTrackMetadata metadata{};
+    metadata.source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT);
+    metadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+    return metadata;
+}
+
+static RecordTrackMetadata generateValidRecordTrackMetadata() {
+    RecordTrackMetadata metadata = generateMinimalRecordTrackMetadata();
+    metadata.tags.resize(1);
+    metadata.tags[0] = "VX_GOOGLE_42";
+    metadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO);
+    metadata.gain = 1.0;
+    return metadata;
+}
+
+static RecordTrackMetadata generateValidRecordTrackMetadataWithDevice() {
+    RecordTrackMetadata metadata = generateValidRecordTrackMetadata();
+    metadata.destination.device({});
+    metadata.destination.device().deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_SPEAKER);
+    return metadata;
+}
+
+using SinkTracks = hidl_vec<RecordTrackMetadata>;
+
+TEST(CoreUtils, ConvertInvalidSinkMetadata) {
+    SinkMetadata invalidSource;
+    invalidSource.tracks = SinkTracks{generateMinimalRecordTrackMetadata()};
+    invalidSource.tracks[0].source = "random string";
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHal(invalidSource, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidSource,
+                                                        false /*ignoreNonVendorTags*/, nullptr));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sinkMetadataToHalV7(invalidSource, true /*ignoreNonVendorTags*/, nullptr));
+    SinkMetadata invalidDeviceType;
+    invalidDeviceType.tracks = SinkTracks{generateValidRecordTrackMetadataWithDevice()};
+    invalidDeviceType.tracks[0].destination.device().deviceType = "random string";
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHal(invalidDeviceType, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidDeviceType,
+                                                        false /*ignoreNonVendorTags*/, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidDeviceType,
+                                                        true /*ignoreNonVendorTags*/, nullptr));
+    SinkMetadata invalidChannelMask;
+    invalidChannelMask.tracks = SinkTracks{generateValidRecordTrackMetadata()};
+    invalidChannelMask.tracks[0].channelMask = "random string";
+    // Channel mask is sliced away by 'sinkMetadataToHal'
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHal(invalidChannelMask, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidChannelMask,
+                                                        false /*ignoreNonVendorTags*/, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidChannelMask,
+                                                        true /*ignoreNonVendorTags*/, nullptr));
+    SinkMetadata invalidTags;
+    invalidTags.tracks = SinkTracks{generateValidRecordTrackMetadata()};
+    invalidTags.tracks[0].tags[0] = "random string";
+    // Tags are sliced away by 'sinkMetadataToHal'
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHal(invalidTags, nullptr));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sinkMetadataToHalV7(invalidTags, false /*ignoreNonVendorTags*/, nullptr));
+    // Non-vendor tags should be filtered out.
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sinkMetadataToHalV7(invalidTags, true /*ignoreNonVendorTags*/, nullptr));
+
+    // Verify that a default-initialized metadata is valid.
+    std::vector<record_track_metadata_t> halValid(1, record_track_metadata_t{});
+    std::vector<record_track_metadata_v7_t> halValidV7(1, record_track_metadata_v7_t{});
+    SinkMetadata valid;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHal(halValid, &valid));
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sinkMetadataFromHalV7(halValidV7, false /*ignoreNonVendorTags*/, &valid));
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sinkMetadataFromHalV7(halValidV7, true /*ignoreNonVendorTags*/, &valid));
+
+    std::vector<record_track_metadata_t> halInvalidSource = {{.source = kInvalidHalSource}};
+    std::vector<record_track_metadata_v7_t> halInvalidSourceV7 = {
+            {.base = {.source = kInvalidHalSource}}};
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataFromHal(halInvalidSource, &invalidSource));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sinkMetadataFromHalV7(halInvalidSourceV7, false /*ignoreNonVendorTags*/,
+                                               &invalidSource));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataFromHalV7(
+                                 halInvalidSourceV7, true /*ignoreNonVendorTags*/, &invalidSource));
+    std::vector<record_track_metadata_t> halInvalidDeviceType = {
+            {.dest_device = kInvalidHalDevice}};
+    std::vector<record_track_metadata_v7_t> halInvalidDeviceTypeV7 = {
+            {.base = {.dest_device = kInvalidHalDevice}}};
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataFromHal(halInvalidDeviceType, &invalidDeviceType));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sinkMetadataFromHalV7(halInvalidDeviceTypeV7,
+                                               false /*ignoreNonVendorTags*/, &invalidDeviceType));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sinkMetadataFromHalV7(halInvalidDeviceTypeV7, true /*ignoreNonVendorTags*/,
+                                               &invalidDeviceType));
+    std::vector<record_track_metadata_v7_t> halInvalidChannelMaskV7 = {
+            {.channel_mask = kInvalidHalChannelMask}};
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sinkMetadataFromHalV7(halInvalidChannelMaskV7,
+                                               false /*ignoreNonVendorTags*/, &invalidChannelMask));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sinkMetadataFromHalV7(halInvalidChannelMaskV7,
+                                               true /*ignoreNonVendorTags*/, &invalidChannelMask));
+    std::vector<record_track_metadata_v7_t> halInvalidTagsV7 = {{.tags = "random string"}};
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataFromHalV7(
+                                 halInvalidTagsV7, false /*ignoreNonVendorTags*/, &invalidTags));
+    // Non-vendor tags should be filtered out.
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHalV7(
+                                halInvalidTagsV7, true /*ignoreNonVendorTags*/, &invalidTags));
+}
+
+TEST(CoreUtils, ConvertEmptySinkMetadata) {
+    SinkMetadata emptySinkMetadata;
+    std::vector<record_track_metadata_t> halEmptySinkMetadata;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHal(emptySinkMetadata, &halEmptySinkMetadata));
+    EXPECT_TRUE(halEmptySinkMetadata.empty());
+    SinkMetadata emptySinkMetadataBack;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sinkMetadataFromHal(halEmptySinkMetadata, &emptySinkMetadataBack));
+    EXPECT_EQ(emptySinkMetadata, emptySinkMetadataBack);
+    std::vector<record_track_metadata_v7_t> halEmptySinkMetadataV7;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sinkMetadataToHalV7(emptySinkMetadata, false /*ignoreNonVendorTags*/,
+                                             &halEmptySinkMetadataV7));
+    EXPECT_TRUE(halEmptySinkMetadataV7.empty());
+    SinkMetadata emptySinkMetadataBackFromV7;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHalV7(halEmptySinkMetadataV7,
+                                                         false /*ignoreNonVendorTags*/,
+                                                         &emptySinkMetadataBackFromV7));
+    EXPECT_EQ(emptySinkMetadata, emptySinkMetadataBackFromV7);
+}
+
+class SinkMetadataConvertTest : public ::testing::TestWithParam<SinkTracks> {};
+
+TEST_P(SinkMetadataConvertTest, ToFromHal) {
+    SinkMetadata sinkMetadata;
+    sinkMetadata.tracks = GetParam();
+    std::vector<record_track_metadata_t> halSinkMetadata;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHal(sinkMetadata, &halSinkMetadata));
+    EXPECT_EQ(sinkMetadata.tracks.size(), halSinkMetadata.size());
+    SinkMetadata sinkMetadataBackTrimmed;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHal(halSinkMetadata, &sinkMetadataBackTrimmed));
+    // Can't compare 'sinkMetadata' to 'sinkMetadataBackTrimmed'
+    std::vector<record_track_metadata_v7_t> halSinkMetadataV7;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHalV7(sinkMetadata, false /*ignoreNonVendorTags*/,
+                                                       &halSinkMetadataV7));
+    EXPECT_EQ(sinkMetadata.tracks.size(), halSinkMetadataV7.size());
+    SinkMetadata sinkMetadataBackFromV7;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sinkMetadataFromHalV7(halSinkMetadataV7, false /*ignoreNonVendorTags*/,
+                                               &sinkMetadataBackFromV7));
+    EXPECT_EQ(sinkMetadata, sinkMetadataBackFromV7);
+    std::vector<record_track_metadata_v7_t> halSinkMetadataV7FromTrimmed;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sinkMetadataToHalV7(sinkMetadataBackTrimmed, false /*ignoreNonVendorTags*/,
+                                             &halSinkMetadataV7FromTrimmed));
+    EXPECT_EQ(sinkMetadata.tracks.size(), halSinkMetadataV7FromTrimmed.size());
+    SinkMetadata sinkMetadataBackFromV7Trimmed;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHalV7(halSinkMetadataV7FromTrimmed,
+                                                         false /*ignoreNonVendorTags*/,
+                                                         &sinkMetadataBackFromV7Trimmed));
+    EXPECT_EQ(sinkMetadataBackTrimmed, sinkMetadataBackFromV7Trimmed);
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidRecordTrackMetadatas, SinkMetadataConvertTest,
+                         ::testing::Values(SinkTracks{generateMinimalRecordTrackMetadata()},
+                                           SinkTracks{generateValidRecordTrackMetadata()},
+                                           SinkTracks{generateValidRecordTrackMetadataWithDevice()},
+                                           SinkTracks{
+                                                   generateMinimalRecordTrackMetadata(),
+                                                   generateValidRecordTrackMetadata(),
+                                                   generateValidRecordTrackMetadataWithDevice()}));
+
+static PlaybackTrackMetadata generateMinimalPlaybackTrackMetadata() {
+    PlaybackTrackMetadata metadata{};
+    metadata.usage = toString(xsd::AudioUsage::AUDIO_USAGE_UNKNOWN);
+    metadata.contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN);
+    metadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+    return metadata;
+}
+
+static PlaybackTrackMetadata generateValidPlaybackTrackMetadata() {
+    PlaybackTrackMetadata metadata = generateMinimalPlaybackTrackMetadata();
+    metadata.tags.resize(1);
+    metadata.tags[0] = "VX_GOOGLE_42";
+    metadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO);
+    metadata.gain = 1.0;
+    return metadata;
+}
+
+using SourceTracks = hidl_vec<PlaybackTrackMetadata>;
+
+TEST(CoreUtils, ConvertInvalidSourceMetadata) {
+    SourceMetadata invalidUsage;
+    invalidUsage.tracks = SourceTracks{generateMinimalPlaybackTrackMetadata()};
+    invalidUsage.tracks[0].usage = "random string";
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHal(invalidUsage, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidUsage,
+                                                          false /*ignoreNonVendorTags*/, nullptr));
+    SourceMetadata invalidContentType;
+    invalidContentType.tracks = SourceTracks{generateMinimalPlaybackTrackMetadata()};
+    invalidContentType.tracks[0].contentType = "random string";
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHal(invalidContentType, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidContentType,
+                                                          false /*ignoreNonVendorTags*/, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidContentType,
+                                                          true /*ignoreNonVendorTags*/, nullptr));
+    SourceMetadata invalidChannelMask;
+    invalidChannelMask.tracks = SourceTracks{generateValidPlaybackTrackMetadata()};
+    invalidChannelMask.tracks[0].channelMask = "random string";
+    // Channel mask is sliced away by 'sourceMetadataToHal'
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataToHal(invalidChannelMask, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidChannelMask,
+                                                          false /*ignoreNonVendorTags*/, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidChannelMask,
+                                                          true /*ignoreNonVendorTags*/, nullptr));
+    SourceMetadata invalidTags;
+    invalidTags.tracks = SourceTracks{generateValidPlaybackTrackMetadata()};
+    invalidTags.tracks[0].tags[0] = "random string";
+    // Tags are sliced away by 'sourceMetadataToHal'
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataToHal(invalidTags, nullptr));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidTags,
+                                                          false /*ignoreNonVendorTags*/, nullptr));
+    // Non-vendor tags should be filtered out.
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sourceMetadataToHalV7(invalidTags, true /*ignoreNonVendorTags*/, nullptr));
+
+    // Verify that a default-initialized metadata is valid.
+    std::vector<playback_track_metadata_t> halValid(1, playback_track_metadata_t{});
+    std::vector<playback_track_metadata_v7_t> halValidV7(1, playback_track_metadata_v7_t{});
+    SourceMetadata valid;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHal(halValid, &valid));
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHalV7(halValidV7,
+                                                           false /*ignoreNonVendorTags*/, &valid));
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sourceMetadataFromHalV7(halValidV7, true /*ignoreNonVendorTags*/, &valid));
+
+    std::vector<playback_track_metadata_t> halInvalidUsage = {{.usage = kInvalidHalUsage}};
+    std::vector<playback_track_metadata_v7_t> halInvalidUsageV7 = {
+            {.base = {.usage = kInvalidHalUsage}}};
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataFromHal(halInvalidUsage, &invalidUsage));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataFromHalV7(
+                                 halInvalidUsageV7, false /*ignoreNonVendorTags*/, &invalidUsage));
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataFromHalV7(
+                                 halInvalidUsageV7, true /*ignoreNonVendorTags*/, &invalidUsage));
+    std::vector<playback_track_metadata_t> halInvalidContentType = {
+            {.content_type = kInvalidHalContentType}};
+    std::vector<playback_track_metadata_v7_t> halInvalidContentTypeV7 = {
+            {.base = {.content_type = kInvalidHalContentType}}};
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sourceMetadataFromHal(halInvalidContentType, &invalidContentType));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sourceMetadataFromHalV7(
+                      halInvalidContentTypeV7, false /*ignoreNonVendorTags*/, &invalidContentType));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sourceMetadataFromHalV7(
+                      halInvalidContentTypeV7, true /*ignoreNonVendorTags*/, &invalidContentType));
+    std::vector<playback_track_metadata_v7_t> halInvalidChannelMaskV7 = {
+            {.channel_mask = kInvalidHalChannelMask}};
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sourceMetadataFromHalV7(
+                      halInvalidChannelMaskV7, false /*ignoreNonVendorTags*/, &invalidChannelMask));
+    EXPECT_EQ(BAD_VALUE,
+              CoreUtils::sourceMetadataFromHalV7(
+                      halInvalidChannelMaskV7, true /*ignoreNonVendorTags*/, &invalidChannelMask));
+    std::vector<playback_track_metadata_v7_t> halInvalidTagsV7 = {{.tags = "random string"}};
+    EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataFromHalV7(
+                                 halInvalidTagsV7, false /*ignoreNonVendorTags*/, &invalidTags));
+    // Non-vendor tags should be filtered out.
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHalV7(
+                                halInvalidTagsV7, true /*ignoreNonVendorTags*/, &invalidTags));
+}
+
+TEST(CoreUtils, ConvertEmptySourceMetadata) {
+    SourceMetadata emptySourceMetadata;
+    std::vector<playback_track_metadata_t> halEmptySourceMetadata;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sourceMetadataToHal(emptySourceMetadata, &halEmptySourceMetadata));
+    EXPECT_TRUE(halEmptySourceMetadata.empty());
+    SourceMetadata emptySourceMetadataBack;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sourceMetadataFromHal(halEmptySourceMetadata, &emptySourceMetadataBack));
+    EXPECT_EQ(emptySourceMetadata, emptySourceMetadataBack);
+    std::vector<playback_track_metadata_v7_t> halEmptySourceMetadataV7;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sourceMetadataToHalV7(emptySourceMetadata, false /*ignoreNonVendorTags*/,
+                                               &halEmptySourceMetadataV7));
+    EXPECT_TRUE(halEmptySourceMetadataV7.empty());
+    SourceMetadata emptySourceMetadataBackFromV7;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHalV7(halEmptySourceMetadataV7,
+                                                           false /*ignoreNonVendorTags*/,
+                                                           &emptySourceMetadataBackFromV7));
+    EXPECT_EQ(emptySourceMetadata, emptySourceMetadataBackFromV7);
+}
+
+class SourceMetadataConvertTest : public ::testing::TestWithParam<SourceTracks> {};
+
+TEST_P(SourceMetadataConvertTest, ToFromHal) {
+    SourceMetadata sourceMetadata;
+    sourceMetadata.tracks = GetParam();
+    std::vector<playback_track_metadata_t> halSourceMetadata;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataToHal(sourceMetadata, &halSourceMetadata));
+    EXPECT_EQ(sourceMetadata.tracks.size(), halSourceMetadata.size());
+    SourceMetadata sourceMetadataBackTrimmed;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sourceMetadataFromHal(halSourceMetadata, &sourceMetadataBackTrimmed));
+    // Can't compare 'sourceMetadata' to 'sourceMetadataBackTrimmed'
+    std::vector<playback_track_metadata_v7_t> halSourceMetadataV7;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sourceMetadataToHalV7(sourceMetadata, false /*ignoreNonVendorTags*/,
+                                               &halSourceMetadataV7));
+    EXPECT_EQ(sourceMetadata.tracks.size(), halSourceMetadataV7.size());
+    SourceMetadata sourceMetadataBackFromV7;
+    EXPECT_EQ(NO_ERROR,
+              CoreUtils::sourceMetadataFromHalV7(halSourceMetadataV7, false /*ignoreNonVendorTags*/,
+                                                 &sourceMetadataBackFromV7));
+    EXPECT_EQ(sourceMetadata, sourceMetadataBackFromV7);
+    std::vector<playback_track_metadata_v7_t> halSourceMetadataV7FromTrimmed;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataToHalV7(sourceMetadataBackTrimmed,
+                                                         false /*ignoreNonVendorTags*/,
+                                                         &halSourceMetadataV7FromTrimmed));
+    EXPECT_EQ(sourceMetadata.tracks.size(), halSourceMetadataV7FromTrimmed.size());
+    SourceMetadata sourceMetadataBackFromV7Trimmed;
+    EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHalV7(halSourceMetadataV7FromTrimmed,
+                                                           false /*ignoreNonVendorTags*/,
+                                                           &sourceMetadataBackFromV7Trimmed));
+    EXPECT_EQ(sourceMetadataBackTrimmed, sourceMetadataBackFromV7Trimmed);
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidPlaybackTrackMetadatas, SourceMetadataConvertTest,
+                         ::testing::Values(SourceTracks{generateMinimalPlaybackTrackMetadata()},
+                                           SourceTracks{generateValidPlaybackTrackMetadata()},
+                                           SourceTracks{generateMinimalPlaybackTrackMetadata(),
+                                                        generateValidPlaybackTrackMetadata()}));
diff --git a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
index 9a4a8b2..f87e5ed 100644
--- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
@@ -72,7 +72,10 @@
         config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
         hidl_vec<hidl_string> flags;
         const SinkMetadata initMetadata = {
-                {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC), .gain = 1}}};
+                {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC),
+                  .gain = 1,
+                  .tags = {},
+                  .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
 #endif
         EventFlag* efGroup;
         for (auto microphone : microphones) {
@@ -234,22 +237,14 @@
 
 TEST_P(InputStreamTest, updateSinkMetadata) {
     doc::test("The HAL should not crash on metadata change");
-
 #if MAJOR_VERSION <= 6
     hidl_enum_range<AudioSource> range;
-#elif MAJOR_VERSION >= 7
-    xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioSource> range;
-#endif
     // Test all possible track configuration
     for (auto source : range) {
         for (float volume : {0.0, 0.5, 1.0}) {
-#if MAJOR_VERSION <= 6
             const SinkMetadata metadata = {{{.source = source, .gain = volume}}};
-#elif MAJOR_VERSION >= 7
-            const SinkMetadata metadata = {{{.source = toString(source), .gain = volume}}};
-#endif
             ASSERT_OK(stream->updateSinkMetadata(metadata))
-                << "source=" << toString(source) << ", volume=" << volume;
+                    << "source=" << toString(source) << ", volume=" << volume;
         }
     }
 
@@ -257,9 +252,30 @@
 
     // Set no metadata as if all stream track had stopped
     ASSERT_OK(stream->updateSinkMetadata({}));
-
     // Restore initial
     ASSERT_OK(stream->updateSinkMetadata(initMetadata));
+
+#elif MAJOR_VERSION >= 7
+    xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioSource> range;
+    // Test all possible track configuration
+    for (auto source : range) {
+        for (float volume : {0.0, 0.5, 1.0}) {
+            const SinkMetadata metadata = {
+                    {{.source = toString(source),
+                      .gain = volume,
+                      .tags = {},
+                      .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
+            ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata(metadata))
+                    << "source=" << toString(source) << ", volume=" << volume;
+        }
+    }
+    // Do not test concurrent capture as this is not officially supported
+
+    // Set no metadata as if all stream track had stopped
+    ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata({}));
+    // Restore initial
+    ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata(initMetadata));
+#endif
 }
 
 TEST_P(OutputStreamTest, SelectPresentation) {
@@ -269,56 +285,82 @@
 
 TEST_P(OutputStreamTest, updateSourceMetadata) {
     doc::test("The HAL should not crash on metadata change");
-
 #if MAJOR_VERSION <= 6
     hidl_enum_range<AudioUsage> usageRange;
     hidl_enum_range<AudioContentType> contentRange;
-#elif MAJOR_VERSION >= 7
-    xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioUsage> usageRange;
-    xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioContentType> contentRange;
-#endif
     // Test all possible track configuration
     for (auto usage : usageRange) {
         for (auto content : contentRange) {
             for (float volume : {0.0, 0.5, 1.0}) {
-#if MAJOR_VERSION <= 6
                 const SourceMetadata metadata = {{{usage, content, volume}}};
-#elif MAJOR_VERSION >= 7
-                const SourceMetadata metadata = {{{toString(usage), toString(content), volume}}};
-#endif
                 ASSERT_OK(stream->updateSourceMetadata(metadata))
                     << "usage=" << toString(usage) << ", content=" << toString(content)
                     << ", volume=" << volume;
             }
         }
     }
-
-    // clang-format off
     // Set many track of different configuration
+    // clang-format off
     ASSERT_OK(stream->updateSourceMetadata(
-#if MAJOR_VERSION <= 6
         {{{AudioUsage::MEDIA, AudioContentType::MUSIC, 0.1},
           {AudioUsage::VOICE_COMMUNICATION, AudioContentType::SPEECH, 1.0},
           {AudioUsage::ALARM, AudioContentType::SONIFICATION, 0.0},
           {AudioUsage::ASSISTANT, AudioContentType::UNKNOWN, 0.3}}}
-#elif MAJOR_VERSION >= 7
-        {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
-                      toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), 0.1},
-          {toString(xsd::AudioUsage::AUDIO_USAGE_VOICE_COMMUNICATION),
-                      toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SPEECH), 1.0},
-          {toString(xsd::AudioUsage::AUDIO_USAGE_ALARM),
-                      toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SONIFICATION), 0.0},
-          {toString(xsd::AudioUsage::AUDIO_USAGE_ASSISTANT),
-                      toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN), 0.3}}}
-#endif
     ));
     // clang-format on
-
     // Set no metadata as if all stream track had stopped
     ASSERT_OK(stream->updateSourceMetadata({}));
-
     // Restore initial
     ASSERT_OK(stream->updateSourceMetadata(initMetadata));
+#elif MAJOR_VERSION >= 7
+    xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioUsage> usageRange;
+    xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioContentType> contentRange;
+    // Test all possible track configuration
+    for (auto usage : usageRange) {
+        for (auto content : contentRange) {
+            for (float volume : {0.0, 0.5, 1.0}) {
+                const SourceMetadata metadata = {
+                        {{toString(usage),
+                          toString(content),
+                          volume,
+                          toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+                          {} /* tags */}}};
+                ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(metadata))
+                        << "usage=" << toString(usage) << ", content=" << toString(content)
+                        << ", volume=" << volume;
+            }
+        }
+    }
+    // Set many track of different configuration
+    // clang-format off
+    ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(
+        {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+                      toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+                      0.1, // gain
+                      toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+                      {}}, // tags
+          {toString(xsd::AudioUsage::AUDIO_USAGE_VOICE_COMMUNICATION),
+                      toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SPEECH),
+                      1.0,
+                      toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+                      {}},
+          {toString(xsd::AudioUsage::AUDIO_USAGE_ALARM),
+                      toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SONIFICATION),
+                      0.0,
+                      toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+                      {}},
+          {toString(xsd::AudioUsage::AUDIO_USAGE_ASSISTANT),
+                      toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN),
+                      0.3,
+                      toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+                      {}}}}
+    ));
+    // clang-format on
+    // Set no metadata as if all stream track had stopped
+    ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata({}));
+    // Restore initial
+    ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(initMetadata));
+#endif
 }
 
 TEST_P(AudioPrimaryHidlTest, setMode) {
diff --git a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
index bd8de2d..0ebe4c2 100644
--- a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
@@ -18,137 +18,131 @@
 #include "5.0/AudioPrimaryHidlHalTest.cpp"
 
 #if MAJOR_VERSION <= 6
-const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
-    static std::vector<DeviceConfigParameter> parameters = [] {
-        std::vector<DeviceConfigParameter> result;
-        for (const auto& device : getDeviceParameters()) {
-            auto module =
-                    getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
-            for (const auto& ioProfile : module->getOutputProfiles()) {
-                for (const auto& profile : ioProfile->getAudioProfiles()) {
-                    const auto& channels = profile->getChannels();
-                    const auto& sampleRates = profile->getSampleRates();
-                    auto configs = ConfigHelper::combineAudioConfig(
-                            std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
-                            std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
-                            profile->getFormat());
-                    auto flags = ioProfile->getFlags();
-                    for (auto& config : configs) {
-                        // Some combinations of flags declared in the config file require special
-                        // treatment.
-                        if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
-                            config.offloadInfo.sampleRateHz = config.sampleRateHz;
-                            config.offloadInfo.channelMask = config.channelMask;
-                            config.offloadInfo.format = config.format;
-                            config.offloadInfo.streamType = AudioStreamType::MUSIC;
-                            config.offloadInfo.bitRatePerSecond = 320;
-                            config.offloadInfo.durationMicroseconds = -1;
-                            config.offloadInfo.bitWidth = 16;
-                            config.offloadInfo.bufferSize = 256;  // arbitrary value
-                            config.offloadInfo.usage = AudioUsage::MEDIA;
-                            result.emplace_back(device, config,
-                                                AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD |
-                                                                AudioOutputFlag::DIRECT));
-                        } else {
-                            if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) {  // ignore the flag
-                                flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY;
-                            }
-                            result.emplace_back(device, config, AudioOutputFlag(flags));
+static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters(
+        bool oneProfilePerDevice) {
+    std::vector<DeviceConfigParameter> result;
+    for (const auto& device : getDeviceParameters()) {
+        auto module =
+                getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+        for (const auto& ioProfile : module->getOutputProfiles()) {
+            for (const auto& profile : ioProfile->getAudioProfiles()) {
+                const auto& channels = profile->getChannels();
+                const auto& sampleRates = profile->getSampleRates();
+                auto configs = ConfigHelper::combineAudioConfig(
+                        std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
+                        std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
+                        profile->getFormat());
+                auto flags = ioProfile->getFlags();
+                for (auto& config : configs) {
+                    // Some combinations of flags declared in the config file require special
+                    // treatment.
+                    if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
+                        config.offloadInfo.sampleRateHz = config.sampleRateHz;
+                        config.offloadInfo.channelMask = config.channelMask;
+                        config.offloadInfo.format = config.format;
+                        config.offloadInfo.streamType = AudioStreamType::MUSIC;
+                        config.offloadInfo.bitRatePerSecond = 320;
+                        config.offloadInfo.durationMicroseconds = -1;
+                        config.offloadInfo.bitWidth = 16;
+                        config.offloadInfo.bufferSize = 256;  // arbitrary value
+                        config.offloadInfo.usage = AudioUsage::MEDIA;
+                        result.emplace_back(device, config,
+                                            AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD |
+                                                            AudioOutputFlag::DIRECT));
+                    } else {
+                        if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) {  // ignore the flag
+                            flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY;
                         }
+                        result.emplace_back(device, config, AudioOutputFlag(flags));
                     }
+                    if (oneProfilePerDevice) break;
                 }
+                if (oneProfilePerDevice) break;
             }
+            if (oneProfilePerDevice) break;
         }
-        return result;
-    }();
+    }
+    return result;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
+    static std::vector<DeviceConfigParameter> parameters =
+            generateOutputDeviceConfigParameters(false);
     return parameters;
 }
 
-const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
-    static std::vector<DeviceConfigParameter> parameters = [] {
-        std::vector<DeviceConfigParameter> result;
-        for (const auto& device : getDeviceParameters()) {
-            auto module =
-                    getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
-            for (const auto& ioProfile : module->getInputProfiles()) {
-                for (const auto& profile : ioProfile->getAudioProfiles()) {
-                    const auto& channels = profile->getChannels();
-                    const auto& sampleRates = profile->getSampleRates();
-                    auto configs = ConfigHelper::combineAudioConfig(
-                            std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
-                            std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
-                            profile->getFormat());
-                    for (const auto& config : configs) {
-                        result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags()));
-                    }
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() {
+    static std::vector<DeviceConfigParameter> parameters =
+            generateOutputDeviceConfigParameters(true);
+    return parameters;
+}
+
+static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters(
+        bool oneProfilePerDevice) {
+    std::vector<DeviceConfigParameter> result;
+    for (const auto& device : getDeviceParameters()) {
+        auto module =
+                getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+        for (const auto& ioProfile : module->getInputProfiles()) {
+            for (const auto& profile : ioProfile->getAudioProfiles()) {
+                const auto& channels = profile->getChannels();
+                const auto& sampleRates = profile->getSampleRates();
+                auto configs = ConfigHelper::combineAudioConfig(
+                        std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
+                        std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
+                        profile->getFormat());
+                for (const auto& config : configs) {
+                    result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags()));
+                    if (oneProfilePerDevice) break;
                 }
+                if (oneProfilePerDevice) break;
             }
+            if (oneProfilePerDevice) break;
         }
-        return result;
-    }();
+    }
+    return result;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
+    static std::vector<DeviceConfigParameter> parameters =
+            generateInputDeviceConfigParameters(false);
+    return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() {
+    static std::vector<DeviceConfigParameter> parameters =
+            generateInputDeviceConfigParameters(true);
     return parameters;
 }
 #endif  // MAJOR_VERSION <= 6
 
-TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedOutputStreams) {
-    doc::test("Verify that a device can't be closed if there are streams opened");
-#if MAJOR_VERSION <= 6
-    DeviceAddress address{.device = AudioDevice::OUT_DEFAULT};
-    SourceMetadata initMetadata = {{{AudioUsage::MEDIA, AudioContentType::MUSIC, 1 /* gain */}}};
-    auto flags = hidl_bitfield<AudioOutputFlag>(AudioOutputFlag::NONE);
-#elif MAJOR_VERSION >= 7
-    DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)};
-    SourceMetadata initMetadata = {
-            {{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
-              toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), 1 /* gain */}}};
-    hidl_vec<AudioInOutFlag> flags;
-#endif
-    AudioConfig config{};
-    sp<IStreamOut> stream;
-    StreamHelper<IStreamOut> helper(stream);
-    AudioConfig suggestedConfig{};
-    ASSERT_NO_FATAL_FAILURE(helper.open(
-            [&](AudioIoHandle handle, AudioConfig config, auto cb) {
-                return getDevice()->openOutputStream(handle, address, config, flags, initMetadata,
-                                                     cb);
-            },
-            config, &res, &suggestedConfig));
+class SingleConfigOutputStreamTest : public OutputStreamTest {};
+TEST_P(SingleConfigOutputStreamTest, CloseDeviceWithOpenedOutputStreams) {
+    doc::test("Verify that a device can't be closed if there are output streams opened");
+    // Opening of the stream is done in SetUp.
     ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close());
-    ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res));
+    ASSERT_OK(closeStream(true /*clear*/));
     ASSERT_OK(getDevice()->close());
     ASSERT_TRUE(resetDevice());
 }
+INSTANTIATE_TEST_CASE_P(SingleConfigOutputStream, SingleConfigOutputStreamTest,
+                        ::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+                        &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleConfigOutputStreamTest);
 
-TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedInputStreams) {
-    doc::test("Verify that a device can't be closed if there are streams opened");
-    if (!getCachedPolicyConfig().haveInputProfilesInModule(getDeviceName())) {
-        GTEST_SKIP() << "Device doesn't have input profiles";
-    }
-#if MAJOR_VERSION <= 6
-    DeviceAddress address{.device = AudioDevice::IN_DEFAULT};
-    SinkMetadata initMetadata = {{{.source = AudioSource::MIC, .gain = 1}}};
-    auto flags = hidl_bitfield<AudioInputFlag>(AudioInputFlag::NONE);
-#elif MAJOR_VERSION >= 7
-    DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)};
-    SinkMetadata initMetadata = {
-            {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC), .gain = 1}}};
-    hidl_vec<AudioInOutFlag> flags;
-#endif
-    AudioConfig config{};
-    sp<IStreamIn> stream;
-    StreamHelper<IStreamIn> helper(stream);
-    AudioConfig suggestedConfig{};
-    ASSERT_NO_FATAL_FAILURE(helper.open(
-            [&](AudioIoHandle handle, AudioConfig config, auto cb) {
-                return getDevice()->openInputStream(handle, address, config, flags, initMetadata,
-                                                    cb);
-            },
-            config, &res, &suggestedConfig));
+class SingleConfigInputStreamTest : public InputStreamTest {};
+TEST_P(SingleConfigInputStreamTest, CloseDeviceWithOpenedInputStreams) {
+    doc::test("Verify that a device can't be closed if there are input streams opened");
+    // Opening of the stream is done in SetUp.
     ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close());
-    ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res));
+    ASSERT_OK(closeStream(true /*clear*/));
     ASSERT_OK(getDevice()->close());
     ASSERT_TRUE(resetDevice());
 }
+INSTANTIATE_TEST_CASE_P(SingleConfigInputStream, SingleConfigInputStreamTest,
+                        ::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+                        &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleConfigInputStreamTest);
 
 TEST_P(AudioPatchHidlTest, UpdatePatchInvalidHandle) {
     doc::test("Verify that passing an invalid handle to updateAudioPatch is checked");
diff --git a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
index 6bb7995..be1ffbb 100644
--- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
@@ -25,7 +25,6 @@
     for (auto channelMask : channelMasks) {
         for (auto sampleRate : sampleRates) {
             AudioConfig config{};
-            // leave offloadInfo to 0
             config.base.channelMask = toString(channelMask);
             config.base.sampleRateHz = sampleRate;
             config.base.format = format;
@@ -35,56 +34,158 @@
     return configs;
 }
 
+static std::tuple<std::vector<AudioInOutFlag>, bool> generateOutFlags(
+        const xsd::MixPorts::MixPort& mixPort) {
+    static const std::vector<AudioInOutFlag> offloadFlags = {
+            toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
+            toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)};
+    std::vector<AudioInOutFlag> flags;
+    bool isOffload = false;
+    if (mixPort.hasFlags()) {
+        auto xsdFlags = mixPort.getFlags();
+        isOffload = std::find(xsdFlags.begin(), xsdFlags.end(),
+                              xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) !=
+                    xsdFlags.end();
+        if (!isOffload) {
+            for (auto flag : xsdFlags) {
+                if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) {
+                    flags.push_back(toString(flag));
+                }
+            }
+        } else {
+            flags = offloadFlags;
+        }
+    }
+    return {flags, isOffload};
+}
+
+static AudioOffloadInfo generateOffloadInfo(const AudioConfigBase& base) {
+    return AudioOffloadInfo{
+            .base = base,
+            .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC),
+            .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+            .bitRatePerSecond = 320,
+            .durationMicroseconds = -1,
+            .bitWidth = 16,
+            .bufferSize = 256  // arbitrary value
+    };
+}
+
+static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters(
+        bool oneProfilePerDevice) {
+    std::vector<DeviceConfigParameter> result;
+    for (const auto& device : getDeviceParameters()) {
+        auto module =
+                getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+        if (!module || !module->getFirstMixPorts()) break;
+        for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+            if (mixPort.getRole() != xsd::Role::source) continue;  // not an output profile
+            auto [flags, isOffload] = generateOutFlags(mixPort);
+            for (const auto& profile : mixPort.getProfile()) {
+                auto configs = combineAudioConfig(profile.getChannelMasks(),
+                                                  profile.getSamplingRates(), profile.getFormat());
+                for (auto& config : configs) {
+                    // Some combinations of flags declared in the config file require special
+                    // treatment.
+                    if (isOffload) {
+                        config.offloadInfo.info(generateOffloadInfo(config.base));
+                    }
+                    result.emplace_back(device, config, flags);
+                    if (oneProfilePerDevice) break;
+                }
+                if (oneProfilePerDevice) break;
+            }
+            if (oneProfilePerDevice) break;
+        }
+    }
+    return result;
+}
+
 const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
-    static std::vector<DeviceConfigParameter> parameters = [] {
+    static std::vector<DeviceConfigParameter> parameters =
+            generateOutputDeviceConfigParameters(false);
+    return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() {
+    static std::vector<DeviceConfigParameter> parameters =
+            generateOutputDeviceConfigParameters(true);
+    return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceInvalidConfigParameters(
+        bool generateInvalidFlags = true) {
+    static std::vector<DeviceConfigParameter> parameters = [&] {
         std::vector<DeviceConfigParameter> result;
-        const std::vector<AudioInOutFlag> offloadFlags = {
-                toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
-                toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)};
         for (const auto& device : getDeviceParameters()) {
             auto module =
                     getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
             if (!module || !module->getFirstMixPorts()) break;
+            bool hasRegularConfig = false, hasOffloadConfig = false;
             for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
                 if (mixPort.getRole() != xsd::Role::source) continue;  // not an output profile
-                std::vector<AudioInOutFlag> flags;
-                bool isOffload = false;
-                if (mixPort.hasFlags()) {
-                    auto xsdFlags = mixPort.getFlags();
-                    isOffload =
-                            std::find(xsdFlags.begin(), xsdFlags.end(),
-                                      xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) !=
-                            xsdFlags.end();
-                    if (!isOffload) {
-                        for (auto flag : xsdFlags) {
-                            if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) {
-                                flags.push_back(toString(flag));
-                            }
-                        }
-                    } else {
-                        flags = offloadFlags;
-                    }
-                }
+                auto [validFlags, isOffload] = generateOutFlags(mixPort);
+                if ((!isOffload && hasRegularConfig) || (isOffload && hasOffloadConfig)) continue;
                 for (const auto& profile : mixPort.getProfile()) {
-                    auto configs =
-                            combineAudioConfig(profile.getChannelMasks(),
-                                               profile.getSamplingRates(), profile.getFormat());
-                    for (auto& config : configs) {
-                        // Some combinations of flags declared in the config file require special
-                        // treatment.
+                    if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+                        !profile.hasChannelMasks())
+                        continue;
+                    AudioConfigBase validBase = {
+                            profile.getFormat(),
+                            static_cast<uint32_t>(profile.getSamplingRates()[0]),
+                            toString(profile.getChannelMasks()[0])};
+                    {
+                        AudioConfig config{.base = validBase};
+                        config.base.channelMask = "random_string";
                         if (isOffload) {
-                            config.offloadInfo.base = config.base;
-                            config.offloadInfo.streamType =
-                                    toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC);
-                            config.offloadInfo.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA);
-                            config.offloadInfo.bitRatePerSecond = 320;
-                            config.offloadInfo.durationMicroseconds = -1;
-                            config.offloadInfo.bitWidth = 16;
-                            config.offloadInfo.bufferSize = 256;  // arbitrary value
+                            config.offloadInfo.info(generateOffloadInfo(validBase));
                         }
+                        result.emplace_back(device, config, validFlags);
+                    }
+                    {
+                        AudioConfig config{.base = validBase};
+                        config.base.format = "random_string";
+                        if (isOffload) {
+                            config.offloadInfo.info(generateOffloadInfo(validBase));
+                        }
+                        result.emplace_back(device, config, validFlags);
+                    }
+                    if (generateInvalidFlags) {
+                        AudioConfig config{.base = validBase};
+                        if (isOffload) {
+                            config.offloadInfo.info(generateOffloadInfo(validBase));
+                        }
+                        std::vector<AudioInOutFlag> flags = {"random_string", ""};
                         result.emplace_back(device, config, flags);
                     }
+                    if (isOffload) {
+                        {
+                            AudioConfig config{.base = validBase};
+                            config.offloadInfo.info(generateOffloadInfo(validBase));
+                            config.offloadInfo.info().base.channelMask = "random_string";
+                        }
+                        {
+                            AudioConfig config{.base = validBase};
+                            config.offloadInfo.info(generateOffloadInfo(validBase));
+                            config.offloadInfo.info().base.format = "random_string";
+                        }
+                        {
+                            AudioConfig config{.base = validBase};
+                            config.offloadInfo.info(generateOffloadInfo(validBase));
+                            config.offloadInfo.info().streamType = "random_string";
+                        }
+                        {
+                            AudioConfig config{.base = validBase};
+                            config.offloadInfo.info(generateOffloadInfo(validBase));
+                            config.offloadInfo.info().usage = "random_string";
+                        }
+                        hasOffloadConfig = true;
+                    } else {
+                        hasRegularConfig = true;
+                    }
+                    break;
                 }
+                if (hasOffloadConfig && hasRegularConfig) break;
             }
         }
         return result;
@@ -92,32 +193,563 @@
     return parameters;
 }
 
+static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters(
+        bool oneProfilePerDevice) {
+    std::vector<DeviceConfigParameter> result;
+    for (const auto& device : getDeviceParameters()) {
+        auto module =
+                getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+        if (!module || !module->getFirstMixPorts()) break;
+        for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+            if (mixPort.getRole() != xsd::Role::sink) continue;  // not an input profile
+            std::vector<AudioInOutFlag> flags;
+            if (mixPort.hasFlags()) {
+                std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
+                               std::back_inserter(flags), [](auto flag) { return toString(flag); });
+            }
+            for (const auto& profile : mixPort.getProfile()) {
+                auto configs = combineAudioConfig(profile.getChannelMasks(),
+                                                  profile.getSamplingRates(), profile.getFormat());
+                for (const auto& config : configs) {
+                    result.emplace_back(device, config, flags);
+                    if (oneProfilePerDevice) break;
+                }
+                if (oneProfilePerDevice) break;
+            }
+            if (oneProfilePerDevice) break;
+        }
+    }
+    return result;
+}
+
 const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
-    static std::vector<DeviceConfigParameter> parameters = [] {
+    static std::vector<DeviceConfigParameter> parameters =
+            generateInputDeviceConfigParameters(false);
+    return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() {
+    static std::vector<DeviceConfigParameter> parameters =
+            generateInputDeviceConfigParameters(true);
+    return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceInvalidConfigParameters(
+        bool generateInvalidFlags = true) {
+    static std::vector<DeviceConfigParameter> parameters = [&] {
         std::vector<DeviceConfigParameter> result;
         for (const auto& device : getDeviceParameters()) {
             auto module =
                     getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
             if (!module || !module->getFirstMixPorts()) break;
+            bool hasConfig = false;
             for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
                 if (mixPort.getRole() != xsd::Role::sink) continue;  // not an input profile
-                std::vector<AudioInOutFlag> flags;
+                std::vector<AudioInOutFlag> validFlags;
                 if (mixPort.hasFlags()) {
                     std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
-                                   std::back_inserter(flags),
+                                   std::back_inserter(validFlags),
                                    [](auto flag) { return toString(flag); });
                 }
                 for (const auto& profile : mixPort.getProfile()) {
-                    auto configs =
-                            combineAudioConfig(profile.getChannelMasks(),
-                                               profile.getSamplingRates(), profile.getFormat());
-                    for (const auto& config : configs) {
+                    if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+                        !profile.hasChannelMasks())
+                        continue;
+                    AudioConfigBase validBase = {
+                            profile.getFormat(),
+                            static_cast<uint32_t>(profile.getSamplingRates()[0]),
+                            toString(profile.getChannelMasks()[0])};
+                    {
+                        AudioConfig config{.base = validBase};
+                        config.base.channelMask = "random_string";
+                        result.emplace_back(device, config, validFlags);
+                    }
+                    {
+                        AudioConfig config{.base = validBase};
+                        config.base.format = "random_string";
+                        result.emplace_back(device, config, validFlags);
+                    }
+                    if (generateInvalidFlags) {
+                        AudioConfig config{.base = validBase};
+                        std::vector<AudioInOutFlag> flags = {"random_string", ""};
                         result.emplace_back(device, config, flags);
                     }
+                    hasConfig = true;
+                    break;
                 }
+                if (hasConfig) break;
             }
         }
         return result;
     }();
     return parameters;
 }
+
+class InvalidInputConfigNoFlagsTest : public AudioHidlTestWithDeviceConfigParameter {};
+TEST_P(InvalidInputConfigNoFlagsTest, InputBufferSizeTest) {
+    doc::test("Verify that invalid config is rejected by IDevice::getInputBufferSize method.");
+    uint64_t bufferSize;
+    ASSERT_OK(getDevice()->getInputBufferSize(getConfig(), returnIn(res, bufferSize)));
+    EXPECT_EQ(Result::INVALID_ARGUMENTS, res);
+}
+INSTANTIATE_TEST_CASE_P(
+        InputBufferSizeInvalidConfig, InvalidInputConfigNoFlagsTest,
+        ::testing::ValuesIn(getInputDeviceInvalidConfigParameters(false /*generateInvalidFlags*/)),
+        &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InvalidInputConfigNoFlagsTest);
+
+static const DeviceAddress& getValidInputDeviceAddress() {
+    static const DeviceAddress valid = {
+            .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)};
+    return valid;
+}
+
+static const DeviceAddress& getValidOutputDeviceAddress() {
+    static const DeviceAddress valid = {
+            .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)};
+    return valid;
+}
+
+static const DeviceAddress& getInvalidDeviceAddress() {
+    static const DeviceAddress valid = {.deviceType = "random_string"};
+    return valid;
+}
+
+TEST_P(AudioHidlDeviceTest, SetConnectedStateInvalidDeviceAddress) {
+    doc::test("Check that invalid device address is rejected by IDevice::setConnectedState");
+    EXPECT_RESULT(Result::INVALID_ARGUMENTS,
+                  getDevice()->setConnectedState(getInvalidDeviceAddress(), true));
+    EXPECT_RESULT(Result::INVALID_ARGUMENTS,
+                  getDevice()->setConnectedState(getInvalidDeviceAddress(), false));
+}
+
+static std::vector<AudioPortConfig>& generatePortConfigs(bool valid) {
+    enum {  // Note: This is for convenience when deriving "invalid" configs from "valid".
+        PORT_CONF_MINIMAL,
+        PORT_CONF_WITH_GAIN,
+        PORT_CONF_EXT_DEVICE,
+        PORT_CONF_EXT_MIX_SOURCE,
+        PORT_CONF_EXT_MIX_SINK,
+        PORT_CONF_EXT_SESSION
+    };
+    static std::vector<AudioPortConfig> valids = [] {
+        std::vector<AudioPortConfig> result;
+        result.reserve(PORT_CONF_EXT_SESSION + 1);
+        result.push_back(AudioPortConfig{});
+        AudioPortConfig configWithGain{};
+        configWithGain.gain.config(AudioGainConfig{
+                .index = 0,
+                .mode = {toString(xsd::AudioGainMode::AUDIO_GAIN_MODE_JOINT)},
+                .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+                .rampDurationMs = 1});
+        configWithGain.gain.config().values.resize(1);
+        configWithGain.gain.config().values[0] = 1000;
+        result.push_back(std::move(configWithGain));
+        AudioPortConfig configWithPortExtDevice{};
+        configWithPortExtDevice.ext.device(getValidOutputDeviceAddress());
+        result.push_back(std::move(configWithPortExtDevice));
+        AudioPortConfig configWithPortExtMixSource{};
+        configWithPortExtMixSource.ext.mix({});
+        configWithPortExtMixSource.ext.mix().useCase.stream(
+                toString(xsd::AudioStreamType::AUDIO_STREAM_VOICE_CALL));
+        result.push_back(std::move(configWithPortExtMixSource));
+        AudioPortConfig configWithPortExtMixSink{};
+        configWithPortExtMixSink.ext.mix({});
+        configWithPortExtMixSink.ext.mix().useCase.source(
+                toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT));
+        result.push_back(std::move(configWithPortExtMixSink));
+        AudioPortConfig configWithPortExtSession{};
+        configWithPortExtSession.ext.session(
+                static_cast<AudioSession>(AudioSessionConsts::OUTPUT_MIX));
+        result.push_back(std::move(configWithPortExtSession));
+        return result;
+    }();
+    static std::vector<AudioPortConfig> invalids = [&] {
+        std::vector<AudioPortConfig> result;
+        AudioPortConfig invalidBaseChannelMask = valids[PORT_CONF_MINIMAL];
+        invalidBaseChannelMask.base.channelMask.value("random_string");
+        result.push_back(std::move(invalidBaseChannelMask));
+        AudioPortConfig invalidBaseFormat = valids[PORT_CONF_MINIMAL];
+        invalidBaseFormat.base.format.value("random_string");
+        result.push_back(std::move(invalidBaseFormat));
+        AudioPortConfig invalidGainMode = valids[PORT_CONF_WITH_GAIN];
+        invalidGainMode.gain.config().mode = {{"random_string"}};
+        result.push_back(std::move(invalidGainMode));
+        AudioPortConfig invalidGainChannelMask = valids[PORT_CONF_WITH_GAIN];
+        invalidGainChannelMask.gain.config().channelMask = "random_string";
+        result.push_back(std::move(invalidGainChannelMask));
+        AudioPortConfig invalidDeviceType = valids[PORT_CONF_EXT_DEVICE];
+        invalidDeviceType.ext.device().deviceType = "random_string";
+        result.push_back(std::move(invalidDeviceType));
+        AudioPortConfig invalidStreamType = valids[PORT_CONF_EXT_MIX_SOURCE];
+        invalidStreamType.ext.mix().useCase.stream() = "random_string";
+        result.push_back(std::move(invalidStreamType));
+        AudioPortConfig invalidSource = valids[PORT_CONF_EXT_MIX_SINK];
+        invalidSource.ext.mix().useCase.source() = "random_string";
+        result.push_back(std::move(invalidSource));
+        return result;
+    }();
+    return valid ? valids : invalids;
+}
+
+TEST_P(AudioHidlDeviceTest, SetAudioPortConfigInvalidArguments) {
+    doc::test("Check that invalid port configs are rejected by IDevice::setAudioPortConfig");
+    for (const auto& invalidConfig : generatePortConfigs(false /*valid*/)) {
+        EXPECT_RESULT(invalidArgsOrNotSupported, getDevice()->setAudioPortConfig(invalidConfig))
+                << ::testing::PrintToString(invalidConfig);
+    }
+}
+
+TEST_P(AudioPatchHidlTest, CreatePatchInvalidArguments) {
+    doc::test("Check that invalid port configs are rejected by IDevice::createAudioPatch");
+    // Note that HAL actually might reject the proposed source / sink combo
+    // due to other reasons than presence of invalid enum-strings.
+    // TODO: Come up with a way to guarantee validity of a source / sink combo.
+    for (const auto& validSource : generatePortConfigs(true /*valid*/)) {
+        for (const auto& invalidSink : generatePortConfigs(false /*valid*/)) {
+            AudioPatchHandle handle;
+            EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{validSource},
+                                                    hidl_vec<AudioPortConfig>{invalidSink},
+                                                    returnIn(res, handle)));
+            EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+                    << "Source: " << ::testing::PrintToString(validSource)
+                    << "; Sink: " << ::testing::PrintToString(invalidSink);
+        }
+    }
+    for (const auto& validSink : generatePortConfigs(true /*valid*/)) {
+        for (const auto& invalidSource : generatePortConfigs(false /*valid*/)) {
+            AudioPatchHandle handle;
+            EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{invalidSource},
+                                                    hidl_vec<AudioPortConfig>{validSink},
+                                                    returnIn(res, handle)));
+            EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+                    << "Source: " << ::testing::PrintToString(invalidSource)
+                    << "; Sink: " << ::testing::PrintToString(validSink);
+        }
+    }
+}
+
+TEST_P(AudioPatchHidlTest, UpdatePatchInvalidArguments) {
+    doc::test("Check that invalid port configs are rejected by IDevice::updateAudioPatch");
+    // Note that HAL actually might reject the proposed source / sink combo
+    // due to other reasons than presence of invalid enum-strings.
+    // TODO: Come up with a way to guarantee validity of a source / sink combo.
+    for (const auto& validSource : generatePortConfigs(true /*valid*/)) {
+        for (const auto& invalidSink : generatePortConfigs(false /*valid*/)) {
+            AudioPatchHandle handle{};
+            EXPECT_OK(getDevice()->updateAudioPatch(handle, hidl_vec<AudioPortConfig>{validSource},
+                                                    hidl_vec<AudioPortConfig>{invalidSink},
+                                                    returnIn(res, handle)));
+            EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+                    << "Source: " << ::testing::PrintToString(validSource)
+                    << "; Sink: " << ::testing::PrintToString(invalidSink);
+        }
+    }
+    for (const auto& validSink : generatePortConfigs(true /*valid*/)) {
+        for (const auto& invalidSource : generatePortConfigs(false /*valid*/)) {
+            AudioPatchHandle handle{};
+            EXPECT_OK(getDevice()->updateAudioPatch(
+                    handle, hidl_vec<AudioPortConfig>{invalidSource},
+                    hidl_vec<AudioPortConfig>{validSink}, returnIn(res, handle)));
+            EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+                    << "Source: " << ::testing::PrintToString(invalidSource)
+                    << "; Sink: " << ::testing::PrintToString(validSink);
+        }
+    }
+}
+
+enum { PARAM_DEVICE_CONFIG, PARAM_ADDRESS, PARAM_METADATA };
+enum { INDEX_SINK, INDEX_SOURCE };
+using SinkOrSourceMetadata = std::variant<SinkMetadata, SourceMetadata>;
+using StreamOpenParameter = std::tuple<DeviceConfigParameter, DeviceAddress, SinkOrSourceMetadata>;
+
+static std::string StreamOpenParameterToString(
+        const ::testing::TestParamInfo<StreamOpenParameter>& info) {
+    return DeviceConfigParameterToString(::testing::TestParamInfo<DeviceConfigParameter>{
+                   std::get<PARAM_DEVICE_CONFIG>(info.param), info.index}) +
+           "__" +
+           SanitizeStringForGTestName(
+                   ::testing::PrintToString(std::get<PARAM_ADDRESS>(info.param))) +
+           "__" +
+           SanitizeStringForGTestName(std::visit(
+                   [](auto&& arg) -> std::string { return ::testing::PrintToString(arg); },
+                   std::get<PARAM_METADATA>(info.param)));
+}
+
+class StreamOpenTest : public HidlTest, public ::testing::WithParamInterface<StreamOpenParameter> {
+  protected:
+    void SetUp() override {
+        ASSERT_NO_FATAL_FAILURE(HidlTest::SetUp());  // setup base
+        ASSERT_TRUE(getDevicesFactory() != nullptr);
+        ASSERT_TRUE(getDevice() != nullptr);
+    }
+    const std::string& getFactoryName() const override {
+        return std::get<PARAM_FACTORY_NAME>(
+                std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam())));
+    }
+    const std::string& getDeviceName() const override {
+        return std::get<PARAM_DEVICE_NAME>(
+                std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam())));
+    }
+    const AudioConfig& getConfig() const {
+        return std::get<PARAM_CONFIG>(std::get<PARAM_DEVICE_CONFIG>(GetParam()));
+    }
+    const hidl_vec<AudioInOutFlag> getFlags() const {
+        return std::get<PARAM_FLAGS>(std::get<PARAM_DEVICE_CONFIG>(GetParam()));
+    }
+    const DeviceAddress& getDeviceAddress() const { return std::get<PARAM_ADDRESS>(GetParam()); }
+    bool isParamForInputStream() const {
+        return std::get<PARAM_METADATA>(GetParam()).index() == INDEX_SINK;
+    }
+    const SinkMetadata& getSinkMetadata() const {
+        return std::get<INDEX_SINK>(std::get<PARAM_METADATA>(GetParam()));
+    }
+    const SourceMetadata& getSourceMetadata() const {
+        return std::get<INDEX_SOURCE>(std::get<PARAM_METADATA>(GetParam()));
+    }
+};
+
+static const RecordTrackMetadata& getValidRecordTrackMetadata() {
+    static const RecordTrackMetadata valid = {
+            .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), .gain = 1};
+    return valid;
+}
+
+static const RecordTrackMetadata& getValidRecordTrackMetadataWithDest() {
+    static const RecordTrackMetadata valid = {
+            .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+            .gain = 1,
+            .destination = [] {
+                RecordTrackMetadata::Destination dest;
+                dest.device(getValidOutputDeviceAddress());
+                return dest;
+            }()};
+    return valid;
+}
+
+static const RecordTrackMetadata& getInvalidSourceRecordTrackMetadata() {
+    static const RecordTrackMetadata invalid = {.source = "random_string", .gain = 1};
+    return invalid;
+}
+
+static const RecordTrackMetadata& getRecordTrackMetadataWithInvalidDest() {
+    static const RecordTrackMetadata invalid = {
+            .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+            .gain = 1,
+            .destination = [] {
+                RecordTrackMetadata::Destination dest;
+                dest.device(getInvalidDeviceAddress());
+                return dest;
+            }()};
+    return invalid;
+}
+
+static const RecordTrackMetadata& getInvalidChannelMaskRecordTrackMetadata() {
+    static const RecordTrackMetadata invalid = {
+            .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+            .gain = 1,
+            .channelMask = "random_string"};
+    return invalid;
+}
+
+static const RecordTrackMetadata& getInvalidTagsRecordTrackMetadata() {
+    static const RecordTrackMetadata invalid = {
+            .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+            .gain = 1,
+            .tags = {{"random_string"}}};
+    return invalid;
+}
+
+static const PlaybackTrackMetadata& getValidPlaybackTrackMetadata() {
+    static const PlaybackTrackMetadata valid = {
+            .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+            .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+            .gain = 1};
+    return valid;
+}
+
+static const PlaybackTrackMetadata& getInvalidUsagePlaybackTrackMetadata() {
+    static const PlaybackTrackMetadata invalid = {
+            .usage = "random_string",
+            .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+            .gain = 1};
+    return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidContentTypePlaybackTrackMetadata() {
+    static const PlaybackTrackMetadata invalid = {
+            .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+            .contentType = "random_string",
+            .gain = 1};
+    return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidChannelMaskPlaybackTrackMetadata() {
+    static const PlaybackTrackMetadata invalid = {
+            .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+            .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+            .gain = 1,
+            .channelMask = "random_string"};
+    return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidTagsPlaybackTrackMetadata() {
+    static const PlaybackTrackMetadata invalid = {
+            .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+            .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+            .gain = 1,
+            .tags = {{"random_string"}}};
+    return invalid;
+}
+
+static const std::vector<SourceMetadata>& getInvalidSourceMetadatas() {
+    static const std::vector<SourceMetadata> invalids = {
+            SourceMetadata{.tracks = {{getInvalidUsagePlaybackTrackMetadata()}}},
+            SourceMetadata{.tracks = {{getInvalidContentTypePlaybackTrackMetadata()}}},
+            SourceMetadata{.tracks = {{getInvalidChannelMaskPlaybackTrackMetadata()}}},
+            SourceMetadata{.tracks = {{getInvalidTagsPlaybackTrackMetadata()}}},
+            SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+                                       getInvalidUsagePlaybackTrackMetadata()}}},
+            SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+                                       getInvalidContentTypePlaybackTrackMetadata()}}},
+            SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+                                       getInvalidChannelMaskPlaybackTrackMetadata()}}},
+            SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+                                       getInvalidTagsPlaybackTrackMetadata()}}}};
+    return invalids;
+}
+static const std::vector<SinkMetadata>& getInvalidSinkMetadatas() {
+    static const std::vector<SinkMetadata> invalids = {
+            SinkMetadata{.tracks = {{getInvalidSourceRecordTrackMetadata()}}},
+            SinkMetadata{.tracks = {{getRecordTrackMetadataWithInvalidDest()}}},
+            SinkMetadata{.tracks = {{getInvalidChannelMaskRecordTrackMetadata()}}},
+            SinkMetadata{.tracks = {{getInvalidTagsRecordTrackMetadata()}}},
+            SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+                                     getInvalidSourceRecordTrackMetadata()}}},
+            SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+                                     getRecordTrackMetadataWithInvalidDest()}}},
+            SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+                                     getInvalidChannelMaskRecordTrackMetadata()}}},
+            SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+                                     getInvalidTagsRecordTrackMetadata()}}}};
+    return invalids;
+}
+template <typename T>
+static inline std::vector<SinkOrSourceMetadata> wrapMetadata(const std::vector<T>& metadata) {
+    return std::vector<SinkOrSourceMetadata>{metadata.begin(), metadata.end()};
+}
+
+TEST_P(StreamOpenTest, OpenInputOrOutputStreamTest) {
+    doc::test(
+            "Verify that invalid arguments are rejected by "
+            "IDevice::open{Input|Output}Stream method.");
+    AudioConfig suggestedConfig{};
+    if (isParamForInputStream()) {
+        sp<IStreamIn> stream;
+        ASSERT_OK(getDevice()->openInputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(),
+                                               getFlags(), getSinkMetadata(),
+                                               returnIn(res, stream, suggestedConfig)));
+        ASSERT_TRUE(stream == nullptr);
+    } else {
+        sp<IStreamOut> stream;
+        ASSERT_OK(getDevice()->openOutputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(),
+                                                getFlags(), getSourceMetadata(),
+                                                returnIn(res, stream, suggestedConfig)));
+        ASSERT_TRUE(stream == nullptr);
+    }
+    EXPECT_EQ(Result::INVALID_ARGUMENTS, res);
+    EXPECT_EQ(AudioConfig{}, suggestedConfig);
+}
+INSTANTIATE_TEST_CASE_P(
+        InputStreamInvalidConfig, StreamOpenTest,
+        ::testing::Combine(::testing::ValuesIn(getInputDeviceInvalidConfigParameters()),
+                           ::testing::Values(getValidInputDeviceAddress()),
+                           ::testing::Values(SinkMetadata{
+                                   .tracks = {{getValidRecordTrackMetadata(),
+                                               getValidRecordTrackMetadataWithDest()}}})),
+        &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+        InputStreamInvalidAddress, StreamOpenTest,
+        ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+                           ::testing::Values(getInvalidDeviceAddress()),
+                           ::testing::Values(SinkMetadata{
+                                   .tracks = {{getValidRecordTrackMetadata(),
+                                               getValidRecordTrackMetadataWithDest()}}})),
+        &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+        InputStreamInvalidMetadata, StreamOpenTest,
+        ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+                           ::testing::Values(getValidInputDeviceAddress()),
+                           ::testing::ValuesIn(wrapMetadata(getInvalidSinkMetadatas()))),
+        &StreamOpenParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(StreamOpenTest);
+
+INSTANTIATE_TEST_CASE_P(
+        OutputStreamInvalidConfig, StreamOpenTest,
+        ::testing::Combine(::testing::ValuesIn(getOutputDeviceInvalidConfigParameters()),
+                           ::testing::Values(getValidOutputDeviceAddress()),
+                           ::testing::Values(SourceMetadata{
+                                   .tracks = {{getValidPlaybackTrackMetadata()}}})),
+        &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+        OutputStreamInvalidAddress, StreamOpenTest,
+        ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+                           ::testing::Values(getInvalidDeviceAddress()),
+                           ::testing::Values(SourceMetadata{
+                                   .tracks = {{getValidPlaybackTrackMetadata()}}})),
+        &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+        OutputStreamInvalidMetadata, StreamOpenTest,
+        ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+                           ::testing::Values(getValidOutputDeviceAddress()),
+                           ::testing::ValuesIn(wrapMetadata(getInvalidSourceMetadatas()))),
+        &StreamOpenParameterToString);
+
+#define TEST_SINGLE_CONFIG_IO_STREAM(test_name, documentation, code) \
+    TEST_P(SingleConfigInputStreamTest, test_name) {                 \
+        doc::test(documentation);                                    \
+        code;                                                        \
+    }                                                                \
+    TEST_P(SingleConfigOutputStreamTest, test_name) {                \
+        doc::test(documentation);                                    \
+        code;                                                        \
+    }
+
+static void testSetDevicesInvalidDeviceAddress(IStream* stream) {
+    ASSERT_RESULT(Result::INVALID_ARGUMENTS, stream->setDevices({getInvalidDeviceAddress()}));
+}
+TEST_SINGLE_CONFIG_IO_STREAM(
+        SetDevicesInvalidDeviceAddress,
+        "Verify that invalid device address is rejected by IStream::setDevices",
+        areAudioPatchesSupported() ? doc::partialTest("Audio patches are supported")
+                                   : testSetDevicesInvalidDeviceAddress(stream.get()));
+
+static void testSetAudioPropertiesInvalidArguments(IStream* stream) {
+    AudioConfigBaseOptional invalidFormat;
+    invalidFormat.format.value("random_string");
+    ASSERT_RESULT(invalidArgsOrNotSupported, stream->setAudioProperties(invalidFormat));
+
+    AudioConfigBaseOptional invalidChannelMask;
+    invalidChannelMask.channelMask.value("random_string");
+    ASSERT_RESULT(invalidArgsOrNotSupported, stream->setAudioProperties(invalidChannelMask));
+}
+TEST_SINGLE_CONFIG_IO_STREAM(
+        SetAudioPropertiesInvalidArguments,
+        "Verify that invalid arguments are rejected by IStream::setAudioProperties",
+        testSetAudioPropertiesInvalidArguments(stream.get()));
+
+TEST_P(SingleConfigOutputStreamTest, UpdateInvalidSourceMetadata) {
+    doc::test("Verify that invalid metadata is rejected by IStreamOut::updateSourceMetadata");
+    for (const auto& metadata : getInvalidSourceMetadatas()) {
+        ASSERT_RESULT(invalidArgsOrNotSupported, stream->updateSourceMetadata(metadata))
+                << ::testing::PrintToString(metadata);
+    }
+}
+
+TEST_P(SingleConfigInputStreamTest, UpdateInvalidSinkMetadata) {
+    doc::test("Verify that invalid metadata is rejected by IStreamIn::updateSinkMetadata");
+    for (const auto& metadata : getInvalidSinkMetadatas()) {
+        ASSERT_RESULT(invalidArgsOrNotSupported, stream->updateSinkMetadata(metadata))
+                << ::testing::PrintToString(metadata);
+    }
+}
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 43c44cb..61e99e8 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -522,7 +522,9 @@
 
 #if MAJOR_VERSION >= 6
 const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters();
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters();
 const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters();
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters();
 #endif
 
 #if MAJOR_VERSION >= 4
@@ -883,7 +885,7 @@
         AudioHidlTestWithDeviceConfigParameter::TearDown();
     }
 
-   protected:
+  protected:
     AudioConfig audioConfig;
     DeviceAddress address = {};
     sp<Stream> stream;
@@ -926,7 +928,9 @@
     const SourceMetadata initMetadata = {
             { { toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
                 toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
-                1 /* gain */ } }};
+                1 /* gain */,
+                toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+                {} } }};
 #endif
 };
 TEST_P(OutputStreamTest, OpenOutputStreamTest) {
@@ -971,7 +975,7 @@
         ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp());  // setup base
 #if MAJOR_VERSION <= 6
         address.device = AudioDevice::IN_DEFAULT;
-#elif MAJOR_VERSION <= 7
+#elif MAJOR_VERSION >= 7
         address.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT);
 #endif
         const AudioConfig& config = getConfig();
@@ -986,12 +990,15 @@
 
    protected:
 #if MAJOR_VERSION == 2
-    const AudioSource initMetadata = AudioSource::DEFAULT;
+     const AudioSource initMetadata = AudioSource::DEFAULT;
 #elif MAJOR_VERSION >= 4 && MAJOR_VERSION <= 6
      const SinkMetadata initMetadata = {{ {.source = AudioSource::DEFAULT, .gain = 1 } }};
 #elif MAJOR_VERSION >= 7
      const SinkMetadata initMetadata = {
-             {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), .gain = 1}}};
+             {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+               .gain = 1,
+               .tags = {},
+               .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
 #endif
 };
 
@@ -1148,13 +1155,14 @@
     for (const auto& profile : profiles) {
         for (const auto& sampleRate : profile.sampleRates) {
             for (const auto& channelMask : profile.channelMasks) {
-                AudioConfigBase config{.format = profile.format,
-                                       .sampleRateHz = sampleRate,
-                                       .channelMask = {{channelMask}}};
+                AudioConfigBaseOptional config;
+                config.format.value(profile.format);
+                config.sampleRateHz.value(sampleRate);
+                config.channelMask.value(channelMask);
                 auto ret = stream->setAudioProperties(config);
                 EXPECT_TRUE(ret.isOk());
-                EXPECT_EQ(Result::OK, ret) << config.format << "; " << config.sampleRateHz << "; "
-                                           << toString(config.channelMask);
+                EXPECT_EQ(Result::OK, ret)
+                        << profile.format << "; " << sampleRate << "; " << channelMask;
             }
         }
     }
@@ -1162,7 +1170,7 @@
 
 TEST_IO_STREAM(SetAudioProperties, "Call setAudioProperties for all supported profiles",
                testSetAudioProperties(stream.get()))
-#endif
+#endif  // MAJOR_VERSION <= 6
 
 static void testGetAudioProperties(IStream* stream, AudioConfig expectedConfig) {
 #if MAJOR_VERSION <= 6
diff --git a/audio/effect/7.0/types.hal b/audio/effect/7.0/types.hal
index c4cb213..bb2d7b3 100644
--- a/audio/effect/7.0/types.hal
+++ b/audio/effect/7.0/types.hal
@@ -17,6 +17,7 @@
 package android.hardware.audio.effect@7.0;
 
 import android.hardware.audio.common@7.0;
+import android.hidl.safe_union@1.0;
 
 enum Result : int32_t {
     OK,
@@ -248,32 +249,19 @@
 };
 
 /**
- * Determines what fields of EffectBufferConfig need to be considered.
- */
-@export(name="", value_prefix="EFFECT_CONFIG_")
-enum EffectConfigParameters : int32_t {
-    /** Buffer field. */
-    BUFFER = 0x0001,
-    /** Sampling rate. */
-    SMP_RATE = 0x0002,
-    /** Channels. */
-    CHANNELS = 0x0004,
-    /** Format. */
-    FORMAT = 0x0008,
-    /** Access mode. */
-    ACC_MODE = 0x0010,
-    // Note that the 2.0 ALL have been moved to an helper function
-};
-
-/**
  * The buffer config structure specifies the input or output audio format
  * to be used by the effect engine.
  */
 struct EffectBufferConfig {
-    AudioBuffer buffer;
-    AudioConfigBase base;
-    EffectBufferAccess accessMode;
-    bitfield<EffectConfigParameters> mask;
+    safe_union OptionalBuffer {
+        Monostate unspecified;
+        AudioBuffer buf;
+    } buffer;
+    AudioConfigBaseOptional base;
+    safe_union OptionalAccessMode {
+        Monostate unspecified;
+        EffectBufferAccess value;
+    } accessMode;
 };
 
 struct EffectConfig {
diff --git a/audio/effect/all-versions/default/Android.bp b/audio/effect/all-versions/default/Android.bp
index a0cd612..99b1120 100644
--- a/audio/effect/all-versions/default/Android.bp
+++ b/audio/effect/all-versions/default/Android.bp
@@ -8,7 +8,6 @@
         "AudioBufferManager.cpp",
         "AutomaticGainControlEffect.cpp",
         "BassBoostEffect.cpp",
-        "Conversions.cpp",
         "DownmixEffect.cpp",
         "Effect.cpp",
         "EffectsFactory.cpp",
@@ -51,6 +50,7 @@
         "android.hardware.audio.common@2.0",
         "android.hardware.audio.common@2.0-util",
         "android.hardware.audio.effect@2.0",
+        "android.hardware.audio.effect@2.0-util",
     ],
     cflags: [
         "-DMAJOR_VERSION=2",
@@ -66,6 +66,7 @@
         "android.hardware.audio.common@4.0",
         "android.hardware.audio.common@4.0-util",
         "android.hardware.audio.effect@4.0",
+        "android.hardware.audio.effect@4.0-util",
     ],
     cflags: [
         "-DMAJOR_VERSION=4",
@@ -81,6 +82,7 @@
         "android.hardware.audio.common@5.0",
         "android.hardware.audio.common@5.0-util",
         "android.hardware.audio.effect@5.0",
+        "android.hardware.audio.effect@5.0-util",
     ],
     cflags: [
         "-DMAJOR_VERSION=5",
@@ -96,6 +98,7 @@
         "android.hardware.audio.common@6.0",
         "android.hardware.audio.common@6.0-util",
         "android.hardware.audio.effect@6.0",
+        "android.hardware.audio.effect@6.0-util",
     ],
     cflags: [
         "-DMAJOR_VERSION=6",
@@ -111,6 +114,7 @@
         "android.hardware.audio.common@7.0",
         "android.hardware.audio.common@7.0-util",
         "android.hardware.audio.effect@7.0",
+        "android.hardware.audio.effect@7.0-util",
     ],
     cflags: [
         "-DMAJOR_VERSION=7",
diff --git a/audio/effect/all-versions/default/Conversions.cpp b/audio/effect/all-versions/default/Conversions.cpp
deleted file mode 100644
index 0cc8767..0000000
--- a/audio/effect/all-versions/default/Conversions.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#include "Conversions.h"
-#include "UuidUtils.h"
-
-#include <memory.h>
-#include <stdio.h>
-
-#include <common/all-versions/VersionUtils.h>
-
-using ::android::hardware::audio::common::utils::EnumBitfield;
-
-namespace android {
-namespace hardware {
-namespace audio {
-namespace effect {
-namespace CPP_VERSION {
-namespace implementation {
-
-using ::android::hardware::audio::common::CPP_VERSION::implementation::UuidUtils;
-
-void effectDescriptorFromHal(const effect_descriptor_t& halDescriptor,
-                             EffectDescriptor* descriptor) {
-    UuidUtils::uuidFromHal(halDescriptor.type, &descriptor->type);
-    UuidUtils::uuidFromHal(halDescriptor.uuid, &descriptor->uuid);
-    descriptor->flags = EnumBitfield<EffectFlags>(halDescriptor.flags);
-    descriptor->cpuLoad = halDescriptor.cpuLoad;
-    descriptor->memoryUsage = halDescriptor.memoryUsage;
-    memcpy(descriptor->name.data(), halDescriptor.name, descriptor->name.size());
-    memcpy(descriptor->implementor.data(), halDescriptor.implementor,
-           descriptor->implementor.size());
-}
-
-std::string uuidToString(const effect_uuid_t& halUuid) {
-    char str[64];
-    snprintf(str, sizeof(str), "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", halUuid.timeLow,
-             halUuid.timeMid, halUuid.timeHiAndVersion, halUuid.clockSeq, halUuid.node[0],
-             halUuid.node[1], halUuid.node[2], halUuid.node[3], halUuid.node[4], halUuid.node[5]);
-    return str;
-}
-
-}  // namespace implementation
-}  // namespace CPP_VERSION
-}  // namespace effect
-}  // namespace audio
-}  // namespace hardware
-}  // namespace android
diff --git a/audio/effect/all-versions/default/Conversions.h b/audio/effect/all-versions/default/Conversions.h
deleted file mode 100644
index 75aab24..0000000
--- a/audio/effect/all-versions/default/Conversions.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#ifndef ANDROID_HARDWARE_AUDIO_EFFECT_CONVERSIONS_H_
-#define ANDROID_HARDWARE_AUDIO_EFFECT_CONVERSIONS_H_
-
-#include PATH(android/hardware/audio/effect/FILE_VERSION/types.h)
-
-#include <string>
-
-#include <system/audio_effect.h>
-
-namespace android {
-namespace hardware {
-namespace audio {
-namespace effect {
-namespace CPP_VERSION {
-namespace implementation {
-
-using ::android::hardware::audio::effect::CPP_VERSION::EffectDescriptor;
-
-void effectDescriptorFromHal(const effect_descriptor_t& halDescriptor,
-                             EffectDescriptor* descriptor);
-std::string uuidToString(const effect_uuid_t& halUuid);
-
-}  // namespace implementation
-}  // namespace CPP_VERSION
-}  // namespace effect
-}  // namespace audio
-}  // namespace hardware
-}  // namespace android
-
-#endif  // ANDROID_HARDWARE_AUDIO_EFFECT_CONVERSIONS_H_
diff --git a/audio/effect/all-versions/default/Effect.cpp b/audio/effect/all-versions/default/Effect.cpp
index edd364c..ccfc6b2 100644
--- a/audio/effect/all-versions/default/Effect.cpp
+++ b/audio/effect/all-versions/default/Effect.cpp
@@ -19,7 +19,6 @@
 #define LOG_TAG "EffectHAL"
 #define ATRACE_TAG ATRACE_TAG_AUDIO
 
-#include "Conversions.h"
 #include "Effect.h"
 #include "common/all-versions/default/EffectMap.h"
 
@@ -30,6 +29,7 @@
 #include <HidlUtils.h>
 #include <android/log.h>
 #include <media/EffectsFactoryApi.h>
+#include <util/EffectUtils.h>
 #include <utils/Trace.h>
 
 #include "VersionUtils.h"
@@ -202,34 +202,6 @@
     halConfig->aux_channels = static_cast<audio_channel_mask_t>(config.auxChannels);
 }
 
-void Effect::effectBufferConfigFromHal(const buffer_config_t& halConfig,
-                                       EffectBufferConfig* config) {
-    config->buffer.id = 0;
-    config->buffer.frameCount = 0;
-    config->samplingRateHz = halConfig.samplingRate;
-    config->channels = AudioChannelBitfield(halConfig.channels);
-    config->format = AudioFormat(halConfig.format);
-    config->accessMode = EffectBufferAccess(halConfig.accessMode);
-    config->mask = static_cast<decltype(config->mask)>(halConfig.mask);
-}
-
-// static
-void Effect::effectBufferConfigToHal(const EffectBufferConfig& config, buffer_config_t* halConfig) {
-    // Note: setting the buffers directly is considered obsolete. They need to be set
-    // using 'setProcessBuffers'.
-    halConfig->buffer.frameCount = 0;
-    halConfig->buffer.raw = NULL;
-    halConfig->samplingRate = config.samplingRateHz;
-    halConfig->channels = static_cast<uint32_t>(config.channels);
-    // Note: The framework code does not use BP.
-    halConfig->bufferProvider.cookie = NULL;
-    halConfig->bufferProvider.getBuffer = NULL;
-    halConfig->bufferProvider.releaseBuffer = NULL;
-    halConfig->format = static_cast<uint8_t>(config.format);
-    halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
-    halConfig->mask = static_cast<uint8_t>(config.mask);
-}
-
 #else  // MAJOR_VERSION <= 6
 
 void Effect::effectAuxChannelsConfigFromHal(const channel_config_t& halConfig,
@@ -247,50 +219,8 @@
     (void)HidlUtils::audioChannelMaskToHal(config.auxChannels, &halConfig->aux_channels);
 }
 
-void Effect::effectBufferConfigFromHal(const buffer_config_t& halConfig,
-                                       EffectBufferConfig* config) {
-    config->buffer.id = 0;
-    config->buffer.frameCount = 0;
-    audio_config_base_t halConfigBase = {halConfig.samplingRate,
-                                         static_cast<audio_channel_mask_t>(halConfig.channels),
-                                         static_cast<audio_format_t>(halConfig.format)};
-    (void)HidlUtils::audioConfigBaseFromHal(halConfigBase, mIsInput, &config->base);
-    config->accessMode = EffectBufferAccess(halConfig.accessMode);
-    config->mask = static_cast<decltype(config->mask)>(halConfig.mask);
-}
-
-// static
-void Effect::effectBufferConfigToHal(const EffectBufferConfig& config, buffer_config_t* halConfig) {
-    // Note: setting the buffers directly is considered obsolete. They need to be set
-    // using 'setProcessBuffers'.
-    halConfig->buffer.frameCount = 0;
-    halConfig->buffer.raw = nullptr;
-    audio_config_base_t halConfigBase;
-    (void)HidlUtils::audioConfigBaseToHal(config.base, &halConfigBase);
-    halConfig->samplingRate = halConfigBase.sample_rate;
-    halConfig->channels = halConfigBase.channel_mask;
-    halConfig->format = halConfigBase.format;
-    // Note: The framework code does not use BP.
-    halConfig->bufferProvider.cookie = nullptr;
-    halConfig->bufferProvider.getBuffer = nullptr;
-    halConfig->bufferProvider.releaseBuffer = nullptr;
-    halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
-    halConfig->mask = static_cast<uint8_t>(config.mask);
-}
-
 #endif  // MAJOR_VERSION <= 6
 
-void Effect::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) {
-    effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg);
-    effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg);
-}
-
-// static
-void Effect::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) {
-    effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg);
-    effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg);
-}
-
 // static
 void Effect::effectOffloadParamToHal(const EffectOffloadParameter& offload,
                                      effect_offload_param_t* halOffload) {
@@ -355,7 +285,7 @@
         (*mHandle)->command(mHandle, commandCode, 0, NULL, &halResultSize, &halConfig);
     EffectConfig config;
     if (status == OK) {
-        effectConfigFromHal(halConfig, &config);
+        status = EffectUtils::effectConfigFromHal(halConfig, mIsInput, &config);
     }
     cb(analyzeCommandStatus(commandName, sContextCallToCommand, status), config);
 }
@@ -520,7 +450,7 @@
                              const sp<IEffectBufferProviderCallback>& inputBufferProvider,
                              const sp<IEffectBufferProviderCallback>& outputBufferProvider) {
     effect_config_t halConfig;
-    effectConfigToHal(config, &halConfig);
+    EffectUtils::effectConfigToHal(config, &halConfig);
     if (inputBufferProvider != 0) {
         LOG_FATAL("Using input buffer provider is not supported");
     }
@@ -715,7 +645,7 @@
     status_t status = (*mHandle)->get_descriptor(mHandle, &halDescriptor);
     EffectDescriptor descriptor;
     if (status == OK) {
-        effectDescriptorFromHal(halDescriptor, &descriptor);
+        status = EffectUtils::effectDescriptorFromHal(halDescriptor, &descriptor);
     }
     _hidl_cb(analyzeStatus("get_descriptor", "", sContextCallFunction, status), descriptor);
     return Void();
diff --git a/audio/effect/all-versions/default/Effect.h b/audio/effect/all-versions/default/Effect.h
index 9aa47ea..d5218f7 100644
--- a/audio/effect/all-versions/default/Effect.h
+++ b/audio/effect/all-versions/default/Effect.h
@@ -203,11 +203,6 @@
                                         EffectAuxChannelsConfig* config);
     static void effectAuxChannelsConfigToHal(const EffectAuxChannelsConfig& config,
                                              channel_config_t* halConfig);
-    void effectBufferConfigFromHal(const buffer_config_t& halConfig, EffectBufferConfig* config);
-    static void effectBufferConfigToHal(const EffectBufferConfig& config,
-                                        buffer_config_t* halConfig);
-    void effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config);
-    static void effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig);
     static void effectOffloadParamToHal(const EffectOffloadParameter& offload,
                                         effect_offload_param_t* halOffload);
     static std::vector<uint8_t> parameterToHal(uint32_t paramSize, const void* paramData,
diff --git a/audio/effect/all-versions/default/EffectsFactory.cpp b/audio/effect/all-versions/default/EffectsFactory.cpp
index 1ea990b..eb1cb49 100644
--- a/audio/effect/all-versions/default/EffectsFactory.cpp
+++ b/audio/effect/all-versions/default/EffectsFactory.cpp
@@ -19,7 +19,6 @@
 #include "AcousticEchoCancelerEffect.h"
 #include "AutomaticGainControlEffect.h"
 #include "BassBoostEffect.h"
-#include "Conversions.h"
 #include "DownmixEffect.h"
 #include "Effect.h"
 #include "EnvironmentalReverbEffect.h"
@@ -27,11 +26,11 @@
 #include "LoudnessEnhancerEffect.h"
 #include "NoiseSuppressionEffect.h"
 #include "PresetReverbEffect.h"
-#include "UuidUtils.h"
 #include "VirtualizerEffect.h"
 #include "VisualizerEffect.h"
 #include "common/all-versions/default/EffectMap.h"
 
+#include <UuidUtils.h>
 #include <android/log.h>
 #include <media/EffectsFactoryApi.h>
 #include <system/audio_effects/effect_aec.h>
@@ -45,6 +44,7 @@
 #include <system/audio_effects/effect_presetreverb.h>
 #include <system/audio_effects/effect_virtualizer.h>
 #include <system/audio_effects/effect_visualizer.h>
+#include <util/EffectUtils.h>
 
 namespace android {
 namespace hardware {
@@ -107,7 +107,7 @@
         effect_descriptor_t halDescriptor;
         status = EffectQueryEffect(i, &halDescriptor);
         if (status == OK) {
-            effectDescriptorFromHal(halDescriptor, &result[i]);
+            EffectUtils::effectDescriptorFromHal(halDescriptor, &result[i]);
         } else {
             ALOGE("Error querying effect at position %d / %d: %s", i, numEffects,
                   strerror(-status));
@@ -141,11 +141,11 @@
     effect_descriptor_t halDescriptor;
     status_t status = EffectGetDescriptor(&halUuid, &halDescriptor);
     EffectDescriptor descriptor;
-    effectDescriptorFromHal(halDescriptor, &descriptor);
+    EffectUtils::effectDescriptorFromHal(halDescriptor, &descriptor);
     Result retval(Result::OK);
     if (status != OK) {
-        ALOGE("Error querying effect descriptor for %s: %s", uuidToString(halUuid).c_str(),
-              strerror(-status));
+        ALOGE("Error querying effect descriptor for %s: %s",
+              UuidUtils::uuidToString(halUuid).c_str(), strerror(-status));
         if (status == -ENOENT) {
             retval = Result::INVALID_ARGUMENTS;
         } else {
@@ -191,13 +191,14 @@
             effect = dispatchEffectInstanceCreation(halDescriptor, handle);
             effectId = EffectMap::getInstance().add(handle);
         } else {
-            ALOGE("Error querying effect descriptor for %s: %s", uuidToString(halUuid).c_str(),
-                  strerror(-status));
+            ALOGE("Error querying effect descriptor for %s: %s",
+                  UuidUtils::uuidToString(halUuid).c_str(), strerror(-status));
             EffectRelease(handle);
         }
     }
     if (status != OK) {
-        ALOGE("Error creating effect %s: %s", uuidToString(halUuid).c_str(), strerror(-status));
+        ALOGE("Error creating effect %s: %s", UuidUtils::uuidToString(halUuid).c_str(),
+              strerror(-status));
         if (status == -ENOENT) {
             retval = Result::INVALID_ARGUMENTS;
         } else {
diff --git a/audio/effect/all-versions/default/TEST_MAPPING b/audio/effect/all-versions/default/TEST_MAPPING
new file mode 100644
index 0000000..b66e4e4
--- /dev/null
+++ b/audio/effect/all-versions/default/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "android.hardware.audio.effect@7.0-util_tests"
+    }
+  ]
+}
diff --git a/audio/effect/all-versions/default/util/Android.bp b/audio/effect/all-versions/default/util/Android.bp
new file mode 100644
index 0000000..5ba19d3
--- /dev/null
+++ b/audio/effect/all-versions/default/util/Android.bp
@@ -0,0 +1,136 @@
+cc_defaults {
+    name: "android.hardware.audio.effect-util_default",
+    defaults: ["hidl_defaults"],
+
+    vendor_available: true,
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "EffectUtils.cpp",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libhidlbase",
+        "android.hardware.audio.common-util",
+    ],
+    export_shared_lib_headers: [
+        "android.hardware.audio.common-util",
+    ],
+
+    header_libs: [
+        "libaudio_system_headers",
+        "libhardware_headers",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.audio.effect@2.0-util",
+    defaults: ["android.hardware.audio.effect-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@2.0",
+        "android.hardware.audio.common@2.0-util",
+        "android.hardware.audio.effect@2.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=2",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.audio.effect@4.0-util",
+    defaults: ["android.hardware.audio.effect-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@4.0",
+        "android.hardware.audio.common@4.0-util",
+        "android.hardware.audio.effect@4.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=4",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.audio.effect@5.0-util",
+    defaults: ["android.hardware.audio.effect-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@5.0",
+        "android.hardware.audio.common@5.0-util",
+        "android.hardware.audio.effect@5.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=5",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.audio.effect@6.0-util",
+    defaults: ["android.hardware.audio.effect-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@6.0",
+        "android.hardware.audio.common@6.0-util",
+        "android.hardware.audio.effect@6.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=6",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+cc_library {
+    name: "android.hardware.audio.effect@7.0-util",
+    defaults: ["android.hardware.audio.effect-util_default"],
+    shared_libs: [
+        "android.hardware.audio.common@7.0",
+        "android.hardware.audio.common@7.0-util",
+        "android.hardware.audio.effect@7.0",
+    ],
+    cflags: [
+        "-DMAJOR_VERSION=7",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+}
+
+// Note: this isn't a VTS test, but rather a unit test
+// to verify correctness of conversion utilities.
+cc_test {
+    name: "android.hardware.audio.effect@7.0-util_tests",
+    defaults: ["android.hardware.audio.effect-util_default"],
+
+    srcs: ["tests/effectutils_tests.cpp"],
+
+    // Use static linking to allow running in presubmit on
+    // targets that don't have HAL V7.
+    static_libs: [
+        "android.hardware.audio.common@7.0",
+        "android.hardware.audio.common@7.0-enums",
+        "android.hardware.audio.common@7.0-util",
+        "android.hardware.audio.effect@7.0",
+        "android.hardware.audio.effect@7.0-util",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libxml2",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-DMAJOR_VERSION=7",
+        "-DMINOR_VERSION=0",
+        "-include common/all-versions/VersionMacro.h",
+    ],
+
+    test_suites: ["device-tests"],
+}
diff --git a/audio/effect/all-versions/default/util/EffectUtils.cpp b/audio/effect/all-versions/default/util/EffectUtils.cpp
new file mode 100644
index 0000000..1c0419a
--- /dev/null
+++ b/audio/effect/all-versions/default/util/EffectUtils.cpp
@@ -0,0 +1,183 @@
+/*
+ * 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 <memory.h>
+
+#include <HidlUtils.h>
+#include <UuidUtils.h>
+#include <common/all-versions/VersionUtils.h>
+
+#include "util/EffectUtils.h"
+
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+using ::android::hardware::audio::common::CPP_VERSION::implementation::UuidUtils;
+using ::android::hardware::audio::common::utils::EnumBitfield;
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace effect {
+namespace CPP_VERSION {
+namespace implementation {
+
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+
+#define CONVERT_CHECKED(expr, result)                   \
+    if (status_t status = (expr); status != NO_ERROR) { \
+        result = status;                                \
+    }
+
+#if MAJOR_VERSION <= 6
+
+status_t EffectUtils::effectBufferConfigFromHal(const buffer_config_t& halConfig, bool /*isInput*/,
+                                                EffectBufferConfig* config) {
+    config->buffer.id = 0;
+    config->buffer.frameCount = 0;
+    config->samplingRateHz = halConfig.samplingRate;
+    config->channels = EnumBitfield<AudioChannelMask>(halConfig.channels);
+    config->format = AudioFormat(halConfig.format);
+    config->accessMode = EffectBufferAccess(halConfig.accessMode);
+    config->mask = EnumBitfield<EffectConfigParameters>(halConfig.mask);
+    return NO_ERROR;
+}
+
+status_t EffectUtils::effectBufferConfigToHal(const EffectBufferConfig& config,
+                                              buffer_config_t* halConfig) {
+    // Note: setting the buffers directly is considered obsolete. They need to be set
+    // using 'setProcessBuffers'.
+    halConfig->buffer.frameCount = 0;
+    halConfig->buffer.raw = nullptr;
+    halConfig->samplingRate = config.samplingRateHz;
+    halConfig->channels = static_cast<uint32_t>(config.channels);
+    // Note: The framework code does not use BP.
+    halConfig->bufferProvider.cookie = nullptr;
+    halConfig->bufferProvider.getBuffer = nullptr;
+    halConfig->bufferProvider.releaseBuffer = nullptr;
+    halConfig->format = static_cast<uint8_t>(config.format);
+    halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
+    halConfig->mask = static_cast<uint8_t>(config.mask);
+    return NO_ERROR;
+}
+
+#else
+
+status_t EffectUtils::effectBufferConfigFromHal(const buffer_config_t& halConfig, bool isInput,
+                                                EffectBufferConfig* config) {
+    status_t result = NO_ERROR;
+    config->buffer.unspecified();
+    audio_config_base_t halConfigBase = {halConfig.samplingRate,
+                                         static_cast<audio_channel_mask_t>(halConfig.channels),
+                                         static_cast<audio_format_t>(halConfig.format)};
+    CONVERT_CHECKED(HidlUtils::audioConfigBaseOptionalFromHal(
+                            halConfigBase, isInput, halConfig.mask & EFFECT_CONFIG_FORMAT,
+                            halConfig.mask & EFFECT_CONFIG_SMP_RATE,
+                            halConfig.mask & EFFECT_CONFIG_CHANNELS, &config->base),
+                    result);
+    if (halConfig.mask & EFFECT_CONFIG_ACC_MODE) {
+        config->accessMode.value(EffectBufferAccess(halConfig.accessMode));
+    }
+    return result;
+}
+
+status_t EffectUtils::effectBufferConfigToHal(const EffectBufferConfig& config,
+                                              buffer_config_t* halConfig) {
+    status_t result = NO_ERROR;
+    // Note: setting the buffers directly is considered obsolete. They need to be set
+    // using 'setProcessBuffers'.
+    halConfig->buffer.frameCount = 0;
+    halConfig->buffer.raw = nullptr;
+    audio_config_base_t halConfigBase = AUDIO_CONFIG_BASE_INITIALIZER;
+    bool formatSpecified = false, sRateSpecified = false, channelMaskSpecified = false;
+    CONVERT_CHECKED(
+            HidlUtils::audioConfigBaseOptionalToHal(config.base, &halConfigBase, &formatSpecified,
+                                                    &sRateSpecified, &channelMaskSpecified),
+            result);
+    halConfig->mask = 0;
+    if (sRateSpecified) {
+        halConfig->mask |= EFFECT_CONFIG_SMP_RATE;
+        halConfig->samplingRate = halConfigBase.sample_rate;
+    }
+    if (channelMaskSpecified) {
+        halConfig->mask |= EFFECT_CONFIG_CHANNELS;
+        halConfig->channels = halConfigBase.channel_mask;
+    }
+    if (formatSpecified) {
+        halConfig->mask |= EFFECT_CONFIG_FORMAT;
+        halConfig->format = halConfigBase.format;
+    }
+    // Note: The framework code does not use BP.
+    halConfig->bufferProvider.cookie = nullptr;
+    halConfig->bufferProvider.getBuffer = nullptr;
+    halConfig->bufferProvider.releaseBuffer = nullptr;
+    if (config.accessMode.getDiscriminator() ==
+        EffectBufferConfig::OptionalAccessMode::hidl_discriminator::value) {
+        halConfig->mask |= EFFECT_CONFIG_ACC_MODE;
+        halConfig->accessMode = static_cast<uint8_t>(config.accessMode.value());
+    }
+    return result;
+}
+
+#endif  // MAJOR_VERSION >= 6
+
+status_t EffectUtils::effectConfigFromHal(const effect_config_t& halConfig, bool isInput,
+                                          EffectConfig* config) {
+    status_t result = NO_ERROR;
+    CONVERT_CHECKED(effectBufferConfigFromHal(halConfig.inputCfg, isInput, &config->inputCfg),
+                    result);
+    CONVERT_CHECKED(effectBufferConfigFromHal(halConfig.outputCfg, isInput, &config->outputCfg),
+                    result);
+    return result;
+}
+
+status_t EffectUtils::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) {
+    status_t result = NO_ERROR;
+    CONVERT_CHECKED(effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg), result);
+    CONVERT_CHECKED(effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg), result);
+    return result;
+}
+
+status_t EffectUtils::effectDescriptorFromHal(const effect_descriptor_t& halDescriptor,
+                                              EffectDescriptor* descriptor) {
+    UuidUtils::uuidFromHal(halDescriptor.type, &descriptor->type);
+    UuidUtils::uuidFromHal(halDescriptor.uuid, &descriptor->uuid);
+    descriptor->flags = EnumBitfield<EffectFlags>(halDescriptor.flags);
+    descriptor->cpuLoad = halDescriptor.cpuLoad;
+    descriptor->memoryUsage = halDescriptor.memoryUsage;
+    memcpy(descriptor->name.data(), halDescriptor.name, descriptor->name.size());
+    memcpy(descriptor->implementor.data(), halDescriptor.implementor,
+           descriptor->implementor.size());
+    return NO_ERROR;
+}
+
+status_t EffectUtils::effectDescriptorToHal(const EffectDescriptor& descriptor,
+                                            effect_descriptor_t* halDescriptor) {
+    UuidUtils::uuidToHal(descriptor.type, &halDescriptor->type);
+    UuidUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid);
+    halDescriptor->flags = static_cast<uint32_t>(descriptor.flags);
+    halDescriptor->cpuLoad = descriptor.cpuLoad;
+    halDescriptor->memoryUsage = descriptor.memoryUsage;
+    memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size());
+    memcpy(halDescriptor->implementor, descriptor.implementor.data(),
+           descriptor.implementor.size());
+    return NO_ERROR;
+}
+
+}  // namespace implementation
+}  // namespace CPP_VERSION
+}  // namespace effect
+}  // namespace audio
+}  // namespace hardware
+}  // namespace android
diff --git a/audio/effect/all-versions/default/util/include/util/EffectUtils.h b/audio/effect/all-versions/default/util/include/util/EffectUtils.h
new file mode 100644
index 0000000..23963b4
--- /dev/null
+++ b/audio/effect/all-versions/default/util/include/util/EffectUtils.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
+
+// clang-format off
+#include PATH(android/hardware/audio/effect/FILE_VERSION/types.h)
+// clang-format on
+
+#include <system/audio_effect.h>
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace effect {
+namespace CPP_VERSION {
+namespace implementation {
+
+using namespace ::android::hardware::audio::effect::CPP_VERSION;
+
+struct EffectUtils {
+    static status_t effectBufferConfigFromHal(const buffer_config_t& halConfig, bool isInput,
+                                              EffectBufferConfig* config);
+    static status_t effectBufferConfigToHal(const EffectBufferConfig& config,
+                                            buffer_config_t* halConfig);
+    static status_t effectConfigFromHal(const effect_config_t& halConfig, bool isInput,
+                                        EffectConfig* config);
+    static status_t effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig);
+    static status_t effectDescriptorFromHal(const effect_descriptor_t& halDescriptor,
+                                            EffectDescriptor* descriptor);
+    static status_t effectDescriptorToHal(const EffectDescriptor& descriptor,
+                                          effect_descriptor_t* halDescriptor);
+};
+
+}  // namespace implementation
+}  // namespace CPP_VERSION
+}  // namespace effect
+}  // namespace audio
+}  // namespace hardware
+}  // namespace android
diff --git a/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp b/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp
new file mode 100644
index 0000000..7eb8cd2
--- /dev/null
+++ b/audio/effect/all-versions/default/util/tests/effectutils_tests.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 <string>
+
+#include <gtest/gtest.h>
+
+#define LOG_TAG "EffectUtils_Test"
+#include <log/log.h>
+
+#include <android_audio_policy_configuration_V7_0-enums.h>
+#include <system/audio_effect.h>
+#include <util/EffectUtils.h>
+#include <xsdc/XsdcSupport.h>
+
+using namespace android;
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using namespace ::android::hardware::audio::effect::CPP_VERSION;
+using ::android::hardware::audio::effect::CPP_VERSION::implementation::EffectUtils;
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+
+static constexpr audio_channel_mask_t kInvalidHalChannelMask = AUDIO_CHANNEL_INVALID;
+static constexpr audio_format_t kInvalidHalFormat = AUDIO_FORMAT_INVALID;
+
+// Not generated automatically because AudioBuffer contains
+// instances of hidl_memory which can't be compared properly
+// in general case due to presence of handles.
+//
+// However, in this particular case, handles must not present
+// thus comparison is possible.
+//
+// operator== must be defined in the same namespace as the structures.
+namespace android {
+namespace hardware {
+namespace audio {
+namespace effect {
+namespace CPP_VERSION {
+inline bool operator==(const AudioBuffer& lhs, const AudioBuffer& rhs) {
+    return lhs.id == rhs.id && lhs.frameCount == rhs.frameCount && lhs.data.handle() == nullptr &&
+           rhs.data.handle() == nullptr;
+}
+
+inline bool operator==(const EffectBufferConfig& lhs, const EffectBufferConfig& rhs) {
+    return lhs.buffer.getDiscriminator() == rhs.buffer.getDiscriminator() &&
+           (lhs.buffer.getDiscriminator() ==
+                    EffectBufferConfig::OptionalBuffer::hidl_discriminator::unspecified ||
+            lhs.buffer.buf() == rhs.buffer.buf()) &&
+           lhs.base == rhs.base && lhs.accessMode == rhs.accessMode;
+}
+
+inline bool operator==(const EffectConfig& lhs, const EffectConfig& rhs) {
+    return lhs.inputCfg == rhs.inputCfg && lhs.outputCfg == rhs.outputCfg;
+}
+}  // namespace CPP_VERSION
+}  // namespace effect
+}  // namespace audio
+}  // namespace hardware
+}  // namespace android
+
+TEST(EffectUtils, ConvertInvalidBufferConfig) {
+    buffer_config_t halInvalid;
+    EffectBufferConfig invalidChannelMask;
+    invalidChannelMask.base.channelMask.value("random string");
+    EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigToHal(invalidChannelMask, &halInvalid));
+    EffectBufferConfig invalidFormat;
+    invalidFormat.base.format.value("random string");
+    EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigToHal(invalidFormat, &halInvalid));
+
+    buffer_config_t halInvalidChannelMask;
+    EffectBufferConfig invalid;
+    halInvalidChannelMask.channels = kInvalidHalChannelMask;
+    halInvalidChannelMask.mask = EFFECT_CONFIG_CHANNELS;
+    EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigFromHal(halInvalidChannelMask,
+                                                                false /*isInput*/, &invalid));
+    EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigFromHal(halInvalidChannelMask,
+                                                                true /*isInput*/, &invalid));
+    buffer_config_t halInvalidFormat;
+    halInvalidFormat.format = (uint8_t)kInvalidHalFormat;
+    halInvalidFormat.mask = EFFECT_CONFIG_FORMAT;
+    EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigFromHal(halInvalidFormat, false /*isInput*/,
+                                                                &invalid));
+    EXPECT_EQ(BAD_VALUE,
+              EffectUtils::effectBufferConfigFromHal(halInvalidFormat, true /*isInput*/, &invalid));
+}
+
+TEST(EffectUtils, ConvertBufferConfig) {
+    EffectBufferConfig empty;
+    buffer_config_t halEmpty;
+    EXPECT_EQ(NO_ERROR, EffectUtils::effectBufferConfigToHal(empty, &halEmpty));
+    EffectBufferConfig emptyBackOut;
+    EXPECT_EQ(NO_ERROR,
+              EffectUtils::effectBufferConfigFromHal(halEmpty, false /*isInput*/, &emptyBackOut));
+    EXPECT_EQ(empty, emptyBackOut);
+    EffectBufferConfig emptyBackIn;
+    EXPECT_EQ(NO_ERROR,
+              EffectUtils::effectBufferConfigFromHal(halEmpty, true /*isInput*/, &emptyBackIn));
+    EXPECT_EQ(empty, emptyBackIn);
+
+    EffectBufferConfig chanMask;
+    chanMask.base.channelMask.value(toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO));
+    buffer_config_t halChanMask;
+    EXPECT_EQ(NO_ERROR, EffectUtils::effectBufferConfigToHal(chanMask, &halChanMask));
+    EffectBufferConfig chanMaskBack;
+    EXPECT_EQ(NO_ERROR, EffectUtils::effectBufferConfigFromHal(halChanMask, false /*isInput*/,
+                                                               &chanMaskBack));
+    EXPECT_EQ(chanMask, chanMaskBack);
+
+    EffectBufferConfig format;
+    format.base.format.value(toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT));
+    buffer_config_t halFormat;
+    EXPECT_EQ(NO_ERROR, EffectUtils::effectBufferConfigToHal(format, &halFormat));
+    EffectBufferConfig formatBackOut;
+    EXPECT_EQ(NO_ERROR,
+              EffectUtils::effectBufferConfigFromHal(halFormat, false /*isInput*/, &formatBackOut));
+    EXPECT_EQ(format, formatBackOut);
+    EffectBufferConfig formatBackIn;
+    EXPECT_EQ(NO_ERROR,
+              EffectUtils::effectBufferConfigFromHal(halFormat, true /*isInput*/, &formatBackIn));
+    EXPECT_EQ(format, formatBackIn);
+}
+
+TEST(EffectUtils, ConvertDescriptor) {
+    EffectDescriptor desc{};
+    effect_descriptor_t halDesc;
+    EXPECT_EQ(NO_ERROR, EffectUtils::effectDescriptorToHal(desc, &halDesc));
+    EffectDescriptor descBack;
+    EXPECT_EQ(NO_ERROR, EffectUtils::effectDescriptorFromHal(halDesc, &descBack));
+    EXPECT_EQ(desc, descBack);
+}
diff --git a/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp b/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp
index d39fbcd..15a2fd9 100644
--- a/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp
+++ b/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <vector>
+
 #define LOG_TAG "AudioEffectHidlHalTest"
 #include <android-base/logging.h>
 #if MAJOR_VERSION <= 6
@@ -262,8 +264,10 @@
     *channelCount = audio_channel_count_from_out_mask(
         static_cast<audio_channel_mask_t>(currentConfig.outputCfg.channels));
 #else
+    ASSERT_EQ(AudioConfigBaseOptional::ChannelMask::hidl_discriminator::value,
+              currentConfig.outputCfg.base.channelMask.getDiscriminator());
     *channelCount = android::audio::policy::configuration::V7_0::getChannelCount(
-            currentConfig.outputCfg.base.channelMask);
+            currentConfig.outputCfg.base.channelMask.value());
     ASSERT_NE(*channelCount, 0);
 #endif
 }
@@ -309,6 +313,47 @@
     EXPECT_EQ(Result::OK, ret2);
 }
 
+#if MAJOR_VERSION >= 7
+std::vector<EffectBufferConfig> generateInvalidConfigs(const EffectBufferConfig& src) {
+    std::vector<EffectBufferConfig> result;
+    EffectBufferConfig invalidFormat = src;
+    invalidFormat.base.format.value("random_string");
+    result.push_back(std::move(invalidFormat));
+    EffectBufferConfig invalidChannelMask = src;
+    invalidChannelMask.base.channelMask.value("random_string");
+    result.push_back(std::move(invalidChannelMask));
+    return result;
+}
+
+TEST_P(AudioEffectHidlTest, SetConfigInvalidArguments) {
+    description("Verify that invalid arguments are rejected by SetConfig");
+    Result retval = Result::NOT_INITIALIZED;
+    EffectConfig currentConfig;
+    Return<void> ret = effect->getConfig([&](Result r, const EffectConfig& conf) {
+        retval = r;
+        if (r == Result::OK) {
+            currentConfig = conf;
+        }
+    });
+    EXPECT_TRUE(ret.isOk());
+    EXPECT_EQ(Result::OK, retval);
+    for (const auto& invalidInputCfg : generateInvalidConfigs(currentConfig.inputCfg)) {
+        EffectConfig invalidConfig = currentConfig;
+        invalidConfig.inputCfg = invalidInputCfg;
+        Return<Result> ret = effect->setConfig(invalidConfig, nullptr, nullptr);
+        EXPECT_TRUE(ret.isOk());
+        EXPECT_EQ(Result::INVALID_ARGUMENTS, ret);
+    }
+    for (const auto& invalidOutputCfg : generateInvalidConfigs(currentConfig.outputCfg)) {
+        EffectConfig invalidConfig = currentConfig;
+        invalidConfig.outputCfg = invalidOutputCfg;
+        Return<Result> ret = effect->setConfig(invalidConfig, nullptr, nullptr);
+        EXPECT_TRUE(ret.isOk());
+        EXPECT_EQ(Result::INVALID_ARGUMENTS, ret);
+    }
+}
+#endif
+
 TEST_P(AudioEffectHidlTest, GetConfigReverse) {
     description("Verify that GetConfigReverse does not crash");
     Return<void> ret = effect->getConfigReverse([&](Result, const EffectConfig&) {});
@@ -352,17 +397,22 @@
            rhs.data.handle() == nullptr;
 }
 
+#if MAJOR_VERSION <= 6
 inline bool operator==(const EffectBufferConfig& lhs, const EffectBufferConfig& rhs) {
     return lhs.buffer == rhs.buffer &&
-#if MAJOR_VERSION <= 6
            lhs.samplingRateHz == rhs.samplingRateHz && lhs.channels == rhs.channels &&
            lhs.format == rhs.format &&
-#else
-           lhs.base.sampleRateHz == rhs.base.sampleRateHz &&
-           lhs.base.channelMask == rhs.base.channelMask && lhs.base.format == rhs.base.format &&
-#endif
            lhs.accessMode == rhs.accessMode && lhs.mask == rhs.mask;
 }
+#else
+inline bool operator==(const EffectBufferConfig& lhs, const EffectBufferConfig& rhs) {
+    return lhs.buffer.getDiscriminator() == rhs.buffer.getDiscriminator() &&
+           (lhs.buffer.getDiscriminator() ==
+                    EffectBufferConfig::OptionalBuffer::hidl_discriminator::unspecified ||
+            lhs.buffer.buf() == rhs.buffer.buf()) &&
+           lhs.base == rhs.base && lhs.accessMode == rhs.accessMode;
+}
+#endif  // MAJOR_VERSION <= 6
 
 inline bool operator==(const EffectConfig& lhs, const EffectConfig& rhs) {
     return lhs.inputCfg == rhs.inputCfg && lhs.outputCfg == rhs.outputCfg;
@@ -413,6 +463,16 @@
     EXPECT_EQ(Result::OK, ret);
 }
 
+#if MAJOR_VERSION >= 7
+TEST_P(AudioEffectHidlTest, SetDeviceInvalidDeviceAddress) {
+    description("Verify that invalid device address is rejected by SetDevice");
+    DeviceAddress device{.deviceType = "random_string"};
+    Return<Result> ret = effect->setDevice(device);
+    EXPECT_TRUE(ret.isOk());
+    EXPECT_EQ(Result::INVALID_ARGUMENTS, ret);
+}
+#endif
+
 TEST_P(AudioEffectHidlTest, SetDevice) {
     description("Verify that SetDevice works for an output chain effect");
 #if MAJOR_VERSION <= 6
@@ -468,6 +528,17 @@
     EXPECT_TRUE(ret.isOk());
 }
 
+#if MAJOR_VERSION >= 7
+TEST_P(AudioEffectHidlTest, SetInputDeviceInvalidDeviceAddress) {
+    description("Verify that invalid device address is rejected by SetInputDevice");
+    DeviceAddress device{.deviceType = "random_string"};
+    Return<Result> ret = effect->setInputDevice(device);
+    EXPECT_TRUE(ret.isOk());
+    EXPECT_TRUE(ret == Result::INVALID_ARGUMENTS || ret == Result::NOT_SUPPORTED)
+            << ::testing::PrintToString(ret);
+}
+#endif
+
 TEST_P(AudioEffectHidlTest, SetInputDevice) {
     description("Verify that SetInputDevice does not crash");
 #if MAJOR_VERSION <= 6
@@ -479,6 +550,16 @@
     EXPECT_TRUE(ret.isOk());
 }
 
+#if MAJOR_VERSION >= 7
+TEST_P(AudioEffectHidlTest, SetInvalidAudioSource) {
+    description("Verify that an invalid audio source is rejected by SetAudioSource");
+    Return<Result> ret = effect->setAudioSource("random_string");
+    ASSERT_TRUE(ret.isOk());
+    EXPECT_TRUE(ret == Result::INVALID_ARGUMENTS || ret == Result::NOT_SUPPORTED)
+            << ::testing::PrintToString(ret);
+}
+#endif
+
 TEST_P(AudioEffectHidlTest, SetAudioSource) {
     description("Verify that SetAudioSource does not crash");
 #if MAJOR_VERSION <= 6
diff --git a/authsecret/aidl/Android.bp b/authsecret/aidl/Android.bp
new file mode 100644
index 0000000..0a05034
--- /dev/null
+++ b/authsecret/aidl/Android.bp
@@ -0,0 +1,16 @@
+aidl_interface {
+    name: "android.hardware.authsecret",
+    vendor_available: true,
+    srcs: ["android/hardware/authsecret/*.aidl"],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+}
diff --git a/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl b/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl
new file mode 100644
index 0000000..14e8325
--- /dev/null
+++ b/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.authsecret;
+@VintfStability
+interface IAuthSecret {
+  oneway void setPrimaryUserCredential(in byte[] secret);
+}
diff --git a/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl b/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl
new file mode 100644
index 0000000..3849ec0
--- /dev/null
+++ b/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.authsecret;
+
+/**
+ * This security HAL allows vendor components to be cryptographically tied to
+ * the primary user's credential. For example, security hardware can require
+ * proof that the credential is known before applying updates.
+ *
+ */
+@VintfStability
+interface IAuthSecret {
+    /**
+     * When the primary user is unlocked, this method is passed a secret to
+     * prove that is has been successfully unlocked. The primary user can either
+     * be unlocked by a person entering their credential or by another party
+     * using an escrow token e.g. a device administrator.
+     *
+     * The first time this is called, the secret must be used to provision state
+     * that depends on the primary user's secret. The same secret must be passed
+     * on each call until the next factory reset.
+     *
+     * Upon factory reset, any dependence on the secret must be removed as that
+     * secret is now lost and must never be derived again. A new secret must be
+     * created for the new primary user which must be used to newly provision
+     * state the first time this method is called after factory reset.
+     *
+     * The secret must be at least 16 bytes, or the secret must be dropped.
+     *
+     * @param secret blob derived from the primary user's credential.
+     */
+    oneway void setPrimaryUserCredential(in byte[] secret);
+}
diff --git a/authsecret/aidl/default/Android.bp b/authsecret/aidl/default/Android.bp
new file mode 100644
index 0000000..44e0711
--- /dev/null
+++ b/authsecret/aidl/default/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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.
+//
+
+cc_binary {
+    name: "android.hardware.authsecret-service.example",
+    relative_install_path: "hw",
+    init_rc: ["android.hardware.authsecret-service.example.rc"],
+    vintf_fragments: ["android.hardware.authsecret-service.example.xml"],
+    vendor: true,
+    srcs: [
+        "service.cpp",
+        "AuthSecret.cpp",
+    ],
+    shared_libs: [
+        "android.hardware.authsecret-V1-ndk_platform",
+        "libbase",
+        "libbinder_ndk",
+    ],
+}
diff --git a/authsecret/aidl/default/AuthSecret.cpp b/authsecret/aidl/default/AuthSecret.cpp
new file mode 100644
index 0000000..9645e4d
--- /dev/null
+++ b/authsecret/aidl/default/AuthSecret.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "AuthSecret.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace authsecret {
+
+// Methods from ::android::hardware::authsecret::IAuthSecret follow.
+::ndk::ScopedAStatus AuthSecret::setPrimaryUserCredential(const std::vector<uint8_t>& in_secret) {
+    (void)in_secret;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace authsecret
+} // namespace hardware
+} // namespace android
+} // aidl
diff --git a/authsecret/aidl/default/AuthSecret.h b/authsecret/aidl/default/AuthSecret.h
new file mode 100644
index 0000000..b40fc48
--- /dev/null
+++ b/authsecret/aidl/default/AuthSecret.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/authsecret/BnAuthSecret.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace authsecret {
+
+struct AuthSecret : public BnAuthSecret {
+    AuthSecret() = default;
+
+    // Methods from ::android::hardware::authsecret::IAuthSecret follow.
+    ::ndk::ScopedAStatus setPrimaryUserCredential(const std::vector<uint8_t>& in_secret) override;
+
+};
+
+} // namespace authsecret
+} // namespace hardware
+} // namespace android
+} // aidl
diff --git a/authsecret/aidl/default/android.hardware.authsecret-service.example.rc b/authsecret/aidl/default/android.hardware.authsecret-service.example.rc
new file mode 100644
index 0000000..fef6e24
--- /dev/null
+++ b/authsecret/aidl/default/android.hardware.authsecret-service.example.rc
@@ -0,0 +1,4 @@
+service vendor.authsecret_default /vendor/bin/hw/android.hardware.authsecret-service.example
+    class hal
+    user hsm
+    group hsm
diff --git a/authsecret/aidl/default/android.hardware.authsecret-service.example.xml b/authsecret/aidl/default/android.hardware.authsecret-service.example.xml
new file mode 100644
index 0000000..9d4e162
--- /dev/null
+++ b/authsecret/aidl/default/android.hardware.authsecret-service.example.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.authsecret</name>
+        <version>1</version>
+        <interface>
+            <name>IAuthSecret</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/authsecret/aidl/default/service.cpp b/authsecret/aidl/default/service.cpp
new file mode 100644
index 0000000..efecf10
--- /dev/null
+++ b/authsecret/aidl/default/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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 <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "AuthSecret.h"
+
+using ::aidl::android::hardware::authsecret::AuthSecret;
+
+int main() {
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+    std::shared_ptr<AuthSecret> authsecret = ndk::SharedRefBase::make<AuthSecret>();
+
+    const std::string instance = std::string() + AuthSecret::descriptor + "/default";
+    binder_status_t status = AServiceManager_addService(authsecret->asBinder().get(), instance.c_str());
+    CHECK(status == STATUS_OK);
+
+    ABinderProcess_joinThreadPool();
+    return -1; // Should never be reached
+}
diff --git a/authsecret/aidl/vts/Android.bp b/authsecret/aidl/vts/Android.bp
new file mode 100644
index 0000000..29b3bcc
--- /dev/null
+++ b/authsecret/aidl/vts/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 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.
+//
+
+cc_test {
+    name: "VtsHalAuthSecretTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: ["VtsHalAuthSecretTargetTest.cpp"],
+    static_libs: ["android.hardware.authsecret-V1-ndk_platform"],
+    shared_libs: ["libbinder_ndk"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+    require_root: true,
+}
diff --git a/authsecret/aidl/vts/OWNERS b/authsecret/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/authsecret/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp b/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp
new file mode 100644
index 0000000..31c2834
--- /dev/null
+++ b/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp
@@ -0,0 +1,96 @@
+/*
+ * 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 <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/authsecret/IAuthSecret.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::hardware::authsecret::IAuthSecret;
+
+using ::ndk::SpAIBinder;
+
+/**
+ * There is no expected behaviour that can be tested so these tests check the
+ * HAL doesn't crash with different execution orders.
+ */
+class AuthSecretAidlTest : public testing::TestWithParam<std::string> {
+  public:
+    virtual void SetUp() override {
+        authsecret = IAuthSecret::fromBinder(
+            SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+        ASSERT_NE(authsecret, nullptr);
+
+        // Notify LSS to generate PIN code '1234' and corresponding secret.
+        (void)system("cmd lock_settings set-pin 1234");
+
+        // All tests must enroll the correct secret first as this cannot be changed
+        // without a factory reset and the order of tests could change.
+        authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+    }
+
+    static void TearDownTestSuite() {
+        // clean up PIN code after testing
+        (void)system("cmd lock_settings clear --old 1234");
+    }
+
+    std::shared_ptr<IAuthSecret> authsecret;
+    std::vector<uint8_t> CORRECT_SECRET{61, 93, 124, 240, 5, 0, 7, 201, 9, 129, 11, 12, 0, 14, 0, 16};
+    std::vector<uint8_t> WRONG_SECRET{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+};
+
+/* Provision the primary user with a secret. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredential) {
+    // Secret provisioned by SetUp()
+}
+
+/* Provision the primary user with a secret and pass the secret again. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndPassAgain) {
+    // Secret provisioned by SetUp()
+    authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+}
+
+/* Provision the primary user with a secret and pass the secret again repeatedly. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndPassAgainMultipleTimes) {
+    // Secret provisioned by SetUp()
+    constexpr int N = 5;
+    for (int i = 0; i < N; ++i) {
+        authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+    }
+}
+
+/* Provision the primary user with a secret and then pass the wrong secret. This
+ * should never happen and is an framework bug if it does. As the secret is
+ * wrong, the HAL implementation may not be able to function correctly but it
+ * should fail gracefully. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndWrongSecret) {
+    // Secret provisioned by SetUp()
+    authsecret->setPrimaryUserCredential(WRONG_SECRET);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthSecretAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+        PerInstance, AuthSecretAidlTest,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IAuthSecret::descriptor)),
+        android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/automotive/occupant_awareness/aidl/default/Android.bp b/automotive/occupant_awareness/aidl/default/Android.bp
index 1b2fba2..1e50930 100644
--- a/automotive/occupant_awareness/aidl/default/Android.bp
+++ b/automotive/occupant_awareness/aidl/default/Android.bp
@@ -27,6 +27,6 @@
         "libbase",
         "libbinder_ndk",
         "libutils",
-        "android.hardware.automotive.occupant_awareness-ndk_platform",
+        "android.hardware.automotive.occupant_awareness-V1-ndk_platform",
     ],
 }
diff --git a/automotive/occupant_awareness/aidl/mock/Android.bp b/automotive/occupant_awareness/aidl/mock/Android.bp
index 4b30866..64ca733 100644
--- a/automotive/occupant_awareness/aidl/mock/Android.bp
+++ b/automotive/occupant_awareness/aidl/mock/Android.bp
@@ -27,6 +27,6 @@
         "libbase",
         "libbinder_ndk",
         "libutils",
-        "android.hardware.automotive.occupant_awareness-ndk_platform",
+        "android.hardware.automotive.occupant_awareness-V1-ndk_platform",
     ],
 }
diff --git a/automotive/occupant_awareness/aidl/vts/functional/Android.bp b/automotive/occupant_awareness/aidl/vts/functional/Android.bp
index 514b0af..dbd0538 100644
--- a/automotive/occupant_awareness/aidl/vts/functional/Android.bp
+++ b/automotive/occupant_awareness/aidl/vts/functional/Android.bp
@@ -9,7 +9,7 @@
         "libbinder",
     ],
     static_libs: [
-        "android.hardware.automotive.occupant_awareness-cpp",
+        "android.hardware.automotive.occupant_awareness-V1-cpp",
     ],
     test_suites: [
         "vts",
diff --git a/automotive/vehicle/2.0/default/Android.bp b/automotive/vehicle/2.0/default/Android.bp
index 4c0963d..312ad11 100644
--- a/automotive/vehicle/2.0/default/Android.bp
+++ b/automotive/vehicle/2.0/default/Android.bp
@@ -32,7 +32,7 @@
     defaults: ["vhal_v2_0_defaults"],
     shared_libs: [
         "libbinder_ndk",
-        "carwatchdog_aidl_interface-ndk_platform",
+        "carwatchdog_aidl_interface-V2-ndk_platform",
     ],
 }
 
diff --git a/bluetooth/1.0/default/test/bluetooth_address_test.cc b/bluetooth/1.0/default/test/bluetooth_address_test.cc
index ee52d33..422d6a3 100644
--- a/bluetooth/1.0/default/test/bluetooth_address_test.cc
+++ b/bluetooth/1.0/default/test/bluetooth_address_test.cc
@@ -14,7 +14,6 @@
 // limitations under the License.
 //
 
-#include <cutils/properties.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <gtest/gtest.h>
@@ -39,12 +38,6 @@
     "00:00:00:00:00:00";
 constexpr uint8_t kZeros_bytes[BluetoothAddress::kBytes] = {0x00, 0x00, 0x00,
                                                             0x00, 0x00, 0x00};
-constexpr char kTestAddrBad1[BluetoothAddress::kStringLength + 1] =
-    "bb:aa:dd:00:00:01";
-constexpr uint8_t kTestAddrBad1_bytes[BluetoothAddress::kBytes] = {
-    0xbb, 0xaa, 0xdd, 0x00, 0x00, 0x01};
-
-constexpr char kAddrPath[] = "/tmp/my_address_in_a_file.txt";
 
 class BluetoothAddressTest : public ::testing::Test {
  public:
@@ -120,45 +113,6 @@
   EXPECT_FALSE(memcmp(addrA, addrB, BluetoothAddress::kStringLength) == 0);
 }
 
-TEST_F(BluetoothAddressTest, get_local_address) {
-  EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, "") == 0);
-  EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, "") == 0);
-  uint8_t address[BluetoothAddress::kBytes];
-
-  // File contains a non-zero Address.
-  FileWriteString(kAddrPath, kTestAddr1);
-  EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, kAddrPath) == 0);
-  EXPECT_TRUE(BluetoothAddress::get_local_address(address));
-  EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-
-  // File contains a zero address.  A random address will be generated.
-  FileWriteString(kAddrPath, kZeros);
-  EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, kAddrPath) == 0);
-  EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddrBad1) == 0);
-  EXPECT_TRUE(BluetoothAddress::get_local_address(address));
-  EXPECT_TRUE(memcmp(address, kZeros_bytes, BluetoothAddress::kBytes) != 0);
-  char prop[PROP_VALUE_MAX] = "Before reading";
-  EXPECT_TRUE(property_get(PERSIST_BDADDR_PROPERTY, prop, NULL) ==
-              BluetoothAddress::kStringLength);
-  char address_str[BluetoothAddress::kStringLength + 1];
-  BluetoothAddress::bytes_to_string(address, address_str);
-  EXPECT_TRUE(memcmp(address_str, prop, BluetoothAddress::kStringLength) == 0);
-
-  // Factory property contains an address.
-  EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddrBad1) == 0);
-  EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, kTestAddr1) == 0);
-  EXPECT_TRUE(BluetoothAddress::get_local_address(address));
-  EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-
-  // Persistent property contains an address.
-  memcpy(address, kTestAddrBad1_bytes, BluetoothAddress::kBytes);
-  EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddr1) == 0);
-  EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, "") == 0);
-  EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, "") == 0);
-  EXPECT_TRUE(BluetoothAddress::get_local_address(address));
-  EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-}
-
 }  // namespace implementation
 }  // namespace V1_0
 }  // namespace bluetooth
diff --git a/bluetooth/audio/2.0/default/Android.bp b/bluetooth/audio/2.0/default/Android.bp
index 0db0028..8ed631e 100644
--- a/bluetooth/audio/2.0/default/Android.bp
+++ b/bluetooth/audio/2.0/default/Android.bp
@@ -23,24 +23,3 @@
         "libutils",
     ],
 }
-
-cc_library_shared {
-    name: "libbluetooth_audio_session",
-    defaults: ["hidl_defaults"],
-    vendor: true,
-    srcs: [
-        "session/BluetoothAudioSession.cpp",
-        "session/BluetoothAudioSupportedCodecsDB.cpp",
-    ],
-    export_include_dirs: ["session/"],
-    header_libs: ["libhardware_headers"],
-    shared_libs: [
-        "android.hardware.bluetooth.audio@2.0",
-        "libbase",
-        "libcutils",
-        "libfmq",
-        "libhidlbase",
-        "liblog",
-        "libutils",
-    ],
-}
diff --git a/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp b/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp
index b4a61b6..3fe1a4d 100644
--- a/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp
@@ -22,8 +22,8 @@
 #include <fmq/MessageQueue.h>
 #include <hidl/MQDescriptor.h>
 
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
 
 namespace android {
 namespace hardware {
@@ -32,7 +32,7 @@
 namespace V2_1 {
 namespace implementation {
 
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
 using ::android::hardware::kSynchronizedReadWrite;
 using ::android::hardware::MessageQueue;
 using ::android::hardware::Void;
@@ -81,8 +81,8 @@
 
 Return<void> A2dpOffloadAudioProvider::onSessionReady(
     startSession_cb _hidl_cb) {
-  BluetoothAudioSessionReport::OnSessionStarted(session_type_, stack_iface_,
-                                                nullptr, audio_config_);
+  BluetoothAudioSessionReport_2_1::OnSessionStarted(session_type_, stack_iface_,
+                                                    nullptr, audio_config_);
   _hidl_cb(BluetoothAudioStatus::SUCCESS, DataMQ::Descriptor());
   return Void();
 }
diff --git a/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp b/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp
index a67c341..a37176b 100644
--- a/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp
@@ -20,8 +20,8 @@
 
 #include <android-base/logging.h>
 
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
 
 namespace android {
 namespace hardware {
@@ -30,7 +30,7 @@
 namespace V2_1 {
 namespace implementation {
 
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
 using ::android::hardware::Void;
 using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration;
 
@@ -96,7 +96,7 @@
 Return<void> A2dpSoftwareAudioProvider::onSessionReady(
     startSession_cb _hidl_cb) {
   if (mDataMQ && mDataMQ->isValid()) {
-    BluetoothAudioSessionReport::OnSessionStarted(
+    BluetoothAudioSessionReport_2_1::OnSessionStarted(
         session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
     _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
   } else {
diff --git a/bluetooth/audio/2.1/default/Android.bp b/bluetooth/audio/2.1/default/Android.bp
index 5381fec..c05aa3f 100644
--- a/bluetooth/audio/2.1/default/Android.bp
+++ b/bluetooth/audio/2.1/default/Android.bp
@@ -16,29 +16,7 @@
         "android.hardware.bluetooth.audio@2.0",
         "android.hardware.bluetooth.audio@2.1",
         "libbase",
-        "libbluetooth_audio_session_2_1",
-        "libcutils",
-        "libfmq",
-        "libhidlbase",
-        "liblog",
-        "libutils",
-    ],
-}
-
-cc_library_shared {
-    name: "libbluetooth_audio_session_2_1",
-    defaults: ["hidl_defaults"],
-    vendor: true,
-    srcs: [
-        "session/BluetoothAudioSession.cpp",
-        "session/BluetoothAudioSupportedCodecsDB.cpp",
-    ],
-    export_include_dirs: ["session/"],
-    header_libs: ["libhardware_headers"],
-    shared_libs: [
-        "android.hardware.bluetooth.audio@2.0",
-        "android.hardware.bluetooth.audio@2.1",
-        "libbase",
+        "libbluetooth_audio_session",
         "libcutils",
         "libfmq",
         "libhidlbase",
diff --git a/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp b/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
index 73fe06c..38889ae 100644
--- a/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
@@ -20,8 +20,8 @@
 
 #include <android-base/logging.h>
 
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
 
 namespace android {
 namespace hardware {
@@ -30,7 +30,7 @@
 namespace V2_1 {
 namespace implementation {
 
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
 using ::android::hardware::kSynchronizedReadWrite;
 using ::android::hardware::MessageQueue;
 using ::android::hardware::Void;
@@ -105,8 +105,8 @@
    * HAL server should start the streaming on data path.
    */
   if (stack_iface_) {
-    BluetoothAudioSessionReport::ReportControlStatus(session_type_, true,
-                                                     status);
+    BluetoothAudioSessionReport_2_1::ReportControlStatus(session_type_, true,
+                                                         status);
   } else {
     LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
                  << ", status=" << toString(status) << " has NO session";
@@ -125,8 +125,8 @@
    * HAL server should suspend the streaming on data path.
    */
   if (stack_iface_) {
-    BluetoothAudioSessionReport::ReportControlStatus(session_type_, false,
-                                                     status);
+    BluetoothAudioSessionReport_2_1::ReportControlStatus(session_type_, false,
+                                                         status);
   } else {
     LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
                  << ", status=" << toString(status) << " has NO session";
@@ -139,7 +139,7 @@
   LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_);
 
   if (stack_iface_) {
-    BluetoothAudioSessionReport::OnSessionEnded(session_type_);
+    BluetoothAudioSessionReport_2_1::OnSessionEnded(session_type_);
     stack_iface_->unlinkToDeath(death_recipient_);
   } else {
     LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
diff --git a/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp b/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp
index adf2717..e1b1ac6 100644
--- a/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp
+++ b/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp
@@ -20,7 +20,7 @@
 
 #include <android-base/logging.h>
 
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
 
 namespace android {
 namespace hardware {
diff --git a/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp b/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp
index aded7e1..712bd4f 100644
--- a/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp
@@ -20,8 +20,8 @@
 
 #include <android-base/logging.h>
 
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
 
 namespace android {
 namespace hardware {
@@ -30,7 +30,7 @@
 namespace V2_1 {
 namespace implementation {
 
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
 using ::android::hardware::Void;
 using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration;
 
@@ -95,7 +95,7 @@
 
 Return<void> HearingAidAudioProvider::onSessionReady(startSession_cb _hidl_cb) {
   if (mDataMQ && mDataMQ->isValid()) {
-    BluetoothAudioSessionReport::OnSessionStarted(
+    BluetoothAudioSessionReport_2_1::OnSessionStarted(
         session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
     _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
   } else {
diff --git a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
index 1fa2dce..2ebf6c5 100644
--- a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
@@ -21,8 +21,8 @@
 
 #include <android-base/logging.h>
 
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
 
 namespace android {
 namespace hardware {
@@ -31,7 +31,7 @@
 namespace V2_1 {
 namespace implementation {
 
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
 using ::android::hardware::Void;
 using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
 using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
@@ -91,35 +91,30 @@
 
   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;
-    case SampleRate::RATE_88200:
-      kDataMqSize = 88200;
-      break;
-    case SampleRate::RATE_96000:
-      kDataMqSize = 96000;
-      break;
-    case SampleRate::RATE_176400:
-      kDataMqSize = 176400;
-      break;
-    case SampleRate::RATE_192000:
-      kDataMqSize = 192000;
-      break;
     default:
-      /* This should never happen it would be caught while validating
-       * parameters.
-       */
-      break;
+      LOG(WARNING) << __func__ << " - Unsupported sampling frequency="
+                   << toString(audioConfig.pcmConfig());
+      _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+               DataMQ::Descriptor());
+      return Void();
   }
 
   /* Number of samples per millisecond */
@@ -184,7 +179,7 @@
 
 Return<void> LeAudioAudioProvider::onSessionReady(startSession_cb _hidl_cb) {
   if (mDataMQ && mDataMQ->isValid()) {
-    BluetoothAudioSessionReport::OnSessionStarted(
+    BluetoothAudioSessionReport_2_1::OnSessionStarted(
         session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
     _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
   } else {
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp b/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp
deleted file mode 100644
index ea2c54a..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright 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 "BTAudioProviderSession"
-
-#include "BluetoothAudioSession.h"
-
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-using ::android::hardware::audio::common::V5_0::AudioContentType;
-using ::android::hardware::audio::common::V5_0::AudioUsage;
-using ::android::hardware::audio::common::V5_0::PlaybackTrackMetadata;
-using ::android::hardware::audio::common::V5_0::SourceMetadata;
-using ::android::hardware::bluetooth::audio::V2_0::CodecType;
-using ::android::hardware::bluetooth::audio::V2_0::TimeSpec;
-
-const CodecConfiguration BluetoothAudioSession::kInvalidCodecConfiguration = {
-    .codecType = CodecType::UNKNOWN,
-    .encodedAudioBitrate = 0x00000000,
-    .peerMtu = 0xffff,
-    .isScmstEnabled = false,
-    .config = {}};
-AudioConfiguration BluetoothAudioSession::invalidSoftwareAudioConfiguration =
-    {};
-AudioConfiguration BluetoothAudioSession::invalidOffloadAudioConfiguration = {};
-
-static constexpr int kFmqSendTimeoutMs = 1000;  // 1000 ms timeout for sending
-static constexpr int kFmqReceiveTimeoutMs =
-    1000;                               // 1000 ms timeout for receiving
-static constexpr int kWritePollMs = 1;  // polled non-blocking interval
-static constexpr int kReadPollMs = 1;   // polled non-blocking interval
-
-static inline timespec timespec_convert_from_hal(const TimeSpec& TS) {
-  return {.tv_sec = static_cast<long>(TS.tvSec),
-          .tv_nsec = static_cast<long>(TS.tvNSec)};
-}
-
-BluetoothAudioSession::BluetoothAudioSession(const SessionType& session_type)
-    : session_type_(session_type), stack_iface_(nullptr), mDataMQ(nullptr) {
-  invalidSoftwareAudioConfiguration.pcmConfig(kInvalidPcmParameters);
-  invalidOffloadAudioConfiguration.codecConfig(kInvalidCodecConfiguration);
-}
-
-// 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::OnSessionStarted(
-    const sp<IBluetoothAudioPort> stack_iface, const DataMQ::Descriptor* dataMQ,
-    const AudioConfiguration& audio_config) {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (stack_iface == nullptr) {
-    LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
-               << ", IBluetoothAudioPort Invalid";
-  } else if (!UpdateAudioConfig(audio_config)) {
-    LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
-               << ", AudioConfiguration=" << toString(audio_config)
-               << " Invalid";
-  } else if (!UpdateDataPath(dataMQ)) {
-    LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
-               << " DataMQ Invalid";
-    audio_config_ =
-        (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH
-             ? kInvalidOffloadAudioConfiguration
-             : kInvalidSoftwareAudioConfiguration);
-  } else {
-    stack_iface_ = stack_iface;
-    LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
-              << ", AudioConfiguration=" << toString(audio_config);
-    ReportSessionStatus();
-  }
-}
-
-// The report function is used to report that the Bluetooth stack has ended the
-// session, and will invoke session_changed_cb_ to notify registered
-// bluetooth_audio outputs
-void BluetoothAudioSession::OnSessionEnded() {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  bool toggled = IsSessionReady();
-  LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_);
-  audio_config_ = (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH
-                       ? kInvalidOffloadAudioConfiguration
-                       : kInvalidSoftwareAudioConfiguration);
-  stack_iface_ = nullptr;
-  UpdateDataPath(nullptr);
-  if (toggled) {
-    ReportSessionStatus();
-  }
-}
-
-// invoking the registered session_changed_cb_
-void BluetoothAudioSession::ReportSessionStatus() {
-  // This is locked already by OnSessionStarted / OnSessionEnded
-  if (observers_.empty()) {
-    LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
-              << " has NO port state observer";
-    return;
-  }
-  for (auto& observer : observers_) {
-    uint16_t cookie = observer.first;
-    std::shared_ptr<struct PortStatusCallbacks> cb = observer.second;
-    LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
-              << " notify to bluetooth_audio=0x"
-              << android::base::StringPrintf("%04x", cookie);
-    cb->session_changed_cb_(cookie);
-  }
-}
-
-// The report function is used to report that the Bluetooth stack has notified
-// the result of startStream or suspendStream, and will invoke
-// control_result_cb_ to notify registered bluetooth_audio outputs
-void BluetoothAudioSession::ReportControlStatus(
-    bool start_resp, const BluetoothAudioStatus& status) {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (observers_.empty()) {
-    LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
-                 << " has NO port state observer";
-    return;
-  }
-  for (auto& observer : observers_) {
-    uint16_t cookie = observer.first;
-    std::shared_ptr<struct PortStatusCallbacks> cb = observer.second;
-    LOG(INFO) << __func__ << " - status=" << toString(status)
-              << " for SessionType=" << toString(session_type_)
-              << ", bluetooth_audio=0x"
-              << android::base::StringPrintf("%04x", cookie)
-              << (start_resp ? " started" : " suspended");
-    cb->control_result_cb_(cookie, start_resp, status);
-  }
-}
-
-// The function helps to check if this session is ready or not
-// @return: true if the Bluetooth stack has started the specified session
-bool BluetoothAudioSession::IsSessionReady() {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  bool dataMQ_valid =
-      (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH ||
-       (mDataMQ != nullptr && mDataMQ->isValid()));
-  return stack_iface_ != nullptr && dataMQ_valid;
-}
-
-bool BluetoothAudioSession::UpdateDataPath(const DataMQ::Descriptor* dataMQ) {
-  if (dataMQ == nullptr) {
-    // usecase of reset by nullptr
-    mDataMQ = nullptr;
-    return true;
-  }
-  std::unique_ptr<DataMQ> tempDataMQ;
-  tempDataMQ.reset(new DataMQ(*dataMQ));
-  if (!tempDataMQ || !tempDataMQ->isValid()) {
-    mDataMQ = nullptr;
-    return false;
-  }
-  mDataMQ = std::move(tempDataMQ);
-  return true;
-}
-
-bool BluetoothAudioSession::UpdateAudioConfig(
-    const AudioConfiguration& audio_config) {
-  bool is_software_session =
-      (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH ||
-       session_type_ == SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH ||
-       session_type_ == SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH ||
-       session_type_ == SessionType::LE_AUDIO_SOFTWARE_DECODED_DATAPATH);
-  bool is_offload_session =
-      (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH);
-  auto audio_config_discriminator = audio_config.getDiscriminator();
-  bool is_software_audio_config =
-      (is_software_session &&
-       audio_config_discriminator ==
-           AudioConfiguration::hidl_discriminator::pcmConfig);
-  bool is_offload_audio_config =
-      (is_offload_session &&
-       audio_config_discriminator ==
-           AudioConfiguration::hidl_discriminator::codecConfig);
-  if (!is_software_audio_config && !is_offload_audio_config) {
-    return false;
-  }
-  audio_config_ = audio_config;
-  return true;
-}
-
-// The control function helps the bluetooth_audio module to register
-// PortStatusCallbacks
-// @return: cookie - the assigned number to this bluetooth_audio output
-uint16_t BluetoothAudioSession::RegisterStatusCback(
-    const PortStatusCallbacks& cbacks) {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  uint16_t cookie = ObserversCookieGetInitValue(session_type_);
-  uint16_t cookie_upper_bound = ObserversCookieGetUpperBound(session_type_);
-
-  while (cookie < cookie_upper_bound) {
-    if (observers_.find(cookie) == observers_.end()) {
-      break;
-    }
-    ++cookie;
-  }
-  if (cookie >= cookie_upper_bound) {
-    LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
-               << " has " << observers_.size()
-               << " observers already (No Resource)";
-    return kObserversCookieUndefined;
-  }
-  std::shared_ptr<struct PortStatusCallbacks> cb =
-      std::make_shared<struct PortStatusCallbacks>();
-  *cb = cbacks;
-  observers_[cookie] = cb;
-  return cookie;
-}
-
-// The control function helps the bluetooth_audio module to unregister
-// PortStatusCallbacks
-// @param: cookie - indicates which bluetooth_audio output is
-void BluetoothAudioSession::UnregisterStatusCback(uint16_t cookie) {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (observers_.erase(cookie) != 1) {
-    LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
-                 << " no such provider=0x"
-                 << android::base::StringPrintf("%04x", cookie);
-  }
-}
-
-// The control function is for the bluetooth_audio module to get the current
-// AudioConfiguration
-const AudioConfiguration& BluetoothAudioSession::GetAudioConfig() {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (IsSessionReady()) {
-    return audio_config_;
-  } else if (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
-    return kInvalidOffloadAudioConfiguration;
-  } else {
-    return kInvalidSoftwareAudioConfiguration;
-  }
-}
-
-// Those control functions are for the bluetooth_audio module to start, suspend,
-// stop stream, to check position, and to update metadata.
-bool BluetoothAudioSession::StartStream() {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (!IsSessionReady()) {
-    LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_)
-               << " has NO session";
-    return false;
-  }
-  auto hal_retval = stack_iface_->startStream();
-  if (!hal_retval.isOk()) {
-    LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
-                 << toString(session_type_) << " failed";
-    return false;
-  }
-  return true;
-}
-
-bool BluetoothAudioSession::SuspendStream() {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (!IsSessionReady()) {
-    LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_)
-               << " has NO session";
-    return false;
-  }
-  auto hal_retval = stack_iface_->suspendStream();
-  if (!hal_retval.isOk()) {
-    LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
-                 << toString(session_type_) << " failed";
-    return false;
-  }
-  return true;
-}
-
-void BluetoothAudioSession::StopStream() {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (!IsSessionReady()) {
-    return;
-  }
-  auto hal_retval = stack_iface_->stopStream();
-  if (!hal_retval.isOk()) {
-    LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
-                 << toString(session_type_) << " failed";
-  }
-}
-
-bool BluetoothAudioSession::GetPresentationPosition(
-    uint64_t* remote_delay_report_ns, uint64_t* total_bytes_readed,
-    timespec* data_position) {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (!IsSessionReady()) {
-    LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_)
-               << " has NO session";
-    return false;
-  }
-  bool retval = false;
-  auto hal_retval = stack_iface_->getPresentationPosition(
-      [&retval, &remote_delay_report_ns, &total_bytes_readed, &data_position](
-          BluetoothAudioStatus status,
-          const uint64_t& remoteDeviceAudioDelayNanos,
-          uint64_t transmittedOctets,
-          const TimeSpec& transmittedOctetsTimeStamp) {
-        if (status == BluetoothAudioStatus::SUCCESS) {
-          if (remote_delay_report_ns)
-            *remote_delay_report_ns = remoteDeviceAudioDelayNanos;
-          if (total_bytes_readed) *total_bytes_readed = transmittedOctets;
-          if (data_position)
-            *data_position =
-                timespec_convert_from_hal(transmittedOctetsTimeStamp);
-          retval = true;
-        }
-      });
-  if (!hal_retval.isOk()) {
-    LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
-                 << toString(session_type_) << " failed";
-    return false;
-  }
-  return retval;
-}
-
-void BluetoothAudioSession::UpdateTracksMetadata(
-    const struct source_metadata* source_metadata) {
-  std::lock_guard<std::recursive_mutex> guard(mutex_);
-  if (!IsSessionReady()) {
-    LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_)
-               << " has NO session";
-    return;
-  }
-
-  ssize_t track_count = source_metadata->track_count;
-  LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << ", "
-            << track_count << " track(s)";
-  if (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH ||
-      session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
-    return;
-  }
-
-  struct playback_track_metadata* track = source_metadata->tracks;
-  SourceMetadata sourceMetadata;
-  PlaybackTrackMetadata* halMetadata;
-
-  sourceMetadata.tracks.resize(track_count);
-  halMetadata = sourceMetadata.tracks.data();
-  while (track_count && track) {
-    halMetadata->usage = static_cast<AudioUsage>(track->usage);
-    halMetadata->contentType =
-        static_cast<AudioContentType>(track->content_type);
-    halMetadata->gain = track->gain;
-    LOG(VERBOSE) << __func__ << " - SessionType=" << toString(session_type_)
-                 << ", usage=" << toString(halMetadata->usage)
-                 << ", content=" << toString(halMetadata->contentType)
-                 << ", gain=" << halMetadata->gain;
-    --track_count;
-    ++track;
-    ++halMetadata;
-  }
-  auto hal_retval = stack_iface_->updateMetadata(sourceMetadata);
-  if (!hal_retval.isOk()) {
-    LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
-                 << toString(session_type_) << " failed";
-  }
-}
-
-// The control function writes stream to FMQ
-size_t BluetoothAudioSession::OutWritePcmData(const void* buffer,
-                                              size_t bytes) {
-  if (buffer == nullptr || !bytes) return 0;
-  size_t totalWritten = 0;
-  int ms_timeout = kFmqSendTimeoutMs;
-  do {
-    std::unique_lock<std::recursive_mutex> lock(mutex_);
-    if (!IsSessionReady()) break;
-    size_t availableToWrite = mDataMQ->availableToWrite();
-    if (availableToWrite) {
-      if (availableToWrite > (bytes - totalWritten)) {
-        availableToWrite = bytes - totalWritten;
-      }
-
-      if (!mDataMQ->write(static_cast<const uint8_t*>(buffer) + totalWritten,
-                          availableToWrite)) {
-        ALOGE("FMQ datapath writing %zu/%zu failed", totalWritten, bytes);
-        return totalWritten;
-      }
-      totalWritten += availableToWrite;
-    } else if (ms_timeout >= kWritePollMs) {
-      lock.unlock();
-      usleep(kWritePollMs * 1000);
-      ms_timeout -= kWritePollMs;
-    } else {
-      ALOGD("out data %zu/%zu overflow %d ms", totalWritten, bytes,
-            (kFmqSendTimeoutMs - ms_timeout));
-      return totalWritten;
-    }
-  } while (totalWritten < bytes);
-  return totalWritten;
-}
-
-// The control function reads stream from FMQ
-size_t BluetoothAudioSession::InReadPcmData(void* buffer, size_t bytes) {
-  if (buffer == nullptr || !bytes) return 0;
-  size_t totalRead = 0;
-  int ms_timeout = kFmqReceiveTimeoutMs;
-  do {
-    std::unique_lock<std::recursive_mutex> lock(mutex_);
-    if (!IsSessionReady()) break;
-    size_t availableToRead = mDataMQ->availableToRead();
-    if (availableToRead) {
-      if (availableToRead > (bytes - totalRead)) {
-        availableToRead = bytes - totalRead;
-      }
-      if (!mDataMQ->read(static_cast<uint8_t*>(buffer) + totalRead,
-                         availableToRead)) {
-        ALOGE("FMQ datapath reading %zu/%zu failed", totalRead, bytes);
-        return totalRead;
-      }
-      totalRead += availableToRead;
-    } else if (ms_timeout >= kReadPollMs) {
-      lock.unlock();
-      usleep(kReadPollMs * 1000);
-      ms_timeout -= kReadPollMs;
-      continue;
-    } else {
-      ALOGD("in data %zu/%zu overflow %d ms", totalRead, bytes,
-            (kFmqReceiveTimeoutMs - ms_timeout));
-      return totalRead;
-    }
-  } while (totalRead < bytes);
-  return totalRead;
-}
-
-std::unique_ptr<BluetoothAudioSessionInstance>
-    BluetoothAudioSessionInstance::instance_ptr =
-        std::unique_ptr<BluetoothAudioSessionInstance>(
-            new BluetoothAudioSessionInstance());
-
-// API to fetch the session
-std::shared_ptr<BluetoothAudioSession>
-BluetoothAudioSessionInstance::GetSessionInstance(
-    const SessionType& 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> session_ptr =
-      std::make_shared<BluetoothAudioSession>(session_type);
-  instance_ptr->sessions_map_[session_type] = session_ptr;
-  return session_ptr;
-}
-
-}  // namespace audio
-}  // namespace bluetooth
-}  // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h
deleted file mode 100644
index 7bc12e6..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#pragma once
-
-#include <mutex>
-#include <unordered_map>
-
-#include <android/hardware/bluetooth/audio/2.0/IBluetoothAudioPort.h>
-#include <android/hardware/bluetooth/audio/2.1/types.h>
-#include <fmq/MessageQueue.h>
-#include <hardware/audio.h>
-#include <hidl/MQDescriptor.h>
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-using ::android::sp;
-using ::android::hardware::kSynchronizedReadWrite;
-using ::android::hardware::MessageQueue;
-using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
-using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
-using ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration;
-using ::android::hardware::bluetooth::audio::V2_0::IBluetoothAudioPort;
-using ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration;
-using ::android::hardware::bluetooth::audio::V2_1::PcmParameters;
-using ::android::hardware::bluetooth::audio::V2_1::SampleRate;
-using ::android::hardware::bluetooth::audio::V2_1::SessionType;
-
-using BluetoothAudioStatus =
-    ::android::hardware::bluetooth::audio::V2_0::Status;
-
-using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
-
-static constexpr uint16_t kObserversCookieSize = 0x0010;  // 0x0000 ~ 0x000f
-constexpr uint16_t kObserversCookieUndefined =
-    (static_cast<uint16_t>(SessionType::UNKNOWN) << 8 & 0xff00);
-inline SessionType ObserversCookieGetSessionType(uint16_t cookie) {
-  return static_cast<SessionType>(cookie >> 8 & 0x00ff);
-}
-inline uint16_t ObserversCookieGetInitValue(SessionType session_type) {
-  return (static_cast<uint16_t>(session_type) << 8 & 0xff00);
-}
-inline uint16_t ObserversCookieGetUpperBound(SessionType session_type) {
-  return (static_cast<uint16_t>(session_type) << 8 & 0xff00) +
-         kObserversCookieSize;
-}
-
-// This presents the callbacks of started / suspended and session changed,
-// and the bluetooth_audio module uses to receive the status notification
-struct PortStatusCallbacks {
-  // control_result_cb_ - when the Bluetooth stack reports results of
-  // streamStarted or streamSuspended, the BluetoothAudioProvider will invoke
-  // this callback to report to the bluetooth_audio module.
-  // @param: cookie - indicates which bluetooth_audio output should handle
-  // @param: start_resp - this report is for startStream or not
-  // @param: status - the result of startStream
-  std::function<void(uint16_t cookie, bool start_resp,
-                     const BluetoothAudioStatus& status)>
-      control_result_cb_;
-  // session_changed_cb_ - when the Bluetooth stack start / end session, the
-  // BluetoothAudioProvider will invoke this callback to notify to the
-  // bluetooth_audio module.
-  // @param: cookie - indicates which bluetooth_audio output should handle
-  std::function<void(uint16_t cookie)> session_changed_cb_;
-};
-
-class BluetoothAudioSession {
- private:
-  // using recursive_mutex to allow hwbinder to re-enter again.
-  std::recursive_mutex mutex_;
-  SessionType session_type_;
-
-  // audio control path to use for both software and offloading
-  sp<IBluetoothAudioPort> stack_iface_;
-  // Audio path (FMQ) for software encoding/decoded data
-  std::unique_ptr<DataMQ> mDataMQ;
-  // audio data configuration for both software and offloading
-  AudioConfiguration audio_config_;
-
-  static AudioConfiguration invalidSoftwareAudioConfiguration;
-  static AudioConfiguration invalidOffloadAudioConfiguration;
-
-  // saving those registered bluetooth_audio's callbacks
-  std::unordered_map<uint16_t, std::shared_ptr<struct PortStatusCallbacks>>
-      observers_;
-
-  bool UpdateDataPath(const DataMQ::Descriptor* dataMQ);
-  bool UpdateAudioConfig(const AudioConfiguration& audio_config);
-  // invoking the registered session_changed_cb_
-  void ReportSessionStatus();
-
- public:
-  BluetoothAudioSession(const 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();
-
-  // 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 AudioConfiguration& audio_config);
-
-  // The report function is used to report that the Bluetooth stack has ended
-  // the session, and will invoke session_changed_cb_ to notify registered
-  // bluetooth_audio outputs
-  void OnSessionEnded();
-
-  // The report function is used to report that the Bluetooth stack has notified
-  // the result of startStream or suspendStream, and will invoke
-  // control_result_cb_ to notify registered bluetooth_audio outputs
-  void ReportControlStatus(bool start_resp, const BluetoothAudioStatus& status);
-
-  // The control function helps the bluetooth_audio module to register
-  // PortStatusCallbacks
-  // @return: cookie - the assigned number to this bluetooth_audio output
-  uint16_t RegisterStatusCback(const PortStatusCallbacks& cbacks);
-
-  // The control function helps the bluetooth_audio module to unregister
-  // PortStatusCallbacks
-  // @param: cookie - indicates which bluetooth_audio output is
-  void UnregisterStatusCback(uint16_t cookie);
-
-  // The control function is for the bluetooth_audio module to get the current
-  // AudioConfiguration
-  const AudioConfiguration& GetAudioConfig();
-
-  // Those control functions are for the bluetooth_audio module to start,
-  // suspend, stop stream, to check position, and to update metadata.
-  bool StartStream();
-  bool SuspendStream();
-  void StopStream();
-  bool GetPresentationPosition(uint64_t* remote_delay_report_ns,
-                               uint64_t* total_bytes_readed,
-                               timespec* data_position);
-  void UpdateTracksMetadata(const struct source_metadata* source_metadata);
-
-  // The control function writes stream to FMQ
-  size_t OutWritePcmData(const void* buffer, size_t bytes);
-  // The control function read stream from FMQ
-  size_t InReadPcmData(void* buffer, size_t bytes);
-
-  static constexpr PcmParameters kInvalidPcmParameters = {
-      .sampleRate = SampleRate::RATE_UNKNOWN,
-      .channelMode = ChannelMode::UNKNOWN,
-      .bitsPerSample = BitsPerSample::BITS_UNKNOWN,
-      .dataIntervalUs = 0,
-  };
-  // can't be constexpr because of non-literal type
-  static const CodecConfiguration kInvalidCodecConfiguration;
-
-  static constexpr AudioConfiguration& kInvalidSoftwareAudioConfiguration =
-      invalidSoftwareAudioConfiguration;
-  static constexpr AudioConfiguration& kInvalidOffloadAudioConfiguration =
-      invalidOffloadAudioConfiguration;
-};
-
-class BluetoothAudioSessionInstance {
- public:
-  // The API is to fetch the specified session
-  static std::shared_ptr<BluetoothAudioSession> GetSessionInstance(
-      const SessionType& session_type);
-
- private:
-  static std::unique_ptr<BluetoothAudioSessionInstance> instance_ptr;
-  std::mutex mutex_;
-  std::unordered_map<SessionType, std::shared_ptr<BluetoothAudioSession>>
-      sessions_map_;
-};
-
-}  // namespace audio
-}  // namespace bluetooth
-}  // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h
deleted file mode 100644
index 017a611..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#pragma once
-
-#include "BluetoothAudioSession.h"
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-class BluetoothAudioSessionControl {
- public:
-  // The control API helps to check if session is ready or not
-  // @return: true if the Bluetooth stack has started th specified session
-  static bool IsSessionReady(const SessionType& session_type) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      return session_ptr->IsSessionReady();
-    }
-    return false;
-  }
-
-  // The control API helps the bluetooth_audio module to register
-  // PortStatusCallbacks
-  // @return: cookie - the assigned number to this bluetooth_audio output
-  static uint16_t RegisterControlResultCback(
-      const SessionType& session_type, const PortStatusCallbacks& cbacks) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      return session_ptr->RegisterStatusCback(cbacks);
-    }
-    return kObserversCookieUndefined;
-  }
-
-  // The control API helps the bluetooth_audio module to unregister
-  // PortStatusCallbacks
-  // @param: cookie - indicates which bluetooth_audio output is
-  static void UnregisterControlResultCback(const SessionType& session_type,
-                                           uint16_t cookie) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      session_ptr->UnregisterStatusCback(cookie);
-    }
-  }
-
-  // The control API for the bluetooth_audio module to get current
-  // AudioConfiguration
-  static const AudioConfiguration& GetAudioConfig(
-      const SessionType& session_type) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      return session_ptr->GetAudioConfig();
-    } else if (session_type == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
-      return BluetoothAudioSession::kInvalidOffloadAudioConfiguration;
-    } else {
-      return BluetoothAudioSession::kInvalidSoftwareAudioConfiguration;
-    }
-  }
-
-  // Those control APIs for the bluetooth_audio module to start / suspend / stop
-  // stream, to check position, and to update metadata.
-  static bool StartStream(const SessionType& session_type) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      return session_ptr->StartStream();
-    }
-    return false;
-  }
-
-  static bool SuspendStream(const SessionType& session_type) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      return session_ptr->SuspendStream();
-    }
-    return false;
-  }
-
-  static void StopStream(const SessionType& session_type) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      session_ptr->StopStream();
-    }
-  }
-
-  static bool GetPresentationPosition(const SessionType& session_type,
-                                      uint64_t* remote_delay_report_ns,
-                                      uint64_t* total_bytes_readed,
-                                      timespec* data_position) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      return session_ptr->GetPresentationPosition(
-          remote_delay_report_ns, total_bytes_readed, data_position);
-    }
-    return false;
-  }
-
-  static void UpdateTracksMetadata(
-      const SessionType& session_type,
-      const struct source_metadata* source_metadata) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      session_ptr->UpdateTracksMetadata(source_metadata);
-    }
-  }
-
-  // The control API writes stream to FMQ
-  static size_t OutWritePcmData(const SessionType& session_type,
-                                const void* buffer, size_t bytes) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      return session_ptr->OutWritePcmData(buffer, bytes);
-    }
-    return 0;
-  }
-
-  // The control API reads stream from FMQ
-  static size_t InReadPcmData(const SessionType& session_type, void* buffer,
-                              size_t bytes) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      return session_ptr->InReadPcmData(buffer, bytes);
-    }
-    return 0;
-  }
-};
-
-}  // namespace audio
-}  // namespace bluetooth
-}  // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h
deleted file mode 100644
index 267bf8f..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#pragma once
-
-#include "BluetoothAudioSession.h"
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-class BluetoothAudioSessionReport {
- public:
-  // The API reports the Bluetooth stack has started the session, and will
-  // inform registered bluetooth_audio session
-  static void OnSessionStarted(const SessionType& session_type,
-                               const sp<IBluetoothAudioPort> host_iface,
-                               const DataMQ::Descriptor* dataMQ,
-                               const AudioConfiguration& audio_config) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::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 SessionType& session_type) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      session_ptr->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 SessionType& session_type,
-                                  const bool& start_resp,
-                                  const BluetoothAudioStatus& status) {
-    std::shared_ptr<BluetoothAudioSession> session_ptr =
-        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
-    if (session_ptr != nullptr) {
-      session_ptr->ReportControlStatus(start_resp, status);
-    }
-  }
-};
-
-}  // namespace audio
-}  // namespace bluetooth
-}  // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp
deleted file mode 100644
index d15db49..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright 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 "BTAudioProviderSessionCodecsDB"
-
-#include "BluetoothAudioSupportedCodecsDB.h"
-
-#include <android-base/logging.h>
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-using ::android::hardware::bluetooth::audio::V2_0::AacObjectType;
-using ::android::hardware::bluetooth::audio::V2_0::AacParameters;
-using ::android::hardware::bluetooth::audio::V2_0::AacVariableBitRate;
-using ::android::hardware::bluetooth::audio::V2_0::AptxParameters;
-using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
-using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
-using ::android::hardware::bluetooth::audio::V2_0::CodecType;
-using ::android::hardware::bluetooth::audio::V2_0::LdacChannelMode;
-using ::android::hardware::bluetooth::audio::V2_0::LdacParameters;
-using ::android::hardware::bluetooth::audio::V2_0::LdacQualityIndex;
-using ::android::hardware::bluetooth::audio::V2_0::SbcAllocMethod;
-using ::android::hardware::bluetooth::audio::V2_0::SbcBlockLength;
-using ::android::hardware::bluetooth::audio::V2_0::SbcChannelMode;
-using ::android::hardware::bluetooth::audio::V2_0::SbcNumSubbands;
-using ::android::hardware::bluetooth::audio::V2_0::SbcParameters;
-using ::android::hardware::bluetooth::audio::V2_1::SampleRate;
-
-// Default Supported PCM Parameters
-static const ::android::hardware::bluetooth::audio::V2_0::PcmParameters
-    kDefaultSoftwarePcmCapabilities = {
-        .sampleRate = static_cast<
-            android::hardware::bluetooth::audio::V2_0::SampleRate>(
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_16000),
-        .channelMode =
-            static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO),
-        .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 |
-                                                    BitsPerSample::BITS_24 |
-                                                    BitsPerSample::BITS_32)};
-
-static const PcmParameters kDefaultSoftwarePcmCapabilities_2_1 = {
-    .sampleRate = static_cast<SampleRate>(
-        SampleRate::RATE_48000 | SampleRate::RATE_44100 |
-        SampleRate::RATE_32000 | SampleRate::RATE_24000 |
-        SampleRate::RATE_16000 | SampleRate::RATE_8000),
-    .channelMode =
-        static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO),
-    .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16)};
-
-// Default Supported Codecs
-// SBC: mSampleRate:(44100), mBitsPerSample:(16), mChannelMode:(MONO|STEREO)
-//      all blocks | subbands 8 | Loudness
-static const SbcParameters kDefaultOffloadSbcCapability = {
-    .sampleRate =
-        android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100,
-    .channelMode = static_cast<SbcChannelMode>(SbcChannelMode::MONO |
-                                               SbcChannelMode::JOINT_STEREO),
-    .blockLength = static_cast<SbcBlockLength>(
-        SbcBlockLength::BLOCKS_4 | SbcBlockLength::BLOCKS_8 |
-        SbcBlockLength::BLOCKS_12 | SbcBlockLength::BLOCKS_16),
-    .numSubbands = SbcNumSubbands::SUBBAND_8,
-    .allocMethod = SbcAllocMethod::ALLOC_MD_L,
-    .bitsPerSample = BitsPerSample::BITS_16,
-    .minBitpool = 2,
-    .maxBitpool = 53};
-
-// AAC: mSampleRate:(44100), mBitsPerSample:(16), mChannelMode:(STEREO)
-static const AacParameters kDefaultOffloadAacCapability = {
-    .objectType = AacObjectType::MPEG2_LC,
-    .sampleRate =
-        android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100,
-    .channelMode = ChannelMode::STEREO,
-    .variableBitRateEnabled = AacVariableBitRate::ENABLED,
-    .bitsPerSample = BitsPerSample::BITS_16};
-
-// LDAC: mSampleRate:(44100|48000|88200|96000), mBitsPerSample:(16|24|32),
-//       mChannelMode:(DUAL|STEREO)
-static const LdacParameters kDefaultOffloadLdacCapability = {
-    .sampleRate =
-        static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>(
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000),
-    .channelMode = static_cast<LdacChannelMode>(LdacChannelMode::DUAL |
-                                                LdacChannelMode::STEREO),
-    .qualityIndex = LdacQualityIndex::QUALITY_HIGH,
-    .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 |
-                                                BitsPerSample::BITS_24 |
-                                                BitsPerSample::BITS_32)};
-
-// aptX: mSampleRate:(44100|48000), mBitsPerSample:(16), mChannelMode:(STEREO)
-static const AptxParameters kDefaultOffloadAptxCapability = {
-    .sampleRate =
-        static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>(
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000),
-    .channelMode = ChannelMode::STEREO,
-    .bitsPerSample = BitsPerSample::BITS_16,
-};
-
-// aptX HD: mSampleRate:(44100|48000), mBitsPerSample:(24),
-//          mChannelMode:(STEREO)
-static const AptxParameters kDefaultOffloadAptxHdCapability = {
-    .sampleRate =
-        static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>(
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 |
-            android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000),
-    .channelMode = ChannelMode::STEREO,
-    .bitsPerSample = BitsPerSample::BITS_24,
-};
-
-const std::vector<CodecCapabilities> kDefaultOffloadA2dpCodecCapabilities = {
-    {.codecType = CodecType::SBC, .capabilities = {}},
-    {.codecType = CodecType::AAC, .capabilities = {}},
-    {.codecType = CodecType::LDAC, .capabilities = {}},
-    {.codecType = CodecType::APTX, .capabilities = {}},
-    {.codecType = CodecType::APTX_HD, .capabilities = {}}};
-
-static bool IsSingleBit(uint32_t bitmasks, uint32_t bitfield) {
-  bool single = false;
-  uint32_t test_bit = 0x00000001;
-  while (test_bit <= bitmasks && test_bit <= bitfield) {
-    if (bitfield & test_bit && bitmasks & test_bit) {
-      if (single) return false;
-      single = true;
-    }
-    if (test_bit == 0x80000000) break;
-    test_bit <<= 1;
-  }
-  return single;
-}
-
-static bool IsOffloadSbcConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific);
-static bool IsOffloadAacConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific);
-static bool IsOffloadLdacConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific);
-static bool IsOffloadAptxConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific);
-static bool IsOffloadAptxHdConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific);
-
-static bool IsOffloadSbcConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific) {
-  if (codec_specific.getDiscriminator() !=
-      CodecConfiguration::CodecSpecific::hidl_discriminator::sbcConfig) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  }
-  const SbcParameters sbc_data = codec_specific.sbcConfig();
-  if (!IsSingleBit(static_cast<uint32_t>(sbc_data.sampleRate), 0xff) ||
-      !IsSingleBit(static_cast<uint32_t>(sbc_data.channelMode), 0x0f) ||
-      !IsSingleBit(static_cast<uint32_t>(sbc_data.blockLength), 0xf0) ||
-      !IsSingleBit(static_cast<uint32_t>(sbc_data.numSubbands), 0x0c) ||
-      !IsSingleBit(static_cast<uint32_t>(sbc_data.allocMethod), 0x03) ||
-      !IsSingleBit(static_cast<uint32_t>(sbc_data.bitsPerSample), 0x07) ||
-      sbc_data.minBitpool > sbc_data.maxBitpool) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  } else if ((sbc_data.sampleRate & kDefaultOffloadSbcCapability.sampleRate) &&
-             (sbc_data.channelMode &
-              kDefaultOffloadSbcCapability.channelMode) &&
-             (sbc_data.blockLength &
-              kDefaultOffloadSbcCapability.blockLength) &&
-             (sbc_data.numSubbands &
-              kDefaultOffloadSbcCapability.numSubbands) &&
-             (sbc_data.allocMethod &
-              kDefaultOffloadSbcCapability.allocMethod) &&
-             (sbc_data.bitsPerSample &
-              kDefaultOffloadSbcCapability.bitsPerSample) &&
-             (kDefaultOffloadSbcCapability.minBitpool <= sbc_data.minBitpool &&
-              sbc_data.maxBitpool <= kDefaultOffloadSbcCapability.maxBitpool)) {
-    return true;
-  }
-  LOG(WARNING) << __func__
-               << ": Unsupported CodecSpecific=" << toString(codec_specific);
-  return false;
-}
-
-static bool IsOffloadAacConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific) {
-  if (codec_specific.getDiscriminator() !=
-      CodecConfiguration::CodecSpecific::hidl_discriminator::aacConfig) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  }
-  const AacParameters aac_data = codec_specific.aacConfig();
-  if (!IsSingleBit(static_cast<uint32_t>(aac_data.objectType), 0xf0) ||
-      !IsSingleBit(static_cast<uint32_t>(aac_data.sampleRate), 0xff) ||
-      !IsSingleBit(static_cast<uint32_t>(aac_data.channelMode), 0x03) ||
-      !IsSingleBit(static_cast<uint32_t>(aac_data.bitsPerSample), 0x07)) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  } else if ((aac_data.objectType & kDefaultOffloadAacCapability.objectType) &&
-             (aac_data.sampleRate & kDefaultOffloadAacCapability.sampleRate) &&
-             (aac_data.channelMode &
-              kDefaultOffloadAacCapability.channelMode) &&
-             (aac_data.variableBitRateEnabled == AacVariableBitRate::DISABLED ||
-              kDefaultOffloadAacCapability.variableBitRateEnabled ==
-                  AacVariableBitRate::ENABLED) &&
-             (aac_data.bitsPerSample &
-              kDefaultOffloadAacCapability.bitsPerSample)) {
-    return true;
-  }
-  LOG(WARNING) << __func__
-               << ": Unsupported CodecSpecific=" << toString(codec_specific);
-  return false;
-}
-
-static bool IsOffloadLdacConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific) {
-  if (codec_specific.getDiscriminator() !=
-      CodecConfiguration::CodecSpecific::hidl_discriminator::ldacConfig) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  }
-  const LdacParameters ldac_data = codec_specific.ldacConfig();
-  if (!IsSingleBit(static_cast<uint32_t>(ldac_data.sampleRate), 0xff) ||
-      !IsSingleBit(static_cast<uint32_t>(ldac_data.channelMode), 0x07) ||
-      (ldac_data.qualityIndex > LdacQualityIndex::QUALITY_LOW &&
-       ldac_data.qualityIndex != LdacQualityIndex::QUALITY_ABR) ||
-      !IsSingleBit(static_cast<uint32_t>(ldac_data.bitsPerSample), 0x07)) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  } else if ((ldac_data.sampleRate &
-              kDefaultOffloadLdacCapability.sampleRate) &&
-             (ldac_data.channelMode &
-              kDefaultOffloadLdacCapability.channelMode) &&
-             (ldac_data.bitsPerSample &
-              kDefaultOffloadLdacCapability.bitsPerSample)) {
-    return true;
-  }
-  LOG(WARNING) << __func__
-               << ": Unsupported CodecSpecific=" << toString(codec_specific);
-  return false;
-}
-
-static bool IsOffloadAptxConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific) {
-  if (codec_specific.getDiscriminator() !=
-      CodecConfiguration::CodecSpecific::hidl_discriminator::aptxConfig) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  }
-  const AptxParameters aptx_data = codec_specific.aptxConfig();
-  if (!IsSingleBit(static_cast<uint32_t>(aptx_data.sampleRate), 0xff) ||
-      !IsSingleBit(static_cast<uint32_t>(aptx_data.channelMode), 0x03) ||
-      !IsSingleBit(static_cast<uint32_t>(aptx_data.bitsPerSample), 0x07)) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  } else if ((aptx_data.sampleRate &
-              kDefaultOffloadAptxCapability.sampleRate) &&
-             (aptx_data.channelMode &
-              kDefaultOffloadAptxCapability.channelMode) &&
-             (aptx_data.bitsPerSample &
-              kDefaultOffloadAptxCapability.bitsPerSample)) {
-    return true;
-  }
-  LOG(WARNING) << __func__
-               << ": Unsupported CodecSpecific=" << toString(codec_specific);
-  return false;
-}
-
-static bool IsOffloadAptxHdConfigurationValid(
-    const CodecConfiguration::CodecSpecific& codec_specific) {
-  if (codec_specific.getDiscriminator() !=
-      CodecConfiguration::CodecSpecific::hidl_discriminator::aptxConfig) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  }
-  const AptxParameters aptx_data = codec_specific.aptxConfig();
-  if (!IsSingleBit(static_cast<uint32_t>(aptx_data.sampleRate), 0xff) ||
-      !IsSingleBit(static_cast<uint32_t>(aptx_data.channelMode), 0x03) ||
-      !IsSingleBit(static_cast<uint32_t>(aptx_data.bitsPerSample), 0x07)) {
-    LOG(WARNING) << __func__
-                 << ": Invalid CodecSpecific=" << toString(codec_specific);
-    return false;
-  } else if ((aptx_data.sampleRate &
-              kDefaultOffloadAptxHdCapability.sampleRate) &&
-             (aptx_data.channelMode &
-              kDefaultOffloadAptxHdCapability.channelMode) &&
-             (aptx_data.bitsPerSample &
-              kDefaultOffloadAptxHdCapability.bitsPerSample)) {
-    return true;
-  }
-  LOG(WARNING) << __func__
-               << ": Unsupported CodecSpecific=" << toString(codec_specific);
-  return false;
-}
-
-std::vector<::android::hardware::bluetooth::audio::V2_0::PcmParameters>
-GetSoftwarePcmCapabilities() {
-  return std::vector<
-      ::android::hardware::bluetooth::audio::V2_0::PcmParameters>(
-      1, kDefaultSoftwarePcmCapabilities);
-}
-
-std::vector<PcmParameters> GetSoftwarePcmCapabilities_2_1() {
-  return std::vector<PcmParameters>(1, kDefaultSoftwarePcmCapabilities_2_1);
-}
-
-std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
-    const ::android::hardware::bluetooth::audio::V2_0::SessionType&
-        session_type) {
-  return GetOffloadCodecCapabilities(static_cast<SessionType>(session_type));
-}
-
-std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
-    const SessionType& session_type) {
-  if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
-    return std::vector<CodecCapabilities>(0);
-  }
-  std::vector<CodecCapabilities> offload_a2dp_codec_capabilities =
-      kDefaultOffloadA2dpCodecCapabilities;
-  for (auto& codec_capability : offload_a2dp_codec_capabilities) {
-    switch (codec_capability.codecType) {
-      case CodecType::SBC:
-        codec_capability.capabilities.sbcCapabilities(
-            kDefaultOffloadSbcCapability);
-        break;
-      case CodecType::AAC:
-        codec_capability.capabilities.aacCapabilities(
-            kDefaultOffloadAacCapability);
-        break;
-      case CodecType::LDAC:
-        codec_capability.capabilities.ldacCapabilities(
-            kDefaultOffloadLdacCapability);
-        break;
-      case CodecType::APTX:
-        codec_capability.capabilities.aptxCapabilities(
-            kDefaultOffloadAptxCapability);
-        break;
-      case CodecType::APTX_HD:
-        codec_capability.capabilities.aptxCapabilities(
-            kDefaultOffloadAptxHdCapability);
-        break;
-      case CodecType::UNKNOWN:
-        codec_capability = {};
-        break;
-    }
-  }
-  return offload_a2dp_codec_capabilities;
-}
-
-bool IsSoftwarePcmConfigurationValid(
-    const ::android::hardware::bluetooth::audio::V2_0::PcmParameters&
-        pcm_config) {
-  if ((pcm_config.sampleRate !=
-           android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 &&
-       pcm_config.sampleRate !=
-           android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 &&
-       pcm_config.sampleRate !=
-           android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 &&
-       pcm_config.sampleRate !=
-           android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000 &&
-       pcm_config.sampleRate !=
-           android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_16000 &&
-       pcm_config.sampleRate !=
-           android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_24000) ||
-      (pcm_config.bitsPerSample != BitsPerSample::BITS_16 &&
-       pcm_config.bitsPerSample != BitsPerSample::BITS_24 &&
-       pcm_config.bitsPerSample != BitsPerSample::BITS_32) ||
-      (pcm_config.channelMode != ChannelMode::MONO &&
-       pcm_config.channelMode != ChannelMode::STEREO)) {
-    LOG(WARNING) << __func__
-                 << ": Invalid PCM Configuration=" << toString(pcm_config);
-    return false;
-  } else if (pcm_config.sampleRate &
-                 kDefaultSoftwarePcmCapabilities.sampleRate &&
-             pcm_config.bitsPerSample &
-                 kDefaultSoftwarePcmCapabilities.bitsPerSample &&
-             pcm_config.channelMode &
-                 kDefaultSoftwarePcmCapabilities.channelMode) {
-    return true;
-  }
-  LOG(WARNING) << __func__
-               << ": Unsupported PCM Configuration=" << toString(pcm_config);
-  return false;
-}
-
-bool IsSoftwarePcmConfigurationValid_2_1(const PcmParameters& pcm_config) {
-  if ((pcm_config.sampleRate != SampleRate::RATE_44100 &&
-       pcm_config.sampleRate != SampleRate::RATE_48000 &&
-       pcm_config.sampleRate != SampleRate::RATE_88200 &&
-       pcm_config.sampleRate != SampleRate::RATE_96000 &&
-       pcm_config.sampleRate != SampleRate::RATE_16000 &&
-       pcm_config.sampleRate != SampleRate::RATE_24000) ||
-      (pcm_config.bitsPerSample != BitsPerSample::BITS_16 &&
-       pcm_config.bitsPerSample != BitsPerSample::BITS_24 &&
-       pcm_config.bitsPerSample != BitsPerSample::BITS_32) ||
-      (pcm_config.channelMode != ChannelMode::MONO &&
-       pcm_config.channelMode != ChannelMode::STEREO)) {
-    LOG(WARNING) << __func__
-                 << ": Invalid PCM Configuration=" << toString(pcm_config);
-    return false;
-  } else if (pcm_config.sampleRate &
-                 kDefaultSoftwarePcmCapabilities_2_1.sampleRate &&
-             pcm_config.bitsPerSample &
-                 kDefaultSoftwarePcmCapabilities_2_1.bitsPerSample &&
-             pcm_config.channelMode &
-                 kDefaultSoftwarePcmCapabilities_2_1.channelMode &&
-             pcm_config.dataIntervalUs != 0) {
-    return true;
-  }
-  LOG(WARNING) << __func__
-               << ": Unsupported PCM Configuration=" << toString(pcm_config);
-  return false;
-}
-
-bool IsOffloadCodecConfigurationValid(const SessionType& session_type,
-                                      const CodecConfiguration& codec_config) {
-  if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
-    LOG(ERROR) << __func__
-               << ": Invalid SessionType=" << toString(session_type);
-    return false;
-  } else if (codec_config.encodedAudioBitrate < 0x00000001 ||
-             0x00ffffff < codec_config.encodedAudioBitrate) {
-    LOG(ERROR) << __func__ << ": Unsupported Codec Configuration="
-               << toString(codec_config);
-    return false;
-  }
-  const CodecConfiguration::CodecSpecific& codec_specific = codec_config.config;
-  switch (codec_config.codecType) {
-    case CodecType::SBC:
-      if (IsOffloadSbcConfigurationValid(codec_specific)) {
-        return true;
-      }
-      return false;
-    case CodecType::AAC:
-      if (IsOffloadAacConfigurationValid(codec_specific)) {
-        return true;
-      }
-      return false;
-    case CodecType::LDAC:
-      if (IsOffloadLdacConfigurationValid(codec_specific)) {
-        return true;
-      }
-      return false;
-    case CodecType::APTX:
-      if (IsOffloadAptxConfigurationValid(codec_specific)) {
-        return true;
-      }
-      return false;
-    case CodecType::APTX_HD:
-      if (IsOffloadAptxHdConfigurationValid(codec_specific)) {
-        return true;
-      }
-      return false;
-    case CodecType::UNKNOWN:
-      return false;
-  }
-  return false;
-}
-
-}  // namespace audio
-}  // namespace bluetooth
-}  // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h
deleted file mode 100644
index 9b2f680..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#pragma once
-
-#include <android/hardware/bluetooth/audio/2.0/types.h>
-#include <android/hardware/bluetooth/audio/2.1/types.h>
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-using ::android::hardware::bluetooth::audio::V2_0::CodecCapabilities;
-using ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration;
-using ::android::hardware::bluetooth::audio::V2_1::PcmParameters;
-using ::android::hardware::bluetooth::audio::V2_1::SessionType;
-
-std::vector<::android::hardware::bluetooth::audio::V2_0::PcmParameters>
-GetSoftwarePcmCapabilities();
-std::vector<PcmParameters> GetSoftwarePcmCapabilities_2_1();
-std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
-    const SessionType& session_type);
-std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
-    const ::android::hardware::bluetooth::audio::V2_0::SessionType&
-        session_type);
-
-bool IsSoftwarePcmConfigurationValid_2_1(const PcmParameters& pcm_config);
-bool IsSoftwarePcmConfigurationValid(
-    const ::android::hardware::bluetooth::audio::V2_0::PcmParameters&
-        pcm_config);
-bool IsOffloadCodecConfigurationValid(const SessionType& session_type,
-                                      const CodecConfiguration& codec_config);
-
-}  // namespace audio
-}  // namespace bluetooth
-}  // namespace android
diff --git a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp
index 37d1281..57fa07b 100644
--- a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp
+++ b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp
@@ -1005,8 +1005,10 @@
     BluetoothAudioProvidersFactoryHidlTest::TearDown();
   }
 
-  static constexpr SampleRate le_audio_output_sample_rates_[3] = {
-      SampleRate::RATE_UNKNOWN, SampleRate::RATE_16000, SampleRate::RATE_24000};
+  static constexpr SampleRate le_audio_output_sample_rates_[11] = {
+      SampleRate::RATE_UNKNOWN, SampleRate::RATE_8000,  SampleRate::RATE_16000,
+      SampleRate::RATE_24000,   SampleRate::RATE_32000, SampleRate::RATE_44100,
+      SampleRate::RATE_48000};
   static constexpr BitsPerSample le_audio_output_bits_per_samples_[3] = {
       BitsPerSample::BITS_UNKNOWN, BitsPerSample::BITS_16,
       BitsPerSample::BITS_24};
@@ -1030,7 +1032,7 @@
  * stopped with different PCM config
  */
 TEST_P(BluetoothAudioProviderLeAudioOutputSoftwareHidlTest,
-       StartAndEndLeAudioOutputSessionWithPossiblePcmConfig) {
+       DISABLED_StartAndEndLeAudioOutputSessionWithPossiblePcmConfig) {
   bool is_codec_config_valid;
   std::unique_ptr<DataMQ> tempDataMQ;
   auto hidl_cb = [&is_codec_config_valid, &tempDataMQ](
@@ -1097,8 +1099,10 @@
     BluetoothAudioProvidersFactoryHidlTest::TearDown();
   }
 
-  static constexpr SampleRate le_audio_output_sample_rates_[3] = {
-      SampleRate::RATE_UNKNOWN, SampleRate::RATE_16000, SampleRate::RATE_24000};
+  static constexpr SampleRate le_audio_output_sample_rates_[11] = {
+      SampleRate::RATE_UNKNOWN, SampleRate::RATE_8000,  SampleRate::RATE_16000,
+      SampleRate::RATE_24000,   SampleRate::RATE_32000, SampleRate::RATE_44100,
+      SampleRate::RATE_48000};
   static constexpr BitsPerSample le_audio_output_bits_per_samples_[3] = {
       BitsPerSample::BITS_UNKNOWN, BitsPerSample::BITS_16,
       BitsPerSample::BITS_24};
@@ -1122,7 +1126,7 @@
  * stopped with different PCM config
  */
 TEST_P(BluetoothAudioProviderLeAudioInputSoftwareHidlTest,
-       StartAndEndLeAudioInputSessionWithPossiblePcmConfig) {
+       DISABLED_StartAndEndLeAudioInputSessionWithPossiblePcmConfig) {
   bool is_codec_config_valid;
   std::unique_ptr<DataMQ> tempDataMQ;
   auto hidl_cb = [&is_codec_config_valid, &tempDataMQ](
diff --git a/bluetooth/audio/utils/Android.bp b/bluetooth/audio/utils/Android.bp
new file mode 100644
index 0000000..35476d2
--- /dev/null
+++ b/bluetooth/audio/utils/Android.bp
@@ -0,0 +1,23 @@
+cc_library_shared {
+    name: "libbluetooth_audio_session",
+    defaults: ["hidl_defaults"],
+    vendor: true,
+    srcs: [
+        "session/BluetoothAudioSession.cpp",
+        "session/BluetoothAudioSession_2_1.cpp",
+        "session/BluetoothAudioSupportedCodecsDB.cpp",
+        "session/BluetoothAudioSupportedCodecsDB_2_1.cpp",
+    ],
+    export_include_dirs: ["session/"],
+    header_libs: ["libhardware_headers"],
+    shared_libs: [
+        "android.hardware.bluetooth.audio@2.0",
+        "android.hardware.bluetooth.audio@2.1",
+        "libbase",
+        "libcutils",
+        "libfmq",
+        "libhidlbase",
+        "liblog",
+        "libutils",
+    ],
+}
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession.cpp
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSession.cpp
rename to bluetooth/audio/utils/session/BluetoothAudioSession.cpp
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.h b/bluetooth/audio/utils/session/BluetoothAudioSession.h
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSession.h
rename to bluetooth/audio/utils/session/BluetoothAudioSession.h
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSessionControl.h b/bluetooth/audio/utils/session/BluetoothAudioSessionControl.h
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSessionControl.h
rename to bluetooth/audio/utils/session/BluetoothAudioSessionControl.h
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h
new file mode 100644
index 0000000..86af468
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h
@@ -0,0 +1,148 @@
+/*
+ * 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 "BluetoothAudioSession_2_1.h"
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothAudioSessionControl_2_1 {
+  using SessionType_2_1 =
+      ::android::hardware::bluetooth::audio::V2_1::SessionType;
+
+ public:
+  // The control API helps to check if session is ready or not
+  // @return: true if the Bluetooth stack has started th specified session
+  static bool IsSessionReady(const SessionType_2_1& session_type) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      return session_ptr->GetAudioSession()->IsSessionReady();
+    }
+    return false;
+  }
+
+  // The control API helps the bluetooth_audio module to register
+  // PortStatusCallbacks
+  // @return: cookie - the assigned number to this bluetooth_audio output
+  static uint16_t RegisterControlResultCback(
+      const SessionType_2_1& session_type, const PortStatusCallbacks& cbacks) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      return session_ptr->GetAudioSession()->RegisterStatusCback(cbacks);
+    }
+    return kObserversCookieUndefined;
+  }
+
+  // The control API helps the bluetooth_audio module to unregister
+  // PortStatusCallbacks
+  // @param: cookie - indicates which bluetooth_audio output is
+  static void UnregisterControlResultCback(const SessionType_2_1& session_type,
+                                           uint16_t cookie) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      session_ptr->GetAudioSession()->UnregisterStatusCback(cookie);
+    }
+  }
+
+  // The control API for the bluetooth_audio module to get current
+  // AudioConfiguration
+  static const AudioConfiguration& GetAudioConfig(
+      const SessionType_2_1& session_type) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      // TODO: map 2.1 to 2.0 audio config inside GetAudioConfig?
+      return session_ptr->GetAudioSession()->GetAudioConfig();
+    } else if (session_type ==
+               SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
+      return BluetoothAudioSession::kInvalidOffloadAudioConfiguration;
+    } else {
+      return BluetoothAudioSession::kInvalidSoftwareAudioConfiguration;
+    }
+  }
+
+  // Those control APIs for the bluetooth_audio module to start / suspend / stop
+  // stream, to check position, and to update metadata.
+  static bool StartStream(const SessionType_2_1& session_type) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      return session_ptr->GetAudioSession()->StartStream();
+    }
+    return false;
+  }
+
+  static bool SuspendStream(const SessionType_2_1& session_type) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      return session_ptr->GetAudioSession()->SuspendStream();
+    }
+    return false;
+  }
+
+  static void StopStream(const SessionType_2_1& session_type) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      session_ptr->GetAudioSession()->StopStream();
+    }
+  }
+
+  static bool GetPresentationPosition(const SessionType_2_1& session_type,
+                                      uint64_t* remote_delay_report_ns,
+                                      uint64_t* total_bytes_readed,
+                                      timespec* data_position) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      return session_ptr->GetAudioSession()->GetPresentationPosition(
+          remote_delay_report_ns, total_bytes_readed, data_position);
+    }
+    return false;
+  }
+
+  static void UpdateTracksMetadata(
+      const SessionType_2_1& session_type,
+      const struct source_metadata* source_metadata) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      session_ptr->GetAudioSession()->UpdateTracksMetadata(source_metadata);
+    }
+  }
+
+  // The control API writes stream to FMQ
+  static size_t OutWritePcmData(const SessionType_2_1& session_type,
+                                const void* buffer, size_t bytes) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      return session_ptr->GetAudioSession()->OutWritePcmData(buffer, bytes);
+    }
+    return 0;
+  }
+};
+
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSessionReport.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport.h
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSessionReport.h
rename to bluetooth/audio/utils/session/BluetoothAudioSessionReport.h
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h
new file mode 100644
index 0000000..ab30536
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h
@@ -0,0 +1,69 @@
+/*
+ * 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 "BluetoothAudioSession_2_1.h"
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothAudioSessionReport_2_1 {
+ 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_1::AudioConfiguration&
+          audio_config) {
+    std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::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_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::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_1> session_ptr =
+        BluetoothAudioSessionInstance_2_1::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_1.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp
new file mode 100644
index 0000000..9e1baf4
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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_1"
+
+#include "BluetoothAudioSession_2_1.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;
+
+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_1::BluetoothAudioSession_2_1(
+    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);
+  }
+}
+
+std::shared_ptr<BluetoothAudioSession>
+BluetoothAudioSession_2_1::GetAudioSession() {
+  return audio_session;
+}
+
+// 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_1::OnSessionStarted(
+    const sp<IBluetoothAudioPort> stack_iface, const DataMQ::Descriptor* dataMQ,
+    const ::android::hardware::bluetooth::audio::V2_1::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_1::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 {
+    LOG(FATAL) << " Not implemented yet!!";
+  }
+}
+
+std::unique_ptr<BluetoothAudioSessionInstance_2_1>
+    BluetoothAudioSessionInstance_2_1::instance_ptr =
+        std::unique_ptr<BluetoothAudioSessionInstance_2_1>(
+            new BluetoothAudioSessionInstance_2_1());
+
+// API to fetch the session of A2DP / Hearing Aid
+std::shared_ptr<BluetoothAudioSession_2_1>
+BluetoothAudioSessionInstance_2_1::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_1> session_ptr =
+      std::make_shared<BluetoothAudioSession_2_1>(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_1.h b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h
new file mode 100644
index 0000000..0e9c12b
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h
@@ -0,0 +1,82 @@
+/*
+ * 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.1/types.h>
+#include "BluetoothAudioSession.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothAudioSession_2_1 {
+ 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_1::AudioConfiguration
+      audio_config_2_1_;
+
+  bool UpdateAudioConfig(
+      const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration&
+          audio_config);
+
+ public:
+  BluetoothAudioSession_2_1(
+      const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+          session_type);
+
+  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_1::AudioConfiguration&
+          audio_config);
+
+  // The control function is for the bluetooth_audio module to get the current
+  // AudioConfiguration
+  const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration&
+  GetAudioConfig();
+};
+
+class BluetoothAudioSessionInstance_2_1 {
+ public:
+  // The API is to fetch the specified session of A2DP / Hearing Aid
+  static std::shared_ptr<BluetoothAudioSession_2_1> GetSessionInstance(
+      const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+          session_type);
+
+ private:
+  static std::unique_ptr<BluetoothAudioSessionInstance_2_1> instance_ptr;
+  std::mutex mutex_;
+  std::unordered_map<::android::hardware::bluetooth::audio::V2_1::SessionType,
+                     std::shared_ptr<BluetoothAudioSession_2_1>>
+      sessions_map_;
+};
+
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp
rename to bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.h
rename to bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp
new file mode 100644
index 0000000..8b0b0f7
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 "BTAudioProviderSessionCodecsDB_2_1"
+
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
+using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
+
+using SampleRate_2_0 = ::android::hardware::bluetooth::audio::V2_0::SampleRate;
+using SampleRate_2_1 = ::android::hardware::bluetooth::audio::V2_1::SampleRate;
+
+using SessionType_2_1 =
+    ::android::hardware::bluetooth::audio::V2_1::SessionType;
+using SessionType_2_0 =
+    ::android::hardware::bluetooth::audio::V2_0::SessionType;
+
+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
+
+static const ::android::hardware::bluetooth::audio::V2_1::PcmParameters
+    kDefaultSoftwarePcmCapabilities_2_1 = {
+        .sampleRate = static_cast<SampleRate_2_1>(
+            SampleRate_2_1::RATE_44100 | SampleRate_2_1::RATE_48000 |
+            SampleRate_2_1::RATE_88200 | SampleRate_2_1::RATE_96000 |
+            SampleRate_2_1::RATE_16000 | SampleRate_2_1::RATE_24000),
+        .channelMode =
+            static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO),
+        .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 |
+                                                    BitsPerSample::BITS_24 |
+                                                    BitsPerSample::BITS_32)};
+
+std::vector<::android::hardware::bluetooth::audio::V2_1::PcmParameters>
+GetSoftwarePcmCapabilities_2_1() {
+  return std::vector<
+      ::android::hardware::bluetooth::audio::V2_1::PcmParameters>(
+      1, kDefaultSoftwarePcmCapabilities_2_1);
+}
+
+std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
+    const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+        session_type) {
+  if (is_2_0_session_type(session_type)) {
+    return GetOffloadCodecCapabilities(
+        static_cast<SessionType_2_0>(session_type));
+  }
+  return std::vector<CodecCapabilities>(0);
+}
+
+bool IsSoftwarePcmConfigurationValid_2_1(
+    const ::android::hardware::bluetooth::audio::V2_1::PcmParameters&
+        pcm_config) {
+  if ((pcm_config.sampleRate != SampleRate_2_1::RATE_44100 &&
+       pcm_config.sampleRate != SampleRate_2_1::RATE_48000 &&
+       pcm_config.sampleRate != SampleRate_2_1::RATE_88200 &&
+       pcm_config.sampleRate != SampleRate_2_1::RATE_96000 &&
+       pcm_config.sampleRate != SampleRate_2_1::RATE_16000 &&
+       pcm_config.sampleRate != SampleRate_2_1::RATE_24000) ||
+      (pcm_config.bitsPerSample != BitsPerSample::BITS_16 &&
+       pcm_config.bitsPerSample != BitsPerSample::BITS_24 &&
+       pcm_config.bitsPerSample != BitsPerSample::BITS_32) ||
+      (pcm_config.channelMode != ChannelMode::MONO &&
+       pcm_config.channelMode != ChannelMode::STEREO)) {
+    LOG(WARNING) << __func__
+                 << ": Invalid PCM Configuration=" << toString(pcm_config);
+    return false;
+  } else if (pcm_config.sampleRate &
+                 kDefaultSoftwarePcmCapabilities_2_1.sampleRate &&
+             pcm_config.bitsPerSample &
+                 kDefaultSoftwarePcmCapabilities_2_1.bitsPerSample &&
+             pcm_config.channelMode &
+                 kDefaultSoftwarePcmCapabilities_2_1.channelMode &&
+             pcm_config.dataIntervalUs != 0) {
+    return true;
+  }
+  LOG(WARNING) << __func__
+               << ": Unsupported PCM Configuration=" << toString(pcm_config);
+  return false;
+}
+
+bool IsOffloadCodecConfigurationValid(
+    const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+        session_type,
+    const ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration&
+        codec_config) {
+  if (is_2_0_session_type(session_type)) {
+    return IsOffloadCodecConfigurationValid(
+        static_cast<SessionType_2_0>(session_type), codec_config);
+  }
+
+  return false;
+}
+
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h
new file mode 100644
index 0000000..746d9c0
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include "BluetoothAudioSupportedCodecsDB.h"
+
+#include <android/hardware/bluetooth/audio/2.1/types.h>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+std::vector<::android::hardware::bluetooth::audio::V2_1::PcmParameters>
+GetSoftwarePcmCapabilities_2_1();
+std::vector<::android::hardware::bluetooth::audio::V2_0::CodecCapabilities>
+GetOffloadCodecCapabilities(
+    const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+        session_type);
+
+bool IsSoftwarePcmConfigurationValid_2_1(
+    const ::android::hardware::bluetooth::audio::V2_1::PcmParameters&
+        pcm_config);
+
+bool IsOffloadCodecConfigurationValid(
+    const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+        session_type,
+    const ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration&
+        codec_config);
+
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
index ca57243..ce50f25 100644
--- a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
+++ b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
@@ -415,7 +415,7 @@
 TEST_P(BroadcastRadioHalTest, FmTune) {
     ASSERT_TRUE(openSession());
 
-    uint64_t freq = 100100;  // 100.1 FM
+    uint64_t freq = 90900;  // 90.9 FM
     auto sel = make_selector_amfm(freq);
 
     /* TODO(b/69958777): there is a race condition between tune() and onCurrentProgramInfoChanged
diff --git a/camera/provider/2.4/default/service.cpp b/camera/provider/2.4/default/service.cpp
index 84f4839..0a4f787 100644
--- a/camera/provider/2.4/default/service.cpp
+++ b/camera/provider/2.4/default/service.cpp
@@ -22,9 +22,9 @@
 
 #include <android/hardware/camera/provider/2.4/ICameraProvider.h>
 #include <binder/ProcessState.h>
-#include <cutils/memory.h>
 #include <cutils/properties.h>
 #include <hidl/LegacySupport.h>
+#include <malloc.h>
 
 using android::status_t;
 using android::hardware::defaultLazyPassthroughServiceImplementation;
@@ -46,7 +46,13 @@
 
     // b/166675194
     if (property_get_bool("ro.vendor.camera.provider24.disable_mem_init", false)) {
-        process_disable_memory_mitigations();
+        if (mallopt(M_BIONIC_ZERO_INIT, 0) == 0) {
+            // Note - heap initialization is only present on devices with Scudo.
+            // Devices with jemalloc don't have heap-init, and thus the mallopt
+            // will fail. On these devices, you probably just want to remove the
+            // property.
+            ALOGE("Disabling heap initialization failed.");
+        }
     }
 
     status_t status;
diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
index 2099dc0..7e6b5ad 100644
--- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
+++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
@@ -888,6 +888,8 @@
     static Status getSystemCameraKind(const camera_metadata_t* staticMeta,
                                       SystemCameraKind* systemCameraKind);
 
+    static V3_2::DataspaceFlags getDataspace(PixelFormat format);
+
     void processCaptureRequestInternal(uint64_t bufferusage, RequestTemplate reqTemplate,
                                        bool useSecureOnlyCameras);
 
@@ -3178,7 +3180,6 @@
     }
 }
 
-
 // Verify that all supported stream formats and sizes can be configured
 // successfully.
 TEST_P(CameraHidlTest, configureStreamsAvailableOutputs) {
@@ -3221,17 +3222,7 @@
         uint32_t streamConfigCounter = 0;
         for (auto& it : outputStreams) {
             V3_2::Stream stream3_2;
-            V3_2::DataspaceFlags dataspaceFlag = 0;
-            switch (static_cast<PixelFormat>(it.format)) {
-                case PixelFormat::BLOB:
-                    dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::V0_JFIF);
-                    break;
-                case PixelFormat::Y16:
-                    dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::DEPTH);
-                    break;
-                default:
-                    dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::UNKNOWN);
-            }
+            V3_2::DataspaceFlags dataspaceFlag = getDataspace(static_cast<PixelFormat>(it.format));
             stream3_2 = {streamId,
                              StreamType::OUTPUT,
                              static_cast<uint32_t>(it.width),
@@ -3351,17 +3342,8 @@
             size_t j = 0;
             for (const auto& it : outputStreams) {
                 V3_2::Stream stream3_2;
-                V3_2::DataspaceFlags dataspaceFlag = 0;
-                switch (static_cast<PixelFormat>(it.format)) {
-                    case PixelFormat::BLOB:
-                        dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::V0_JFIF);
-                        break;
-                    case PixelFormat::Y16:
-                        dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::DEPTH);
-                        break;
-                    default:
-                        dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::UNKNOWN);
-                }
+                V3_2::DataspaceFlags dataspaceFlag = getDataspace(
+                        static_cast<PixelFormat>(it.format));
                 stream3_2 = {streamId++,
                              StreamType::OUTPUT,
                              static_cast<uint32_t>(it.width),
@@ -5901,6 +5883,23 @@
     return ret;
 }
 
+// Select an appropriate dataspace given a specific pixel format.
+V3_2::DataspaceFlags CameraHidlTest::getDataspace(PixelFormat format) {
+    switch (format) {
+        case PixelFormat::BLOB:
+            return static_cast<V3_2::DataspaceFlags>(Dataspace::V0_JFIF);
+        case PixelFormat::Y16:
+            return static_cast<V3_2::DataspaceFlags>(Dataspace::DEPTH);
+        case PixelFormat::RAW16:
+        case PixelFormat::RAW_OPAQUE:
+        case PixelFormat::RAW10:
+        case PixelFormat::RAW12:
+            return  static_cast<V3_2::DataspaceFlags>(Dataspace::ARBITRARY);
+        default:
+            return static_cast<V3_2::DataspaceFlags>(Dataspace::UNKNOWN);
+    }
+}
+
 // Check whether this is a monochrome camera using the static camera characteristics.
 Status CameraHidlTest::isMonochromeCamera(const camera_metadata_t *staticMeta) {
     Status ret = Status::METHOD_NOT_SUPPORTED;
@@ -6275,17 +6274,8 @@
     ASSERT_EQ(Status::OK, rc);
     ASSERT_FALSE(outputStreams.empty());
 
-    V3_2::DataspaceFlags dataspaceFlag = 0;
-    switch (static_cast<PixelFormat>(outputStreams[idx].format)) {
-        case PixelFormat::BLOB:
-            dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::V0_JFIF);
-            break;
-        case PixelFormat::Y16:
-            dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::DEPTH);
-            break;
-        default:
-            dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::UNKNOWN);
-    }
+    V3_2::DataspaceFlags dataspaceFlag = getDataspace(
+            static_cast<PixelFormat>(outputStreams[idx].format));
 
     ::android::hardware::hidl_vec<V3_4::Stream> streams3_4(/*size*/1);
     V3_4::Stream stream3_4 = {{ 0 /*streamId*/, StreamType::OUTPUT,
diff --git a/common/fmq/aidl/Android.bp b/common/fmq/aidl/Android.bp
index 004adab..9389712 100644
--- a/common/fmq/aidl/Android.bp
+++ b/common/fmq/aidl/Android.bp
@@ -9,6 +9,9 @@
     srcs: [
         "android/hardware/common/fmq/*.aidl",
     ],
+    imports: [
+        "android.hardware.common",
+    ],
     stability: "vintf",
     backend: {
         java: {
diff --git a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/GrantorDescriptor.aidl b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/GrantorDescriptor.aidl
index 7ac1930..0327796 100644
--- a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/GrantorDescriptor.aidl
+++ b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/GrantorDescriptor.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -18,6 +19,7 @@
 package android.hardware.common.fmq;
 @VintfStability
 parcelable GrantorDescriptor {
+  int fdIndex;
   int offset;
   long extent;
 }
diff --git a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/MQDescriptor.aidl b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/MQDescriptor.aidl
index 2607369..56f1de3 100644
--- a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/MQDescriptor.aidl
+++ b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/MQDescriptor.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -19,7 +20,7 @@
 @VintfStability
 parcelable MQDescriptor {
   android.hardware.common.fmq.GrantorDescriptor[] grantors;
-  ParcelFileDescriptor fileDescriptor;
+  android.hardware.common.NativeHandle handle;
   int quantum;
   int flags;
 }
diff --git a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/SynchronizedReadWrite.aidl b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/SynchronizedReadWrite.aidl
index 2142bdb..264171d 100644
--- a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/SynchronizedReadWrite.aidl
+++ b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/SynchronizedReadWrite.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/UnsynchronizedWrite.aidl b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/UnsynchronizedWrite.aidl
index 1220674..eaf2ffd 100644
--- a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/UnsynchronizedWrite.aidl
+++ b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/UnsynchronizedWrite.aidl
@@ -2,13 +2,14 @@
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/common/fmq/aidl/android/hardware/common/fmq/GrantorDescriptor.aidl b/common/fmq/aidl/android/hardware/common/fmq/GrantorDescriptor.aidl
index ca69d94..672415e 100644
--- a/common/fmq/aidl/android/hardware/common/fmq/GrantorDescriptor.aidl
+++ b/common/fmq/aidl/android/hardware/common/fmq/GrantorDescriptor.aidl
@@ -22,6 +22,10 @@
 @VintfStability
 parcelable GrantorDescriptor {
     /*
+     * Index of file descriptor for this grantor
+     */
+    int fdIndex;
+    /*
      * The offset of this descriptor in the shared memory in bytes.
      */
     int offset;
diff --git a/common/fmq/aidl/android/hardware/common/fmq/MQDescriptor.aidl b/common/fmq/aidl/android/hardware/common/fmq/MQDescriptor.aidl
index 82917d6..46622f0 100644
--- a/common/fmq/aidl/android/hardware/common/fmq/MQDescriptor.aidl
+++ b/common/fmq/aidl/android/hardware/common/fmq/MQDescriptor.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.common.fmq;
 
+import android.hardware.common.NativeHandle;
 import android.hardware.common.fmq.GrantorDescriptor;
 
 /*
@@ -34,8 +35,11 @@
      * for blocking operations in the shared memory.
      */
     GrantorDescriptor[] grantors;
-    /* File descriptor for shared memory used in the message queue */
-    ParcelFileDescriptor fileDescriptor;
+    /*
+     * NativeHandle that contains the file descriptors for shared memory used
+     * in the message queue
+     */
+    NativeHandle handle;
     /* Size of each item, T, in bytes */
     int quantum;
     /* EventFlag word for blocking operations */
diff --git a/common/support/Android.bp b/common/support/Android.bp
index 3bb4804..ce3aa3f 100644
--- a/common/support/Android.bp
+++ b/common/support/Android.bp
@@ -6,7 +6,7 @@
     srcs: ["NativeHandle.cpp"],
     export_include_dirs: ["include"],
     shared_libs: [
-        "android.hardware.common-unstable-ndk_platform",
+        "android.hardware.common-V2-ndk_platform",
         "libcutils",
     ],
 }
@@ -17,10 +17,10 @@
     defaults: ["libbinder_ndk_host_user"],
     srcs: ["test.cpp"],
     static_libs: [
+        "android.hardware.common-V2-ndk_platform",
         "libaidlcommonsupport",
     ],
     shared_libs: [
-        "android.hardware.common-unstable-ndk_platform",
         "libcutils",
     ],
     test_suites: ["general-tests"],
diff --git a/compatibility_matrices/compatibility_matrix.3.xml b/compatibility_matrices/compatibility_matrix.3.xml
index 5888ab9..608890b 100644
--- a/compatibility_matrices/compatibility_matrix.3.xml
+++ b/compatibility_matrices/compatibility_matrix.3.xml
@@ -310,6 +310,10 @@
             <instance>slot2</instance>
             <instance>slot3</instance>
         </interface>
+    </hal>
+    <hal format="hidl" optional="true">
+        <name>android.hardware.radio</name>
+        <version>1.0-2</version>
         <interface>
             <name>ISap</name>
             <instance>slot1</instance>
diff --git a/compatibility_matrices/compatibility_matrix.4.xml b/compatibility_matrices/compatibility_matrix.4.xml
index 5e1266e..e5e012c 100644
--- a/compatibility_matrices/compatibility_matrix.4.xml
+++ b/compatibility_matrices/compatibility_matrix.4.xml
@@ -432,7 +432,6 @@
     <hal format="hidl" optional="true">
         <name>android.hardware.tv.cec</name>
         <version>1.0</version>
-        <version>2.0</version>
         <interface>
             <name>IHdmiCec</name>
             <instance>default</instance>
diff --git a/compatibility_matrices/compatibility_matrix.5.xml b/compatibility_matrices/compatibility_matrix.5.xml
index 8bc663d..96a3692 100644
--- a/compatibility_matrices/compatibility_matrix.5.xml
+++ b/compatibility_matrices/compatibility_matrix.5.xml
@@ -86,7 +86,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.biometrics.face</name>
-        <version>1.0-1</version>
+        <version>1.0</version>
         <interface>
             <name>IBiometricsFace</name>
             <instance>default</instance>
@@ -467,7 +467,6 @@
     <hal format="hidl" optional="true">
         <name>android.hardware.tv.cec</name>
         <version>1.0</version>
-        <version>2.0</version>
         <interface>
             <name>IHdmiCec</name>
             <instance>default</instance>
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index a994b23..6631d8a 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -9,7 +9,6 @@
     </hal>
     <hal format="hidl" optional="false">
         <name>android.hardware.audio</name>
-        <!-- TODO(b/142480271): remove 6.0 when implemented on reference device. -->
         <version>6.0</version>
         <version>7.0</version>
         <interface>
@@ -19,7 +18,6 @@
     </hal>
     <hal format="hidl" optional="false">
         <name>android.hardware.audio.effect</name>
-        <!-- TODO(b/142480271): remove 6.0 when implemented on reference device. -->
         <version>6.0</version>
         <version>7.0</version>
         <interface>
@@ -27,6 +25,14 @@
             <instance>default</instance>
         </interface>
     </hal>
+    <hal format="aidl" optional="true">
+         <name>android.hardware.authsecret</name>
+         <version>1</version>
+         <interface>
+             <name>IAuthSecret</name>
+             <instance>default</instance>
+         </interface>
+    </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.authsecret</name>
         <version>1.0</version>
@@ -67,6 +73,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.automotive.occupant_awareness</name>
+        <version>1</version>
         <interface>
             <name>IOccupantAwareness</name>
             <instance>default</instance>
@@ -76,7 +83,7 @@
         <name>android.hardware.automotive.sv</name>
         <version>1.0</version>
         <interface>
-            <name>ISurroundView</name>
+            <name>ISurroundViewService</name>
             <instance>default</instance>
         </interface>
     </hal>
@@ -90,7 +97,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.biometrics.face</name>
-        <version>1.0-1</version>
+        <version>1.0</version>
         <interface>
             <name>IBiometricsFace</name>
             <instance>default</instance>
@@ -250,9 +257,9 @@
             <instance>default</instance>
         </interface>
     </hal>
-    <hal format="hidl" optional="true">
+    <hal format="aidl" optional="true">
         <name>android.hardware.health.storage</name>
-        <version>1.0</version>
+        <version>1</version>
         <interface>
             <name>IStorage</name>
             <instance>default</instance>
@@ -260,11 +267,20 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.identity</name>
+        <version>1-3</version>
         <interface>
             <name>IIdentityCredentialStore</name>
             <instance>default</instance>
         </interface>
     </hal>
+    <hal format="aidl" optional="true">
+        <name>android.hardware.oemlock</name>
+        <version>1</version>
+        <interface>
+            <name>IOemLock</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.ir</name>
         <version>1.0</version>
@@ -300,6 +316,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.security.keymint</name>
+        <version>1</version>
         <interface>
             <name>IKeyMintDevice</name>
             <instance>default</instance>
@@ -307,7 +324,15 @@
         </interface>
     </hal>
     <hal format="aidl" optional="true">
+        <name>android.hardware.security.keymint</name>
+        <interface>
+            <name>IRemotelyProvisionedComponent</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+    <hal format="aidl" optional="true">
         <name>android.hardware.light</name>
+        <version>1</version>
         <interface>
             <name>ILights</name>
             <instance>default</instance>
@@ -336,6 +361,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.memtrack</name>
+        <version>1</version>
         <interface>
             <name>IMemtrack</name>
             <instance>default</instance>
@@ -357,6 +383,13 @@
             <regex-instance>.*</regex-instance>
         </interface>
     </hal>
+    <hal format="aidl" optional="true">
+        <name>android.hardware.neuralnetworks</name>
+        <interface>
+            <name>IDevice</name>
+            <regex-instance>.*</regex-instance>
+        </interface>
+    </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.nfc</name>
         <version>1.2</version>
@@ -375,14 +408,14 @@
     </hal>
     <hal format="aidl" optional="false">
         <name>android.hardware.power</name>
+        <version>1</version>
         <interface>
             <name>IPower</name>
             <instance>default</instance>
         </interface>
     </hal>
-    <hal format="hidl" optional="true">
+    <hal format="aidl" optional="true">
         <name>android.hardware.power.stats</name>
-        <version>1.0</version>
         <interface>
             <name>IPowerStats</name>
             <instance>default</instance>
@@ -418,6 +451,14 @@
         </interface>
     </hal>
     <hal format="hidl" optional="true">
+        <name>android.hardware.radio.config</name>
+        <version>1.3</version>
+        <interface>
+            <name>IRadioConfig</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+    <hal format="hidl" optional="true">
         <name>android.hardware.renderscript</name>
         <version>1.0</version>
         <interface>
@@ -427,6 +468,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.rebootescrow</name>
+        <version>1</version>
         <interface>
             <name>IRebootEscrow</name>
             <instance>default</instance>
@@ -441,6 +483,22 @@
             <regex-instance>SIM[1-9][0-9]*</regex-instance>
         </interface>
     </hal>
+    <hal format="aidl" optional="true">
+        <name>android.hardware.security.secureclock</name>
+        <version>1</version>
+        <interface>
+            <name>ISecureClock</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+    <hal format="aidl" optional="true">
+        <name>android.hardware.security.sharedsecret</name>
+        <version>1</version>
+        <interface>
+            <name>ISharedSecret</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.sensors</name>
         <version>1.0</version>
@@ -468,7 +526,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.tetheroffload.control</name>
-        <version>1.0</version>
+        <version>1.1</version>
         <interface>
             <name>IOffloadControl</name>
             <instance>default</instance>
@@ -485,7 +543,6 @@
     <hal format="hidl" optional="true">
         <name>android.hardware.tv.cec</name>
         <version>1.0</version>
-        <version>2.0</version>
         <interface>
             <name>IHdmiCec</name>
             <instance>default</instance>
@@ -531,16 +588,16 @@
         </interface>
     </hal>
     <hal format="hidl" optional="true">
-        <name>android.hardware.vr</name>
+        <name>android.hardware.weaver</name>
         <version>1.0</version>
         <interface>
-            <name>IVr</name>
+            <name>IWeaver</name>
             <instance>default</instance>
         </interface>
     </hal>
-    <hal format="hidl" optional="true">
+    <hal format="aidl" optional="true">
         <name>android.hardware.weaver</name>
-        <version>1.0</version>
+        <version>1</version>
         <interface>
             <name>IWeaver</name>
             <instance>default</instance>
diff --git a/compatibility_matrices/exclude/fcm_exclude.cpp b/compatibility_matrices/exclude/fcm_exclude.cpp
index 849df94..6a4d251 100644
--- a/compatibility_matrices/exclude/fcm_exclude.cpp
+++ b/compatibility_matrices/exclude/fcm_exclude.cpp
@@ -66,7 +66,7 @@
             // TODO(b/171260360) Remove when HAL definition is removed
             "android.hardware.audio.effect@2.0",
             "android.hardware.audio@2.0",
-            // TODO(b/171260613) Remove when HAL definition is removed
+            // Health 1.0 HAL is deprecated. The top level interface are deleted.
             "android.hardware.health@1.0",
             // TODO(b/171260670) Remove when HAL definition is removed
             "android.hardware.nfc@1.0",
diff --git a/drm/1.0/default/Android.bp b/drm/1.0/default/Android.bp
index 93b3278..d5063fb 100644
--- a/drm/1.0/default/Android.bp
+++ b/drm/1.0/default/Android.bp
@@ -46,7 +46,7 @@
 
 android_hardware_drm_1_0_multilib {
     name: "android.hardware.drm@1.0-multilib-lib",
-    compile_multilib: "32",
+    compile_multilib: "prefer32",
     soong_config_variables: {
         TARGET_ENABLE_MEDIADRM_64: {
             compile_multilib: "both",
@@ -56,7 +56,7 @@
 
 android_hardware_drm_1_0_multilib {
     name: "android.hardware.drm@1.0-multilib-exe",
-    compile_multilib: "32",
+    compile_multilib: "prefer32",
     soong_config_variables: {
         TARGET_ENABLE_MEDIADRM_64: {
             compile_multilib: "first",
diff --git a/graphics/allocator/2.0/default/OWNERS b/graphics/allocator/2.0/default/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/allocator/2.0/default/OWNERS
+++ b/graphics/allocator/2.0/default/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/allocator/2.0/utils/OWNERS b/graphics/allocator/2.0/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/allocator/2.0/utils/OWNERS
+++ b/graphics/allocator/2.0/utils/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/composer/2.1/default/OWNERS b/graphics/composer/2.1/default/OWNERS
index db8fb80..709c4d1 100644
--- a/graphics/composer/2.1/default/OWNERS
+++ b/graphics/composer/2.1/default/OWNERS
@@ -1,6 +1,3 @@
 # Graphics team
-courtneygo@google.com
-jessehall@google.com
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.1/utils/OWNERS b/graphics/composer/2.1/utils/OWNERS
index 5acc631..7af69b4 100644
--- a/graphics/composer/2.1/utils/OWNERS
+++ b/graphics/composer/2.1/utils/OWNERS
@@ -1,5 +1,2 @@
-courtneygo@google.com
-jessehall@google.com
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.2/default/OWNERS b/graphics/composer/2.2/default/OWNERS
index db8fb80..709c4d1 100644
--- a/graphics/composer/2.2/default/OWNERS
+++ b/graphics/composer/2.2/default/OWNERS
@@ -1,6 +1,3 @@
 # Graphics team
-courtneygo@google.com
-jessehall@google.com
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.2/utils/OWNERS b/graphics/composer/2.2/utils/OWNERS
index 3f1e82c..709c4d1 100644
--- a/graphics/composer/2.2/utils/OWNERS
+++ b/graphics/composer/2.2/utils/OWNERS
@@ -1,5 +1,3 @@
 # Graphics team
-courtneygo@google.com
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.2/vts/functional/OWNERS b/graphics/composer/2.2/vts/functional/OWNERS
index a17a50c..ea06752 100644
--- a/graphics/composer/2.2/vts/functional/OWNERS
+++ b/graphics/composer/2.2/vts/functional/OWNERS
@@ -1,8 +1,6 @@
 # Graphics team
-courtneygo@google.com
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
 
 # VTS team
 yim@google.com
diff --git a/graphics/composer/2.3/default/OWNERS b/graphics/composer/2.3/default/OWNERS
index 820ebe6..709c4d1 100644
--- a/graphics/composer/2.3/default/OWNERS
+++ b/graphics/composer/2.3/default/OWNERS
@@ -1,5 +1,3 @@
 # Graphics team
-jessehall@google.com
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.3/utils/OWNERS b/graphics/composer/2.3/utils/OWNERS
index cc6d937..709c4d1 100644
--- a/graphics/composer/2.3/utils/OWNERS
+++ b/graphics/composer/2.3/utils/OWNERS
@@ -1,4 +1,3 @@
 # Graphics team
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.3/vts/functional/OWNERS b/graphics/composer/2.3/vts/functional/OWNERS
index b3ea6be..ea06752 100644
--- a/graphics/composer/2.3/vts/functional/OWNERS
+++ b/graphics/composer/2.3/vts/functional/OWNERS
@@ -1,7 +1,6 @@
 # Graphics team
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
 
 # VTS team
 yim@google.com
diff --git a/graphics/composer/2.4/default/OWNERS b/graphics/composer/2.4/default/OWNERS
index cc6d937..709c4d1 100644
--- a/graphics/composer/2.4/default/OWNERS
+++ b/graphics/composer/2.4/default/OWNERS
@@ -1,4 +1,3 @@
 # Graphics team
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.4/utils/OWNERS b/graphics/composer/2.4/utils/OWNERS
index cc6d937..709c4d1 100644
--- a/graphics/composer/2.4/utils/OWNERS
+++ b/graphics/composer/2.4/utils/OWNERS
@@ -1,4 +1,3 @@
 # Graphics team
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.4/vts/functional/OWNERS b/graphics/composer/2.4/vts/functional/OWNERS
index b3ea6be..ea06752 100644
--- a/graphics/composer/2.4/vts/functional/OWNERS
+++ b/graphics/composer/2.4/vts/functional/OWNERS
@@ -1,7 +1,6 @@
 # Graphics team
+adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
 
 # VTS team
 yim@google.com
diff --git a/graphics/mapper/2.0/default/OWNERS b/graphics/mapper/2.0/default/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/2.0/default/OWNERS
+++ b/graphics/mapper/2.0/default/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/2.0/utils/OWNERS b/graphics/mapper/2.0/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/2.0/utils/OWNERS
+++ b/graphics/mapper/2.0/utils/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/2.0/vts/OWNERS b/graphics/mapper/2.0/vts/OWNERS
index 11b7d21..4177296 100644
--- a/graphics/mapper/2.0/vts/OWNERS
+++ b/graphics/mapper/2.0/vts/OWNERS
@@ -1,7 +1,7 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
 
 # VTS team
 yim@google.com
diff --git a/graphics/mapper/2.1/default/OWNERS b/graphics/mapper/2.1/default/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/2.1/default/OWNERS
+++ b/graphics/mapper/2.1/default/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/2.1/utils/OWNERS b/graphics/mapper/2.1/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/2.1/utils/OWNERS
+++ b/graphics/mapper/2.1/utils/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/2.1/vts/OWNERS b/graphics/mapper/2.1/vts/OWNERS
index 11b7d21..4177296 100644
--- a/graphics/mapper/2.1/vts/OWNERS
+++ b/graphics/mapper/2.1/vts/OWNERS
@@ -1,7 +1,7 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
 
 # VTS team
 yim@google.com
diff --git a/graphics/mapper/3.0/utils/OWNERS b/graphics/mapper/3.0/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/3.0/utils/OWNERS
+++ b/graphics/mapper/3.0/utils/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/3.0/vts/OWNERS b/graphics/mapper/3.0/vts/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/3.0/vts/OWNERS
+++ b/graphics/mapper/3.0/vts/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/4.0/utils/OWNERS b/graphics/mapper/4.0/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/4.0/utils/OWNERS
+++ b/graphics/mapper/4.0/utils/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/4.0/vts/OWNERS b/graphics/mapper/4.0/vts/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/4.0/vts/OWNERS
+++ b/graphics/mapper/4.0/vts/OWNERS
@@ -1,4 +1,4 @@
 # Graphics team
 chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/4.0/vts/functional/Android.bp b/graphics/mapper/4.0/vts/functional/Android.bp
index 8bda425..2d39daa 100644
--- a/graphics/mapper/4.0/vts/functional/Android.bp
+++ b/graphics/mapper/4.0/vts/functional/Android.bp
@@ -19,7 +19,7 @@
     defaults: ["VtsHalTargetTestDefaults"],
     srcs: ["VtsHalGraphicsMapperV4_0TargetTest.cpp"],
     static_libs: [
-        "android.hardware.graphics.common-unstable-ndk_platform",
+        "android.hardware.graphics.common-V2-ndk_platform",
         "android.hardware.graphics.mapper@4.0-vts",
         "libgralloctypes",
         "libsync",
diff --git a/health/1.0/Android.bp b/health/1.0/Android.bp
index 7845871..7786c08 100644
--- a/health/1.0/Android.bp
+++ b/health/1.0/Android.bp
@@ -5,7 +5,6 @@
     root: "android.hardware",
     srcs: [
         "types.hal",
-        "IHealth.hal",
     ],
     interfaces: [
         "android.hidl.base@1.0",
diff --git a/health/1.0/IHealth.hal b/health/1.0/IHealth.hal
deleted file mode 100644
index 3828589..0000000
--- a/health/1.0/IHealth.hal
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 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@1.0;
-
-interface IHealth {
-    /**
-     * This function lets you change healthd configuration from default if
-     * desired. It must be called exactly once at startup time.
-     *
-     * The configuration values are described in 'struct HealthConfig'.
-     * To use default configuration, simply return without modifying the
-     * fields of the config parameter.
-     *
-     * @param default healthd configuration.
-     */
-    init(HealthConfig config) generates (HealthConfig configOut);
-
-    /**
-     * This function is a hook to update/change device's HealthInfo (as described
-     * in 'struct HealthInfo').
-     *
-     * 'HealthInfo' describes device's battery and charging status, typically
-     * read from kernel. These values may be modified in this call.
-     *
-     * @param   Device Health info as described in 'struct HealthInfo'.
-     * @return  skipLogging Indication to the caller to add 'or' skip logging the health
-     *          information. Return 'true' to skip logging the update.
-     * @return  infoOut HealthInfo to be sent to client code. (May or may
-     *          not be modified).
-     */
-    update(HealthInfo info) generates (bool skipLogging, HealthInfo infoOut);
-
-    /**
-     * This function is called by healthd when framework queries for remaining
-     * energy in the Battery through BatteryManager APIs.
-     *
-     * @return  result Result of querying enery counter for the battery.
-     * @return  energy Battery remaining energy in nanowatt-hours.
-     *          Must be '0' if result is anything other than Result::SUCCESS.
-     */
-    energyCounter() generates (Result result, int64_t energy);
-};
diff --git a/health/1.0/default/Android.bp b/health/1.0/default/Android.bp
index aab9cc7..ff4b875 100644
--- a/health/1.0/default/Android.bp
+++ b/health/1.0/default/Android.bp
@@ -17,62 +17,3 @@
     ],
 
 }
-
-cc_library_static {
-    name: "android.hardware.health@1.0-impl-helper",
-    vendor: true,
-    srcs: ["Health.cpp"],
-
-    header_libs: [
-        "libbase_headers",
-        "libhealthd_headers",
-    ],
-
-    shared_libs: [
-        "libcutils",
-        "libhidlbase",
-        "liblog",
-        "libutils",
-        "android.hardware.health@1.0",
-    ],
-
-    static_libs: [
-        "android.hardware.health@1.0-convert",
-    ],
-}
-
-cc_library_shared {
-    name: "android.hardware.health@1.0-impl",
-    vendor: true,
-    relative_install_path: "hw",
-
-    static_libs: [
-        "android.hardware.health@1.0-impl-helper",
-        "android.hardware.health@1.0-convert",
-        "libhealthd.default",
-    ],
-
-    shared_libs: [
-        "libhidlbase",
-        "libutils",
-        "android.hardware.health@1.0",
-    ],
-}
-
-cc_binary {
-    name: "android.hardware.health@1.0-service",
-    vendor: true,
-    relative_install_path: "hw",
-    init_rc: ["android.hardware.health@1.0-service.rc"],
-    srcs: ["HealthService.cpp"],
-
-    shared_libs: [
-        "liblog",
-        "libcutils",
-        "libdl",
-        "libbase",
-        "libutils",
-        "libhidlbase",
-        "android.hardware.health@1.0",
-    ],
-}
diff --git a/health/1.0/default/Health.cpp b/health/1.0/default/Health.cpp
deleted file mode 100644
index 1a02956..0000000
--- a/health/1.0/default/Health.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2016 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-hal"
-
-#include <Health.h>
-#include <include/hal_conversion.h>
-
-namespace android {
-namespace hardware {
-namespace health {
-namespace V1_0 {
-namespace implementation {
-
-using ::android::hardware::health::V1_0::hal_conversion::convertToHealthConfig;
-using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthConfig;
-using ::android::hardware::health::V1_0::hal_conversion::convertToHealthInfo;
-using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthInfo;
-
-// Methods from ::android::hardware::health::V1_0::IHealth follow.
-Return<void> Health::init(const HealthConfig& config, init_cb _hidl_cb)  {
-    struct healthd_config healthd_config = {};
-    HealthConfig configOut;
-
-    // To keep working with existing healthd static HALs,
-    // convert the new HealthConfig to the old healthd_config
-    // and back.
-
-    convertFromHealthConfig(config, &healthd_config);
-    healthd_board_init(&healthd_config);
-    mGetEnergyCounter = healthd_config.energyCounter;
-    convertToHealthConfig(&healthd_config, configOut);
-
-    _hidl_cb(configOut);
-
-    return Void();
-}
-
-Return<void> Health::update(const HealthInfo& info, update_cb _hidl_cb)  {
-    struct android::BatteryProperties p = {};
-    HealthInfo infoOut;
-
-    // To keep working with existing healthd static HALs,
-    // convert the new HealthInfo to android::Batteryproperties
-    // and back.
-
-    convertFromHealthInfo(info, &p);
-    int skipLogging = healthd_board_battery_update(&p);
-    convertToHealthInfo(&p, infoOut);
-
-    _hidl_cb(!!skipLogging, infoOut);
-
-    return Void();
-}
-
-Return<void> Health::energyCounter(energyCounter_cb _hidl_cb) {
-    int64_t energy = 0;
-    Result result = Result::NOT_SUPPORTED;
-
-    if (mGetEnergyCounter) {
-        int status = mGetEnergyCounter(&energy);
-        if (status == 0) {
-            result = Result::SUCCESS;
-        }
-    }
-
-    _hidl_cb(result, energy);
-
-   return Void();
-}
-
-IHealth* HIDL_FETCH_IHealth(const char* /* name */) {
-    return new Health();
-}
-
-} // namespace implementation
-}  // namespace V1_0
-}  // namespace health
-}  // namespace hardware
-}  // namespace android
diff --git a/health/1.0/default/Health.h b/health/1.0/default/Health.h
deleted file mode 100644
index ed364c1..0000000
--- a/health/1.0/default/Health.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 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_HEALTH_V1_0_HEALTH_H
-#define ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <hidl/Status.h>
-#include <hidl/MQDescriptor.h>
-#include <healthd/healthd.h>
-#include <utils/String8.h>
-
-namespace android {
-namespace hardware {
-namespace health {
-namespace V1_0 {
-namespace implementation {
-
-using ::android::hardware::health::V1_0::HealthInfo;
-using ::android::hardware::health::V1_0::HealthConfig;
-using ::android::hardware::health::V1_0::IHealth;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_string;
-using ::android::sp;
-
-struct Health : public IHealth {
-    // Methods from ::android::hardware::health::V1_0::IHealth follow.
-    Return<void> init(const HealthConfig& config, init_cb _hidl_cb)  override;
-    Return<void> update(const HealthInfo& info, update_cb _hidl_cb)  override;
-    Return<void> energyCounter(energyCounter_cb _hidl_cb) override;
-private:
-    std::function<int(int64_t *)> mGetEnergyCounter;
-};
-
-extern "C" IHealth* HIDL_FETCH_IHealth(const char* name);
-
-}  // namespace implementation
-}  // namespace V1_0
-}  // namespace health
-}  // namespace hardware
-}  // namespace android
-
-#endif  // ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H
diff --git a/health/1.0/default/HealthService.cpp b/health/1.0/default/HealthService.cpp
deleted file mode 100644
index 55848d2..0000000
--- a/health/1.0/default/HealthService.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2016 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.health@1.0-service"
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <hidl/LegacySupport.h>
-
-using android::hardware::health::V1_0::IHealth;
-using android::hardware::defaultPassthroughServiceImplementation;
-
-int main() {
-    return defaultPassthroughServiceImplementation<IHealth>();
-}
diff --git a/health/1.0/default/README.md b/health/1.0/default/README.md
deleted file mode 100644
index 1ded7de..0000000
--- a/health/1.0/default/README.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Implement the 2.1 HAL instead!
-
-It is strongly recommended that you implement the 2.1 HAL directly. See
-`hardware/interfaces/health/2.1/README.md` for more details.
-
-# Implement Health 1.0 HAL
-
-1. Install common binderized service. The binderized service `dlopen()`s
-   passthrough implementations on the device, so there is no need to write
-   your own.
-
-    ```mk
-    # Install default binderized implementation to vendor.
-    PRODUCT_PACKAGES += android.hardware.health@1.0-service
-    ```
-
-1. Add proper VINTF manifest entry to your device manifest. Example:
-
-    ```xml
-    <hal format="hidl">
-        <name>android.hardware.health</name>
-        <transport>hwbinder</transport>
-        <version>1.0</version>
-        <interface>
-            <name>IHealth</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
-    ```
-
-1. Install the proper passthrough implemetation.
-
-    1. If you want to use the default implementation (with default `libhealthd`),
-       add the following to `device.mk`:
-
-        ```mk
-        PRODUCT_PACKAGES += \
-            android.hardware.health@1.0-impl
-        ```
-
-    1. Otherwise, if you have a customized `libhealthd.<board>`:
-
-        1. Define your passthrough implementation. Example (replace `<device>`
-           and `<board>` accordingly):
-
-            ```bp
-            cc_library_shared {
-                name: "android.hardware.health@1.0-impl-<device>",
-                vendor: true,
-                relative_install_path: "hw",
-
-                static_libs: [
-                    "android.hardware.health@1.0-impl-helper",
-                    "android.hardware.health@1.0-convert",
-                    "libhealthd.<board>",
-                ],
-            }
-            ```
-
-        1. Add to `device.mk`.
-
-            ```
-            PRODUCT_PACKAGES += android.hardware.health@1.0-impl-<device>
-            ```
-
-        1. Define appropriate SELinux permissions.
diff --git a/health/1.0/default/android.hardware.health@1.0-service.rc b/health/1.0/default/android.hardware.health@1.0-service.rc
deleted file mode 100644
index 569dc88..0000000
--- a/health/1.0/default/android.hardware.health@1.0-service.rc
+++ /dev/null
@@ -1,5 +0,0 @@
-service vendor.health-hal-1-0 /vendor/bin/hw/android.hardware.health@1.0-service
-    class hal
-    user system
-    group system
-    capabilities WAKE_ALARM BLOCK_SUSPEND
diff --git a/health/1.0/default/include/hal_conversion.h b/health/1.0/default/include/hal_conversion.h
index a92b208..a8ddb73 100644
--- a/health/1.0/default/include/hal_conversion.h
+++ b/health/1.0/default/include/hal_conversion.h
@@ -17,7 +17,7 @@
 #ifndef HARDWARE_INTERFACES_HEALTH_V1_0_DEFAULT_INCLUDE_HAL_CONVERSION_H_
 #define HARDWARE_INTERFACES_HEALTH_V1_0_DEFAULT_INCLUDE_HAL_CONVERSION_H_
 
-#include <android/hardware/health/1.0/IHealth.h>
+#include <android/hardware/health/1.0/types.h>
 #include <healthd/healthd.h>
 
 namespace android {
diff --git a/health/1.0/default/libhealthd/Android.bp b/health/1.0/default/libhealthd/Android.bp
deleted file mode 100644
index 43463eb..0000000
--- a/health/1.0/default/libhealthd/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2016 The Android Open Source Project
-
-cc_library_static {
-    srcs: ["healthd_board_default.cpp"],
-    name: "libhealthd.default",
-    vendor_available: true,
-    recovery_available: true,
-    cflags: ["-Werror"],
-    include_dirs: ["system/libbase/include"],
-    header_libs: ["libhealthd_headers"],
-}
diff --git a/health/1.0/vts/functional/Android.bp b/health/1.0/vts/functional/Android.bp
deleted file mode 100644
index f4a04a7..0000000
--- a/health/1.0/vts/functional/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// Copyright (C) 2017 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.
-//
-
-cc_test {
-    name: "VtsHalHealthV1_0TargetTest",
-    defaults: ["VtsHalTargetTestDefaults"],
-    srcs: ["VtsHalHealthV1_0TargetTest.cpp"],
-    static_libs: ["android.hardware.health@1.0"],
-    test_suites: ["general-tests", "vts"],
-}
diff --git a/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp b/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp
deleted file mode 100644
index 8b3dcc1..0000000
--- a/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 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_hidl_hal_test"
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <android/hardware/health/1.0/types.h>
-#include <gtest/gtest.h>
-#include <hidl/GtestPrinter.h>
-#include <hidl/ServiceManagement.h>
-#include <log/log.h>
-
-using HealthConfig = ::android::hardware::health::V1_0::HealthConfig;
-using HealthInfo = ::android::hardware::health::V1_0::HealthInfo;
-using IHealth = ::android::hardware::health::V1_0::IHealth;
-using Result = ::android::hardware::health::V1_0::Result;
-
-using ::android::sp;
-
-class HealthHidlTest : public ::testing::TestWithParam<std::string> {
-   public:
-    virtual void SetUp() override {
-        health = IHealth::getService(GetParam());
-        ASSERT_NE(health, nullptr);
-        health->init(config,
-                     [&](const auto& halConfigOut) { config = halConfigOut; });
-    }
-
-    sp<IHealth> health;
-    HealthConfig config;
-};
-
-/**
- * Ensure EnergyCounter call returns positive energy counter or NOT_SUPPORTED
- */
-TEST_P(HealthHidlTest, TestEnergyCounter) {
-    Result result;
-    int64_t energy = 0;
-    health->energyCounter([&](Result ret, int64_t energyOut) {
-        result = ret;
-        energy = energyOut;
-    });
-
-    ASSERT_TRUE(result == Result::SUCCESS || result == Result::NOT_SUPPORTED);
-    ASSERT_TRUE(result != Result::SUCCESS || energy > 0);
-}
-
-GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthHidlTest);
-INSTANTIATE_TEST_SUITE_P(
-        PerInstance, HealthHidlTest,
-        testing::ValuesIn(android::hardware::getAllHalInstanceNames(IHealth::descriptor)),
-        android::hardware::PrintInstanceNameToString);
diff --git a/health/storage/1.0/default/Android.bp b/health/storage/1.0/default/Android.bp
index 3156dfe..3834244 100644
--- a/health/storage/1.0/default/Android.bp
+++ b/health/storage/1.0/default/Android.bp
@@ -38,6 +38,7 @@
     ],
 
     static_libs: [
+        "libhealth_storage_impl_common",
         "libfstab",
     ],
 
diff --git a/health/storage/1.0/default/Storage.cpp b/health/storage/1.0/default/Storage.cpp
index 561deaa..02b6a3d 100644
--- a/health/storage/1.0/default/Storage.cpp
+++ b/health/storage/1.0/default/Storage.cpp
@@ -18,11 +18,8 @@
 
 #include <sstream>
 
-#include <android-base/chrono_utils.h>
-#include <android-base/file.h>
 #include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <fstab/fstab.h>
+#include <health-storage-impl/common.h>
 
 namespace android {
 namespace hardware {
@@ -31,69 +28,9 @@
 namespace V1_0 {
 namespace implementation {
 
-using base::ReadFileToString;
-using base::Timer;
-using base::Trim;
-using base::WriteStringToFd;
-using base::WriteStringToFile;
-using fs_mgr::Fstab;
-using fs_mgr::ReadDefaultFstab;
-
-std::string getGarbageCollectPath() {
-    Fstab fstab;
-    ReadDefaultFstab(&fstab);
-
-    for (const auto& entry : fstab) {
-        if (!entry.sysfs_path.empty()) {
-            return entry.sysfs_path + "/manual_gc";
-        }
-    }
-
-    return "";
-}
-
 Return<void> Storage::garbageCollect(uint64_t timeoutSeconds,
                                      const sp<IGarbageCollectCallback>& cb) {
-    Result result = Result::SUCCESS;
-    std::string path = getGarbageCollectPath();
-
-    if (path.empty()) {
-        LOG(WARNING) << "Cannot find Dev GC path";
-        result = Result::UNKNOWN_ERROR;
-    } else {
-        Timer timer;
-        LOG(INFO) << "Start Dev GC on " << path;
-        while (1) {
-            std::string require;
-            if (!ReadFileToString(path, &require)) {
-                PLOG(WARNING) << "Reading manual_gc failed in " << path;
-                result = Result::IO_ERROR;
-                break;
-            }
-            require = Trim(require);
-            if (require == "" || require == "off" || require == "disabled") {
-                LOG(DEBUG) << "No more to do Dev GC";
-                break;
-            }
-            LOG(DEBUG) << "Trigger Dev GC on " << path;
-            if (!WriteStringToFile("1", path)) {
-                PLOG(WARNING) << "Start Dev GC failed on " << path;
-                result = Result::IO_ERROR;
-                break;
-            }
-            if (timer.duration() >= std::chrono::seconds(timeoutSeconds)) {
-                LOG(WARNING) << "Dev GC timeout";
-                // Timeout is not treated as an error. Try next time.
-                break;
-            }
-            sleep(2);
-        }
-        LOG(INFO) << "Stop Dev GC on " << path;
-        if (!WriteStringToFile("0", path)) {
-            PLOG(WARNING) << "Stop Dev GC failed on " << path;
-            result = Result::IO_ERROR;
-        }
-    }
+    Result result = GarbageCollect(timeoutSeconds);
 
     if (cb != nullptr) {
         auto ret = cb->onFinish(result);
@@ -110,28 +47,7 @@
     }
 
     int fd = handle->data[0];
-    std::stringstream output;
-
-    std::string path = getGarbageCollectPath();
-    if (path.empty()) {
-        output << "Cannot find Dev GC path";
-    } else {
-        std::string require;
-
-        if (ReadFileToString(path, &require)) {
-            output << path << ":" << require << std::endl;
-        }
-
-        if (WriteStringToFile("0", path)) {
-            output << "stop success" << std::endl;
-        }
-    }
-
-    if (!WriteStringToFd(output.str(), fd)) {
-        PLOG(WARNING) << "debug: cannot write to fd";
-    }
-
-    fsync(fd);
+    DebugDump(fd);
 
     return Void();
 }
diff --git a/health/storage/1.0/vts/functional/Android.bp b/health/storage/1.0/vts/functional/Android.bp
index 2201031..731ad62 100644
--- a/health/storage/1.0/vts/functional/Android.bp
+++ b/health/storage/1.0/vts/functional/Android.bp
@@ -19,6 +19,9 @@
     defaults: ["VtsHalTargetTestDefaults"],
     srcs: ["VtsHalHealthStorageV1_0TargetTest.cpp"],
     static_libs: ["android.hardware.health.storage@1.0"],
+    header_libs: [
+        "libhealth_storage_test_common_headers",
+    ],
     shared_libs: [
         "libhidlbase",
     ],
diff --git a/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp b/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
index 24ddc5d..ddb6b5a 100644
--- a/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
+++ b/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
+#include <unistd.h>
+
+#include <thread>
+
 #include <android-base/logging.h>
 #include <android/hardware/health/storage/1.0/IStorage.h>
 #include <gtest/gtest.h>
+#include <health-storage-test/common.h>
 #include <hidl/GtestPrinter.h>
 #include <hidl/HidlTransportSupport.h>
 #include <hidl/ServiceManagement.h>
-#include <unistd.h>
-#include <thread>
 
 namespace android {
 namespace hardware {
@@ -29,61 +32,17 @@
 namespace storage {
 namespace V1_0 {
 
+using namespace ::android::hardware::health::storage::test;
 using ::std::literals::chrono_literals::operator""ms;
 
 #define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) << ret.description()
 
-// Dev GC timeout. This is the timeout used by vold.
-const uint64_t kDevGcTimeoutSec = 120;
-const std::chrono::seconds kDevGcTimeout{kDevGcTimeoutSec};
-// Dev GC timeout tolerance. The HAL may not immediately return after the
-// timeout, so include an acceptable tolerance.
-const std::chrono::seconds kDevGcTolerance{3};
-// Time accounted for RPC calls.
-const std::chrono::milliseconds kRpcTime{1000};
-
-template <typename R>
-std::string toString(std::chrono::duration<R, std::milli> time) {
-    return std::to_string(time.count()) + "ms";
-}
-
-/** An atomic boolean flag that indicates whether a task has finished. */
-class Flag {
-   public:
-    void onFinish() {
-        std::unique_lock<std::mutex> lock(mMutex);
-        onFinishLocked(&lock);
-    }
-    template <typename R, typename P>
-    bool wait(std::chrono::duration<R, P> duration) {
-        std::unique_lock<std::mutex> lock(mMutex);
-        return waitLocked(&lock, duration);
-    }
-
-   protected:
-    /** Will unlock. */
-    void onFinishLocked(std::unique_lock<std::mutex>* lock) {
-        mFinished = true;
-        lock->unlock();
-        mCv.notify_all();
-    }
-    template <typename R, typename P>
-    bool waitLocked(std::unique_lock<std::mutex>* lock, std::chrono::duration<R, P> duration) {
-        mCv.wait_for(*lock, duration, [this] { return mFinished; });
-        return mFinished;
-    }
-
-    bool mFinished{false};
-    std::mutex mMutex;
-    std::condition_variable mCv;
-};
-
 class GcCallback : public IGarbageCollectCallback, public Flag {
-   public:
+  public:
     Return<void> onFinish(Result result) override {
-        std::unique_lock<std::mutex> lock(mMutex);
-        mResult = result;
-        Flag::onFinishLocked(&lock);
+        std::unique_lock<std::mutex> lock(mutex_);
+        result_ = result;
+        Flag::OnFinishLocked(&lock);
         return Void();
     }
 
@@ -93,13 +52,13 @@
      */
     template <typename R, typename P>
     void waitForResult(std::chrono::duration<R, P> timeout, Result expected) {
-        std::unique_lock<std::mutex> lock(mMutex);
-        ASSERT_TRUE(waitLocked(&lock, timeout)) << "timeout after " << toString(timeout);
-        EXPECT_EQ(expected, mResult);
+        std::unique_lock<std::mutex> lock(mutex_);
+        ASSERT_TRUE(WaitLocked(&lock, timeout)) << "timeout after " << to_string(timeout);
+        EXPECT_EQ(expected, result_);
     }
 
-   private:
-    Result mResult{Result::UNKNOWN_ERROR};
+  private:
+    Result result_{Result::UNKNOWN_ERROR};
 };
 
 class HealthStorageHidlTest : public ::testing::TestWithParam<std::string> {
@@ -127,10 +86,10 @@
         auto pingFlag = std::make_shared<Flag>();
         std::thread([service, pingFlag] {
             service->ping();
-            pingFlag->onFinish();
+            pingFlag->OnFinish();
         })
             .detach();
-        return pingFlag->wait(timeout);
+        return pingFlag->Wait(timeout);
     }
 
     sp<IStorage> fs;
@@ -147,7 +106,7 @@
     // Hold test process because HAL can be single-threaded and doing GC.
     ASSERT_TRUE(ping(kDevGcTimeout + kDevGcTolerance + kRpcTime))
             << "Service must be available after "
-            << toString(kDevGcTimeout + kDevGcTolerance + kRpcTime);
+            << to_string(kDevGcTimeout + kDevGcTolerance + kRpcTime);
 }
 
 /**
diff --git a/health/storage/aidl/Android.bp b/health/storage/aidl/Android.bp
new file mode 100644
index 0000000..c39a46d
--- /dev/null
+++ b/health/storage/aidl/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.
+
+aidl_interface {
+    name: "android.hardware.health.storage",
+    vendor_available: true,
+    srcs: ["android/hardware/health/storage/*.aidl"],
+    stability: "vintf",
+    backend: {
+        cpp: {
+            enabled: false,
+        },
+        java: {
+            enabled: false,
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl
new file mode 100644
index 0000000..0f382d7
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.health.storage;
+@VintfStability
+interface IGarbageCollectCallback {
+  oneway void onFinish(in android.hardware.health.storage.Result result);
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl
new file mode 100644
index 0000000..61f838a
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.health.storage;
+@VintfStability
+interface IStorage {
+  oneway void garbageCollect(in long timeoutSeconds, in android.hardware.health.storage.IGarbageCollectCallback callback);
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl
new file mode 100644
index 0000000..a345808
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.health.storage;
+@Backing(type="int") @VintfStability
+enum Result {
+  SUCCESS = 0,
+  IO_ERROR = 1,
+  UNKNOWN_ERROR = 2,
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl b/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl
new file mode 100644
index 0000000..ccd1b44
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.storage;
+
+import android.hardware.health.storage.Result;
+
+/**
+ * Callback interface to IStorage.garbageCollect.
+ */
+@VintfStability
+interface IGarbageCollectCallback {
+    /**
+     * When garbage collection has finished, the implementation must
+     * invoke this function to indicate the result of the garbage collection.
+     *
+     * @param out result Execution result. See documentation for Result for
+     *     details.
+     */
+    oneway void onFinish(in Result result);
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/IStorage.aidl b/health/storage/aidl/android/hardware/health/storage/IStorage.aidl
new file mode 100644
index 0000000..78992a2
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/IStorage.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.storage;
+
+import android.hardware.health.storage.IGarbageCollectCallback;
+
+/**
+ * IStorage is an interface that provides operations on underlying storage
+ * devices, including flash memory.
+ */
+@VintfStability
+interface IStorage {
+    /**
+     * Start garbage collection on the driver of storage devices.
+     *
+     * Garbage collection must be started at regular intervals when it is a good
+     * time for a longer-running cleanup tasks, roughly daily.
+     *
+     * When garbage collection finishes or encounters an error before the
+     * specified timeout, the implementation must call IGarbageCollect.finish
+     * immediately with appropriate result.
+     *
+     * If garbage collection does not finish within the specified timeout,
+     * the implementation must stop garbage collection, and must not call
+     * IGarbageCollect.finish.
+     *
+     * @param timeoutSeconds timeout in seconds. The implementation must
+     *     return after the timeout is reached.
+     *
+     * @param callback callback interface. Callback must be null if the client
+     *     does not need to receive any callbacks.
+     *
+     */
+    oneway void garbageCollect(in long timeoutSeconds, in IGarbageCollectCallback callback);
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/Result.aidl b/health/storage/aidl/android/hardware/health/storage/Result.aidl
new file mode 100644
index 0000000..73bb779
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/Result.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.storage;
+
+/**
+ * Status values for HAL methods.
+ */
+@VintfStability
+@Backing(type="int")
+enum Result {
+    /**
+     * Execution of the method is successful.
+     */
+    SUCCESS = 0,
+    /**
+     * An IO error is encountered when the HAL communicates with the device.
+     */
+    IO_ERROR,
+    /**
+     * An unknown error is encountered.
+     */
+    UNKNOWN_ERROR,
+}
diff --git a/health/storage/aidl/default/Android.bp b/health/storage/aidl/default/Android.bp
new file mode 100644
index 0000000..b53bc35
--- /dev/null
+++ b/health/storage/aidl/default/Android.bp
@@ -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.
+ */
+
+cc_defaults {
+    name: "libhealth_storage_impl_defaults",
+    vendor: true,
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "android.hardware.health.storage-V1-ndk_platform",
+    ],
+    static_libs: [
+        "libfstab",
+        "libhealth_storage_impl_common",
+    ],
+}
+
+cc_library_static {
+    name: "libhealth_storage_default_impl",
+    defaults: ["libhealth_storage_impl_defaults"],
+    srcs: [
+        "Storage.cpp",
+    ],
+    visibility: [
+        ":__subpackages__",
+        "//hardware/interfaces/tests/extension/health/storage:__subpackages__",
+    ],
+}
+
+cc_binary {
+    name: "android.hardware.health.storage-service.default",
+    defaults: ["libhealth_storage_impl_defaults"],
+    relative_install_path: "hw",
+    init_rc: ["health-storage-default.rc"],
+    vintf_fragments: ["health-storage-default.xml"],
+    srcs: ["main.cpp"],
+    static_libs: [
+        "libhealth_storage_default_impl",
+    ],
+}
diff --git a/health/storage/aidl/default/Storage.cpp b/health/storage/aidl/default/Storage.cpp
new file mode 100644
index 0000000..faa4ff6
--- /dev/null
+++ b/health/storage/aidl/default/Storage.cpp
@@ -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.
+ */
+
+#include "Storage.h"
+
+#include <sstream>
+
+#include <android-base/logging.h>
+#include <health-storage-impl/common.h>
+
+using ::android::hardware::health::storage::DebugDump;
+using ::android::hardware::health::storage::GarbageCollect;
+
+using HResult = android::hardware::health::storage::V1_0::Result;
+using AResult = aidl::android::hardware::health::storage::Result;
+// Ensure static_cast<AResult>(any HResult) works
+static_assert(static_cast<AResult>(HResult::SUCCESS) == AResult::SUCCESS);
+static_assert(static_cast<AResult>(HResult::IO_ERROR) == AResult::IO_ERROR);
+static_assert(static_cast<AResult>(HResult::UNKNOWN_ERROR) == AResult::UNKNOWN_ERROR);
+
+namespace aidl::android::hardware::health::storage {
+
+ndk::ScopedAStatus Storage::garbageCollect(
+        int64_t timeout_seconds, const std::shared_ptr<IGarbageCollectCallback>& callback) {
+    AResult result = static_cast<AResult>(GarbageCollect(static_cast<uint64_t>(timeout_seconds)));
+    if (callback != nullptr) {
+        auto status = callback->onFinish(result);
+        if (!status.isOk()) {
+            LOG(WARNING) << "Cannot return result " << toString(result)
+                         << " to callback: " << status.getDescription();
+        }
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+binder_status_t Storage::dump(int fd, const char**, uint32_t) {
+    DebugDump(fd);
+    return STATUS_OK;
+}
+
+}  // namespace aidl::android::hardware::health::storage
diff --git a/health/storage/aidl/default/Storage.h b/health/storage/aidl/default/Storage.h
new file mode 100644
index 0000000..049991b
--- /dev/null
+++ b/health/storage/aidl/default/Storage.h
@@ -0,0 +1,30 @@
+/*
+ * 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/storage/BnStorage.h>
+
+namespace aidl::android::hardware::health::storage {
+
+class Storage : public BnStorage {
+    ndk::ScopedAStatus garbageCollect(
+            int64_t timeout_seconds,
+            const std::shared_ptr<IGarbageCollectCallback>& callback) override;
+    binder_status_t dump(int fd, const char** args, uint32_t num_args) override;
+};
+
+}  // namespace aidl::android::hardware::health::storage
diff --git a/health/storage/aidl/default/health-storage-default.rc b/health/storage/aidl/default/health-storage-default.rc
new file mode 100644
index 0000000..fc1cc8b
--- /dev/null
+++ b/health/storage/aidl/default/health-storage-default.rc
@@ -0,0 +1,7 @@
+service vendor.health-storage-default /vendor/bin/hw/android.hardware.health.storage-service.default
+    interface aidl android.hardware.health.storage.IStorage/default
+    oneshot
+    disabled
+    class hal
+    user system
+    group system
diff --git a/health/storage/aidl/default/health-storage-default.xml b/health/storage/aidl/default/health-storage-default.xml
new file mode 100644
index 0000000..14d4901
--- /dev/null
+++ b/health/storage/aidl/default/health-storage-default.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.health.storage</name>
+        <version>1</version>
+        <fqname>IStorage/default</fqname>
+    </hal>
+</manifest>
diff --git a/health/storage/aidl/default/main.cpp b/health/storage/aidl/default/main.cpp
new file mode 100644
index 0000000..186b64c
--- /dev/null
+++ b/health/storage/aidl/default/main.cpp
@@ -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.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Storage.h"
+
+using aidl::android::hardware::health::storage::Storage;
+using std::string_literals::operator""s;
+
+int main() {
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+    // make a default storage service
+    auto storage = ndk::SharedRefBase::make<Storage>();
+    const std::string name = Storage::descriptor + "/default"s;
+    CHECK_EQ(STATUS_OK,
+             AServiceManager_registerLazyService(storage->asBinder().get(), name.c_str()));
+
+    ABinderProcess_joinThreadPool();
+    return EXIT_FAILURE;  // should not reach
+}
diff --git a/health/storage/aidl/vts/functional/Android.bp b/health/storage/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..0e7671d
--- /dev/null
+++ b/health/storage/aidl/vts/functional/Android.bp
@@ -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.
+
+cc_test {
+    name: "VtsHalHealthStorageTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "VtsHalHealthStorageTargetTest.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "android.hardware.health.storage-V1-ndk_platform",
+    ],
+    header_libs: [
+        "libhealth_storage_test_common_headers",
+    ],
+    test_suites: [
+        "vts",
+    ],
+    test_config: "VtsHalHealthStorageTargetTest.xml",
+}
diff --git a/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp
new file mode 100644
index 0000000..3b6b6b4
--- /dev/null
+++ b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.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 <unistd.h>
+
+#include <chrono>
+#include <set>
+#include <string>
+#include <thread>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/health/storage/BnGarbageCollectCallback.h>
+#include <aidl/android/hardware/health/storage/IStorage.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include <health-storage-test/common.h>
+
+namespace aidl::android::hardware::health::storage {
+
+using namespace ::android::hardware::health::storage::test;
+using std::chrono_literals::operator""ms;
+
+#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) << ret.getDescription()
+#define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk()) << ret.getDescription()
+
+class GcCallback : public BnGarbageCollectCallback, public Flag {
+  public:
+    ndk::ScopedAStatus onFinish(Result result) override {
+        std::unique_lock<std::mutex> lock(mutex_);
+        result_ = result;
+        OnFinishLocked(&lock);
+        return ndk::ScopedAStatus::ok();
+    }
+
+    /**
+     * Wait for a specific "timeout". If GC has finished, test that the result
+     * is equal to the "expected" value.
+     */
+    template <typename R, typename P>
+    void WaitForResult(std::chrono::duration<R, P> timeout, Result expected) {
+        std::unique_lock<std::mutex> lock(mutex_);
+        ASSERT_TRUE(WaitLocked(&lock, timeout)) << "timeout after " << to_string(timeout);
+        EXPECT_EQ(expected, result_);
+    }
+
+  private:
+    Result result_{Result::UNKNOWN_ERROR};
+};
+
+class HealthStorageAidl : public testing::TestWithParam<std::string> {
+  public:
+    virtual void SetUp() override {
+        std::string name = GetParam();
+        ASSERT_TRUE(AServiceManager_isDeclared(name.c_str())) << name;
+        ndk::SpAIBinder binder(AServiceManager_waitForService(name.c_str()));
+        ASSERT_NE(binder, nullptr);
+        storage_ = IStorage::fromBinder(binder);
+        ASSERT_NE(storage_, nullptr);
+    }
+
+    virtual void TearDown() override {
+        EXPECT_TRUE(ping(kRpcTime))
+                << "Service is not responsive; expect subsequent tests to fail.";
+    }
+
+    /**
+     * Ping the service and expect it to return after "timeout". Return true
+     * iff the service is responsive within "timeout".
+     */
+    template <typename R, typename P>
+    bool ping(std::chrono::duration<R, P> timeout) {
+        // Ensure the service is responsive after the test.
+        std::shared_ptr<IStorage> service = storage_;
+        auto ping_flag = std::make_shared<Flag>();
+        std::thread([service, ping_flag] {
+            EXPECT_EQ(STATUS_OK, AIBinder_ping(service->asBinder().get()));
+            ping_flag->OnFinish();
+        }).detach();
+        return ping_flag->Wait(timeout);
+    }
+
+    std::shared_ptr<IStorage> storage_;
+};
+
+/**
+ * Ensure garbage collection works on null callback.
+ */
+TEST_P(HealthStorageAidl, GcNullCallback) {
+    ASSERT_OK(storage_->garbageCollect(kDevGcTimeoutSec, nullptr));
+
+    // Hold test process because HAL can be single-threaded and doing GC.
+    ASSERT_TRUE(ping(kDevGcTimeout + kDevGcTolerance + kRpcTime))
+            << "Service must be available after "
+            << to_string(kDevGcTimeout + kDevGcTolerance + kRpcTime);
+}
+
+/**
+ * Ensure garbage collection works on non-null callback.
+ */
+TEST_P(HealthStorageAidl, GcNonNullCallback) {
+    std::shared_ptr<GcCallback> cb = ndk::SharedRefBase::make<GcCallback>();
+    ASSERT_OK(storage_->garbageCollect(kDevGcTimeoutSec, cb));
+    cb->WaitForResult(kDevGcTimeout + kDevGcTolerance + kRpcTime, Result::SUCCESS);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthStorageAidl);
+INSTANTIATE_TEST_SUITE_P(
+        HealthStorage, HealthStorageAidl,
+        testing::ValuesIn(::android::getAidlHalInstanceNames(IStorage::descriptor)),
+        ::android::PrintInstanceNameToString);
+
+}  // namespace aidl::android::hardware::health::storage
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml
new file mode 100644
index 0000000..f8a1c87
--- /dev/null
+++ b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs VtsHalHealthStorageTargetTest.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="VtsHalHealthStorageTargetTest->/data/local/tmp/VtsHalHealthStorageTargetTest" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="VtsHalHealthStorageTargetTest" />
+        <option name="native-test-timeout" value="3m" />
+    </test>
+</configuration>
diff --git a/health/storage/impl_common/Android.bp b/health/storage/impl_common/Android.bp
new file mode 100644
index 0000000..e1149c0
--- /dev/null
+++ b/health/storage/impl_common/Android.bp
@@ -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.
+ */
+
+// Common implementation between HIDL and AIDL HAL.
+cc_library_static {
+    name: "libhealth_storage_impl_common",
+    vendor: true,
+    srcs: [
+        "impl_common.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libhidlbase",
+        "liblog",
+        "android.hardware.health.storage@1.0",
+    ],
+
+    static_libs: [
+        "libfstab",
+    ],
+
+    export_shared_lib_headers: [
+        "android.hardware.health.storage@1.0",
+    ],
+}
diff --git a/health/storage/impl_common/impl_common.cpp b/health/storage/impl_common/impl_common.cpp
new file mode 100644
index 0000000..1a7a7f1
--- /dev/null
+++ b/health/storage/impl_common/impl_common.cpp
@@ -0,0 +1,154 @@
+/*
+ * 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-storage-impl/common.h>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <fstab/fstab.h>
+
+using ::android::base::ReadFileToString;
+using ::android::base::Timer;
+using ::android::base::Trim;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+using ::android::fs_mgr::Fstab;
+using ::android::fs_mgr::ReadDefaultFstab;
+using ::android::hardware::health::storage::V1_0::Result;
+
+namespace android::hardware::health::storage {
+
+static std::string GetGarbageCollectPath() {
+    Fstab fstab;
+    ReadDefaultFstab(&fstab);
+
+    for (const auto& entry : fstab) {
+        if (!entry.sysfs_path.empty()) {
+            return entry.sysfs_path + "/manual_gc";
+        }
+    }
+
+    return "";
+}
+
+static std::string GetWriteBoosterPath() {
+    Fstab fstab;
+    ReadDefaultFstab(&fstab);
+
+    for (const auto& entry : fstab) {
+        if (!entry.sysfs_path.empty()) {
+            return entry.sysfs_path + "/attributes/wb_avail_buf";
+        }
+    }
+
+    return "";
+}
+
+Result GarbageCollect(uint64_t timeout_seconds) {
+    std::string gc_path = GetGarbageCollectPath();
+
+    if (gc_path.empty()) {
+        LOG(WARNING) << "Cannot find Dev GC path";
+        return Result::UNKNOWN_ERROR;
+    }
+
+    Result result = Result::SUCCESS;
+    Timer timer;
+    LOG(INFO) << "Start Dev GC on " << gc_path;
+    while (1) {
+        std::string require_gc;
+        if (!ReadFileToString(gc_path, &require_gc)) {
+            PLOG(WARNING) << "Reading manual_gc failed in " << gc_path;
+            result = Result::IO_ERROR;
+            break;
+        }
+        require_gc = Trim(require_gc);
+
+        std::string wb_path = GetWriteBoosterPath();
+        // Let's flush WB till 100% available
+        std::string wb_avail = "0x0000000A";
+        if (!wb_path.empty() && !ReadFileToString(wb_path, &wb_avail)) {
+            PLOG(WARNING) << "Reading wb_avail_buf failed in " << wb_path;
+        }
+        wb_avail = Trim(wb_avail);
+
+        if (require_gc == "disabled") {
+            LOG(DEBUG) << "Disabled Dev GC";
+            break;
+        }
+        if ((require_gc == "" || require_gc == "off") && wb_avail == "0x0000000A") {
+            LOG(DEBUG) << "No more to do Dev GC";
+            break;
+        }
+        LOG(DEBUG) << "Trigger Dev GC on " << gc_path << " having " << require_gc << ", WB on "
+                   << wb_path << " having " << wb_avail;
+        if (!WriteStringToFile("1", gc_path)) {
+            PLOG(WARNING) << "Start Dev GC failed on " << gc_path;
+            result = Result::IO_ERROR;
+            break;
+        }
+        if (timer.duration() >= std::chrono::seconds(timeout_seconds)) {
+            LOG(WARNING) << "Dev GC timeout";
+            // Timeout is not treated as an error. Try next time.
+            break;
+        }
+        sleep(2);
+    }
+    LOG(INFO) << "Stop Dev GC on " << gc_path;
+    if (!WriteStringToFile("0", gc_path)) {
+        PLOG(WARNING) << "Stop Dev GC failed on " << gc_path;
+        result = Result::IO_ERROR;
+    }
+
+    return result;
+}
+
+void DebugDump(int fd) {
+    std::stringstream output;
+
+    std::string path = GetGarbageCollectPath();
+    if (path.empty()) {
+        output << "Cannot find Dev GC path";
+    } else {
+        std::string require_gc;
+
+        if (ReadFileToString(path, &require_gc)) {
+            output << path << ":" << require_gc << std::endl;
+        }
+
+        if (WriteStringToFile("0", path)) {
+            output << "stop success" << std::endl;
+        }
+    }
+    std::string wb_path = GetWriteBoosterPath();
+    if (wb_path.empty()) {
+        output << "Cannot find Dev WriteBooster path";
+    } else {
+        std::string wb_available;
+
+        if (ReadFileToString(wb_path, &wb_available)) {
+            output << wb_path << ":" << wb_available << std::endl;
+        }
+    }
+    if (!WriteStringToFd(output.str(), fd)) {
+        PLOG(WARNING) << "debug: cannot write to fd";
+    }
+
+    fsync(fd);
+}
+
+}  // namespace android::hardware::health::storage
diff --git a/health/storage/impl_common/include/health-storage-impl/common.h b/health/storage/impl_common/include/health-storage-impl/common.h
new file mode 100644
index 0000000..c84a6a9
--- /dev/null
+++ b/health/storage/impl_common/include/health-storage-impl/common.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 <android/hardware/health/storage/1.0/types.h>
+#include <string>
+
+namespace android::hardware::health::storage {
+
+// Run debug on fd
+void DebugDump(int fd);
+
+// Run garbage collection on GetGarbageCollectPath(). Blocks until garbage
+// collect finishes or |timeout_seconds| has reached.
+V1_0::Result GarbageCollect(uint64_t timeout_seconds);
+
+}  // namespace android::hardware::health::storage
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/health/storage/test_common/Android.bp
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to health/storage/test_common/Android.bp
index 127f98e..7c6bef4 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/health/storage/test_common/Android.bp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,15 +14,7 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
-
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
+cc_library_headers {
+    name: "libhealth_storage_test_common_headers",
+    export_include_dirs: ["include"],
 }
diff --git a/health/storage/test_common/include/health-storage-test/common.h b/health/storage/test_common/include/health-storage-test/common.h
new file mode 100644
index 0000000..dfda830
--- /dev/null
+++ b/health/storage/test_common/include/health-storage-test/common.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <string>
+
+namespace android::hardware::health::storage::test {
+
+// Dev GC timeout. This is the timeout used by vold.
+const uint64_t kDevGcTimeoutSec = 120;
+const std::chrono::seconds kDevGcTimeout{kDevGcTimeoutSec};
+// Dev GC timeout tolerance. The HAL may not immediately return after the
+// timeout, so include an acceptable tolerance.
+const std::chrono::seconds kDevGcTolerance{3};
+// Time accounted for RPC calls.
+const std::chrono::milliseconds kRpcTime{1000};
+
+template <typename R>
+std::string to_string(std::chrono::duration<R, std::milli> time) {
+    return std::to_string(time.count()) + "ms";
+}
+
+/** An atomic boolean flag that indicates whether a task has finished. */
+class Flag {
+  public:
+    void OnFinish() {
+        std::unique_lock<std::mutex> lock(mutex_);
+        OnFinishLocked(&lock);
+    }
+    template <typename R, typename P>
+    bool Wait(std::chrono::duration<R, P> duration) {
+        std::unique_lock<std::mutex> lock(mutex_);
+        return WaitLocked(&lock, duration);
+    }
+
+  protected:
+    /** Will unlock. */
+    void OnFinishLocked(std::unique_lock<std::mutex>* lock) {
+        finished_ = true;
+        lock->unlock();
+        cv_.notify_all();
+    }
+    template <typename R, typename P>
+    bool WaitLocked(std::unique_lock<std::mutex>* lock, std::chrono::duration<R, P> duration) {
+        cv_.wait_for(*lock, duration, [this] { return finished_; });
+        return finished_;
+    }
+
+    bool finished_{false};
+    std::mutex mutex_;
+    std::condition_variable cv_;
+};
+
+}  // namespace android::hardware::health::storage::test
diff --git a/health/utils/libhealth2impl/Health.cpp b/health/utils/libhealth2impl/Health.cpp
index f4684ae..035b36f 100644
--- a/health/utils/libhealth2impl/Health.cpp
+++ b/health/utils/libhealth2impl/Health.cpp
@@ -80,14 +80,14 @@
 
 Return<Result> Health::update() {
     Result result = Result::UNKNOWN;
-    getHealthInfo_2_1([&](auto res, const auto& /* health_info */) {
+    getHealthInfo_2_1([&](auto res, const auto& health_info) {
         result = res;
         if (res != Result::SUCCESS) {
             LOG(ERROR) << "Cannot call getHealthInfo_2_1: " << toString(res);
             return;
         }
 
-        battery_monitor_.logValues();
+        BatteryMonitor::logValues(health_info, *healthd_config_);
     });
     return result;
 }
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl
index 7e3002d..d8a8128 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl
index 447203f..2685525 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl
index e1296e0..f8d5a9e 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
index 88104d9..a097895 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -18,6 +33,9 @@
 package android.hardware.identity;
 @VintfStability
 interface IIdentityCredential {
+  /**
+   * @deprecated use deleteCredentalWithChallenge() instead.
+   */
   byte[] deleteCredential();
   byte[] createEphemeralKeyPair();
   void setReaderEphemeralPublicKey(in byte[] publicKey);
@@ -29,4 +47,7 @@
   android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
   void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
   void setVerificationToken(in android.hardware.keymaster.VerificationToken verificationToken);
+  byte[] deleteCredentialWithChallenge(in byte[] challenge);
+  byte[] proveOwnership(in byte[] challenge);
+  android.hardware.identity.IWritableIdentityCredential updateCredential();
 }
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl
index 5dafb76..c6fb3c8 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
index c5ac9d6..a713462 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
index 24ec26a..c9c2b9f 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
index af00f3b..aaf1e20 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl
index dfc1ad0..695fb3f 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
index 702334d..d23f88c 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -19,6 +19,7 @@
 import android.hardware.identity.Certificate;
 import android.hardware.identity.RequestNamespace;
 import android.hardware.identity.SecureAccessControlProfile;
+import android.hardware.identity.IWritableIdentityCredential;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.hardware.keymaster.VerificationToken;
 
@@ -40,7 +41,11 @@
      * After this method has been called, the persistent storage used for credentialData should
      * be deleted.
      *
-     * @return a COSE_Sign1 signature described above.
+     * This method was deprecated in API version 3 because there's no challenge so freshness
+     * can't be checked. Use deleteCredentalWithChallenge() instead.
+     *
+     * @return a COSE_Sign1 signature described above
+     * @deprecated use deleteCredentalWithChallenge() instead.
      */
     byte[] deleteCredential();
 
@@ -353,6 +358,18 @@
      *
      *  - subjectPublicKeyInfo: must contain attested public key.
      *
+     * As of API version 3, the certificate shall also have an X.509 extension at
+     * OID 1.3.6.1.4.1.11129.2.1.26 which shall contain an OCTET STRING with the
+     * bytes of the CBOR with the following CDDL:
+     *
+     *   ProofOfBinding = [
+     *     "ProofOfBinding",
+     *     bstr,              // Contains SHA-256(ProofOfProvisioning)
+     *   ]
+     *
+     * This CBOR enables an issuer to determine the exact state of the credential it
+     * returns issuer-signed data for.
+     *
      * @param out signingKeyBlob contains an AES-GCM-ENC(storageKey, R, signingKey, docType)
      *     where signingKey is an EC private key in uncompressed form. That is, the returned
      *     blob is an encrypted copy of the newly-generated private signing key.
@@ -381,4 +398,63 @@
     *   The verification token. This token is only valid if the timestamp field is non-zero.
     */
     void setVerificationToken(in VerificationToken verificationToken);
+
+    /**
+     * Delete a credential.
+     *
+     * This method returns a COSE_Sign1 data structure signed by CredentialKey
+     * with payload set to the ProofOfDeletion CBOR below:
+     *
+     *     ProofOfDeletion = [
+     *          "ProofOfDeletion",            ; tstr
+     *          tstr,                         ; DocType
+     *          bstr,                         ; Challenge
+     *          bool                          ; true if this is a test credential, should
+     *                                        ; always be false.
+     *     ]
+     *
+     * After this method has been called, the persistent storage used for credentialData should
+     * be deleted.
+     *
+     * This method was introduced in API version 3.
+     *
+     * @param challenge a challenge set by the issuer to ensure freshness. Maximum size is 32 bytes
+     *     and it may be empty. Fails with STATUS_INVALID_DATA if bigger than 32 bytes.
+     * @return a COSE_Sign1 signature described above.
+     */
+    byte[] deleteCredentialWithChallenge(in byte[] challenge);
+
+    /**
+     * Prove ownership of credential.
+     *
+     * This method returns a COSE_Sign1 data structure signed by CredentialKey with payload
+     * set to the ProofOfOwnership CBOR below.
+     *
+     *     ProofOfOwnership = [
+     *          "ProofOfOwnership",           ; tstr
+     *          tstr,                         ; DocType
+     *          bstr,                         ; Challenge
+     *          bool                          ; true if this is a test credential, should
+     *                                        ; always be false.
+     *     ]
+     *
+     * This method was introduced in API version 3.
+     *
+     * @param challenge a challenge set by the issuer to ensure freshness. Maximum size is 32 bytes
+     *     and it may be empty. Fails with STATUS_INVALID_DATA if bigger than 32 bytes.
+     * @return a COSE_Sign1 signature described above.
+     */
+    byte[] proveOwnership(in byte[] challenge);
+
+    /**
+     * Called to start updating the credential with new data items.
+     *
+     * If the getAttestationCertificate() method is called on the returned object
+     * it fails with the error STATUS_FAILED.
+     *
+     * This method was introduced in API version 3.
+     *
+     * @return an IWritableIdentityCredential
+     */
+    IWritableIdentityCredential updateCredential();
 }
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
index 33e25b1..638be79 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
@@ -104,6 +104,11 @@
  * All binder calls in the HAL may return a ServiceSpecificException with statuses from the
  * STATUS_* integers defined in this interface. Each method states which status can be returned
  * and under which circumstances.
+ *
+ * The API described here is API version 3 which corresponds to feature version 202101
+ * of the android.security.identity Framework API. An XML file declaring the feature
+ * android.hardware.identity_credential (or android.hardware.identity_credential.direct_access
+ * if implementing the Direct Access HAL) should be included declaring this feature version.
  */
 @VintfStability
 interface IIdentityCredentialStore {
@@ -230,6 +235,9 @@
      *     return argument of the same name in finishAddingEntries(), in
      *     IWritableIdentityCredential.
      *
+     *     Note that the format of credentialData may depend on the feature version.
+     *     Implementations must support credentialData created by an earlier feature version.
+     *
      * @return an IIdentityCredential interface that provides operations on the Credential.
      */
     IIdentityCredential getCredential(in CipherSuite cipherSuite, in byte[] credentialData);
diff --git a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
index c48cb66..5f878ee 100644
--- a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -263,7 +263,9 @@
      *
      *     where HBK is a unique hardware-bound key that has never existed outside of the secure
      *     environment (except it's all zeroes if testCredential is True) and CredentialKeys is
-     *     the CBOR-encoded structure (in CDDL notation):
+     *     the CBOR-encoded structure (in CDDL notation) given below.
+     *
+     *     In API versions 1 and 2 it was the following
      *
      *         CredentialKeys = [
      *              bstr,   ; storageKey, a 128-bit AES key
@@ -271,6 +273,17 @@
      *                      ; in uncompressed form
      *         ]
      *
+     *     In API version 3 or later it must be the following
+     *
+     *         CredentialKeys = [
+     *              bstr,   ; storageKey, a 128-bit AES key
+     *              bstr    ; credentialPrivKey, the private key for credentialKey
+     *                      ; in uncompressed form
+     *              bstr    ; SHA-256(ProofOfProvisioning)
+     *         ]
+     *
+     *     Additional elements may be added to the CredentialKeys array in future versions.
+     *
      * @param out proofOfProvisioningSignature proves to the IA that the credential was imported
      *     into the secure hardware without alteration or error.  When the final addEntry() call is
      *     made (when the number of provisioned entries equals the sum of the items in
@@ -321,4 +334,5 @@
      * @param expectedProofOfProvisioningSize the expected size of ProofOfProvisioning.
      */
     void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
+
 }
diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp
index 2eb0faa..9659f57 100644
--- a/identity/aidl/default/Android.bp
+++ b/identity/aidl/default/Android.bp
@@ -1,3 +1,68 @@
+cc_library_static {
+    name: "android.hardware.identity-libeic-hal-common",
+    vendor_available: true,
+    srcs: [
+        "common/IdentityCredential.cpp",
+        "common/IdentityCredentialStore.cpp",
+        "common/WritableIdentityCredential.cpp",
+    ],
+    export_include_dirs: [
+        "common",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Wno-deprecated-declarations",
+    ],
+    shared_libs: [
+        "liblog",
+        "libcrypto",
+        "libbinder_ndk",
+        "libkeymaster_messages",
+    ],
+    static_libs: [
+        "libbase",
+        "libcppbor",
+        "libutils",
+        "libsoft_attestation_cert",
+        "libkeymaster_portable",
+        "libsoft_attestation_cert",
+        "libpuresoftkeymasterdevice",
+        "android.hardware.identity-support-lib",
+        "android.hardware.identity-V3-ndk_platform",
+        "android.hardware.keymaster-V3-ndk_platform",
+    ],
+}
+
+cc_library_static {
+    name: "android.hardware.identity-libeic-library",
+    vendor_available: true,
+    srcs: [
+        "libeic/EicCbor.c",
+        "libeic/EicPresentation.c",
+        "libeic/EicProvisioning.c",
+        "EicOpsImpl.cc",
+    ],
+    export_include_dirs: [
+        "libeic",
+    ],
+    cflags: [
+        "-DEIC_COMPILATION",
+        "-Wall",
+        "-Wextra",
+        "-DEIC_DEBUG",
+        // Allow using C2x extensions such as omitting parameter names
+        "-Wno-c2x-extensions",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcrypto",
+    ],
+    static_libs: [
+        "android.hardware.identity-support-lib",
+    ],
+}
+
 cc_binary {
     name: "android.hardware.identity-service.example",
     relative_install_path: "hw",
@@ -7,23 +72,40 @@
     cflags: [
         "-Wall",
         "-Wextra",
+        "-g",
     ],
     shared_libs: [
-        "libbase",
-        "libbinder_ndk",
-        "libcppbor",
-        "libcrypto",
         "liblog",
+        "libcrypto",
+        "libbinder_ndk",
+        "libkeymaster_messages",
+    ],
+    static_libs: [
+        "libbase",
+        "libcppbor",
         "libutils",
+        "libsoft_attestation_cert",
+        "libkeymaster_portable",
+        "libsoft_attestation_cert",
+        "libpuresoftkeymasterdevice",
         "android.hardware.identity-support-lib",
-        "android.hardware.identity-ndk_platform",
-        "android.hardware.keymaster-ndk_platform",
+        "android.hardware.identity-V3-ndk_platform",
+        "android.hardware.keymaster-V3-ndk_platform",
+        "android.hardware.identity-libeic-hal-common",
+        "android.hardware.identity-libeic-library",
     ],
     srcs: [
-        "IdentityCredential.cpp",
-        "IdentityCredentialStore.cpp",
-        "WritableIdentityCredential.cpp",
-        "Util.cpp",
         "service.cpp",
+        "FakeSecureHardwareProxy.cpp",
     ],
+    required: [
+        "android.hardware.identity_credential.xml",
+    ],
+}
+
+prebuilt_etc {
+    name: "android.hardware.identity_credential.xml",
+    sub_dir: "permissions",
+    vendor: true,
+    src: "android.hardware.identity_credential.xml",
 }
diff --git a/identity/aidl/default/EicOpsImpl.cc b/identity/aidl/default/EicOpsImpl.cc
new file mode 100644
index 0000000..8ec4cc9
--- /dev/null
+++ b/identity/aidl/default/EicOpsImpl.cc
@@ -0,0 +1,515 @@
+/*
+ * Copyright 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 "EicOpsImpl"
+
+#include <optional>
+#include <tuple>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include "EicOps.h"
+
+using ::std::map;
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+void* eicMemSet(void* s, int c, size_t n) {
+    return memset(s, c, n);
+}
+
+void* eicMemCpy(void* dest, const void* src, size_t n) {
+    return memcpy(dest, src, n);
+}
+
+size_t eicStrLen(const char* s) {
+    return strlen(s);
+}
+
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n) {
+    return CRYPTO_memcmp(s1, s2, n);
+}
+
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize) {
+    HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+    HMAC_CTX_init(realCtx);
+    if (HMAC_Init_ex(realCtx, key, keySize, EVP_sha256(), nullptr /* impl */) != 1) {
+        LOG(ERROR) << "Error initializing HMAC_CTX";
+    }
+}
+
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len) {
+    HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+    if (HMAC_Update(realCtx, data, len) != 1) {
+        LOG(ERROR) << "Error updating HMAC_CTX";
+    }
+}
+
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+    HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+    unsigned int size = 0;
+    if (HMAC_Final(realCtx, digest, &size) != 1) {
+        LOG(ERROR) << "Error finalizing HMAC_CTX";
+    }
+    if (size != EIC_SHA256_DIGEST_SIZE) {
+        LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size;
+    }
+}
+
+void eicOpsSha256Init(EicSha256Ctx* ctx) {
+    SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+    SHA256_Init(realCtx);
+}
+
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len) {
+    SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+    SHA256_Update(realCtx, data, len);
+}
+
+void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+    SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+    SHA256_Final(digest, realCtx);
+}
+
+bool eicOpsRandom(uint8_t* buf, size_t numBytes) {
+    optional<vector<uint8_t>> bytes = ::android::hardware::identity::support::getRandom(numBytes);
+    if (!bytes.has_value()) {
+        return false;
+    }
+    memcpy(buf, bytes.value().data(), numBytes);
+    return true;
+}
+
+bool eicOpsEncryptAes128Gcm(
+        const uint8_t* key,    // Must be 16 bytes
+        const uint8_t* nonce,  // Must be 12 bytes
+        const uint8_t* data,   // May be NULL if size is 0
+        size_t dataSize,
+        const uint8_t* additionalAuthenticationData,  // May be NULL if size is 0
+        size_t additionalAuthenticationDataSize, uint8_t* encryptedData) {
+    vector<uint8_t> cppKey;
+    cppKey.resize(16);
+    memcpy(cppKey.data(), key, 16);
+
+    vector<uint8_t> cppData;
+    cppData.resize(dataSize);
+    if (dataSize > 0) {
+        memcpy(cppData.data(), data, dataSize);
+    }
+
+    vector<uint8_t> cppAAD;
+    cppAAD.resize(additionalAuthenticationDataSize);
+    if (additionalAuthenticationDataSize > 0) {
+        memcpy(cppAAD.data(), additionalAuthenticationData, additionalAuthenticationDataSize);
+    }
+
+    vector<uint8_t> cppNonce;
+    cppNonce.resize(12);
+    memcpy(cppNonce.data(), nonce, 12);
+
+    optional<vector<uint8_t>> cppEncryptedData =
+            android::hardware::identity::support::encryptAes128Gcm(cppKey, cppNonce, cppData,
+                                                                   cppAAD);
+    if (!cppEncryptedData.has_value()) {
+        return false;
+    }
+
+    memcpy(encryptedData, cppEncryptedData.value().data(), cppEncryptedData.value().size());
+    return true;
+}
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key,  // Must be 16 bytes
+                            const uint8_t* encryptedData, size_t encryptedDataSize,
+                            const uint8_t* additionalAuthenticationData,
+                            size_t additionalAuthenticationDataSize, uint8_t* data) {
+    vector<uint8_t> keyVec;
+    keyVec.resize(16);
+    memcpy(keyVec.data(), key, 16);
+
+    vector<uint8_t> encryptedDataVec;
+    encryptedDataVec.resize(encryptedDataSize);
+    if (encryptedDataSize > 0) {
+        memcpy(encryptedDataVec.data(), encryptedData, encryptedDataSize);
+    }
+
+    vector<uint8_t> aadVec;
+    aadVec.resize(additionalAuthenticationDataSize);
+    if (additionalAuthenticationDataSize > 0) {
+        memcpy(aadVec.data(), additionalAuthenticationData, additionalAuthenticationDataSize);
+    }
+
+    optional<vector<uint8_t>> decryptedDataVec =
+            android::hardware::identity::support::decryptAes128Gcm(keyVec, encryptedDataVec,
+                                                                   aadVec);
+    if (!decryptedDataVec.has_value()) {
+        eicDebug("Error decrypting data");
+        return false;
+    }
+    if (decryptedDataVec.value().size() != encryptedDataSize - 28) {
+        eicDebug("Decrypted data is size %zd, expected %zd", decryptedDataVec.value().size(),
+                 encryptedDataSize - 28);
+        return false;
+    }
+
+    if (decryptedDataVec.value().size() > 0) {
+        memcpy(data, decryptedDataVec.value().data(), decryptedDataVec.value().size());
+    }
+    return true;
+}
+
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                       uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]) {
+    optional<vector<uint8_t>> keyPair = android::hardware::identity::support::createEcKeyPair();
+    if (!keyPair) {
+        eicDebug("Error creating EC keypair");
+        return false;
+    }
+    optional<vector<uint8_t>> privKey =
+            android::hardware::identity::support::ecKeyPairGetPrivateKey(keyPair.value());
+    if (!privKey) {
+        eicDebug("Error extracting private key");
+        return false;
+    }
+    if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+        eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
+                 (size_t)EIC_P256_PRIV_KEY_SIZE);
+        return false;
+    }
+
+    optional<vector<uint8_t>> pubKey =
+            android::hardware::identity::support::ecKeyPairGetPublicKey(keyPair.value());
+    if (!pubKey) {
+        eicDebug("Error extracting public key");
+        return false;
+    }
+    // ecKeyPairGetPublicKey() returns 0x04 | x | y, we don't want the leading 0x04.
+    if (pubKey.value().size() != EIC_P256_PUB_KEY_SIZE + 1) {
+        eicDebug("Public key is %zd bytes long, expected %zd", pubKey.value().size(),
+                 (size_t)EIC_P256_PRIV_KEY_SIZE + 1);
+        return false;
+    }
+
+    memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+    memcpy(publicKey, pubKey.value().data() + 1, EIC_P256_PUB_KEY_SIZE);
+
+    return true;
+}
+
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge,
+                               size_t challengeSize, const uint8_t* applicationId,
+                               size_t applicationIdSize, bool testCredential, uint8_t* cert,
+                               size_t* certSize) {
+    vector<uint8_t> challengeVec(challengeSize);
+    memcpy(challengeVec.data(), challenge, challengeSize);
+
+    vector<uint8_t> applicationIdVec(applicationIdSize);
+    memcpy(applicationIdVec.data(), applicationId, applicationIdSize);
+
+    optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> ret =
+            android::hardware::identity::support::createEcKeyPairAndAttestation(
+                    challengeVec, applicationIdVec, testCredential);
+    if (!ret) {
+        eicDebug("Error generating CredentialKey and attestation");
+        return false;
+    }
+
+    // Extract certificate chain.
+    vector<uint8_t> flatChain =
+            android::hardware::identity::support::certificateChainJoin(ret.value().second);
+    if (*certSize < flatChain.size()) {
+        eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize,
+                 flatChain.size());
+        return false;
+    }
+    memcpy(cert, flatChain.data(), flatChain.size());
+    *certSize = flatChain.size();
+
+    // Extract private key.
+    optional<vector<uint8_t>> privKey =
+            android::hardware::identity::support::ecKeyPairGetPrivateKey(ret.value().first);
+    if (!privKey) {
+        eicDebug("Error extracting private key");
+        return false;
+    }
+    if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+        eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
+                 (size_t)EIC_P256_PRIV_KEY_SIZE);
+        return false;
+    }
+
+    memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+
+    return true;
+}
+
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+                     const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial,
+                     const char* issuerName, const char* subjectName, time_t validityNotBefore,
+                     time_t validityNotAfter, const uint8_t* proofOfBinding,
+                     size_t proofOfBindingSize, uint8_t* cert, size_t* certSize) {  // inout
+    vector<uint8_t> signingKeyVec(EIC_P256_PRIV_KEY_SIZE);
+    memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE);
+
+    vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+    pubKeyVec[0] = 0x04;
+    memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+    string serialDecimal = android::base::StringPrintf("%d", serial);
+
+    map<string, vector<uint8_t>> extensions;
+    if (proofOfBinding != nullptr) {
+        vector<uint8_t> proofOfBindingVec(proofOfBinding, proofOfBinding + proofOfBindingSize);
+        extensions["1.3.6.1.4.1.11129.2.1.26"] = proofOfBindingVec;
+    }
+
+    optional<vector<uint8_t>> certVec =
+            android::hardware::identity::support::ecPublicKeyGenerateCertificate(
+                    pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName,
+                    validityNotBefore, validityNotAfter, extensions);
+    if (!certVec) {
+        eicDebug("Error generating certificate");
+        return false;
+    }
+
+    if (*certSize < certVec.value().size()) {
+        eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize,
+                 certVec.value().size());
+        return false;
+    }
+    memcpy(cert, certVec.value().data(), certVec.value().size());
+    *certSize = certVec.value().size();
+
+    return true;
+}
+
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                 const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+                 uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+    vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+    memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+    vector<uint8_t> digestVec(EIC_SHA256_DIGEST_SIZE);
+    memcpy(digestVec.data(), digestOfData, EIC_SHA256_DIGEST_SIZE);
+
+    optional<vector<uint8_t>> derSignature =
+            android::hardware::identity::support::signEcDsaDigest(privKeyVec, digestVec);
+    if (!derSignature) {
+        eicDebug("Error signing data");
+        return false;
+    }
+
+    ECDSA_SIG* sig;
+    const unsigned char* p = derSignature.value().data();
+    sig = d2i_ECDSA_SIG(nullptr, &p, derSignature.value().size());
+    if (sig == nullptr) {
+        eicDebug("Error decoding DER signature");
+        return false;
+    }
+
+    if (BN_bn2binpad(sig->r, signature, 32) != 32) {
+        eicDebug("Error encoding r");
+        return false;
+    }
+    if (BN_bn2binpad(sig->s, signature + 32, 32) != 32) {
+        eicDebug("Error encoding s");
+        return false;
+    }
+
+    return true;
+}
+
+static const uint8_t hbkTest[16] = {0};
+static const uint8_t hbkReal[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential) {
+    if (testCredential) {
+        return hbkTest;
+    }
+    return hbkReal;
+}
+
+bool eicOpsValidateAuthToken(uint64_t /* challenge */, uint64_t /* secureUserId */,
+                             uint64_t /* authenticatorId */, int /* hardwareAuthenticatorType */,
+                             uint64_t /* timeStamp */, const uint8_t* /* mac */,
+                             size_t /* macSize */, uint64_t /* verificationTokenChallenge */,
+                             uint64_t /* verificationTokenTimeStamp */,
+                             int /* verificationTokenSecurityLevel */,
+                             const uint8_t* /* verificationTokenMac */,
+                             size_t /* verificationTokenMacSize */) {
+    // Here's where we would validate the passed-in |authToken| to assure ourselves
+    // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
+    //
+    // However this involves calculating the MAC which requires access to the to
+    // a pre-shared key which we don't have...
+    //
+    return true;
+}
+
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey,
+                            size_t* publicKeySize) {
+    vector<uint8_t> chain;
+    chain.resize(x509CertSize);
+    memcpy(chain.data(), x509Cert, x509CertSize);
+    optional<vector<uint8_t>> res =
+            android::hardware::identity::support::certificateChainGetTopMostKey(chain);
+    if (!res) {
+        return false;
+    }
+    if (res.value().size() > *publicKeySize) {
+        eicDebug("Public key size is %zd but buffer only has room for %zd bytes",
+                 res.value().size(), *publicKeySize);
+        return false;
+    }
+    *publicKeySize = res.value().size();
+    memcpy(publicKey, res.value().data(), *publicKeySize);
+    eicDebug("Extracted %zd bytes public key from %zd bytes X.509 cert", *publicKeySize,
+             x509CertSize);
+    return true;
+}
+
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+                                     const uint8_t* publicKey, size_t publicKeySize) {
+    vector<uint8_t> certVec(x509Cert, x509Cert + x509CertSize);
+    vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+    return android::hardware::identity::support::certificateSignedByPublicKey(certVec,
+                                                                              publicKeyVec);
+}
+
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+                                    const uint8_t* signature, size_t signatureSize,
+                                    const uint8_t* publicKey, size_t publicKeySize) {
+    vector<uint8_t> digestVec(digest, digest + digestSize);
+    vector<uint8_t> signatureVec(signature, signature + signatureSize);
+    vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+
+    vector<uint8_t> derSignature;
+    if (!android::hardware::identity::support::ecdsaSignatureCoseToDer(signatureVec,
+                                                                       derSignature)) {
+        LOG(ERROR) << "Error convering signature to DER format";
+        return false;
+    }
+
+    if (!android::hardware::identity::support::checkEcDsaSignature(digestVec, derSignature,
+                                                                   publicKeyVec)) {
+        LOG(ERROR) << "Signature check failed";
+        return false;
+    }
+    return true;
+}
+
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+                const uint8_t privateKey[EIC_P256_PUB_KEY_SIZE],
+                uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]) {
+    vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+    pubKeyVec[0] = 0x04;
+    memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+    vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+    memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+    optional<vector<uint8_t>> shared =
+            android::hardware::identity::support::ecdh(pubKeyVec, privKeyVec);
+    if (!shared) {
+        LOG(ERROR) << "Error performing ECDH";
+        return false;
+    }
+    if (shared.value().size() != EIC_P256_COORDINATE_SIZE) {
+        LOG(ERROR) << "Unexpected size of shared secret " << shared.value().size() << " expected "
+                   << EIC_P256_COORDINATE_SIZE << " bytes";
+        return false;
+    }
+    memcpy(sharedSecret, shared.value().data(), EIC_P256_COORDINATE_SIZE);
+    return true;
+}
+
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt,
+                size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output,
+                size_t outputSize) {
+    vector<uint8_t> sharedSecretVec(sharedSecretSize);
+    memcpy(sharedSecretVec.data(), sharedSecret, sharedSecretSize);
+    vector<uint8_t> saltVec(saltSize);
+    memcpy(saltVec.data(), salt, saltSize);
+    vector<uint8_t> infoVec(infoSize);
+    memcpy(infoVec.data(), info, infoSize);
+
+    optional<vector<uint8_t>> result = android::hardware::identity::support::hkdf(
+            sharedSecretVec, saltVec, infoVec, outputSize);
+    if (!result) {
+        LOG(ERROR) << "Error performing HKDF";
+        return false;
+    }
+    if (result.value().size() != outputSize) {
+        LOG(ERROR) << "Unexpected size of HKDF " << result.value().size() << " expected "
+                   << outputSize;
+        return false;
+    }
+    memcpy(output, result.value().data(), outputSize);
+    return true;
+}
+
+#ifdef EIC_DEBUG
+
+void eicPrint(const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    vfprintf(stderr, format, args);
+    va_end(args);
+}
+
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize) {
+    vector<uint8_t> dataVec(dataSize);
+    memcpy(dataVec.data(), data, dataSize);
+    android::hardware::identity::support::hexdump(message, dataVec);
+}
+
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize) {
+    vector<uint8_t> cborDataVec(cborDataSize);
+    memcpy(cborDataVec.data(), cborData, cborDataSize);
+    string str =
+            android::hardware::identity::support::cborPrettyPrint(cborDataVec, maxBStrSize, {});
+    fprintf(stderr, "%s\n", str.c_str());
+}
+
+#endif  // EIC_DEBUG
diff --git a/identity/aidl/default/EicOpsImpl.h b/identity/aidl/default/EicOpsImpl.h
new file mode 100644
index 0000000..333cdce
--- /dev/null
+++ b/identity/aidl/default/EicOpsImpl.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Add whatever includes are needed for definitions below.
+//
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Set the following defines to match the implementation of the supplied
+// eicOps*() operations. See EicOps.h for details.
+//
+
+#define EIC_SHA256_CONTEXT_SIZE sizeof(SHA256_CTX)
+
+#define EIC_HMAC_SHA256_CONTEXT_SIZE sizeof(HMAC_CTX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EMBEDDED_IC_H
diff --git a/identity/aidl/default/FakeSecureHardwareProxy.cpp b/identity/aidl/default/FakeSecureHardwareProxy.cpp
new file mode 100644
index 0000000..287ffb8
--- /dev/null
+++ b/identity/aidl/default/FakeSecureHardwareProxy.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright 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 "FakeSecureHardwareProxy"
+
+#include "FakeSecureHardwareProxy.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include <libeic.h>
+
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+namespace android::hardware::identity {
+
+// ----------------------------------------------------------------------
+
+FakeSecureHardwareProvisioningProxy::FakeSecureHardwareProvisioningProxy() {}
+
+FakeSecureHardwareProvisioningProxy::~FakeSecureHardwareProvisioningProxy() {}
+
+bool FakeSecureHardwareProvisioningProxy::shutdown() {
+    LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown";
+    return true;
+}
+
+bool FakeSecureHardwareProvisioningProxy::initialize(bool testCredential) {
+    LOG(INFO) << "FakeSecureHardwareProvisioningProxy created, sizeof(EicProvisioning): "
+              << sizeof(EicProvisioning);
+    return eicProvisioningInit(&ctx_, testCredential);
+}
+
+bool FakeSecureHardwareProvisioningProxy::initializeForUpdate(
+        bool testCredential, string docType, vector<uint8_t> encryptedCredentialKeys) {
+    return eicProvisioningInitForUpdate(&ctx_, testCredential, docType.c_str(),
+                                        encryptedCredentialKeys.data(),
+                                        encryptedCredentialKeys.size());
+}
+
+// Returns public key certificate.
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::createCredentialKey(
+        const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) {
+    uint8_t publicKeyCert[4096];
+    size_t publicKeyCertSize = sizeof publicKeyCert;
+    if (!eicProvisioningCreateCredentialKey(&ctx_, challenge.data(), challenge.size(),
+                                            applicationId.data(), applicationId.size(),
+                                            publicKeyCert, &publicKeyCertSize)) {
+        return {};
+    }
+    vector<uint8_t> pubKeyCert(publicKeyCertSize);
+    memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize);
+    return pubKeyCert;
+}
+
+bool FakeSecureHardwareProvisioningProxy::startPersonalization(
+        int accessControlProfileCount, vector<int> entryCounts, const string& docType,
+        size_t expectedProofOfProvisioningSize) {
+    if (!eicProvisioningStartPersonalization(&ctx_, accessControlProfileCount, entryCounts.data(),
+                                             entryCounts.size(), docType.c_str(),
+                                             expectedProofOfProvisioningSize)) {
+        return false;
+    }
+    return true;
+}
+
+// Returns MAC (28 bytes).
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addAccessControlProfile(
+        int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+        uint64_t timeoutMillis, uint64_t secureUserId) {
+    vector<uint8_t> mac(28);
+    if (!eicProvisioningAddAccessControlProfile(
+                &ctx_, id, readerCertificate.data(), readerCertificate.size(),
+                userAuthenticationRequired, timeoutMillis, secureUserId, mac.data())) {
+        return {};
+    }
+    return mac;
+}
+
+bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector<int>& accessControlProfileIds,
+                                                        const string& nameSpace, const string& name,
+                                                        uint64_t entrySize) {
+    uint8_t scratchSpace[512];
+    return eicProvisioningBeginAddEntry(&ctx_, accessControlProfileIds.data(),
+                                        accessControlProfileIds.size(), nameSpace.c_str(),
+                                        name.c_str(), entrySize, scratchSpace, sizeof scratchSpace);
+}
+
+// Returns encryptedContent.
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addEntryValue(
+        const vector<int>& accessControlProfileIds, const string& nameSpace, const string& name,
+        const vector<uint8_t>& content) {
+    vector<uint8_t> eicEncryptedContent;
+    uint8_t scratchSpace[512];
+    eicEncryptedContent.resize(content.size() + 28);
+    if (!eicProvisioningAddEntryValue(
+                &ctx_, accessControlProfileIds.data(), accessControlProfileIds.size(),
+                nameSpace.c_str(), name.c_str(), content.data(), content.size(),
+                eicEncryptedContent.data(), scratchSpace, sizeof scratchSpace)) {
+        return {};
+    }
+    return eicEncryptedContent;
+}
+
+// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishAddingEntries() {
+    vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+    if (!eicProvisioningFinishAddingEntries(&ctx_, signatureOfToBeSigned.data())) {
+        return {};
+    }
+    return signatureOfToBeSigned;
+}
+
+// Returns encryptedCredentialKeys.
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredentialData(
+        const string& docType) {
+    vector<uint8_t> encryptedCredentialKeys(116);
+    size_t size = encryptedCredentialKeys.size();
+    if (!eicProvisioningFinishGetCredentialData(&ctx_, docType.c_str(),
+                                                encryptedCredentialKeys.data(), &size)) {
+        return {};
+    }
+    encryptedCredentialKeys.resize(size);
+    return encryptedCredentialKeys;
+}
+
+// ----------------------------------------------------------------------
+
+FakeSecureHardwarePresentationProxy::FakeSecureHardwarePresentationProxy() {}
+
+FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() {}
+
+bool FakeSecureHardwarePresentationProxy::initialize(bool testCredential, string docType,
+                                                     vector<uint8_t> encryptedCredentialKeys) {
+    LOG(INFO) << "FakeSecureHardwarePresentationProxy created, sizeof(EicPresentation): "
+              << sizeof(EicPresentation);
+    return eicPresentationInit(&ctx_, testCredential, docType.c_str(),
+                               encryptedCredentialKeys.data(), encryptedCredentialKeys.size());
+}
+
+// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+optional<pair<vector<uint8_t>, vector<uint8_t>>>
+FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time_t now) {
+    uint8_t publicKeyCert[512];
+    size_t publicKeyCertSize = sizeof(publicKeyCert);
+    vector<uint8_t> signingKeyBlob(60);
+
+    if (!eicPresentationGenerateSigningKeyPair(&ctx_, docType.c_str(), now, publicKeyCert,
+                                               &publicKeyCertSize, signingKeyBlob.data())) {
+        return {};
+    }
+
+    vector<uint8_t> cert;
+    cert.resize(publicKeyCertSize);
+    memcpy(cert.data(), publicKeyCert, publicKeyCertSize);
+
+    return std::make_pair(cert, signingKeyBlob);
+}
+
+// Returns private key
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::createEphemeralKeyPair() {
+    vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE);
+    if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) {
+        return {};
+    }
+    return priv;
+}
+
+optional<uint64_t> FakeSecureHardwarePresentationProxy::createAuthChallenge() {
+    uint64_t challenge;
+    if (!eicPresentationCreateAuthChallenge(&ctx_, &challenge)) {
+        return {};
+    }
+    return challenge;
+}
+
+bool FakeSecureHardwarePresentationProxy::shutdown() {
+    LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown";
+    return true;
+}
+
+bool FakeSecureHardwarePresentationProxy::pushReaderCert(const vector<uint8_t>& certX509) {
+    return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size());
+}
+
+bool FakeSecureHardwarePresentationProxy::validateRequestMessage(
+        const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& requestMessage,
+        int coseSignAlg, const vector<uint8_t>& readerSignatureOfToBeSigned) {
+    return eicPresentationValidateRequestMessage(
+            &ctx_, sessionTranscript.data(), sessionTranscript.size(), requestMessage.data(),
+            requestMessage.size(), coseSignAlg, readerSignatureOfToBeSigned.data(),
+            readerSignatureOfToBeSigned.size());
+}
+
+bool FakeSecureHardwarePresentationProxy::setAuthToken(
+        uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+        int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac,
+        uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
+        int verificationTokenSecurityLevel, const vector<uint8_t>& verificationTokenMac) {
+    return eicPresentationSetAuthToken(&ctx_, challenge, secureUserId, authenticatorId,
+                                       hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(),
+                                       verificationTokenChallenge, verificationTokenTimestamp,
+                                       verificationTokenSecurityLevel, verificationTokenMac.data(),
+                                       verificationTokenMac.size());
+}
+
+optional<bool> FakeSecureHardwarePresentationProxy::validateAccessControlProfile(
+        int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+        int timeoutMillis, uint64_t secureUserId, const vector<uint8_t>& mac) {
+    bool accessGranted = false;
+    if (!eicPresentationValidateAccessControlProfile(&ctx_, id, readerCertificate.data(),
+                                                     readerCertificate.size(),
+                                                     userAuthenticationRequired, timeoutMillis,
+                                                     secureUserId, mac.data(), &accessGranted)) {
+        return {};
+    }
+    return accessGranted;
+}
+
+bool FakeSecureHardwarePresentationProxy::startRetrieveEntries() {
+    return eicPresentationStartRetrieveEntries(&ctx_);
+}
+
+bool FakeSecureHardwarePresentationProxy::calcMacKey(
+        const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey,
+        const vector<uint8_t>& signingKeyBlob, const string& docType,
+        unsigned int numNamespacesWithValues, size_t expectedProofOfProvisioningSize) {
+    if (signingKeyBlob.size() != 60) {
+        eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size());
+        return false;
+    }
+    return eicPresentationCalcMacKey(&ctx_, sessionTranscript.data(), sessionTranscript.size(),
+                                     readerEphemeralPublicKey.data(), signingKeyBlob.data(),
+                                     docType.c_str(), numNamespacesWithValues,
+                                     expectedProofOfProvisioningSize);
+}
+
+AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue(
+        const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+        int32_t entrySize, const vector<int32_t>& accessControlProfileIds) {
+    uint8_t scratchSpace[512];
+    EicAccessCheckResult result = eicPresentationStartRetrieveEntryValue(
+            &ctx_, nameSpace.c_str(), name.c_str(), newNamespaceNumEntries, entrySize,
+            accessControlProfileIds.data(), accessControlProfileIds.size(), scratchSpace,
+            sizeof scratchSpace);
+    switch (result) {
+        case EIC_ACCESS_CHECK_RESULT_OK:
+            return AccessCheckResult::kOk;
+        case EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES:
+            return AccessCheckResult::kNoAccessControlProfiles;
+        case EIC_ACCESS_CHECK_RESULT_FAILED:
+            return AccessCheckResult::kFailed;
+        case EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED:
+            return AccessCheckResult::kUserAuthenticationFailed;
+        case EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED:
+            return AccessCheckResult::kReaderAuthenticationFailed;
+    }
+    eicDebug("Unknown result with code %d, returning kFailed", (int)result);
+    return AccessCheckResult::kFailed;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::retrieveEntryValue(
+        const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+        const vector<int32_t>& accessControlProfileIds) {
+    uint8_t scratchSpace[512];
+    vector<uint8_t> content;
+    content.resize(encryptedContent.size() - 28);
+    if (!eicPresentationRetrieveEntryValue(
+                &ctx_, encryptedContent.data(), encryptedContent.size(), content.data(),
+                nameSpace.c_str(), name.c_str(), accessControlProfileIds.data(),
+                accessControlProfileIds.size(), scratchSpace, sizeof scratchSpace)) {
+        return {};
+    }
+    return content;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval() {
+    vector<uint8_t> mac(32);
+    size_t macSize = 32;
+    if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) {
+        return {};
+    }
+    mac.resize(macSize);
+    return mac;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::deleteCredential(
+        const string& docType, const vector<uint8_t>& challenge, bool includeChallenge,
+        size_t proofOfDeletionCborSize) {
+    vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+    if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), challenge.data(), challenge.size(),
+                                         includeChallenge, proofOfDeletionCborSize,
+                                         signatureOfToBeSigned.data())) {
+        return {};
+    }
+    return signatureOfToBeSigned;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::proveOwnership(
+        const string& docType, bool testCredential, const vector<uint8_t>& challenge,
+        size_t proofOfOwnershipCborSize) {
+    vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+    if (!eicPresentationProveOwnership(&ctx_, docType.c_str(), testCredential, challenge.data(),
+                                       challenge.size(), proofOfOwnershipCborSize,
+                                       signatureOfToBeSigned.data())) {
+        return {};
+    }
+    return signatureOfToBeSigned;
+}
+
+}  // namespace android::hardware::identity
diff --git a/identity/aidl/default/FakeSecureHardwareProxy.h b/identity/aidl/default/FakeSecureHardwareProxy.h
new file mode 100644
index 0000000..6852c1a
--- /dev/null
+++ b/identity/aidl/default/FakeSecureHardwareProxy.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+
+#include <libeic/libeic.h>
+
+#include "SecureHardwareProxy.h"
+
+namespace android::hardware::identity {
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningProxy {
+  public:
+    FakeSecureHardwareProvisioningProxy();
+    virtual ~FakeSecureHardwareProvisioningProxy();
+
+    bool initialize(bool testCredential) override;
+
+    bool initializeForUpdate(bool testCredential, string docType,
+                             vector<uint8_t> encryptedCredentialKeys) override;
+
+    bool shutdown() override;
+
+    // Returns public key certificate.
+    optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
+                                                  const vector<uint8_t>& applicationId) override;
+
+    bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
+                              const string& docType,
+                              size_t expectedProofOfProvisioningSize) override;
+
+    // Returns MAC (28 bytes).
+    optional<vector<uint8_t>> addAccessControlProfile(int id,
+                                                      const vector<uint8_t>& readerCertificate,
+                                                      bool userAuthenticationRequired,
+                                                      uint64_t timeoutMillis,
+                                                      uint64_t secureUserId) override;
+
+    bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace,
+                       const string& name, uint64_t entrySize) override;
+
+    // Returns encryptedContent.
+    optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds,
+                                            const string& nameSpace, const string& name,
+                                            const vector<uint8_t>& content) override;
+
+    // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+    optional<vector<uint8_t>> finishAddingEntries() override;
+
+    // Returns encryptedCredentialKeys (80 bytes).
+    optional<vector<uint8_t>> finishGetCredentialData(const string& docType) override;
+
+  protected:
+    EicProvisioning ctx_;
+};
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationProxy {
+  public:
+    FakeSecureHardwarePresentationProxy();
+    virtual ~FakeSecureHardwarePresentationProxy();
+
+    bool initialize(bool testCredential, string docType,
+                    vector<uint8_t> encryptedCredentialKeys) override;
+
+    // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+    optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
+                                                                            time_t now) override;
+
+    // Returns private key
+    optional<vector<uint8_t>> createEphemeralKeyPair() override;
+
+    optional<uint64_t> createAuthChallenge() override;
+
+    bool startRetrieveEntries() override;
+
+    bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+                      int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac,
+                      uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
+                      int verificationTokenSecurityLevel,
+                      const vector<uint8_t>& verificationTokenMac) override;
+
+    bool pushReaderCert(const vector<uint8_t>& certX509) override;
+
+    optional<bool> validateAccessControlProfile(int id, const vector<uint8_t>& readerCertificate,
+                                                bool userAuthenticationRequired, int timeoutMillis,
+                                                uint64_t secureUserId,
+                                                const vector<uint8_t>& mac) override;
+
+    bool validateRequestMessage(const vector<uint8_t>& sessionTranscript,
+                                const vector<uint8_t>& requestMessage, int coseSignAlg,
+                                const vector<uint8_t>& readerSignatureOfToBeSigned) override;
+
+    bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+                    const vector<uint8_t>& readerEphemeralPublicKey,
+                    const vector<uint8_t>& signingKeyBlob, const string& docType,
+                    unsigned int numNamespacesWithValues,
+                    size_t expectedProofOfProvisioningSize) override;
+
+    AccessCheckResult startRetrieveEntryValue(
+            const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+            int32_t entrySize, const vector<int32_t>& accessControlProfileIds) override;
+
+    optional<vector<uint8_t>> retrieveEntryValue(
+            const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+            const vector<int32_t>& accessControlProfileIds) override;
+
+    optional<vector<uint8_t>> finishRetrieval() override;
+
+    optional<vector<uint8_t>> deleteCredential(const string& docType,
+                                               const vector<uint8_t>& challenge,
+                                               bool includeChallenge,
+                                               size_t proofOfDeletionCborSize) override;
+
+    optional<vector<uint8_t>> proveOwnership(const string& docType, bool testCredential,
+                                             const vector<uint8_t>& challenge,
+                                             size_t proofOfOwnershipCborSize) override;
+
+    bool shutdown() override;
+
+  protected:
+    EicPresentation ctx_;
+};
+
+// Factory implementation.
+//
+class FakeSecureHardwareProxyFactory : public SecureHardwareProxyFactory {
+  public:
+    FakeSecureHardwareProxyFactory() {}
+    virtual ~FakeSecureHardwareProxyFactory() {}
+
+    sp<SecureHardwareProvisioningProxy> createProvisioningProxy() override {
+        return new FakeSecureHardwareProvisioningProxy();
+    }
+
+    sp<SecureHardwarePresentationProxy> createPresentationProxy() override {
+        return new FakeSecureHardwarePresentationProxy();
+    }
+};
+
+}  // namespace android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
diff --git a/identity/aidl/default/Util.cpp b/identity/aidl/default/Util.cpp
deleted file mode 100644
index 66b9f13..0000000
--- a/identity/aidl/default/Util.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2019, 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 "Util"
-
-#include "Util.h"
-
-#include <android/hardware/identity/support/IdentityCredentialSupport.h>
-
-#include <string.h>
-
-#include <android-base/logging.h>
-
-#include <cppbor.h>
-#include <cppbor_parse.h>
-
-namespace aidl::android::hardware::identity {
-
-using namespace ::android::hardware::identity;
-
-// This is not a very random HBK but that's OK because this is the SW
-// implementation where it can't be kept secret.
-vector<uint8_t> hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
-
-const vector<uint8_t>& getHardwareBoundKey() {
-    return hardwareBoundKey;
-}
-
-vector<uint8_t> secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) {
-    cppbor::Map map;
-    map.add("id", profile.id);
-
-    if (profile.readerCertificate.encodedCertificate.size() > 0) {
-        map.add("readerCertificate", cppbor::Bstr(profile.readerCertificate.encodedCertificate));
-    }
-
-    if (profile.userAuthenticationRequired) {
-        map.add("userAuthenticationRequired", profile.userAuthenticationRequired);
-        map.add("timeoutMillis", profile.timeoutMillis);
-        map.add("secureUserId", profile.secureUserId);
-    }
-
-    return map.encode();
-}
-
-optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
-        const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey) {
-    vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
-
-    optional<vector<uint8_t>> nonce = support::getRandom(12);
-    if (!nonce) {
-        return {};
-    }
-    optional<vector<uint8_t>> macO =
-            support::encryptAes128Gcm(storageKey, nonce.value(), {}, cborData);
-    if (!macO) {
-        return {};
-    }
-    return macO.value();
-}
-
-bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
-                                        const vector<uint8_t>& storageKey) {
-    vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
-
-    if (profile.mac.size() < support::kAesGcmIvSize) {
-        return false;
-    }
-    vector<uint8_t> nonce =
-            vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + support::kAesGcmIvSize);
-    optional<vector<uint8_t>> mac = support::encryptAes128Gcm(storageKey, nonce, {}, cborData);
-    if (!mac) {
-        return false;
-    }
-    if (mac.value() != profile.mac) {
-        return false;
-    }
-    return true;
-}
-
-vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
-                                          const vector<int32_t> accessControlProfileIds) {
-    cppbor::Map map;
-    map.add("Namespace", nameSpace);
-    map.add("Name", name);
-
-    cppbor::Array acpIds;
-    for (auto id : accessControlProfileIds) {
-        acpIds.add(id);
-    }
-    map.add("AccessControlProfileIds", std::move(acpIds));
-    return map.encode();
-}
-
-}  // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/Util.h b/identity/aidl/default/Util.h
deleted file mode 100644
index 9fccba2..0000000
--- a/identity/aidl/default/Util.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2019, 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_IDENTITY_UTIL_H
-#define ANDROID_HARDWARE_IDENTITY_UTIL_H
-
-#include <aidl/android/hardware/identity/BnIdentityCredential.h>
-#include <android/hardware/identity/support/IdentityCredentialSupport.h>
-
-#include <map>
-#include <optional>
-#include <set>
-#include <string>
-#include <vector>
-
-#include <cppbor/cppbor.h>
-
-namespace aidl::android::hardware::identity {
-
-using ::std::optional;
-using ::std::string;
-using ::std::vector;
-
-// Returns the hardware-bound AES-128 key.
-const vector<uint8_t>& getHardwareBoundKey();
-
-// Calculates the MAC for |profile| using |storageKey|.
-optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
-        const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey);
-
-// Checks authenticity of the MAC in |profile| using |storageKey|.
-bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
-                                        const vector<uint8_t>& storageKey);
-
-// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method.
-vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
-                                          const vector<int32_t> accessControlProfileIds);
-
-}  // namespace aidl::android::hardware::identity
-
-#endif  // ANDROID_HARDWARE_IDENTITY_UTIL_H
diff --git a/identity/aidl/default/android.hardware.identity_credential.xml b/identity/aidl/default/android.hardware.identity_credential.xml
new file mode 100644
index 0000000..5149792
--- /dev/null
+++ b/identity/aidl/default/android.hardware.identity_credential.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<permissions>
+  <feature name="android.hardware.identity_credential" version="202101" />
+</permissions>
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp
similarity index 61%
rename from identity/aidl/default/IdentityCredential.cpp
rename to identity/aidl/default/common/IdentityCredential.cpp
index dfcd4f5..9477997 100644
--- a/identity/aidl/default/IdentityCredential.cpp
+++ b/identity/aidl/default/common/IdentityCredential.cpp
@@ -18,7 +18,6 @@
 
 #include "IdentityCredential.h"
 #include "IdentityCredentialStore.h"
-#include "Util.h"
 
 #include <android/hardware/identity/support/IdentityCredentialSupport.h>
 
@@ -30,6 +29,9 @@
 #include <cppbor.h>
 #include <cppbor_parse.h>
 
+#include "FakeSecureHardwareProxy.h"
+#include "WritableIdentityCredential.h"
+
 namespace aidl::android::hardware::identity {
 
 using ::aidl::android::hardware::keymaster::Timestamp;
@@ -69,53 +71,52 @@
     docType_ = docTypeItem->value();
     testCredential_ = testCredentialItem->value();
 
-    vector<uint8_t> hardwareBoundKey;
-    if (testCredential_) {
-        hardwareBoundKey = support::getTestHardwareBoundKey();
-    } else {
-        hardwareBoundKey = getHardwareBoundKey();
+    encryptedCredentialKeys_ = encryptedCredentialKeysItem->value();
+    if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys_)) {
+        LOG(ERROR) << "hwProxy->initialize failed";
+        return false;
     }
 
-    const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
-    const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
-    optional<vector<uint8_t>> decryptedCredentialKeys =
-            support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
-    if (!decryptedCredentialKeys) {
-        LOG(ERROR) << "Error decrypting CredentialKeys";
-        return IIdentityCredentialStore::STATUS_INVALID_DATA;
-    }
-
-    auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
-    if (dckItem == nullptr) {
-        LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
-        return IIdentityCredentialStore::STATUS_INVALID_DATA;
-    }
-    const cppbor::Array* dckArrayItem = dckItem->asArray();
-    if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
-        LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
-        return IIdentityCredentialStore::STATUS_INVALID_DATA;
-    }
-    const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
-    const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
-    if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
-        LOG(ERROR) << "CredentialKeys unexpected item types";
-        return IIdentityCredentialStore::STATUS_INVALID_DATA;
-    }
-    storageKey_ = storageKeyItem->value();
-    credentialPrivKey_ = credentialPrivKeyItem->value();
-
     return IIdentityCredentialStore::STATUS_OK;
 }
 
 ndk::ScopedAStatus IdentityCredential::deleteCredential(
         vector<uint8_t>* outProofOfDeletionSignature) {
-    cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
-    vector<uint8_t> proofOfDeletion = array.encode();
+    return deleteCredentialCommon({}, false, outProofOfDeletionSignature);
+}
 
-    optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
-                                                                 proofOfDeletion,  // payload
-                                                                 {},               // additionalData
-                                                                 {});  // certificateChain
+ndk::ScopedAStatus IdentityCredential::deleteCredentialWithChallenge(
+        const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfDeletionSignature) {
+    return deleteCredentialCommon(challenge, true, outProofOfDeletionSignature);
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredentialCommon(
+        const vector<uint8_t>& challenge, bool includeChallenge,
+        vector<uint8_t>* outProofOfDeletionSignature) {
+    if (challenge.size() > 32) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
+    }
+
+    cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
+    if (includeChallenge) {
+        array = {"ProofOfDeletion", docType_, challenge, testCredential_};
+    }
+
+    vector<uint8_t> proofOfDeletionCbor = array.encode();
+    vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
+
+    optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->deleteCredential(
+            docType_, challenge, includeChallenge, proofOfDeletionCbor.size());
+    if (!signatureOfToBeSigned) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion"));
+    }
+
+    optional<vector<uint8_t>> signature =
+            support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+                                                proofOfDeletionCbor,  // data
+                                                {});                  // certificateChain
     if (!signature) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
@@ -125,23 +126,61 @@
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
-    optional<vector<uint8_t>> kp = support::createEcKeyPair();
-    if (!kp) {
+ndk::ScopedAStatus IdentityCredential::proveOwnership(
+        const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfOwnershipSignature) {
+    if (challenge.size() > 32) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
+                IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
+    }
+
+    cppbor::Array array;
+    array = {"ProofOfOwnership", docType_, challenge, testCredential_};
+    vector<uint8_t> proofOfOwnershipCbor = array.encode();
+    vector<uint8_t> podDigest = support::sha256(proofOfOwnershipCbor);
+
+    optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->proveOwnership(
+            docType_, testCredential_, challenge, proofOfOwnershipCbor.size());
+    if (!signatureOfToBeSigned) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfOwnership"));
+    }
+
+    optional<vector<uint8_t>> signature =
+            support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+                                                proofOfOwnershipCbor,  // data
+                                                {});                   // certificateChain
+    if (!signature) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+    }
+
+    *outProofOfOwnershipSignature = signature.value();
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
+    optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
+    if (!ephemeralPriv) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key"));
+    }
+    optional<vector<uint8_t>> keyPair = support::ecPrivateKeyToKeyPair(ephemeralPriv.value());
+    if (!keyPair) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair"));
     }
 
     // Stash public key of this key-pair for later check in startRetrieval().
-    optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
+    optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPair.value());
     if (!publicKey) {
+        LOG(ERROR) << "Error getting public part of ephemeral key pair";
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED,
                 "Error getting public part of ephemeral key pair"));
     }
     ephemeralPublicKey_ = publicKey.value();
 
-    *outKeyPair = kp.value();
+    *outKeyPair = keyPair.value();
     return ndk::ScopedAStatus::ok();
 }
 
@@ -152,109 +191,15 @@
 }
 
 ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
-    uint64_t challenge = 0;
-    while (challenge == 0) {
-        optional<vector<uint8_t>> bytes = support::getRandom(8);
-        if (!bytes) {
-            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                    IIdentityCredentialStore::STATUS_FAILED,
-                    "Error getting random data for challenge"));
-        }
-
-        challenge = 0;
-        for (size_t n = 0; n < bytes.value().size(); n++) {
-            challenge |= ((bytes.value())[n] << (n * 8));
-        }
+    optional<uint64_t> challenge = hwProxy_->createAuthChallenge();
+    if (!challenge) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge"));
     }
-
-    *outChallenge = challenge;
-    authChallenge_ = challenge;
+    *outChallenge = challenge.value();
     return ndk::ScopedAStatus::ok();
 }
 
-// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
-// ahead of time.
-bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
-                               const vector<uint8_t>& readerCertificateChain) {
-    optional<vector<uint8_t>> acpPubKey =
-            support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
-    if (!acpPubKey) {
-        LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
-        return false;
-    }
-
-    optional<vector<vector<uint8_t>>> certificatesInChain =
-            support::certificateChainSplit(readerCertificateChain);
-    if (!certificatesInChain) {
-        LOG(ERROR) << "Error splitting readerCertificateChain";
-        return false;
-    }
-    for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
-        optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
-        if (!certPubKey) {
-            LOG(ERROR)
-                    << "Error extracting public key from certificate in chain presented by reader";
-            return false;
-        }
-        if (acpPubKey.value() == certPubKey.value()) {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool checkUserAuthentication(const SecureAccessControlProfile& profile,
-                             const VerificationToken& verificationToken,
-                             const HardwareAuthToken& authToken, uint64_t authChallenge) {
-    if (profile.secureUserId != authToken.userId) {
-        LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
-                   << ") differs from userId in authToken (" << authToken.userId << ")";
-        return false;
-    }
-
-    if (verificationToken.timestamp.milliSeconds == 0) {
-        LOG(ERROR) << "VerificationToken is not set";
-        return false;
-    }
-    if (authToken.timestamp.milliSeconds == 0) {
-        LOG(ERROR) << "AuthToken is not set";
-        return false;
-    }
-
-    if (profile.timeoutMillis == 0) {
-        if (authToken.challenge == 0) {
-            LOG(ERROR) << "No challenge in authToken";
-            return false;
-        }
-
-        if (authToken.challenge != int64_t(authChallenge)) {
-            LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") "
-                       << "doesn't match the challenge we created (" << authChallenge << ")";
-            return false;
-        }
-        return true;
-    }
-
-    // Timeout-based user auth follows. The verification token conveys what the
-    // time is right now in the environment which generated the auth token. This
-    // is what makes it possible to do timeout-based checks.
-    //
-    const Timestamp now = verificationToken.timestamp;
-    if (authToken.timestamp.milliSeconds > now.milliSeconds) {
-        LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
-                   << ") is in the future (now: " << now.milliSeconds << ")";
-        return false;
-    }
-    if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
-        LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
-                   << profile.timeoutMillis << " = "
-                   << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
-                   << ") is in the past (now: " << now.milliSeconds << ")";
-        return false;
-    }
-    return true;
-}
-
 ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
         const vector<RequestNamespace>& requestNamespaces) {
     requestNamespaces_ = requestNamespaces;
@@ -284,6 +229,7 @@
     }
     if (numStartRetrievalCalls_ > 0) {
         if (sessionTranscript_ != sessionTranscript) {
+            LOG(ERROR) << "Session Transcript changed";
             return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                     IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
                     "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
@@ -291,6 +237,40 @@
     }
     sessionTranscript_ = sessionTranscript;
 
+    // This resets various state in the TA...
+    if (!hwProxy_->startRetrieveEntries()) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
+    }
+
+    optional<vector<uint8_t>> signatureOfToBeSigned;
+    if (readerSignature.size() > 0) {
+        signatureOfToBeSigned = support::coseSignGetSignature(readerSignature);
+        if (!signatureOfToBeSigned) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                    "Error extracting signatureOfToBeSigned from COSE_Sign1"));
+        }
+    }
+
+    // Feed the auth token to secure hardware.
+    if (!hwProxy_->setAuthToken(authToken.challenge, authToken.userId, authToken.authenticatorId,
+                                int(authToken.authenticatorType), authToken.timestamp.milliSeconds,
+                                authToken.mac, verificationToken_.challenge,
+                                verificationToken_.timestamp.milliSeconds,
+                                int(verificationToken_.securityLevel), verificationToken_.mac)) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token"));
+    }
+
+    // We'll be feeding ACPs interleaved with certificates from the reader
+    // certificate chain...
+    vector<SecureAccessControlProfile> remainingAcps = accessControlProfiles;
+
+    // ... and we'll use those ACPs to build up a 32-bit mask indicating which
+    // of the possible 32 ACPs grants access.
+    uint32_t accessControlProfileMask = 0;
+
     // If there is a signature, validate that it was made with the top-most key in the
     // certificate chain embedded in the COSE_Sign1 structure.
     optional<vector<uint8_t>> readerCertificateChain;
@@ -302,45 +282,113 @@
                     "Unable to get reader certificate chain from COSE_Sign1"));
         }
 
-        if (!support::certificateChainValidate(readerCertificateChain.value())) {
+        // First, feed all the reader certificates to the secure hardware. We start
+        // at the end..
+        optional<vector<vector<uint8_t>>> splitCerts =
+                support::certificateChainSplit(readerCertificateChain.value());
+        if (!splitCerts || splitCerts.value().size() == 0) {
             return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                     IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
-                    "Error validating reader certificate chain"));
+                    "Error splitting certificate chain from COSE_Sign1"));
+        }
+        for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) {
+            const vector<uint8_t>& x509Cert = splitCerts.value()[n];
+            if (!hwProxy_->pushReaderCert(x509Cert)) {
+                return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                        IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                        StringPrintf("Error validating reader certificate %zd", n).c_str()));
+            }
+
+            // If we have ACPs for that particular certificate, send them to the
+            // TA right now...
+            //
+            // Remember in this case certificate equality is done by comparing public keys,
+            // not bitwise comparison of the certificates.
+            //
+            optional<vector<uint8_t>> x509CertPubKey =
+                    support::certificateChainGetTopMostKey(x509Cert);
+            if (!x509CertPubKey) {
+                return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                        IIdentityCredentialStore::STATUS_FAILED,
+                        StringPrintf("Error getting public key from reader certificate %zd", n)
+                                .c_str()));
+            }
+            vector<SecureAccessControlProfile>::iterator it = remainingAcps.begin();
+            while (it != remainingAcps.end()) {
+                const SecureAccessControlProfile& profile = *it;
+                if (profile.readerCertificate.encodedCertificate.size() == 0) {
+                    ++it;
+                    continue;
+                }
+                optional<vector<uint8_t>> profilePubKey = support::certificateChainGetTopMostKey(
+                        profile.readerCertificate.encodedCertificate);
+                if (!profilePubKey) {
+                    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                            IIdentityCredentialStore::STATUS_FAILED,
+                            "Error getting public key from profile"));
+                }
+                if (profilePubKey.value() == x509CertPubKey.value()) {
+                    optional<bool> res = hwProxy_->validateAccessControlProfile(
+                            profile.id, profile.readerCertificate.encodedCertificate,
+                            profile.userAuthenticationRequired, profile.timeoutMillis,
+                            profile.secureUserId, profile.mac);
+                    if (!res) {
+                        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                                "Error validating access control profile"));
+                    }
+                    if (res.value()) {
+                        accessControlProfileMask |= (1 << profile.id);
+                    }
+                    it = remainingAcps.erase(it);
+                } else {
+                    ++it;
+                }
+            }
         }
 
-        optional<vector<uint8_t>> readerPublicKey =
-                support::certificateChainGetTopMostKey(readerCertificateChain.value());
-        if (!readerPublicKey) {
-            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                    IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
-                    "Unable to get public key from reader certificate chain"));
-        }
-
-        const vector<uint8_t>& itemsRequestBytes = itemsRequest;
-        vector<uint8_t> encodedReaderAuthentication =
-                cppbor::Array()
-                        .add("ReaderAuthentication")
-                        .add(std::move(sessionTranscriptItem))
-                        .add(cppbor::Semantic(24, itemsRequestBytes))
-                        .encode();
-        vector<uint8_t> encodedReaderAuthenticationBytes =
-                cppbor::Semantic(24, encodedReaderAuthentication).encode();
-        if (!support::coseCheckEcDsaSignature(readerSignature,
-                                              encodedReaderAuthenticationBytes,  // detached content
-                                              readerPublicKey.value())) {
-            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                    IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
-                    "readerSignature check failed"));
+        // ... then pass the request message and have the TA check it's signed by the
+        // key in last certificate we pushed.
+        if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 && readerSignature.size() > 0) {
+            optional<vector<uint8_t>> tbsSignature = support::coseSignGetSignature(readerSignature);
+            if (!tbsSignature) {
+                return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                        IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                        "Error extracting toBeSigned from COSE_Sign1"));
+            }
+            optional<int> coseSignAlg = support::coseSignGetAlg(readerSignature);
+            if (!coseSignAlg) {
+                return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                        IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                        "Error extracting signature algorithm from COSE_Sign1"));
+            }
+            if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest,
+                                                  coseSignAlg.value(), tbsSignature.value())) {
+                return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                        IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                        "readerMessage is not signed by top-level certificate"));
+            }
         }
     }
 
-    // Here's where we would validate the passed-in |authToken| to assure ourselves
-    // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
-    //
-    // However this involves calculating the MAC. However this requires access
-    // to the key needed to a pre-shared key which we don't have...
-    //
+    // Feed remaining access control profiles...
+    for (const SecureAccessControlProfile& profile : remainingAcps) {
+        optional<bool> res = hwProxy_->validateAccessControlProfile(
+                profile.id, profile.readerCertificate.encodedCertificate,
+                profile.userAuthenticationRequired, profile.timeoutMillis, profile.secureUserId,
+                profile.mac);
+        if (!res) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Error validating access control profile"));
+        }
+        if (res.value()) {
+            accessControlProfileMask |= (1 << profile.id);
+        }
+    }
 
+    // TODO: move this check to the TA
+#if 1
     // To prevent replay-attacks, we check that the public part of the ephemeral
     // key we previously created, is present in the DeviceEngagement part of
     // SessionTranscript as a COSE_Key, in uncompressed form.
@@ -364,6 +412,7 @@
                     "SessionTranscript (make sure leading zeroes are not used)"));
         }
     }
+#endif
 
     // itemsRequest: If non-empty, contains request data that may be signed by the
     // reader.  The content can be defined in the way appropriate for the
@@ -463,30 +512,6 @@
         }
     }
 
-    // Validate all the access control profiles in the requestData.
-    bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
-    for (const auto& profile : accessControlProfiles) {
-        if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
-            LOG(ERROR) << "Error checking MAC for profile";
-            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                    IIdentityCredentialStore::STATUS_INVALID_DATA,
-                    "Error checking MAC for profile"));
-        }
-        int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
-        if (profile.userAuthenticationRequired) {
-            if (!haveAuthToken ||
-                !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
-                accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
-            }
-        } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
-            if (!readerCertificateChain ||
-                !checkReaderAuthentication(profile, readerCertificateChain.value())) {
-                accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
-            }
-        }
-        profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
-    }
-
     deviceNameSpacesMap_ = cppbor::Map();
     currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
 
@@ -496,8 +521,36 @@
     itemsRequest_ = itemsRequest;
     signingKeyBlob_ = signingKeyBlob;
 
-    // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
-    expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
+    // calculate the size of DeviceNameSpaces. We need to know it ahead of time.
+    calcDeviceNameSpacesSize(accessControlProfileMask);
+
+    // Count the number of non-empty namespaces
+    size_t numNamespacesWithValues = 0;
+    for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) {
+        if (expectedNumEntriesPerNamespace_[n] > 0) {
+            numNamespacesWithValues += 1;
+        }
+    }
+
+    // Finally, pass info so the HMAC key can be derived and the TA can start
+    // creating the DeviceNameSpaces CBOR...
+    if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && signingKeyBlob.size() > 0) {
+        // We expect the reader ephemeral public key to be same size and curve
+        // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
+        // won't work. So its length should be 65 bytes and it should be
+        // starting with 0x04.
+        if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_FAILED,
+                    "Reader public key is not in expected format"));
+        }
+        vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end());
+        if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
+                                  numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
+        }
+    }
 
     numStartRetrievalCalls_ += 1;
     return ndk::ScopedAStatus::ok();
@@ -520,7 +573,7 @@
     return 1 + cborNumBytesForLength(value.size()) + value.size();
 }
 
-size_t IdentityCredential::calcDeviceNameSpacesSize() {
+void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileMask) {
     /*
      * This is how DeviceNameSpaces is defined:
      *
@@ -539,7 +592,7 @@
      * encoded.
      */
     size_t ret = 0;
-    size_t numNamespacesWithValues = 0;
+    vector<unsigned int> numEntriesPerNamespace;
     for (const RequestNamespace& rns : requestNamespaces_) {
         vector<RequestDataItem> itemsToInclude;
 
@@ -562,13 +615,9 @@
             //
             bool authorized = false;
             for (auto id : rdi.accessControlProfileIds) {
-                auto it = profileIdToAccessCheckResult_.find(id);
-                if (it != profileIdToAccessCheckResult_.end()) {
-                    int accessControlForProfile = it->second;
-                    if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
-                        authorized = true;
-                        break;
-                    }
+                if (accessControlProfileMask & (1 << id)) {
+                    authorized = true;
+                    break;
                 }
             }
             if (!authorized) {
@@ -578,7 +627,10 @@
             itemsToInclude.push_back(rdi);
         }
 
-        // If no entries are to be in the namespace, we don't include it...
+        numEntriesPerNamespace.push_back(itemsToInclude.size());
+
+        // If no entries are to be in the namespace, we don't include it in
+        // the CBOR...
         if (itemsToInclude.size() == 0) {
             continue;
         }
@@ -597,15 +649,14 @@
             // that.
             ret += item.size;
         }
-
-        numNamespacesWithValues++;
     }
 
-    // Now that we now the nunber of namespaces with values, we know how many
+    // Now that we know the number of namespaces with values, we know how many
     // bytes the DeviceNamespaces map in the beginning is going to take up.
-    ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
+    ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size());
 
-    return ret;
+    expectedDeviceNameSpacesSize_ = ret;
+    expectedNumEntriesPerNamespace_ = numEntriesPerNamespace;
 }
 
 ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
@@ -626,9 +677,11 @@
                 "No more name spaces left to go through"));
     }
 
+    bool newNamespace;
     if (currentNameSpace_ == "") {
         // First call.
         currentNameSpace_ = nameSpace;
+        newNamespace = true;
     }
 
     if (nameSpace == currentNameSpace_) {
@@ -655,6 +708,7 @@
 
         requestCountsRemaining_.erase(requestCountsRemaining_.begin());
         currentNameSpace_ = nameSpace;
+        newNamespace = true;
     }
 
     // It's permissible to have an empty itemsRequest... but if non-empty you can
@@ -674,35 +728,52 @@
         }
     }
 
-    // Enforce access control.
-    //
-    // Access is granted if at least one of the profiles grants access.
-    //
-    // If an item is configured without any profiles, access is denied.
-    //
-    int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
-    for (auto id : accessControlProfileIds) {
-        auto search = profileIdToAccessCheckResult_.find(id);
-        if (search == profileIdToAccessCheckResult_.end()) {
+    unsigned int newNamespaceNumEntries = 0;
+    if (newNamespace) {
+        if (expectedNumEntriesPerNamespace_.size() == 0) {
             return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                     IIdentityCredentialStore::STATUS_INVALID_DATA,
-                    "Requested entry with unvalidated profile id"));
+                    "No more populated name spaces left to go through"));
         }
-        int accessControlForProfile = search->second;
-        if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
-            accessControl = IIdentityCredentialStore::STATUS_OK;
-            break;
-        }
-        accessControl = accessControlForProfile;
-    }
-    if (accessControl != IIdentityCredentialStore::STATUS_OK) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                int(accessControl), "Access control check failed"));
+        newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0];
+        expectedNumEntriesPerNamespace_.erase(expectedNumEntriesPerNamespace_.begin());
     }
 
-    entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+    // Access control is enforced in the secure hardware.
+    //
+    // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO:
+    // consolidate).
+    //
+    AccessCheckResult res = hwProxy_->startRetrieveEntryValue(
+            nameSpace, name, newNamespaceNumEntries, entrySize, accessControlProfileIds);
+    switch (res) {
+        case AccessCheckResult::kOk:
+            /* Do nothing. */
+            break;
+        case AccessCheckResult::kFailed:
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_FAILED,
+                    "Access control check failed (failed)"));
+            break;
+        case AccessCheckResult::kNoAccessControlProfiles:
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES,
+                    "Access control check failed (no access control profiles)"));
+            break;
+        case AccessCheckResult::kUserAuthenticationFailed:
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED,
+                    "Access control check failed (user auth)"));
+            break;
+        case AccessCheckResult::kReaderAuthenticationFailed:
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED,
+                    "Access control check failed (reader auth)"));
+            break;
+    }
 
     currentName_ = name;
+    currentAccessControlProfileIds_ = accessControlProfileIds;
     entryRemainingBytes_ = entrySize;
     entryValue_.resize(0);
 
@@ -711,8 +782,8 @@
 
 ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
                                                           vector<uint8_t>* outContent) {
-    optional<vector<uint8_t>> content =
-            support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
+    optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue(
+            encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_);
     if (!content) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
@@ -777,28 +848,14 @@
     optional<vector<uint8_t>> mac;
     if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
         readerPublicKey_.size() > 0) {
-        vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
-        optional<vector<uint8_t>> signingKey =
-                support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
-        if (!signingKey) {
+        optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
+        if (!digestToBeMaced || digestToBeMaced.value().size() != 32) {
             return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                     IIdentityCredentialStore::STATUS_INVALID_DATA,
-                    "Error decrypting signingKeyBlob"));
+                    "Error generating digestToBeMaced"));
         }
-
-        vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
-        optional<vector<uint8_t>> eMacKey =
-                support::calcEMacKey(signingKey.value(), readerPublicKey_, sessionTranscriptBytes);
-        if (!eMacKey) {
-            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                    IIdentityCredentialStore::STATUS_FAILED, "Error calculating EMacKey"));
-        }
-        mac = support::calcMac(sessionTranscript_, docType_, encodedDeviceNameSpaces,
-                               eMacKey.value());
-        if (!mac) {
-            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                    IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
-        }
+        // Now construct COSE_Mac0 from the returned MAC...
+        mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
     }
 
     *outMac = mac.value_or(vector<uint8_t>({}));
@@ -808,56 +865,33 @@
 
 ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
         vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
-    string serialDecimal = "1";
-    string issuer = "Android Identity Credential Key";
-    string subject = "Android Identity Credential Authentication Key";
-    time_t validityNotBefore = time(nullptr);
-    time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
-
-    optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
-    if (!signingKeyPKCS8) {
+    time_t now = time(NULL);
+    optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
+            hwProxy_->generateSigningKeyPair(docType_, now);
+    if (!pair) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
     }
 
-    optional<vector<uint8_t>> signingPublicKey =
-            support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
-    if (!signingPublicKey) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED,
-                "Error getting public part of signingKey"));
-    }
-
-    optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
-    if (!signingKey) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED,
-                "Error getting private part of signingKey"));
-    }
-
-    optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
-            signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
-            validityNotBefore, validityNotAfter);
-    if (!certificate) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
-    }
-
-    optional<vector<uint8_t>> nonce = support::getRandom(12);
-    if (!nonce) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
-    }
-    vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
-    optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
-            storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
-    if (!encryptedSigningKey) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
-    }
-    *outSigningKeyBlob = encryptedSigningKey.value();
     *outSigningKeyCertificate = Certificate();
-    outSigningKeyCertificate->encodedCertificate = certificate.value();
+    outSigningKeyCertificate->encodedCertificate = pair->first;
+
+    *outSigningKeyBlob = pair->second;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::updateCredential(
+        shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+    sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy();
+    shared_ptr<WritableIdentityCredential> wc =
+            ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType_,
+                                                                 testCredential_);
+    if (!wc->initializeForUpdate(encryptedCredentialKeys_)) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error initializing WritableIdentityCredential for update"));
+    }
+    *outWritableCredential = wc;
     return ndk::ScopedAStatus::ok();
 }
 
diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/common/IdentityCredential.h
similarity index 75%
rename from identity/aidl/default/IdentityCredential.h
rename to identity/aidl/default/common/IdentityCredential.h
index a8a6409..9913b86 100644
--- a/identity/aidl/default/IdentityCredential.h
+++ b/identity/aidl/default/common/IdentityCredential.h
@@ -29,10 +29,15 @@
 
 #include <cppbor/cppbor.h>
 
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
 namespace aidl::android::hardware::identity {
 
 using ::aidl::android::hardware::keymaster::HardwareAuthToken;
 using ::aidl::android::hardware::keymaster::VerificationToken;
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwarePresentationProxy;
 using ::std::map;
 using ::std::set;
 using ::std::string;
@@ -40,10 +45,13 @@
 
 class IdentityCredential : public BnIdentityCredential {
   public:
-    IdentityCredential(const vector<uint8_t>& credentialData)
-        : credentialData_(credentialData),
+    IdentityCredential(sp<SecureHardwareProxyFactory> hwProxyFactory,
+                       sp<SecureHardwarePresentationProxy> hwProxy,
+                       const vector<uint8_t>& credentialData)
+        : hwProxyFactory_(hwProxyFactory),
+          hwProxy_(hwProxy),
+          credentialData_(credentialData),
           numStartRetrievalCalls_(0),
-          authChallenge_(0),
           expectedDeviceNameSpacesSize_(0) {}
 
     // Parses and decrypts credentialData_, return a status code from
@@ -52,6 +60,11 @@
 
     // Methods from IIdentityCredential follow.
     ndk::ScopedAStatus deleteCredential(vector<uint8_t>* outProofOfDeletionSignature) override;
+    ndk::ScopedAStatus deleteCredentialWithChallenge(
+            const vector<uint8_t>& challenge,
+            vector<uint8_t>* outProofOfDeletionSignature) override;
+    ndk::ScopedAStatus proveOwnership(const vector<uint8_t>& challenge,
+                                      vector<uint8_t>* outProofOfOwnershipSignature) override;
     ndk::ScopedAStatus createEphemeralKeyPair(vector<uint8_t>* outKeyPair) override;
     ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override;
     ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
@@ -73,16 +86,24 @@
     ndk::ScopedAStatus generateSigningKeyPair(vector<uint8_t>* outSigningKeyBlob,
                                               Certificate* outSigningKeyCertificate) override;
 
+    ndk::ScopedAStatus updateCredential(
+            shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
+
   private:
+    ndk::ScopedAStatus deleteCredentialCommon(const vector<uint8_t>& challenge,
+                                              bool includeChallenge,
+                                              vector<uint8_t>* outProofOfDeletionSignature);
+
     // Set by constructor
+    sp<SecureHardwareProxyFactory> hwProxyFactory_;
+    sp<SecureHardwarePresentationProxy> hwProxy_;
     vector<uint8_t> credentialData_;
     int numStartRetrievalCalls_;
 
     // Set by initialize()
     string docType_;
     bool testCredential_;
-    vector<uint8_t> storageKey_;
-    vector<uint8_t> credentialPrivKey_;
+    vector<uint8_t> encryptedCredentialKeys_;
 
     // Set by createEphemeralKeyPair()
     vector<uint8_t> ephemeralPublicKey_;
@@ -90,9 +111,6 @@
     // Set by setReaderEphemeralPublicKey()
     vector<uint8_t> readerPublicKey_;
 
-    // Set by createAuthChallenge()
-    uint64_t authChallenge_;
-
     // Set by setRequestedNamespaces()
     vector<RequestNamespace> requestNamespaces_;
 
@@ -100,7 +118,6 @@
     VerificationToken verificationToken_;
 
     // Set at startRetrieval() time.
-    map<int32_t, int> profileIdToAccessCheckResult_;
     vector<uint8_t> signingKeyBlob_;
     vector<uint8_t> sessionTranscript_;
     vector<uint8_t> itemsRequest_;
@@ -111,15 +128,16 @@
 
     // Calculated at startRetrieval() time.
     size_t expectedDeviceNameSpacesSize_;
+    vector<unsigned int> expectedNumEntriesPerNamespace_;
 
     // Set at startRetrieveEntryValue() time.
     string currentNameSpace_;
     string currentName_;
+    vector<int32_t> currentAccessControlProfileIds_;
     size_t entryRemainingBytes_;
     vector<uint8_t> entryValue_;
-    vector<uint8_t> entryAdditionalData_;
 
-    size_t calcDeviceNameSpacesSize();
+    void calcDeviceNameSpacesSize(uint32_t accessControlProfileMask);
 };
 
 }  // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/IdentityCredentialStore.cpp b/identity/aidl/default/common/IdentityCredentialStore.cpp
similarity index 89%
rename from identity/aidl/default/IdentityCredentialStore.cpp
rename to identity/aidl/default/common/IdentityCredentialStore.cpp
index 30dc6f3..e6b5466 100644
--- a/identity/aidl/default/IdentityCredentialStore.cpp
+++ b/identity/aidl/default/common/IdentityCredentialStore.cpp
@@ -39,8 +39,9 @@
 ndk::ScopedAStatus IdentityCredentialStore::createCredential(
         const string& docType, bool testCredential,
         shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+    sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy();
     shared_ptr<WritableIdentityCredential> wc =
-            ndk::SharedRefBase::make<WritableIdentityCredential>(docType, testCredential);
+            ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType, testCredential);
     if (!wc->initialize()) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED,
@@ -60,8 +61,9 @@
                 "Unsupported cipher suite"));
     }
 
+    sp<SecureHardwarePresentationProxy> hwProxy = hwProxyFactory_->createPresentationProxy();
     shared_ptr<IdentityCredential> credential =
-            ndk::SharedRefBase::make<IdentityCredential>(credentialData);
+            ndk::SharedRefBase::make<IdentityCredential>(hwProxyFactory_, hwProxy, credentialData);
     auto ret = credential->initialize();
     if (ret != IIdentityCredentialStore::STATUS_OK) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
diff --git a/identity/aidl/default/IdentityCredentialStore.h b/identity/aidl/default/common/IdentityCredentialStore.h
similarity index 85%
rename from identity/aidl/default/IdentityCredentialStore.h
rename to identity/aidl/default/common/IdentityCredentialStore.h
index 4f3a421..d35e632 100644
--- a/identity/aidl/default/IdentityCredentialStore.h
+++ b/identity/aidl/default/common/IdentityCredentialStore.h
@@ -19,15 +19,20 @@
 
 #include <aidl/android/hardware/identity/BnIdentityCredentialStore.h>
 
+#include "SecureHardwareProxy.h"
+
 namespace aidl::android::hardware::identity {
 
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
 using ::std::shared_ptr;
 using ::std::string;
 using ::std::vector;
 
 class IdentityCredentialStore : public BnIdentityCredentialStore {
   public:
-    IdentityCredentialStore() {}
+    IdentityCredentialStore(sp<SecureHardwareProxyFactory> hwProxyFactory)
+        : hwProxyFactory_(hwProxyFactory) {}
 
     // The GCM chunk size used by this implementation is 64 KiB.
     static constexpr size_t kGcmChunkSize = 64 * 1024;
@@ -41,6 +46,9 @@
 
     ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector<uint8_t>& credentialData,
                                      shared_ptr<IIdentityCredential>* outCredential) override;
+
+  private:
+    sp<SecureHardwareProxyFactory> hwProxyFactory_;
 };
 
 }  // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/common/SecureHardwareProxy.h b/identity/aidl/default/common/SecureHardwareProxy.h
new file mode 100644
index 0000000..a1ed1ef
--- /dev/null
+++ b/identity/aidl/default/common/SecureHardwareProxy.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+
+#include <utils/RefBase.h>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::identity {
+
+using ::android::RefBase;
+using ::std::optional;
+using ::std::pair;
+using ::std::string;
+using ::std::vector;
+
+// These classes are used to communicate with Secure Hardware. They mimic the
+// API in libEmbeddedIC 1:1 (except for using C++ types) as each call is intended
+// to be forwarded to the Secure Hardware.
+//
+// Instances are instantiated when a provisioning or presentation session
+// starts. When the session is complete, the shutdown() method is called.
+//
+
+// Forward declare.
+//
+class SecureHardwareProvisioningProxy;
+class SecureHardwarePresentationProxy;
+
+// This is a class used to create proxies.
+//
+class SecureHardwareProxyFactory : public RefBase {
+  public:
+    SecureHardwareProxyFactory() {}
+    virtual ~SecureHardwareProxyFactory() {}
+
+    virtual sp<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0;
+    virtual sp<SecureHardwarePresentationProxy> createPresentationProxy() = 0;
+};
+
+// The proxy used for provisioning.
+//
+class SecureHardwareProvisioningProxy : public RefBase {
+  public:
+    SecureHardwareProvisioningProxy() {}
+    virtual ~SecureHardwareProvisioningProxy() {}
+
+    virtual bool initialize(bool testCredential) = 0;
+
+    virtual bool initializeForUpdate(bool testCredential, string docType,
+                                     vector<uint8_t> encryptedCredentialKeys) = 0;
+
+    // Returns public key certificate chain with attestation.
+    //
+    // This must return an entire certificate chain and its implementation must
+    // be coordinated with the implementation of eicOpsCreateCredentialKey() on
+    // the TA side (which may return just a single certificate or the entire
+    // chain).
+    virtual optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
+                                                          const vector<uint8_t>& applicationId) = 0;
+
+    virtual bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
+                                      const string& docType,
+                                      size_t expectedProofOfProvisioningSize) = 0;
+
+    // Returns MAC (28 bytes).
+    virtual optional<vector<uint8_t>> addAccessControlProfile(
+            int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+            uint64_t timeoutMillis, uint64_t secureUserId) = 0;
+
+    virtual bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace,
+                               const string& name, uint64_t entrySize) = 0;
+
+    // Returns encryptedContent.
+    virtual optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds,
+                                                    const string& nameSpace, const string& name,
+                                                    const vector<uint8_t>& content) = 0;
+
+    // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+    virtual optional<vector<uint8_t>> finishAddingEntries() = 0;
+
+    // Returns encryptedCredentialKeys (80 bytes).
+    virtual optional<vector<uint8_t>> finishGetCredentialData(const string& docType) = 0;
+
+    virtual bool shutdown() = 0;
+};
+
+enum AccessCheckResult {
+    kOk,
+    kFailed,
+    kNoAccessControlProfiles,
+    kUserAuthenticationFailed,
+    kReaderAuthenticationFailed,
+};
+
+// The proxy used for presentation.
+//
+class SecureHardwarePresentationProxy : public RefBase {
+  public:
+    SecureHardwarePresentationProxy() {}
+    virtual ~SecureHardwarePresentationProxy() {}
+
+    virtual bool initialize(bool testCredential, string docType,
+                            vector<uint8_t> encryptedCredentialKeys) = 0;
+
+    // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+    virtual optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
+                                                                                    time_t now) = 0;
+
+    // Returns private key
+    virtual optional<vector<uint8_t>> createEphemeralKeyPair() = 0;
+
+    virtual optional<uint64_t> createAuthChallenge() = 0;
+
+    virtual bool startRetrieveEntries() = 0;
+
+    virtual bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+                              int hardwareAuthenticatorType, uint64_t timeStamp,
+                              const vector<uint8_t>& mac, uint64_t verificationTokenChallenge,
+                              uint64_t verificationTokenTimestamp,
+                              int verificationTokenSecurityLevel,
+                              const vector<uint8_t>& verificationTokenMac) = 0;
+
+    virtual bool pushReaderCert(const vector<uint8_t>& certX509) = 0;
+
+    virtual optional<bool> validateAccessControlProfile(int id,
+                                                        const vector<uint8_t>& readerCertificate,
+                                                        bool userAuthenticationRequired,
+                                                        int timeoutMillis, uint64_t secureUserId,
+                                                        const vector<uint8_t>& mac) = 0;
+
+    virtual bool validateRequestMessage(const vector<uint8_t>& sessionTranscript,
+                                        const vector<uint8_t>& requestMessage, int coseSignAlg,
+                                        const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
+
+    virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+                            const vector<uint8_t>& readerEphemeralPublicKey,
+                            const vector<uint8_t>& signingKeyBlob, const string& docType,
+                            unsigned int numNamespacesWithValues,
+                            size_t expectedProofOfProvisioningSize) = 0;
+
+    virtual AccessCheckResult startRetrieveEntryValue(
+            const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+            int32_t entrySize, const vector<int32_t>& accessControlProfileIds) = 0;
+
+    virtual optional<vector<uint8_t>> retrieveEntryValue(
+            const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+            const vector<int32_t>& accessControlProfileIds) = 0;
+
+    virtual optional<vector<uint8_t>> finishRetrieval();
+
+    virtual optional<vector<uint8_t>> deleteCredential(const string& docType,
+                                                       const vector<uint8_t>& challenge,
+                                                       bool includeChallenge,
+                                                       size_t proofOfDeletionCborSize) = 0;
+
+    virtual optional<vector<uint8_t>> proveOwnership(const string& docType, bool testCredential,
+                                                     const vector<uint8_t>& challenge,
+                                                     size_t proofOfOwnershipCborSize) = 0;
+
+    virtual bool shutdown() = 0;
+};
+
+}  // namespace android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/common/WritableIdentityCredential.cpp
similarity index 70%
rename from identity/aidl/default/WritableIdentityCredential.cpp
rename to identity/aidl/default/common/WritableIdentityCredential.cpp
index 141b4de..2d897c7 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/common/WritableIdentityCredential.cpp
@@ -17,7 +17,6 @@
 #define LOG_TAG "WritableIdentityCredential"
 
 #include "WritableIdentityCredential.h"
-#include "IdentityCredentialStore.h"
 
 #include <android/hardware/identity/support/IdentityCredentialSupport.h>
 
@@ -30,8 +29,8 @@
 #include <utility>
 
 #include "IdentityCredentialStore.h"
-#include "Util.h"
-#include "WritableIdentityCredential.h"
+
+#include "FakeSecureHardwareProxy.h"
 
 namespace aidl::android::hardware::identity {
 
@@ -40,74 +39,68 @@
 using namespace ::android::hardware::identity;
 
 bool WritableIdentityCredential::initialize() {
-    optional<vector<uint8_t>> random = support::getRandom(16);
-    if (!random) {
-        LOG(ERROR) << "Error creating storageKey";
+    if (!hwProxy_->initialize(testCredential_)) {
+        LOG(ERROR) << "hwProxy->initialize() failed";
         return false;
     }
-    storageKey_ = random.value();
     startPersonalizationCalled_ = false;
     firstEntry_ = true;
 
     return true;
 }
 
-// This function generates the attestation certificate using the passed in
-// |attestationApplicationId| and |attestationChallenge|.  It will generate an
-// attestation certificate with current time and expires one year from now.  The
-// certificate shall contain all values as specified in hal.
+// Used when updating a credential. Returns false on failure.
+bool WritableIdentityCredential::initializeForUpdate(
+        const vector<uint8_t>& encryptedCredentialKeys) {
+    if (!hwProxy_->initializeForUpdate(testCredential_, docType_, encryptedCredentialKeys)) {
+        LOG(ERROR) << "hwProxy->initializeForUpdate() failed";
+        return false;
+    }
+    startPersonalizationCalled_ = false;
+    firstEntry_ = true;
+
+    return true;
+}
+
+WritableIdentityCredential::~WritableIdentityCredential() {}
+
 ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
-        const vector<uint8_t>& attestationApplicationId,  //
-        const vector<uint8_t>& attestationChallenge,      //
-        vector<Certificate>* outCertificateChain) {
-    if (!credentialPrivKey_.empty() || !credentialPubKey_.empty() || !certificateChain_.empty()) {
+        const vector<uint8_t>& attestationApplicationId,
+        const vector<uint8_t>& attestationChallenge, vector<Certificate>* outCertificateChain) {
+    if (getAttestationCertificateAlreadyCalled_) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED,
                 "Error attestation certificate previously generated"));
     }
+    getAttestationCertificateAlreadyCalled_ = true;
+
     if (attestationChallenge.empty()) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty"));
     }
 
-    vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end());
-    vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
-
-    optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> keyAttestationPair =
-            support::createEcKeyPairAndAttestation(challenge, appId, testCredential_);
-    if (!keyAttestationPair) {
-        LOG(ERROR) << "Error creating credentialKey and attestation";
+    optional<vector<uint8_t>> certChain =
+            hwProxy_->createCredentialKey(attestationChallenge, attestationApplicationId);
+    if (!certChain) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED,
-                "Error creating credentialKey and attestation"));
+                "Error generating attestation certificate chain"));
     }
 
-    vector<uint8_t> keyPair = keyAttestationPair.value().first;
-    certificateChain_ = keyAttestationPair.value().second;
-
-    optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair);
-    if (!pubKey) {
+    optional<vector<vector<uint8_t>>> certs = support::certificateChainSplit(certChain.value());
+    if (!certs) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED,
-                "Error getting public part of credentialKey"));
+                "Error splitting chain into separate certificates"));
     }
-    credentialPubKey_ = pubKey.value();
 
-    optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair);
-    if (!privKey) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED,
-                "Error getting private part of credentialKey"));
-    }
-    credentialPrivKey_ = privKey.value();
-
-    // convert from vector<vector<uint8_t>>> to vector<Certificate>*
     *outCertificateChain = vector<Certificate>();
-    for (const vector<uint8_t>& cert : certificateChain_) {
+    for (const vector<uint8_t>& cert : certs.value()) {
         Certificate c = Certificate();
         c.encodedCertificate = cert;
         outCertificateChain->push_back(std::move(c));
     }
+
     return ndk::ScopedAStatus::ok();
 }
 
@@ -123,8 +116,8 @@
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
     }
-
     startPersonalizationCalled_ = true;
+
     numAccessControlProfileRemaining_ = accessControlProfileCount;
     remainingEntryCounts_ = entryCounts;
     entryNameSpace_ = "";
@@ -133,6 +126,12 @@
     signedDataNamespaces_ = cppbor::Map();
     signedDataCurrentNamespace_ = cppbor::Array();
 
+    if (!hwProxy_->startPersonalization(accessControlProfileCount, entryCounts, docType_,
+                                        expectedProofOfProvisioningSize_)) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "eicStartPersonalization"));
+    }
+
     return ndk::ScopedAStatus::ok();
 }
 
@@ -140,8 +139,6 @@
         int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired,
         int64_t timeoutMillis, int64_t secureUserId,
         SecureAccessControlProfile* outSecureAccessControlProfile) {
-    SecureAccessControlProfile profile;
-
     if (numAccessControlProfileRemaining_ == 0) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_INVALID_DATA,
@@ -169,25 +166,21 @@
                 "userAuthenticationRequired is false but timeout is non-zero"));
     }
 
-    // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero.
-    if (userAuthenticationRequired && secureUserId == 0) {
+    optional<vector<uint8_t>> mac = hwProxy_->addAccessControlProfile(
+            id, readerCertificate.encodedCertificate, userAuthenticationRequired, timeoutMillis,
+            secureUserId);
+    if (!mac) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_INVALID_DATA,
-                "userAuthenticationRequired is true but secureUserId is zero"));
+                IIdentityCredentialStore::STATUS_FAILED, "eicAddAccessControlProfile"));
     }
 
+    SecureAccessControlProfile profile;
     profile.id = id;
     profile.readerCertificate = readerCertificate;
     profile.userAuthenticationRequired = userAuthenticationRequired;
     profile.timeoutMillis = timeoutMillis;
     profile.secureUserId = secureUserId;
-    optional<vector<uint8_t>> mac = secureAccessControlProfileCalcMac(profile, storageKey_);
-    if (!mac) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error calculating MAC for profile"));
-    }
     profile.mac = mac.value();
-
     cppbor::Map profileMap;
     profileMap.add("id", profile.id);
     if (profile.readerCertificate.encodedCertificate.size() > 0) {
@@ -261,14 +254,18 @@
         remainingEntryCounts_[0] -= 1;
     }
 
-    entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
-
     entryRemainingBytes_ = entrySize;
     entryNameSpace_ = nameSpace;
     entryName_ = name;
     entryAccessControlProfileIds_ = accessControlProfileIds;
     entryBytes_.resize(0);
     // LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
+
+    if (!hwProxy_->beginAddEntry(accessControlProfileIds, nameSpace, name, entrySize)) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "eicBeginAddEntry"));
+    }
+
     return ndk::ScopedAStatus::ok();
 }
 
@@ -297,16 +294,11 @@
         }
     }
 
-    optional<vector<uint8_t>> nonce = support::getRandom(12);
-    if (!nonce) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce"));
-    }
-    optional<vector<uint8_t>> encryptedContent =
-            support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_);
+    optional<vector<uint8_t>> encryptedContent = hwProxy_->addEntryValue(
+            entryAccessControlProfileIds_, entryNameSpace_, entryName_, content);
     if (!encryptedContent) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error encrypting content"));
+                IIdentityCredentialStore::STATUS_FAILED, "eicAddEntryValue"));
     }
 
     if (entryRemainingBytes_ == 0) {
@@ -332,50 +324,6 @@
     return ndk::ScopedAStatus::ok();
 }
 
-// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and
-// |credentialPrivKey|.
-static bool generateCredentialKeys(const vector<uint8_t>& storageKey,
-                                   const vector<uint8_t>& credentialPrivKey,
-                                   vector<uint8_t>& credentialKeys) {
-    if (storageKey.size() != 16) {
-        LOG(ERROR) << "Size of storageKey is not 16";
-        return false;
-    }
-
-    cppbor::Array array;
-    array.add(cppbor::Bstr(storageKey));
-    array.add(cppbor::Bstr(credentialPrivKey));
-    credentialKeys = array.encode();
-    return true;
-}
-
-// Writes CBOR-encoded structure to |credentialData| containing |docType|,
-// |testCredential| and |credentialKeys|. The latter element will be stored in
-// encrypted form, using |hardwareBoundKey| as the encryption key.
-bool generateCredentialData(const vector<uint8_t>& hardwareBoundKey, const string& docType,
-                            bool testCredential, const vector<uint8_t>& credentialKeys,
-                            vector<uint8_t>& credentialData) {
-    optional<vector<uint8_t>> nonce = support::getRandom(12);
-    if (!nonce) {
-        LOG(ERROR) << "Error getting random";
-        return false;
-    }
-    vector<uint8_t> docTypeAsVec(docType.begin(), docType.end());
-    optional<vector<uint8_t>> credentialBlob = support::encryptAes128Gcm(
-            hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec);
-    if (!credentialBlob) {
-        LOG(ERROR) << "Error encrypting CredentialKeys blob";
-        return false;
-    }
-
-    cppbor::Array array;
-    array.add(docType);
-    array.add(testCredential);
-    array.add(cppbor::Bstr(credentialBlob.value()));
-    credentialData = array.encode();
-    return true;
-}
-
 ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
         vector<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) {
     if (numAccessControlProfileRemaining_ != 0) {
@@ -411,31 +359,37 @@
                         .c_str()));
     }
 
-    optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
-                                                                 encodedCbor,  // payload
-                                                                 {},           // additionalData
-                                                                 {});          // certificateChain
+    optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->finishAddingEntries();
+    if (!signatureOfToBeSigned) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries"));
+    }
+
+    optional<vector<uint8_t>> signature =
+            support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+                                                encodedCbor,  // data
+                                                {});          // certificateChain
     if (!signature) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
     }
 
-    vector<uint8_t> credentialKeys;
-    if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) {
+    optional<vector<uint8_t>> encryptedCredentialKeys = hwProxy_->finishGetCredentialData(docType_);
+    if (!encryptedCredentialKeys) {
         return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys"));
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error generating encrypted CredentialKeys"));
     }
-
-    vector<uint8_t> credentialData;
-    if (!generateCredentialData(
-                testCredential_ ? support::getTestHardwareBoundKey() : getHardwareBoundKey(),
-                docType_, testCredential_, credentialKeys, credentialData)) {
-        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
-                IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialData"));
-    }
+    cppbor::Array array;
+    array.add(docType_);
+    array.add(testCredential_);
+    array.add(encryptedCredentialKeys.value());
+    vector<uint8_t> credentialData = array.encode();
 
     *outCredentialData = credentialData;
     *outProofOfProvisioningSignature = signature.value();
+    hwProxy_->shutdown();
+
     return ndk::ScopedAStatus::ok();
 }
 
diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/common/WritableIdentityCredential.h
similarity index 78%
rename from identity/aidl/default/WritableIdentityCredential.h
rename to identity/aidl/default/common/WritableIdentityCredential.h
index 5645852..36ad430 100644
--- a/identity/aidl/default/WritableIdentityCredential.h
+++ b/identity/aidl/default/common/WritableIdentityCredential.h
@@ -23,21 +23,35 @@
 #include <cppbor.h>
 #include <set>
 
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
 namespace aidl::android::hardware::identity {
 
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProvisioningProxy;
 using ::std::set;
 using ::std::string;
 using ::std::vector;
 
 class WritableIdentityCredential : public BnWritableIdentityCredential {
   public:
-    WritableIdentityCredential(const string& docType, bool testCredential)
-        : docType_(docType), testCredential_(testCredential) {}
+    // For a new credential, call initialize() right after construction.
+    //
+    // For an updated credential, call initializeForUpdate() right after construction.
+    //
+    WritableIdentityCredential(sp<SecureHardwareProvisioningProxy> hwProxy, const string& docType,
+                               bool testCredential)
+        : hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {}
 
-    // Creates the Credential Key. Returns false on failure. Must be called
-    // right after construction.
+    ~WritableIdentityCredential();
+
+    // Creates the Credential Key. Returns false on failure.
     bool initialize();
 
+    // Used when updating a credential. Returns false on failure.
+    bool initializeForUpdate(const vector<uint8_t>& encryptedCredentialKeys);
+
     // Methods from IWritableIdentityCredential follow.
     ndk::ScopedAStatus getAttestationCertificate(const vector<uint8_t>& attestationApplicationId,
                                                  const vector<uint8_t>& attestationChallenge,
@@ -57,7 +71,6 @@
     ndk::ScopedAStatus beginAddEntry(const vector<int32_t>& accessControlProfileIds,
                                      const string& nameSpace, const string& name,
                                      int32_t entrySize) override;
-
     ndk::ScopedAStatus addEntryValue(const vector<uint8_t>& content,
                                      vector<uint8_t>* outEncryptedContent) override;
 
@@ -66,18 +79,17 @@
             vector<uint8_t>* outProofOfProvisioningSignature) override;
 
   private:
+    // Set by constructor.
+    sp<SecureHardwareProvisioningProxy> hwProxy_;
     string docType_;
     bool testCredential_;
 
     // This is set in initialize().
-    vector<uint8_t> storageKey_;
     bool startPersonalizationCalled_;
     bool firstEntry_;
 
-    // These are set in getAttestationCertificate().
-    vector<uint8_t> credentialPrivKey_;
-    vector<uint8_t> credentialPubKey_;
-    vector<vector<uint8_t>> certificateChain_;
+    // This is set in getAttestationCertificate().
+    bool getAttestationCertificateAlreadyCalled_ = false;
 
     // These fields are initialized during startPersonalization()
     size_t numAccessControlProfileRemaining_;
@@ -92,7 +104,6 @@
 
     // These fields are initialized during beginAddEntry()
     size_t entryRemainingBytes_;
-    vector<uint8_t> entryAdditionalData_;
     string entryNameSpace_;
     string entryName_;
     vector<int32_t> entryAccessControlProfileIds_;
diff --git a/identity/aidl/default/identity-default.xml b/identity/aidl/default/identity-default.xml
index a47d354..a074250 100644
--- a/identity/aidl/default/identity-default.xml
+++ b/identity/aidl/default/identity-default.xml
@@ -1,6 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.identity</name>
+        <version>3</version>
         <interface>
             <name>IIdentityCredentialStore</name>
             <instance>default</instance>
diff --git a/identity/aidl/default/libeic/EicCbor.c b/identity/aidl/default/libeic/EicCbor.c
new file mode 100644
index 0000000..fe131eb
--- /dev/null
+++ b/identity/aidl/default/libeic/EicCbor.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright 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 "EicCbor.h"
+
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) {
+    eicMemSet(cbor, '\0', sizeof(EicCbor));
+    cbor->size = 0;
+    cbor->bufferSize = bufferSize;
+    cbor->buffer = buffer;
+    cbor->digestType = EIC_CBOR_DIGEST_TYPE_SHA256;
+    eicOpsSha256Init(&cbor->digester.sha256);
+}
+
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+                           const uint8_t* hmacKey, size_t hmacKeySize) {
+    eicMemSet(cbor, '\0', sizeof(EicCbor));
+    cbor->size = 0;
+    cbor->bufferSize = bufferSize;
+    cbor->buffer = buffer;
+    cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256;
+    eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize);
+}
+
+void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256) {
+    cbor->secondaryDigesterSha256 = sha256;
+}
+
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+    switch (cbor->digestType) {
+        case EIC_CBOR_DIGEST_TYPE_SHA256:
+            eicOpsSha256Final(&cbor->digester.sha256, digest);
+            break;
+        case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+            eicOpsHmacSha256Final(&cbor->digester.hmacSha256, digest);
+            break;
+    }
+}
+
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) {
+    switch (cbor->digestType) {
+        case EIC_CBOR_DIGEST_TYPE_SHA256:
+            eicOpsSha256Update(&cbor->digester.sha256, data, size);
+            break;
+        case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+            eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size);
+            break;
+    }
+    if (cbor->secondaryDigesterSha256 != NULL) {
+        eicOpsSha256Update(cbor->secondaryDigesterSha256, data, size);
+    }
+
+    if (cbor->size >= cbor->bufferSize) {
+        cbor->size += size;
+        return;
+    }
+
+    size_t numBytesLeft = cbor->bufferSize - cbor->size;
+    size_t numBytesToCopy = size;
+    if (numBytesToCopy > numBytesLeft) {
+        numBytesToCopy = numBytesLeft;
+    }
+    eicMemCpy(cbor->buffer + cbor->size, data, numBytesToCopy);
+
+    cbor->size += size;
+}
+
+size_t eicCborAdditionalLengthBytesFor(size_t size) {
+    if (size < 24) {
+        return 0;
+    } else if (size <= 0xff) {
+        return 1;
+    } else if (size <= 0xffff) {
+        return 2;
+    } else if (size <= 0xffffffff) {
+        return 4;
+    }
+    return 8;
+}
+
+void eicCborBegin(EicCbor* cbor, int majorType, size_t size) {
+    uint8_t data[9];
+
+    if (size < 24) {
+        data[0] = (majorType << 5) | size;
+        eicCborAppend(cbor, data, 1);
+    } else if (size <= 0xff) {
+        data[0] = (majorType << 5) | 24;
+        data[1] = size;
+        eicCborAppend(cbor, data, 2);
+    } else if (size <= 0xffff) {
+        data[0] = (majorType << 5) | 25;
+        data[1] = size >> 8;
+        data[2] = size & 0xff;
+        eicCborAppend(cbor, data, 3);
+    } else if (size <= 0xffffffff) {
+        data[0] = (majorType << 5) | 26;
+        data[1] = (size >> 24) & 0xff;
+        data[2] = (size >> 16) & 0xff;
+        data[3] = (size >> 8) & 0xff;
+        data[4] = size & 0xff;
+        eicCborAppend(cbor, data, 5);
+    } else {
+        data[0] = (majorType << 5) | 24;
+        data[1] = (((uint64_t)size) >> 56) & 0xff;
+        data[2] = (((uint64_t)size) >> 48) & 0xff;
+        data[3] = (((uint64_t)size) >> 40) & 0xff;
+        data[4] = (((uint64_t)size) >> 32) & 0xff;
+        data[5] = (((uint64_t)size) >> 24) & 0xff;
+        data[6] = (((uint64_t)size) >> 16) & 0xff;
+        data[7] = (((uint64_t)size) >> 8) & 0xff;
+        data[8] = ((uint64_t)size) & 0xff;
+        eicCborAppend(cbor, data, 9);
+    }
+}
+
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize) {
+    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dataSize);
+    eicCborAppend(cbor, data, dataSize);
+}
+
+void eicCborAppendString(EicCbor* cbor, const char* str) {
+    size_t length = eicStrLen(str);
+    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_STRING, length);
+    eicCborAppend(cbor, (const uint8_t*)str, length);
+}
+
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue) {
+    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SIMPLE, simpleValue);
+}
+
+void eicCborAppendBool(EicCbor* cbor, bool value) {
+    uint8_t simpleValue = value ? EIC_CBOR_SIMPLE_VALUE_TRUE : EIC_CBOR_SIMPLE_VALUE_FALSE;
+    eicCborAppendSimple(cbor, simpleValue);
+}
+
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value) {
+    size_t encoded = value;
+    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SEMANTIC, encoded);
+}
+
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value) {
+    size_t encoded = value;
+    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_UNSIGNED, encoded);
+}
+
+void eicCborAppendNumber(EicCbor* cbor, int64_t value) {
+    if (value < 0) {
+        size_t encoded = -1 - value;
+        eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_NEGATIVE, encoded);
+    } else {
+        eicCborAppendUnsigned(cbor, value);
+    }
+}
+
+void eicCborAppendArray(EicCbor* cbor, size_t numElements) {
+    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, numElements);
+}
+
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs) {
+    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_MAP, numPairs);
+}
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate,
+                              size_t readerCertificateSize, bool userAuthenticationRequired,
+                              uint64_t timeoutMillis, uint64_t secureUserId) {
+    size_t numPairs = 1;
+    if (readerCertificateSize > 0) {
+        numPairs += 1;
+    }
+    if (userAuthenticationRequired) {
+        numPairs += 2;
+        if (secureUserId > 0) {
+            numPairs += 1;
+        }
+    }
+    eicCborAppendMap(cborBuilder, numPairs);
+    eicCborAppendString(cborBuilder, "id");
+    eicCborAppendUnsigned(cborBuilder, id);
+    if (readerCertificateSize > 0) {
+        eicCborAppendString(cborBuilder, "readerCertificate");
+        eicCborAppendByteString(cborBuilder, readerCertificate, readerCertificateSize);
+    }
+    if (userAuthenticationRequired) {
+        eicCborAppendString(cborBuilder, "userAuthenticationRequired");
+        eicCborAppendBool(cborBuilder, userAuthenticationRequired);
+        eicCborAppendString(cborBuilder, "timeoutMillis");
+        eicCborAppendUnsigned(cborBuilder, timeoutMillis);
+        if (secureUserId > 0) {
+            eicCborAppendString(cborBuilder, "secureUserId");
+            eicCborAppendUnsigned(cborBuilder, secureUserId);
+        }
+    }
+
+    if (cborBuilder->size > cborBuilder->bufferSize) {
+        eicDebug("Buffer for ACP CBOR is too small (%zd) - need %zd bytes", cborBuilder->bufferSize,
+                 cborBuilder->size);
+        return false;
+    }
+
+    return true;
+}
+
+bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds,
+                                    size_t numAccessControlProfileIds, const char* nameSpace,
+                                    const char* name, uint8_t* cborBuffer, size_t cborBufferSize,
+                                    size_t* outAdditionalDataCborSize,
+                                    uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]) {
+    EicCbor cborBuilder;
+
+    eicCborInit(&cborBuilder, cborBuffer, cborBufferSize);
+    eicCborAppendMap(&cborBuilder, 3);
+    eicCborAppendString(&cborBuilder, "Namespace");
+    eicCborAppendString(&cborBuilder, nameSpace);
+    eicCborAppendString(&cborBuilder, "Name");
+    eicCborAppendString(&cborBuilder, name);
+    eicCborAppendString(&cborBuilder, "AccessControlProfileIds");
+    eicCborAppendArray(&cborBuilder, numAccessControlProfileIds);
+    for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+        eicCborAppendNumber(&cborBuilder, accessControlProfileIds[n]);
+    }
+    if (cborBuilder.size > cborBufferSize) {
+        eicDebug("Not enough space for additionalData - buffer is only %zd bytes, content is %zd",
+                 cborBufferSize, cborBuilder.size);
+        return false;
+    }
+    if (outAdditionalDataCborSize != NULL) {
+        *outAdditionalDataCborSize = cborBuilder.size;
+    }
+    eicCborFinal(&cborBuilder, additionalDataSha256);
+    return true;
+}
diff --git a/identity/aidl/default/libeic/EicCbor.h b/identity/aidl/default/libeic/EicCbor.h
new file mode 100644
index 0000000..9c0f531
--- /dev/null
+++ b/identity/aidl/default/libeic/EicCbor.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright 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.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicOps.h"
+
+typedef enum {
+    EIC_CBOR_DIGEST_TYPE_SHA256,
+    EIC_CBOR_DIGEST_TYPE_HMAC_SHA256,
+} EicCborDigestType;
+
+/* EicCbor is a utility class to build CBOR data structures and calculate
+ * digests on the fly.
+ */
+typedef struct {
+    // Contains the size of the built CBOR, even if it exceeds bufferSize (will
+    // never write to buffer beyond bufferSize though)
+    size_t size;
+
+    // The size of the buffer. Is zero if no data is recorded in which case
+    // only digesting is performed.
+    size_t bufferSize;
+
+    // Whether we're producing a SHA-256 or HMAC-SHA256 digest.
+    EicCborDigestType digestType;
+
+    // The SHA-256 digester object.
+    union {
+        EicSha256Ctx sha256;
+        EicHmacSha256Ctx hmacSha256;
+    } digester;
+
+    // The secondary digester, may be unset.
+    EicSha256Ctx* secondaryDigesterSha256;
+
+    // The buffer used for building up CBOR or NULL if bufferSize is 0.
+    uint8_t* buffer;
+} EicCbor;
+
+/* Initializes an EicCbor.
+ *
+ * The given buffer will be used, up to bufferSize.
+ *
+ * If bufferSize is 0, buffer may be NULL.
+ */
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize);
+
+/* Like eicCborInit() but uses HMAC-SHA256 instead of SHA-256.
+ */
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+                           const uint8_t* hmacKey, size_t hmacKeySize);
+
+/* Enables a secondary digester.
+ *
+ * May be enabled midway through processing, this can be used to e.g. calculate
+ * a digest of Sig_structure (for COSE_Sign1) and a separate digest of its
+ * payload.
+ */
+void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256);
+
+/* Finishes building CBOR and returns the digest. */
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+/* Appends CBOR data to the EicCbor. */
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size);
+
+#define EIC_CBOR_MAJOR_TYPE_UNSIGNED 0
+#define EIC_CBOR_MAJOR_TYPE_NEGATIVE 1
+#define EIC_CBOR_MAJOR_TYPE_BYTE_STRING 2
+#define EIC_CBOR_MAJOR_TYPE_STRING 3
+#define EIC_CBOR_MAJOR_TYPE_ARRAY 4
+#define EIC_CBOR_MAJOR_TYPE_MAP 5
+#define EIC_CBOR_MAJOR_TYPE_SEMANTIC 6
+#define EIC_CBOR_MAJOR_TYPE_SIMPLE 7
+
+#define EIC_CBOR_SIMPLE_VALUE_FALSE 20
+#define EIC_CBOR_SIMPLE_VALUE_TRUE 21
+
+#define EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR 24
+
+/* Begins a new CBOR value. */
+void eicCborBegin(EicCbor* cbor, int majorType, size_t size);
+
+/* Appends a bytestring. */
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize);
+
+/* Appends a NUL-terminated UTF-8 string. */
+void eicCborAppendString(EicCbor* cbor, const char* str);
+
+/* Appends a simple value. */
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue);
+
+/* Appends a boolean. */
+void eicCborAppendBool(EicCbor* cbor, bool value);
+
+/* Appends a semantic */
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value);
+
+/* Appends an unsigned number. */
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value);
+
+/* Appends a number. */
+void eicCborAppendNumber(EicCbor* cbor, int64_t value);
+
+/* Starts appending an array.
+ *
+ * After this numElements CBOR elements must follow.
+ */
+void eicCborAppendArray(EicCbor* cbor, size_t numElements);
+
+/* Starts appending a map.
+ *
+ * After this numPairs pairs of CBOR elements must follow.
+ */
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs);
+
+/* Calculates how many bytes are needed to store a size. */
+size_t eicCborAdditionalLengthBytesFor(size_t size);
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate,
+                              size_t readerCertificateSize, bool userAuthenticationRequired,
+                              uint64_t timeoutMillis, uint64_t secureUserId);
+
+bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds,
+                                    size_t numAccessControlProfileIds, const char* nameSpace,
+                                    const char* name, uint8_t* cborBuffer, size_t cborBufferSize,
+                                    size_t* outAdditionalDataCborSize,
+                                    uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]);
+
+// The maximum size of an encoded Secure Access Control Profile that we
+// support. Since the SACP may contain a reader certificate chain these can get
+// pretty big.
+//
+// Currently we allocate space on the stack for this structure which is why we
+// have a maximum size. We can get rid of the maximum size by incrementally
+// building/verifying the SACP. TODO: actually do this.
+//
+#define EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE 512
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
diff --git a/identity/aidl/default/libeic/EicOps.h b/identity/aidl/default/libeic/EicOps.h
new file mode 100644
index 0000000..d4fcf0e
--- /dev/null
+++ b/identity/aidl/default/libeic/EicOps.h
@@ -0,0 +1,302 @@
+/*
+ * Copyright 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.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Uncomment or define if debug messages are needed.
+//
+//#define EIC_DEBUG
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// The following defines must be set to something appropriate
+//
+//   EIC_SHA256_CONTEXT_SIZE - the size of EicSha256Ctx
+//   EIC_HMAC_SHA256_CONTEXT_SIZE - the size of EicHmacSha256Ctx
+//
+// For example, if EicSha256Ctx is implemented using BoringSSL this would be defined
+// as sizeof(SHA256_CTX).
+//
+// We expect the implementation to provide a header file with the name
+// EicOpsImpl.h to do all this.
+//
+#include "EicOpsImpl.h"
+
+#define EIC_SHA256_DIGEST_SIZE 32
+
+// The size of a P-256 private key.
+//
+#define EIC_P256_PRIV_KEY_SIZE 32
+
+// The size of a P-256 public key in uncompressed form.
+//
+// The public key is stored in uncompressed form, first the X coordinate, then
+// the Y coordinate.
+//
+#define EIC_P256_PUB_KEY_SIZE 64
+
+// Size of one of the coordinates in a curve-point.
+//
+#define EIC_P256_COORDINATE_SIZE 32
+
+// The size of an ECSDA signature using P-256.
+//
+// The R and S values are stored here, first R then S.
+//
+#define EIC_ECDSA_P256_SIGNATURE_SIZE 64
+
+#define EIC_AES_128_KEY_SIZE 16
+
+// The following are definitions of implementation functions the
+// underlying platform must provide.
+//
+
+struct EicSha256Ctx {
+    uint8_t reserved[EIC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicSha256Ctx EicSha256Ctx;
+
+struct EicHmacSha256Ctx {
+    uint8_t reserved[EIC_HMAC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicHmacSha256Ctx EicHmacSha256Ctx;
+
+#ifdef EIC_DEBUG
+// Debug macro. Don't include a new-line in message.
+//
+#define eicDebug(...)                            \
+    do {                                         \
+        eicPrint("%s:%d: ", __FILE__, __LINE__); \
+        eicPrint(__VA_ARGS__);                   \
+        eicPrint("\n");                          \
+    } while (0)
+#else
+#define eicDebug(...) \
+    do {              \
+    } while (0)
+#endif
+
+// Prints message which should include new-line character. Can be no-op.
+//
+// Don't use this from code, use eicDebug() instead.
+//
+#ifdef EIC_DEBUG
+void eicPrint(const char* format, ...);
+#else
+inline void eicPrint(const char*, ...) {}
+#endif
+
+// Dumps data as pretty-printed hex. Can be no-op.
+//
+#ifdef EIC_DEBUG
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize);
+#else
+inline void eicHexdump(const char*, const uint8_t*, size_t) {}
+#endif
+
+// Pretty-prints encoded CBOR. Can be no-op.
+//
+// If a byte-string is larger than |maxBStrSize| its contents will not be
+// printed, instead the value of the form "<bstr size=1099016
+// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" will be printed. Pass zero
+// for |maxBStrSize| to disable this.
+//
+#ifdef EIC_DEBUG
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize);
+#else
+inline void eicCborPrettyPrint(const uint8_t*, size_t, size_t) {}
+#endif
+
+// Memory setting, see memset(3).
+void* eicMemSet(void* s, int c, size_t n);
+
+// Memory copying, see memcpy(3).
+void* eicMemCpy(void* dest, const void* src, size_t n);
+
+// String length, see strlen(3).
+size_t eicStrLen(const char* s);
+
+// Memory compare, see CRYPTO_memcmp(3SSL)
+//
+// It takes an amount of time dependent on len, but independent of the contents of the
+// memory regions pointed to by s1 and s2.
+//
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n);
+
+// Random number generation.
+bool eicOpsRandom(uint8_t* buf, size_t numBytes);
+
+// If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16 bytes).
+//
+// Otherwise returns all zeroes (16 bytes).
+//
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential);
+
+// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|,
+// returns the resulting (nonce || ciphertext || tag) in |encryptedData| which
+// must be of size |dataSize| + 28.
+bool eicOpsEncryptAes128Gcm(
+        const uint8_t* key,    // Must be 16 bytes
+        const uint8_t* nonce,  // Must be 12 bytes
+        const uint8_t* data,   // May be NULL if size is 0
+        size_t dataSize,
+        const uint8_t* additionalAuthenticationData,  // May be NULL if size is 0
+        size_t additionalAuthenticationDataSize, uint8_t* encryptedData);
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key,  // Must be 16 bytes
+                            const uint8_t* encryptedData, size_t encryptedDataSize,
+                            const uint8_t* additionalAuthenticationData,
+                            size_t additionalAuthenticationDataSize, uint8_t* data);
+
+// Creates an EC key using the P-256 curve. The private key is written to
+// |privateKey|. The public key is written to |publicKey|.
+//
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                       uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]);
+
+// Generates CredentialKey plus an attestation certificate.
+//
+// The attestation certificate will be signed by the attestation keys the secure
+// area has been provisioned with. The given |challenge| and |applicationId|
+// will be used as will |testCredential|.
+//
+// The generated certificate will be in X.509 format and returned in |cert|
+// and |certSize| must be set to the size of this array and this function will
+// set it to the size of the certification chain on successfully return.
+//
+// This may return either a single certificate or an entire certificate
+// chain. If it returns only a single certificate, the implementation of
+// SecureHardwareProvisioningProxy::createCredentialKey() should amend the
+// remainder of the certificate chain on the HAL side.
+//
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge,
+                               size_t challengeSize, const uint8_t* applicationId,
+                               size_t applicationIdSize, bool testCredential, uint8_t* cert,
+                               size_t* certSize);  // inout
+
+// Generate an X.509 certificate for the key identified by |publicKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+// If proofOfBinding is not NULL, it will be included as an OCTET_STRING
+// X.509 extension at OID 1.3.6.1.4.1.11129.2.1.26.
+//
+// The certificate will be signed by the key identified by |signingKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+                     const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial,
+                     const char* issuerName, const char* subjectName, time_t validityNotBefore,
+                     time_t validityNotAfter, const uint8_t* proofOfBinding,
+                     size_t proofOfBindingSize, uint8_t* cert, size_t* certSize);  // inout
+
+// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must
+// be given by |digestOfData|). Returns the signature in |signature|.
+//
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                 const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+                 uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// Performs Elliptic Curve Diffie-Helman.
+//
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+                const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]);
+
+// Performs HKDF.
+//
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt,
+                size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output,
+                size_t outputSize);
+
+// SHA-256 functions.
+void eicOpsSha256Init(EicSha256Ctx* ctx);
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len);
+void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// HMAC SHA-256 functions.
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize);
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len);
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// Extracts the public key in the given X.509 certificate.
+//
+// If the key is not an EC key, this function fails.
+//
+// Otherwise the public key is stored in uncompressed form in |publicKey| which
+// size should be set in |publicKeySize|. On successful return |publicKeySize|
+// is set to the length of the key. If there is not enough space, the function
+// fails.
+//
+// (The public key returned is not necessarily a P-256 key, even if it is note
+// that its size is not EIC_P256_PUBLIC_KEY_SIZE because of the leading 0x04.)
+//
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey,
+                            size_t* publicKeySize);
+
+// Checks that the X.509 certificate given by |x509Cert| is signed by the public
+// key given by |publicKey| which must be an EC key in uncompressed form (e.g.
+// same formatt as returned by eicOpsX509GetPublicKey()).
+//
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+                                     const uint8_t* publicKey, size_t publicKeySize);
+
+// Checks that |signature| is a signature of some data (given by |digest|),
+// signed by the public key given by |publicKey|.
+//
+// The key must be an EC key in uncompressed form (e.g.  same format as returned
+// by eicOpsX509GetPublicKey()).
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// The size of digest must match the size of the key.
+//
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+                                    const uint8_t* signature, size_t signatureSize,
+                                    const uint8_t* publicKey, size_t publicKeySize);
+
+// Validates that the passed in data constitutes a valid auth- and verification tokens.
+//
+bool eicOpsValidateAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+                             int hardwareAuthenticatorType, uint64_t timeStamp, const uint8_t* mac,
+                             size_t macSize, uint64_t verificationTokenChallenge,
+                             uint64_t verificationTokenTimeStamp,
+                             int verificationTokenSecurityLevel,
+                             const uint8_t* verificationTokenMac, size_t verificationTokenMacSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
diff --git a/identity/aidl/default/libeic/EicPresentation.c b/identity/aidl/default/libeic/EicPresentation.c
new file mode 100644
index 0000000..5e9a280
--- /dev/null
+++ b/identity/aidl/default/libeic/EicPresentation.c
@@ -0,0 +1,850 @@
+/*
+ * Copyright 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 "EicPresentation.h"
+
+#include <inttypes.h>
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
+                         const uint8_t* encryptedCredentialKeys,
+                         size_t encryptedCredentialKeysSize) {
+    uint8_t credentialKeys[86];
+    bool expectPopSha256 = false;
+
+    // For feature version 202009 it's 52 bytes long and for feature version 202101 it's 86
+    // bytes (the additional data is the ProofOfProvisioning SHA-256). We need
+    // to support loading all feature versions.
+    //
+    if (encryptedCredentialKeysSize == 52 + 28) {
+        /* do nothing */
+    } else if (encryptedCredentialKeysSize == 86 + 28) {
+        expectPopSha256 = true;
+    } else {
+        eicDebug("Unexpected size %zd for encryptedCredentialKeys", encryptedCredentialKeysSize);
+        return false;
+    }
+
+    eicMemSet(ctx, '\0', sizeof(EicPresentation));
+
+    if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+                                encryptedCredentialKeysSize,
+                                // DocType is the additionalAuthenticatedData
+                                (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) {
+        eicDebug("Error decrypting CredentialKeys");
+        return false;
+    }
+
+    // It's supposed to look like this;
+    //
+    // Feature version 202009:
+    //
+    //         CredentialKeys = [
+    //              bstr,   ; storageKey, a 128-bit AES key
+    //              bstr,   ; credentialPrivKey, the private key for credentialKey
+    //         ]
+    //
+    // Feature version 202101:
+    //
+    //         CredentialKeys = [
+    //              bstr,   ; storageKey, a 128-bit AES key
+    //              bstr,   ; credentialPrivKey, the private key for credentialKey
+    //              bstr    ; proofOfProvisioning SHA-256
+    //         ]
+    //
+    // where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and proofOfProvisioning
+    // SHA-256 is 32 bytes.
+    //
+    if (credentialKeys[0] != (expectPopSha256 ? 0x83 : 0x82) ||  // array of two or three elements
+        credentialKeys[1] != 0x50 ||                             // 16-byte bstr
+        credentialKeys[18] != 0x58 || credentialKeys[19] != 0x20) {  // 32-byte bstr
+        eicDebug("Invalid CBOR for CredentialKeys");
+        return false;
+    }
+    if (expectPopSha256) {
+        if (credentialKeys[52] != 0x58 || credentialKeys[53] != 0x20) {  // 32-byte bstr
+            eicDebug("Invalid CBOR for CredentialKeys");
+            return false;
+        }
+    }
+    eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+    eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE);
+    ctx->testCredential = testCredential;
+    if (expectPopSha256) {
+        eicMemCpy(ctx->proofOfProvisioningSha256, credentialKeys + 54, EIC_SHA256_DIGEST_SIZE);
+    }
+    return true;
+}
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now,
+                                           uint8_t* publicKeyCert, size_t* publicKeyCertSize,
+                                           uint8_t signingKeyBlob[60]) {
+    uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+    uint8_t signingKeyPub[EIC_P256_PUB_KEY_SIZE];
+    uint8_t cborBuf[64];
+
+    // Generate the ProofOfBinding CBOR to include in the X.509 certificate in
+    // IdentityCredentialAuthenticationKeyExtension CBOR. This CBOR is defined
+    // by the following CDDL
+    //
+    //   ProofOfBinding = [
+    //     "ProofOfBinding",
+    //     bstr,                  // Contains the SHA-256 of ProofOfProvisioning
+    //   ]
+    //
+    // This array may grow in the future if other information needs to be
+    // conveyed.
+    //
+    // The bytes of ProofOfBinding is is represented as an OCTET_STRING
+    // and stored at OID 1.3.6.1.4.1.11129.2.1.26.
+    //
+
+    EicCbor cbor;
+    eicCborInit(&cbor, cborBuf, sizeof cborBuf);
+    eicCborAppendArray(&cbor, 2);
+    eicCborAppendString(&cbor, "ProofOfBinding");
+    eicCborAppendByteString(&cbor, ctx->proofOfProvisioningSha256, EIC_SHA256_DIGEST_SIZE);
+    if (cbor.size > sizeof(cborBuf)) {
+        eicDebug("Exceeded buffer size");
+        return false;
+    }
+    const uint8_t* proofOfBinding = cborBuf;
+    size_t proofOfBindingSize = cbor.size;
+
+    if (!eicOpsCreateEcKey(signingKeyPriv, signingKeyPub)) {
+        eicDebug("Error creating signing key");
+        return false;
+    }
+
+    const int secondsInOneYear = 365 * 24 * 60 * 60;
+    time_t validityNotBefore = now;
+    time_t validityNotAfter = now + secondsInOneYear;  // One year from now.
+    if (!eicOpsSignEcKey(signingKeyPub, ctx->credentialPrivateKey, 1,
+                         "Android Identity Credential Key",                 // issuer CN
+                         "Android Identity Credential Authentication Key",  // subject CN
+                         validityNotBefore, validityNotAfter, proofOfBinding, proofOfBindingSize,
+                         publicKeyCert, publicKeyCertSize)) {
+        eicDebug("Error creating certificate for signing key");
+        return false;
+    }
+
+    uint8_t nonce[12];
+    if (!eicOpsRandom(nonce, 12)) {
+        eicDebug("Error getting random");
+        return false;
+    }
+    if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, signingKeyPriv, sizeof(signingKeyPriv),
+                                // DocType is the additionalAuthenticatedData
+                                (const uint8_t*)docType, eicStrLen(docType), signingKeyBlob)) {
+        eicDebug("Error encrypting signing key");
+        return false;
+    }
+
+    return true;
+}
+
+bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx,
+                                           uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) {
+    uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE];
+    if (!eicOpsCreateEcKey(ctx->ephemeralPrivateKey, ephemeralPublicKey)) {
+        eicDebug("Error creating ephemeral key");
+        return false;
+    }
+    eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE);
+    return true;
+}
+
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge) {
+    do {
+        if (!eicOpsRandom((uint8_t*)&(ctx->authChallenge), sizeof(uint64_t))) {
+            eicDebug("Failed generating random challenge");
+            return false;
+        }
+    } while (ctx->authChallenge == 0);
+    eicDebug("Created auth challenge %" PRIu64, ctx->authChallenge);
+    *authChallenge = ctx->authChallenge;
+    return true;
+}
+
+// From "COSE Algorithms" registry
+//
+#define COSE_ALG_ECDSA_256 -7
+
+bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript,
+                                           size_t sessionTranscriptSize,
+                                           const uint8_t* requestMessage, size_t requestMessageSize,
+                                           int coseSignAlg,
+                                           const uint8_t* readerSignatureOfToBeSigned,
+                                           size_t readerSignatureOfToBeSignedSize) {
+    if (ctx->readerPublicKeySize == 0) {
+        eicDebug("No public key for reader");
+        return false;
+    }
+
+    // Right now we only support ECDSA with SHA-256 (e.g. ES256).
+    //
+    if (coseSignAlg != COSE_ALG_ECDSA_256) {
+        eicDebug(
+                "COSE Signature algorithm for reader signature is %d, "
+                "only ECDSA with SHA-256 is supported right now",
+                coseSignAlg);
+        return false;
+    }
+
+    // What we're going to verify is the COSE ToBeSigned structure which
+    // looks like the following:
+    //
+    //   Sig_structure = [
+    //     context : "Signature" / "Signature1" / "CounterSignature",
+    //     body_protected : empty_or_serialized_map,
+    //     ? sign_protected : empty_or_serialized_map,
+    //     external_aad : bstr,
+    //     payload : bstr
+    //   ]
+    //
+    // So we're going to build that CBOR...
+    //
+    EicCbor cbor;
+    eicCborInit(&cbor, NULL, 0);
+    eicCborAppendArray(&cbor, 4);
+    eicCborAppendString(&cbor, "Signature1");
+
+    // The COSE Encoded protected headers is just a single field with
+    // COSE_LABEL_ALG (1) -> coseSignAlg (e.g. -7). For simplicitly we just
+    // hard-code the CBOR encoding:
+    static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+    eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+                            sizeof(coseEncodedProtectedHeaders));
+
+    // External_aad is the empty bstr
+    static const uint8_t externalAad[0] = {};
+    eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+    // For the payload, the _encoded_ form follows here. We handle this by simply
+    // opening a bstr, and then writing the CBOR. This requires us to know the
+    // size of said bstr, ahead of time... the CBOR to be written is
+    //
+    //   ReaderAuthentication = [
+    //      "ReaderAuthentication",
+    //      SessionTranscript,
+    //      ItemsRequestBytes
+    //   ]
+    //
+    //   ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+    //
+    //   ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
+    //
+    // which is easily calculated below
+    //
+    size_t calculatedSize = 0;
+    calculatedSize += 1;  // Array of size 3
+    calculatedSize += 1;  // "ReaderAuthentication" less than 24 bytes
+    calculatedSize += sizeof("ReaderAuthentication") - 1;  // Don't include trailing NUL
+    calculatedSize += sessionTranscriptSize;               // Already CBOR encoded
+    calculatedSize += 2;  // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+    calculatedSize += 1 + eicCborAdditionalLengthBytesFor(requestMessageSize);
+    calculatedSize += requestMessageSize;
+
+    // However note that we're authenticating ReaderAuthenticationBytes which
+    // is a tagged bstr of the bytes of ReaderAuthentication. So need to get
+    // that in front.
+    size_t rabCalculatedSize = 0;
+    rabCalculatedSize += 2;  // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+    rabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+    rabCalculatedSize += calculatedSize;
+
+    // Begin the bytestring for ReaderAuthenticationBytes;
+    eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, rabCalculatedSize);
+
+    eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+    // Begins the bytestring for ReaderAuthentication;
+    eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+    // And now that we know the size, let's fill it in...
+    //
+    size_t payloadOffset = cbor.size;
+    eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, 3);
+    eicCborAppendString(&cbor, "ReaderAuthentication");
+    eicCborAppend(&cbor, sessionTranscript, sessionTranscriptSize);
+    eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+    eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, requestMessageSize);
+    eicCborAppend(&cbor, requestMessage, requestMessageSize);
+
+    if (cbor.size != payloadOffset + calculatedSize) {
+        eicDebug("CBOR size is %zd but we expected %zd", cbor.size, payloadOffset + calculatedSize);
+        return false;
+    }
+    uint8_t toBeSignedDigest[EIC_SHA256_DIGEST_SIZE];
+    eicCborFinal(&cbor, toBeSignedDigest);
+
+    if (!eicOpsEcDsaVerifyWithPublicKey(
+                toBeSignedDigest, EIC_SHA256_DIGEST_SIZE, readerSignatureOfToBeSigned,
+                readerSignatureOfToBeSignedSize, ctx->readerPublicKey, ctx->readerPublicKeySize)) {
+        eicDebug("Request message is not signed by public key");
+        return false;
+    }
+    ctx->requestMessageValidated = true;
+    return true;
+}
+
+// Validates the next certificate in the reader certificate chain.
+bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509,
+                                   size_t certX509Size) {
+    // If we had a previous certificate, use its public key to validate this certificate.
+    if (ctx->readerPublicKeySize > 0) {
+        if (!eicOpsX509CertSignedByPublicKey(certX509, certX509Size, ctx->readerPublicKey,
+                                             ctx->readerPublicKeySize)) {
+            eicDebug("Certificate is not signed by public key in the previous certificate");
+            return false;
+        }
+    }
+
+    // Store the key of this certificate, this is used to validate the next certificate
+    // and also ACPs with certificates that use the same public key...
+    ctx->readerPublicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+    if (!eicOpsX509GetPublicKey(certX509, certX509Size, ctx->readerPublicKey,
+                                &ctx->readerPublicKeySize)) {
+        eicDebug("Error extracting public key from certificate");
+        return false;
+    }
+    if (ctx->readerPublicKeySize == 0) {
+        eicDebug("Zero-length public key in certificate");
+        return false;
+    }
+
+    return true;
+}
+
+bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+                                 uint64_t authenticatorId, int hardwareAuthenticatorType,
+                                 uint64_t timeStamp, const uint8_t* mac, size_t macSize,
+                                 uint64_t verificationTokenChallenge,
+                                 uint64_t verificationTokenTimestamp,
+                                 int verificationTokenSecurityLevel,
+                                 const uint8_t* verificationTokenMac,
+                                 size_t verificationTokenMacSize) {
+    if (!eicOpsValidateAuthToken(
+                challenge, secureUserId, authenticatorId, hardwareAuthenticatorType, timeStamp, mac,
+                macSize, verificationTokenChallenge, verificationTokenTimestamp,
+                verificationTokenSecurityLevel, verificationTokenMac, verificationTokenMacSize)) {
+        return false;
+    }
+    ctx->authTokenChallenge = challenge;
+    ctx->authTokenSecureUserId = secureUserId;
+    ctx->authTokenTimestamp = timeStamp;
+    ctx->verificationTokenTimestamp = verificationTokenTimestamp;
+    return true;
+}
+
+static bool checkUserAuth(EicPresentation* ctx, bool userAuthenticationRequired, int timeoutMillis,
+                          uint64_t secureUserId) {
+    if (!userAuthenticationRequired) {
+        return true;
+    }
+
+    if (secureUserId != ctx->authTokenSecureUserId) {
+        eicDebug("secureUserId in profile differs from userId in authToken");
+        return false;
+    }
+
+    if (timeoutMillis == 0) {
+        if (ctx->authTokenChallenge == 0) {
+            eicDebug("No challenge in authToken");
+            return false;
+        }
+
+        // If we didn't create a challenge, too bad but user auth with
+        // timeoutMillis set to 0 needs it.
+        if (ctx->authChallenge == 0) {
+            eicDebug("No challenge was created for this session");
+            return false;
+        }
+        if (ctx->authTokenChallenge != ctx->authChallenge) {
+            eicDebug("Challenge in authToken (%" PRIu64
+                     ") doesn't match the challenge "
+                     "that was created (%" PRIu64 ") for this session",
+                     ctx->authTokenChallenge, ctx->authChallenge);
+            return false;
+        }
+    }
+
+    uint64_t now = ctx->verificationTokenTimestamp;
+    if (ctx->authTokenTimestamp > now) {
+        eicDebug("Timestamp in authToken is in the future");
+        return false;
+    }
+
+    if (timeoutMillis > 0) {
+        if (now > ctx->authTokenTimestamp + timeoutMillis) {
+            eicDebug("Deadline for authToken is in the past");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool checkReaderAuth(EicPresentation* ctx, const uint8_t* readerCertificate,
+                            size_t readerCertificateSize) {
+    uint8_t publicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+    size_t publicKeySize;
+
+    if (readerCertificateSize == 0) {
+        return true;
+    }
+
+    // Remember in this case certificate equality is done by comparing public
+    // keys, not bitwise comparison of the certificates.
+    //
+    publicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+    if (!eicOpsX509GetPublicKey(readerCertificate, readerCertificateSize, publicKey,
+                                &publicKeySize)) {
+        eicDebug("Error extracting public key from certificate");
+        return false;
+    }
+    if (publicKeySize == 0) {
+        eicDebug("Zero-length public key in certificate");
+        return false;
+    }
+
+    if ((ctx->readerPublicKeySize != publicKeySize) ||
+        (eicCryptoMemCmp(ctx->readerPublicKey, publicKey, ctx->readerPublicKeySize) != 0)) {
+        return false;
+    }
+    return true;
+}
+
+// Note: This function returns false _only_ if an error occurred check for access, _not_
+// whether access is granted. Whether access is granted is returned in |accessGranted|.
+//
+bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id,
+                                                 const uint8_t* readerCertificate,
+                                                 size_t readerCertificateSize,
+                                                 bool userAuthenticationRequired, int timeoutMillis,
+                                                 uint64_t secureUserId, const uint8_t mac[28],
+                                                 bool* accessGranted) {
+    *accessGranted = false;
+
+    if (id < 0 || id >= 32) {
+        eicDebug("id value of %d is out of allowed range [0, 32[", id);
+        return false;
+    }
+
+    // Validate the MAC
+    uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE];
+    EicCbor cborBuilder;
+    eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+    if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+                                  userAuthenticationRequired, timeoutMillis, secureUserId)) {
+        return false;
+    }
+    if (!eicOpsDecryptAes128Gcm(ctx->storageKey, mac, 28, cborBuilder.buffer, cborBuilder.size,
+                                NULL)) {
+        eicDebug("MAC for AccessControlProfile doesn't match");
+        return false;
+    }
+
+    bool passedUserAuth =
+            checkUserAuth(ctx, userAuthenticationRequired, timeoutMillis, secureUserId);
+    bool passedReaderAuth = checkReaderAuth(ctx, readerCertificate, readerCertificateSize);
+
+    ctx->accessControlProfileMaskValidated |= (1 << id);
+    if (readerCertificateSize > 0) {
+        ctx->accessControlProfileMaskUsesReaderAuth |= (1 << id);
+    }
+    if (!passedReaderAuth) {
+        ctx->accessControlProfileMaskFailedReaderAuth |= (1 << id);
+    }
+    if (!passedUserAuth) {
+        ctx->accessControlProfileMaskFailedUserAuth |= (1 << id);
+    }
+
+    if (passedUserAuth && passedReaderAuth) {
+        *accessGranted = true;
+        eicDebug("Access granted for id %d", id);
+    }
+    return true;
+}
+
+bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
+                               size_t sessionTranscriptSize,
+                               const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+                               const uint8_t signingKeyBlob[60], const char* docType,
+                               unsigned int numNamespacesWithValues,
+                               size_t expectedDeviceNamespacesSize) {
+    uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+    if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType,
+                                eicStrLen(docType), signingKeyPriv)) {
+        eicDebug("Error decrypting signingKeyBlob");
+        return false;
+    }
+
+    uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE];
+    if (!eicOpsEcdh(readerEphemeralPublicKey, signingKeyPriv, sharedSecret)) {
+        eicDebug("ECDH failed");
+        return false;
+    }
+
+    EicCbor cbor;
+    eicCborInit(&cbor, NULL, 0);
+    eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+    eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize);
+    uint8_t salt[EIC_SHA256_DIGEST_SIZE];
+    eicCborFinal(&cbor, salt);
+
+    const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
+    uint8_t derivedKey[32];
+    if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt), info, sizeof(info),
+                    derivedKey, sizeof(derivedKey))) {
+        eicDebug("HKDF failed");
+        return false;
+    }
+
+    eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey));
+    ctx->buildCbor = true;
+
+    // What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced
+    // structure which looks like the following:
+    //
+    // MAC_structure = [
+    //   context : "MAC" / "MAC0",
+    //   protected : empty_or_serialized_map,
+    //   external_aad : bstr,
+    //   payload : bstr
+    // ]
+    //
+    eicCborAppendArray(&ctx->cbor, 4);
+    eicCborAppendString(&ctx->cbor, "MAC0");
+
+    // The COSE Encoded protected headers is just a single field with
+    // COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just
+    // hard-code the CBOR encoding:
+    static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05};
+    eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+                            sizeof(coseEncodedProtectedHeaders));
+
+    // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+    // so external_aad is the empty bstr
+    static const uint8_t externalAad[0] = {};
+    eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+    // For the payload, the _encoded_ form follows here. We handle this by simply
+    // opening a bstr, and then writing the CBOR. This requires us to know the
+    // size of said bstr, ahead of time... the CBOR to be written is
+    //
+    //   DeviceAuthentication = [
+    //      "DeviceAuthentication",
+    //      SessionTranscript,
+    //      DocType,                ; DocType as used in Documents structure in OfflineResponse
+    //      DeviceNameSpacesBytes
+    //   ]
+    //
+    //   DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+    //
+    //   DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
+    //
+    // which is easily calculated below
+    //
+    size_t calculatedSize = 0;
+    calculatedSize += 1;  // Array of size 4
+    calculatedSize += 1;  // "DeviceAuthentication" less than 24 bytes
+    calculatedSize += sizeof("DeviceAuthentication") - 1;  // Don't include trailing NUL
+    calculatedSize += sessionTranscriptSize;               // Already CBOR encoded
+    size_t docTypeLen = eicStrLen(docType);
+    calculatedSize += 1 + eicCborAdditionalLengthBytesFor(docTypeLen) + docTypeLen;
+    calculatedSize += 2;  // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+    calculatedSize += 1 + eicCborAdditionalLengthBytesFor(expectedDeviceNamespacesSize);
+    calculatedSize += expectedDeviceNamespacesSize;
+
+    // However note that we're authenticating DeviceAuthenticationBytes which
+    // is a tagged bstr of the bytes of DeviceAuthentication. So need to get
+    // that in front.
+    size_t dabCalculatedSize = 0;
+    dabCalculatedSize += 2;  // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+    dabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+    dabCalculatedSize += calculatedSize;
+
+    // Begin the bytestring for DeviceAuthenticationBytes;
+    eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize);
+
+    eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+    // Begins the bytestring for DeviceAuthentication;
+    eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+    eicCborAppendArray(&ctx->cbor, 4);
+    eicCborAppendString(&ctx->cbor, "DeviceAuthentication");
+    eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize);
+    eicCborAppendString(&ctx->cbor, docType);
+
+    // For the payload, the _encoded_ form follows here. We handle this by simply
+    // opening a bstr, and then writing the CBOR. This requires us to know the
+    // size of said bstr, ahead of time.
+    eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+    eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize);
+    ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size;
+
+    eicCborAppendMap(&ctx->cbor, numNamespacesWithValues);
+    return true;
+}
+
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) {
+    // HAL may use this object multiple times to retrieve data so need to reset various
+    // state objects here.
+    ctx->requestMessageValidated = false;
+    ctx->buildCbor = false;
+    ctx->accessControlProfileMaskValidated = 0;
+    ctx->accessControlProfileMaskUsesReaderAuth = 0;
+    ctx->accessControlProfileMaskFailedReaderAuth = 0;
+    ctx->accessControlProfileMaskFailedUserAuth = 0;
+    ctx->readerPublicKeySize = 0;
+    return true;
+}
+
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+        EicPresentation* ctx, const char* nameSpace, const char* name,
+        unsigned int newNamespaceNumEntries, int32_t /* entrySize */,
+        const int* accessControlProfileIds, size_t numAccessControlProfileIds,
+        uint8_t* scratchSpace, size_t scratchSpaceSize) {
+    uint8_t* additionalDataCbor = scratchSpace;
+    const size_t additionalDataCborBufSize = scratchSpaceSize;
+    size_t additionalDataCborSize;
+
+    if (newNamespaceNumEntries > 0) {
+        eicCborAppendString(&ctx->cbor, nameSpace);
+        eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries);
+    }
+
+    // We'll need to calc and store a digest of additionalData to check that it's the same
+    // additionalData being passed in for every eicPresentationRetrieveEntryValue() call...
+    if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+                                        nameSpace, name, additionalDataCbor,
+                                        additionalDataCborBufSize, &additionalDataCborSize,
+                                        ctx->additionalDataSha256)) {
+        return EIC_ACCESS_CHECK_RESULT_FAILED;
+    }
+
+    if (numAccessControlProfileIds == 0) {
+        return EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES;
+    }
+
+    // Access is granted if at least one of the profiles grants access.
+    //
+    // If an item is configured without any profiles, access is denied.
+    //
+    EicAccessCheckResult result = EIC_ACCESS_CHECK_RESULT_FAILED;
+    for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+        int id = accessControlProfileIds[n];
+        uint32_t idBitMask = (1 << id);
+
+        // If the access control profile wasn't validated, this is an error and we
+        // fail immediately.
+        bool validated = ((ctx->accessControlProfileMaskValidated & idBitMask) != 0);
+        if (!validated) {
+            eicDebug("No ACP for profile id %d", id);
+            return EIC_ACCESS_CHECK_RESULT_FAILED;
+        }
+
+        // Otherwise, we _did_ validate the profile. If none of the checks
+        // failed, we're done
+        bool failedUserAuth = ((ctx->accessControlProfileMaskFailedUserAuth & idBitMask) != 0);
+        bool failedReaderAuth = ((ctx->accessControlProfileMaskFailedReaderAuth & idBitMask) != 0);
+        if (!failedUserAuth && !failedReaderAuth) {
+            result = EIC_ACCESS_CHECK_RESULT_OK;
+            break;
+        }
+        // One of the checks failed, convey which one
+        if (failedUserAuth) {
+            result = EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED;
+        } else {
+            result = EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED;
+        }
+    }
+    eicDebug("Result %d for name %s", result, name);
+
+    if (result == EIC_ACCESS_CHECK_RESULT_OK) {
+        eicCborAppendString(&ctx->cbor, name);
+    }
+    return result;
+}
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent,
+                                       size_t encryptedContentSize, uint8_t* content,
+                                       const char* nameSpace, const char* name,
+                                       const int* accessControlProfileIds,
+                                       size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+                                       size_t scratchSpaceSize) {
+    uint8_t* additionalDataCbor = scratchSpace;
+    const size_t additionalDataCborBufSize = scratchSpaceSize;
+    size_t additionalDataCborSize;
+
+    uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+    if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+                                        nameSpace, name, additionalDataCbor,
+                                        additionalDataCborBufSize, &additionalDataCborSize,
+                                        calculatedSha256)) {
+        return false;
+    }
+    if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) {
+        eicDebug("SHA-256 mismatch of additionalData");
+        return false;
+    }
+
+    if (!eicOpsDecryptAes128Gcm(ctx->storageKey, encryptedContent, encryptedContentSize,
+                                additionalDataCbor, additionalDataCborSize, content)) {
+        eicDebug("Error decrypting content");
+        return false;
+    }
+
+    eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28);
+
+    return true;
+}
+
+bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced,
+                                    size_t* digestToBeMacedSize) {
+    if (!ctx->buildCbor) {
+        *digestToBeMacedSize = 0;
+        return true;
+    }
+    if (*digestToBeMacedSize != 32) {
+        return false;
+    }
+
+    // This verifies that the correct expectedDeviceNamespacesSize value was
+    // passed in at eicPresentationCalcMacKey() time.
+    if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+        eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd);
+        return false;
+    }
+    eicCborFinal(&ctx->cbor, digestToBeMaced);
+    return true;
+}
+
+bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
+                                     const uint8_t* challenge, size_t challengeSize,
+                                     bool includeChallenge, size_t proofOfDeletionCborSize,
+                                     uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+    EicCbor cbor;
+
+    eicCborInit(&cbor, NULL, 0);
+
+    // What we're going to sign is the COSE ToBeSigned structure which
+    // looks like the following:
+    //
+    // Sig_structure = [
+    //   context : "Signature" / "Signature1" / "CounterSignature",
+    //   body_protected : empty_or_serialized_map,
+    //   ? sign_protected : empty_or_serialized_map,
+    //   external_aad : bstr,
+    //   payload : bstr
+    //  ]
+    //
+    eicCborAppendArray(&cbor, 4);
+    eicCborAppendString(&cbor, "Signature1");
+
+    // The COSE Encoded protected headers is just a single field with
+    // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+    // hard-code the CBOR encoding:
+    static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+    eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+                            sizeof(coseEncodedProtectedHeaders));
+
+    // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+    // so external_aad is the empty bstr
+    static const uint8_t externalAad[0] = {};
+    eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+    // For the payload, the _encoded_ form follows here. We handle this by simply
+    // opening a bstr, and then writing the CBOR. This requires us to know the
+    // size of said bstr, ahead of time.
+    eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfDeletionCborSize);
+
+    // Finally, the CBOR that we're actually signing.
+    eicCborAppendArray(&cbor, includeChallenge ? 4 : 3);
+    eicCborAppendString(&cbor, "ProofOfDeletion");
+    eicCborAppendString(&cbor, docType);
+    if (includeChallenge) {
+        eicCborAppendByteString(&cbor, challenge, challengeSize);
+    }
+    eicCborAppendBool(&cbor, ctx->testCredential);
+
+    uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+    eicCborFinal(&cbor, cborSha256);
+    if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
+        eicDebug("Error signing proofOfDeletion");
+        return false;
+    }
+
+    return true;
+}
+
+bool eicPresentationProveOwnership(EicPresentation* ctx, const char* docType, bool testCredential,
+                                   const uint8_t* challenge, size_t challengeSize,
+                                   size_t proofOfOwnershipCborSize,
+                                   uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+    EicCbor cbor;
+
+    eicCborInit(&cbor, NULL, 0);
+
+    // What we're going to sign is the COSE ToBeSigned structure which
+    // looks like the following:
+    //
+    // Sig_structure = [
+    //   context : "Signature" / "Signature1" / "CounterSignature",
+    //   body_protected : empty_or_serialized_map,
+    //   ? sign_protected : empty_or_serialized_map,
+    //   external_aad : bstr,
+    //   payload : bstr
+    //  ]
+    //
+    eicCborAppendArray(&cbor, 4);
+    eicCborAppendString(&cbor, "Signature1");
+
+    // The COSE Encoded protected headers is just a single field with
+    // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+    // hard-code the CBOR encoding:
+    static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+    eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+                            sizeof(coseEncodedProtectedHeaders));
+
+    // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+    // so external_aad is the empty bstr
+    static const uint8_t externalAad[0] = {};
+    eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+    // For the payload, the _encoded_ form follows here. We handle this by simply
+    // opening a bstr, and then writing the CBOR. This requires us to know the
+    // size of said bstr, ahead of time.
+    eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfOwnershipCborSize);
+
+    // Finally, the CBOR that we're actually signing.
+    eicCborAppendArray(&cbor, 4);
+    eicCborAppendString(&cbor, "ProofOfOwnership");
+    eicCborAppendString(&cbor, docType);
+    eicCborAppendByteString(&cbor, challenge, challengeSize);
+    eicCborAppendBool(&cbor, testCredential);
+
+    uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+    eicCborFinal(&cbor, cborSha256);
+    if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
+        eicDebug("Error signing proofOfDeletion");
+        return false;
+    }
+
+    return true;
+}
diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h
new file mode 100644
index 0000000..7cad068
--- /dev/null
+++ b/identity/aidl/default/libeic/EicPresentation.h
@@ -0,0 +1,246 @@
+/*
+ * Copyright 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.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+// The maximum size we support for public keys in reader certificates.
+#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65
+
+typedef struct {
+    int featureLevel;
+
+    uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+    uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+    uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+    // The challenge generated with eicPresentationCreateAuthChallenge()
+    uint64_t authChallenge;
+
+    // Set by eicPresentationSetAuthToken() and contains the fields
+    // from the passed in authToken and verificationToken.
+    //
+    uint64_t authTokenChallenge;
+    uint64_t authTokenSecureUserId;
+    uint64_t authTokenTimestamp;
+    uint64_t verificationTokenTimestamp;
+
+    // The public key for the reader.
+    //
+    // (During the process of pushing reader certificates, this is also used to store
+    // the public key of the previously pushed certificate.)
+    //
+    uint8_t readerPublicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+    size_t readerPublicKeySize;
+
+    // This is set to true only if eicPresentationValidateRequestMessage() successfully
+    // validated the requestMessage.
+    //
+    // Why even record this? Because there's no requirement the HAL actually calls that
+    // function and we validate ACPs before it's called... so it's possible that a
+    // compromised HAL could trick us into marking ACPs as authorized while they in fact
+    // aren't.
+    bool requestMessageValidated;
+    bool buildCbor;
+
+    // Set to true initialized as a test credential.
+    bool testCredential;
+
+    // These are bitmasks indicating which of the possible 32 access control profiles are
+    // authorized. They are built up by eicPresentationValidateAccessControlProfile().
+    //
+    uint32_t accessControlProfileMaskValidated;         // True if the profile was validated.
+    uint32_t accessControlProfileMaskUsesReaderAuth;    // True if the ACP is using reader auth
+    uint32_t accessControlProfileMaskFailedReaderAuth;  // True if failed reader auth
+    uint32_t accessControlProfileMaskFailedUserAuth;    // True if failed user auth
+
+    // SHA-256 for AdditionalData, updated for each entry.
+    uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+    // SHA-256 of ProofOfProvisioning. Set to NUL-bytes or initialized from CredentialKeys data
+    // if credential was created with feature version 202101 or later.
+    uint8_t proofOfProvisioningSha256[EIC_SHA256_DIGEST_SIZE];
+
+    size_t expectedCborSizeAtEnd;
+    EicCbor cbor;
+} EicPresentation;
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
+                         const uint8_t* encryptedCredentialKeys,
+                         size_t encryptedCredentialKeysSize);
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now,
+                                           uint8_t* publicKeyCert, size_t* publicKeyCertSize,
+                                           uint8_t signingKeyBlob[60]);
+
+// Create an ephemeral key-pair.
+//
+// The private key is stored in |ctx->ephemeralPrivateKey| and also returned in
+// |ephemeralPrivateKey|.
+//
+bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx,
+                                           uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]);
+
+// Returns a non-zero challenge in |authChallenge|.
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge);
+
+// Starts retrieveing entries.
+//
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx);
+
+// Sets the auth-token.
+bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+                                 uint64_t authenticatorId, int hardwareAuthenticatorType,
+                                 uint64_t timeStamp, const uint8_t* mac, size_t macSize,
+                                 uint64_t verificationTokenChallenge,
+                                 uint64_t verificationTokenTimeStamp,
+                                 int verificationTokenSecurityLevel,
+                                 const uint8_t* verificationTokenMac,
+                                 size_t verificationTokenMacSize);
+
+// Function to push certificates in the reader certificate chain.
+//
+// This should start with the root certificate (e.g. the last in the chain) and
+// continue up the chain, ending with the certificate for the reader.
+//
+// Calls to this function should be interleaved with calls to the
+// eicPresentationValidateAccessControlProfile() function, see below.
+//
+bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509,
+                                   size_t certX509Size);
+
+// Checks an access control profile.
+//
+// Returns false if an error occurred while checking the profile (e.g. MAC doesn't check out).
+//
+// Returns in |accessGranted| whether access is granted.
+//
+// If |readerCertificate| is non-empty and the public key of one of those
+// certificates appear in the chain presented by the reader, this function must
+// be called after pushing that certificate using
+// eicPresentationPushReaderCert().
+//
+bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id,
+                                                 const uint8_t* readerCertificate,
+                                                 size_t readerCertificateSize,
+                                                 bool userAuthenticationRequired, int timeoutMillis,
+                                                 uint64_t secureUserId, const uint8_t mac[28],
+                                                 bool* accessGranted);
+
+// Validates that the given requestMessage is signed by the public key in the
+// certificate last set with eicPresentationPushReaderCert().
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// Must be called after eicPresentationPushReaderCert() have been used to push
+// the final certificate. Which is the certificate of the reader itself.
+//
+bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript,
+                                           size_t sessionTranscriptSize,
+                                           const uint8_t* requestMessage, size_t requestMessageSize,
+                                           int coseSignAlg,
+                                           const uint8_t* readerSignatureOfToBeSigned,
+                                           size_t readerSignatureOfToBeSignedSize);
+
+typedef enum {
+    // Returned if access is granted.
+    EIC_ACCESS_CHECK_RESULT_OK,
+
+    // Returned if an error occurred checking for access.
+    EIC_ACCESS_CHECK_RESULT_FAILED,
+
+    // Returned if access was denied because item is configured without any
+    // access control profiles.
+    EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES,
+
+    // Returned if access was denied because of user authentication.
+    EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED,
+
+    // Returned if access was denied because of reader authentication.
+    EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED,
+} EicAccessCheckResult;
+
+// Passes enough information to calculate the MACing key
+//
+bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
+                               size_t sessionTranscriptSize,
+                               const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+                               const uint8_t signingKeyBlob[60], const char* docType,
+                               unsigned int numNamespacesWithValues,
+                               size_t expectedDeviceNamespacesSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024
+// bytes, the bigger the better). It's done this way to avoid allocating stack
+// space.
+//
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+        EicPresentation* ctx, const char* nameSpace, const char* name,
+        unsigned int newNamespaceNumEntries, int32_t entrySize, const int* accessControlProfileIds,
+        size_t numAccessControlProfileIds, uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent,
+                                       size_t encryptedContentSize, uint8_t* content,
+                                       const char* nameSpace, const char* name,
+                                       const int* accessControlProfileIds,
+                                       size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+                                       size_t scratchSpaceSize);
+
+// Returns the HMAC-SHA256 of |ToBeMaced| as per RFC 8051 "6.3. How to Compute
+// and Verify a MAC".
+bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced,
+                                    size_t* digestToBeMacedSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfDeletion CBOR.
+//
+bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
+                                     const uint8_t* challenge, size_t challengeSize,
+                                     bool includeChallenge, size_t proofOfDeletionCborSize,
+                                     uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfOwnership CBOR.
+//
+bool eicPresentationProveOwnership(EicPresentation* ctx, const char* docType, bool testCredential,
+                                   const uint8_t* challenge, size_t challengeSize,
+                                   size_t proofOfOwnershipCborSize,
+                                   uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
diff --git a/identity/aidl/default/libeic/EicProvisioning.c b/identity/aidl/default/libeic/EicProvisioning.c
new file mode 100644
index 0000000..3b4148e
--- /dev/null
+++ b/identity/aidl/default/libeic/EicProvisioning.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright 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 "EicProvisioning.h"
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) {
+    eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+    ctx->testCredential = testCredential;
+    if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, const char* docType,
+                                  const uint8_t* encryptedCredentialKeys,
+                                  size_t encryptedCredentialKeysSize) {
+    uint8_t credentialKeys[86];
+
+    // For feature version 202009 it's 52 bytes long and for feature version 202101 it's 86
+    // bytes (the additional data is the ProofOfProvisioning SHA-256). We need
+    // to support loading all feature versions.
+    //
+    bool expectPopSha256 = false;
+    if (encryptedCredentialKeysSize == 52 + 28) {
+        /* do nothing */
+    } else if (encryptedCredentialKeysSize == 86 + 28) {
+        expectPopSha256 = true;
+    } else {
+        eicDebug("Unexpected size %zd for encryptedCredentialKeys", encryptedCredentialKeysSize);
+        return false;
+    }
+
+    eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+    ctx->testCredential = testCredential;
+
+    if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+                                encryptedCredentialKeysSize,
+                                // DocType is the additionalAuthenticatedData
+                                (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) {
+        eicDebug("Error decrypting CredentialKeys");
+        return false;
+    }
+
+    // It's supposed to look like this;
+    //
+    // Feature version 202009:
+    //
+    //         CredentialKeys = [
+    //              bstr,   ; storageKey, a 128-bit AES key
+    //              bstr,   ; credentialPrivKey, the private key for credentialKey
+    //         ]
+    //
+    // Feature version 202101:
+    //
+    //         CredentialKeys = [
+    //              bstr,   ; storageKey, a 128-bit AES key
+    //              bstr,   ; credentialPrivKey, the private key for credentialKey
+    //              bstr    ; proofOfProvisioning SHA-256
+    //         ]
+    //
+    // where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and proofOfProvisioning
+    // SHA-256 is 32 bytes.
+    //
+    if (credentialKeys[0] != (expectPopSha256 ? 0x83 : 0x82) ||  // array of two or three elements
+        credentialKeys[1] != 0x50 ||                             // 16-byte bstr
+        credentialKeys[18] != 0x58 || credentialKeys[19] != 0x20) {  // 32-byte bstr
+        eicDebug("Invalid CBOR for CredentialKeys");
+        return false;
+    }
+    if (expectPopSha256) {
+        if (credentialKeys[52] != 0x58 || credentialKeys[53] != 0x20) {  // 32-byte bstr
+            eicDebug("Invalid CBOR for CredentialKeys");
+            return false;
+        }
+    }
+    eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+    eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE);
+    // Note: We don't care about the previous ProofOfProvisioning SHA-256
+    ctx->isUpdate = true;
+    return true;
+}
+
+bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
+                                        size_t challengeSize, const uint8_t* applicationId,
+                                        size_t applicationIdSize, uint8_t* publicKeyCert,
+                                        size_t* publicKeyCertSize) {
+    if (ctx->isUpdate) {
+        eicDebug("Cannot create CredentialKey on update");
+        return false;
+    }
+
+    if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge, challengeSize,
+                                   applicationId, applicationIdSize, ctx->testCredential,
+                                   publicKeyCert, publicKeyCertSize)) {
+        return false;
+    }
+    return true;
+}
+
+bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount,
+                                         const int* entryCounts, size_t numEntryCounts,
+                                         const char* docType,
+                                         size_t expectedProofOfProvisioningSize) {
+    if (numEntryCounts >= EIC_MAX_NUM_NAMESPACES) {
+        return false;
+    }
+    if (accessControlProfileCount >= EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS) {
+        return false;
+    }
+
+    ctx->numEntryCounts = numEntryCounts;
+    if (numEntryCounts > EIC_MAX_NUM_NAMESPACES) {
+        return false;
+    }
+    for (size_t n = 0; n < numEntryCounts; n++) {
+        if (entryCounts[n] >= 256) {
+            return false;
+        }
+        ctx->entryCounts[n] = entryCounts[n];
+    }
+    ctx->curNamespace = -1;
+    ctx->curNamespaceNumProcessed = 0;
+
+    eicCborInit(&ctx->cbor, NULL, 0);
+
+    // What we're going to sign is the COSE ToBeSigned structure which
+    // looks like the following:
+    //
+    // Sig_structure = [
+    //   context : "Signature" / "Signature1" / "CounterSignature",
+    //   body_protected : empty_or_serialized_map,
+    //   ? sign_protected : empty_or_serialized_map,
+    //   external_aad : bstr,
+    //   payload : bstr
+    //  ]
+    //
+    eicCborAppendArray(&ctx->cbor, 4);
+    eicCborAppendString(&ctx->cbor, "Signature1");
+
+    // The COSE Encoded protected headers is just a single field with
+    // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+    // hard-code the CBOR encoding:
+    static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+    eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+                            sizeof(coseEncodedProtectedHeaders));
+
+    // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+    // so external_aad is the empty bstr
+    static const uint8_t externalAad[0] = {};
+    eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+    // For the payload, the _encoded_ form follows here. We handle this by simply
+    // opening a bstr, and then writing the CBOR. This requires us to know the
+    // size of said bstr, ahead of time.
+    eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedProofOfProvisioningSize);
+    ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size;
+
+    eicOpsSha256Init(&ctx->proofOfProvisioningDigester);
+    eicCborEnableSecondaryDigesterSha256(&ctx->cbor, &ctx->proofOfProvisioningDigester);
+
+    eicCborAppendArray(&ctx->cbor, 5);
+    eicCborAppendString(&ctx->cbor, "ProofOfProvisioning");
+    eicCborAppendString(&ctx->cbor, docType);
+
+    eicCborAppendArray(&ctx->cbor, accessControlProfileCount);
+
+    return true;
+}
+
+bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id,
+                                            const uint8_t* readerCertificate,
+                                            size_t readerCertificateSize,
+                                            bool userAuthenticationRequired, uint64_t timeoutMillis,
+                                            uint64_t secureUserId, uint8_t outMac[28]) {
+    uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE];
+    EicCbor cborBuilder;
+
+    eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+
+    if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+                                  userAuthenticationRequired, timeoutMillis, secureUserId)) {
+        return false;
+    }
+
+    // Calculate and return MAC
+    uint8_t nonce[12];
+    if (!eicOpsRandom(nonce, 12)) {
+        return false;
+    }
+    if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, NULL, 0, cborBuilder.buffer,
+                                cborBuilder.size, outMac)) {
+        return false;
+    }
+
+    // The ACP CBOR in the provisioning receipt doesn't include secureUserId so build
+    // it again.
+    eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+    if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+                                  userAuthenticationRequired, timeoutMillis,
+                                  0 /* secureUserId */)) {
+        return false;
+    }
+
+    // Append the CBOR from the local builder to the digester.
+    eicCborAppend(&ctx->cbor, cborBuilder.buffer, cborBuilder.size);
+
+    return true;
+}
+
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds,
+                                  size_t numAccessControlProfileIds, const char* nameSpace,
+                                  const char* name, uint64_t entrySize, uint8_t* scratchSpace,
+                                  size_t scratchSpaceSize) {
+    uint8_t* additionalDataCbor = scratchSpace;
+    const size_t additionalDataCborBufSize = scratchSpaceSize;
+    size_t additionalDataCborSize;
+
+    // We'll need to calc and store a digest of additionalData to check that it's the same
+    // additionalData being passed in for every eicProvisioningAddEntryValue() call...
+    if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+                                        nameSpace, name, additionalDataCbor,
+                                        additionalDataCborBufSize, &additionalDataCborSize,
+                                        ctx->additionalDataSha256)) {
+        return false;
+    }
+
+    if (ctx->curNamespace == -1) {
+        ctx->curNamespace = 0;
+        ctx->curNamespaceNumProcessed = 0;
+        // Opens the main map: { * Namespace => [ + Entry ] }
+        eicCborAppendMap(&ctx->cbor, ctx->numEntryCounts);
+        eicCborAppendString(&ctx->cbor, nameSpace);
+        // Opens the per-namespace array: [ + Entry ]
+        eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+    }
+
+    if (ctx->curNamespaceNumProcessed == ctx->entryCounts[ctx->curNamespace]) {
+        ctx->curNamespace += 1;
+        ctx->curNamespaceNumProcessed = 0;
+        eicCborAppendString(&ctx->cbor, nameSpace);
+        // Opens the per-namespace array: [ + Entry ]
+        eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+    }
+
+    eicCborAppendMap(&ctx->cbor, 3);
+    eicCborAppendString(&ctx->cbor, "name");
+    eicCborAppendString(&ctx->cbor, name);
+
+    ctx->curEntrySize = entrySize;
+    ctx->curEntryNumBytesReceived = 0;
+
+    eicCborAppendString(&ctx->cbor, "value");
+
+    ctx->curNamespaceNumProcessed += 1;
+    return true;
+}
+
+bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds,
+                                  size_t numAccessControlProfileIds, const char* nameSpace,
+                                  const char* name, const uint8_t* content, size_t contentSize,
+                                  uint8_t* outEncryptedContent, uint8_t* scratchSpace,
+                                  size_t scratchSpaceSize) {
+    uint8_t* additionalDataCbor = scratchSpace;
+    const size_t additionalDataCborBufSize = scratchSpaceSize;
+    size_t additionalDataCborSize;
+
+    uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+    if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+                                        nameSpace, name, additionalDataCbor,
+                                        additionalDataCborBufSize, &additionalDataCborSize,
+                                        calculatedSha256)) {
+        return false;
+    }
+    if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) {
+        eicDebug("SHA-256 mismatch of additionalData");
+        return false;
+    }
+
+    eicCborAppend(&ctx->cbor, content, contentSize);
+
+    uint8_t nonce[12];
+    if (!eicOpsRandom(nonce, 12)) {
+        return false;
+    }
+    if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, content, contentSize, additionalDataCbor,
+                                additionalDataCborSize, outEncryptedContent)) {
+        return false;
+    }
+
+    // If done with this entry, close the map
+    ctx->curEntryNumBytesReceived += contentSize;
+    if (ctx->curEntryNumBytesReceived == ctx->curEntrySize) {
+        eicCborAppendString(&ctx->cbor, "accessControlProfiles");
+        eicCborAppendArray(&ctx->cbor, numAccessControlProfileIds);
+        for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+            eicCborAppendNumber(&ctx->cbor, accessControlProfileIds[n]);
+        }
+    }
+    return true;
+}
+
+bool eicProvisioningFinishAddingEntries(
+        EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+    uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+
+    eicCborAppendBool(&ctx->cbor, ctx->testCredential);
+    eicCborFinal(&ctx->cbor, cborSha256);
+
+    // This verifies that the correct expectedProofOfProvisioningSize value was
+    // passed in at eicStartPersonalization() time.
+    if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+        eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd);
+        return false;
+    }
+
+    if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
+        eicDebug("Error signing proofOfProvisioning");
+        return false;
+    }
+
+    return true;
+}
+
+bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
+                                            uint8_t* encryptedCredentialKeys,
+                                            size_t* encryptedCredentialKeysSize) {
+    EicCbor cbor;
+    uint8_t cborBuf[86];
+
+    if (*encryptedCredentialKeysSize < 86 + 28) {
+        eicDebug("encryptedCredentialKeysSize is %zd which is insufficient");
+        return false;
+    }
+
+    eicCborInit(&cbor, cborBuf, sizeof(cborBuf));
+    eicCborAppendArray(&cbor, 3);
+    eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE);
+    eicCborAppendByteString(&cbor, ctx->credentialPrivateKey, EIC_P256_PRIV_KEY_SIZE);
+    uint8_t popSha256[EIC_SHA256_DIGEST_SIZE];
+    eicOpsSha256Final(&ctx->proofOfProvisioningDigester, popSha256);
+    eicCborAppendByteString(&cbor, popSha256, EIC_SHA256_DIGEST_SIZE);
+    if (cbor.size > sizeof(cborBuf)) {
+        eicDebug("Exceeded buffer size");
+        return false;
+    }
+
+    uint8_t nonce[12];
+    if (!eicOpsRandom(nonce, 12)) {
+        eicDebug("Error getting random");
+        return false;
+    }
+    if (!eicOpsEncryptAes128Gcm(
+                eicOpsGetHardwareBoundKey(ctx->testCredential), nonce, cborBuf, cbor.size,
+                // DocType is the additionalAuthenticatedData
+                (const uint8_t*)docType, eicStrLen(docType), encryptedCredentialKeys)) {
+        eicDebug("Error encrypting CredentialKeys");
+        return false;
+    }
+    *encryptedCredentialKeysSize = cbor.size + 28;
+
+    return true;
+}
diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h
new file mode 100644
index 0000000..f064787
--- /dev/null
+++ b/identity/aidl/default/libeic/EicProvisioning.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright 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.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+#define EIC_MAX_NUM_NAMESPACES 32
+#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32
+
+typedef struct {
+    // Set by eicCreateCredentialKey() OR eicProvisioningInitForUpdate()
+    uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+    int numEntryCounts;
+    uint8_t entryCounts[EIC_MAX_NUM_NAMESPACES];
+
+    int curNamespace;
+    int curNamespaceNumProcessed;
+
+    size_t curEntrySize;
+    size_t curEntryNumBytesReceived;
+
+    // Set by eicProvisioningInit() OR eicProvisioningInitForUpdate()
+    uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+
+    size_t expectedCborSizeAtEnd;
+
+    // SHA-256 for AdditionalData, updated for each entry.
+    uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+    // Digester just for ProofOfProvisioning (without Sig_structure).
+    EicSha256Ctx proofOfProvisioningDigester;
+
+    EicCbor cbor;
+
+    bool testCredential;
+
+    // Set to true if this is an update.
+    bool isUpdate;
+} EicProvisioning;
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential);
+
+bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, const char* docType,
+                                  const uint8_t* encryptedCredentialKeys,
+                                  size_t encryptedCredentialKeysSize);
+
+bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
+                                        size_t challengeSize, const uint8_t* applicationId,
+                                        size_t applicationIdSize, uint8_t* publicKeyCert,
+                                        size_t* publicKeyCertSize);
+
+bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount,
+                                         const int* entryCounts, size_t numEntryCounts,
+                                         const char* docType,
+                                         size_t expectedProofOfProvisioningingSize);
+
+bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id,
+                                            const uint8_t* readerCertificate,
+                                            size_t readerCertificateSize,
+                                            bool userAuthenticationRequired, uint64_t timeoutMillis,
+                                            uint64_t secureUserId, uint8_t outMac[28]);
+
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds,
+                                  size_t numAccessControlProfileIds, const char* nameSpace,
+                                  const char* name, uint64_t entrySize, uint8_t* scratchSpace,
+                                  size_t scratchSpaceSize);
+
+// The outEncryptedContent array must be contentSize + 28 bytes long.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds,
+                                  size_t numAccessControlProfileIds, const char* nameSpace,
+                                  const char* name, const uint8_t* content, size_t contentSize,
+                                  uint8_t* outEncryptedContent, uint8_t* scratchSpace,
+                                  size_t scratchSpaceSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfProvisioninging CBOR.
+//
+bool eicProvisioningFinishAddingEntries(
+        EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+//
+//
+// The |encryptedCredentialKeys| array is set to AES-GCM-ENC(HBK, R, CredentialKeys, docType)
+// where
+//
+//   CredentialKeys = [
+//     bstr,   ; storageKey, a 128-bit AES key
+//     bstr    ; credentialPrivKey, the private key for credentialKey
+//     bstr    ; SHA-256(ProofOfProvisioning)
+//   ]
+//
+// for feature version 202101. For feature version 202009 the third field was not present.
+//
+// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the
+// encoded CBOR for CredentialKeys is 86 bytes and consequently
+// |encryptedCredentialKeys| will be no longer than 86 + 28 = 114 bytes.
+//
+bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
+                                            uint8_t* encryptedCredentialKeys,
+                                            size_t* encryptedCredentialKeysSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
diff --git a/identity/aidl/default/libeic/libeic.h b/identity/aidl/default/libeic/libeic.h
new file mode 100644
index 0000000..88abef8
--- /dev/null
+++ b/identity/aidl/default/libeic/libeic.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+#define ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The EIC_INSIDE_LIBEIC_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+#define EIC_INSIDE_LIBEIC_H
+#include "EicCbor.h"
+#include "EicOps.h"
+#include "EicPresentation.h"
+#include "EicProvisioning.h"
+#undef EIC_INSIDE_LIBEIC_H
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_LIBEIC_H
diff --git a/identity/aidl/default/service.cpp b/identity/aidl/default/service.cpp
index bf95df5..c290c08 100644
--- a/identity/aidl/default/service.cpp
+++ b/identity/aidl/default/service.cpp
@@ -22,20 +22,26 @@
 
 #include "IdentityCredentialStore.h"
 
+#include "FakeSecureHardwareProxy.h"
+
+using ::android::sp;
 using ::android::base::InitLogging;
 using ::android::base::StderrLogger;
 
-using aidl::android::hardware::identity::IdentityCredentialStore;
+using ::aidl::android::hardware::identity::IdentityCredentialStore;
+using ::android::hardware::identity::FakeSecureHardwareProxyFactory;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
 
 int main(int /*argc*/, char* argv[]) {
     InitLogging(argv, StderrLogger);
 
+    sp<SecureHardwareProxyFactory> hwProxyFactory = new FakeSecureHardwareProxyFactory();
+
     ABinderProcess_setThreadPoolMaxThreadCount(0);
     std::shared_ptr<IdentityCredentialStore> store =
-            ndk::SharedRefBase::make<IdentityCredentialStore>();
+            ndk::SharedRefBase::make<IdentityCredentialStore>(hwProxyFactory);
 
     const std::string instance = std::string() + IdentityCredentialStore::descriptor + "/default";
-    LOG(INFO) << "instance: " << instance;
     binder_status_t status = AServiceManager_addService(store->asBinder().get(), instance.c_str());
     CHECK(status == STATUS_OK);
 
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
index 03966de..f487a64 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -4,13 +4,21 @@
         "VtsHalTargetTestDefaults",
         "use_libaidlvintf_gtest_helper_static",
     ],
+    cflags: [
+        "-Wno-deprecated-declarations",
+    ],
     srcs: [
-        "VtsHalIdentityEndToEndTest.cpp",
         "VtsIWritableIdentityCredentialTests.cpp",
-        "VtsIdentityTestUtils.cpp",
+        "Util.cpp",
         "VtsAttestationTests.cpp",
         "UserAuthTests.cpp",
         "ReaderAuthTests.cpp",
+        "DeleteCredentialTests.cpp",
+        "ProveOwnershipTests.cpp",
+        "UpdateCredentialTests.cpp",
+        "EndToEndTests.cpp",
+        "TestCredentialTests.cpp",
+        "AuthenticationKeyTests.cpp",
     ],
     shared_libs: [
         "libbinder",
@@ -22,9 +30,9 @@
         "libpuresoftkeymasterdevice",
         "android.hardware.keymaster@4.0",
         "android.hardware.identity-support-lib",
-        "android.hardware.identity-cpp",
-        "android.hardware.keymaster-cpp",
-        "android.hardware.keymaster-ndk_platform",
+        "android.hardware.identity-V3-cpp",
+        "android.hardware.keymaster-V3-cpp",
+        "android.hardware.keymaster-V3-ndk_platform",
         "libkeymaster4support",
         "libkeymaster4_1support",
     ],
diff --git a/identity/aidl/vts/AuthenticationKeyTests.cpp b/identity/aidl/vts/AuthenticationKeyTests.cpp
new file mode 100644
index 0000000..bda3e70
--- /dev/null
+++ b/identity/aidl/vts/AuthenticationKeyTests.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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 "TestCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class AuthenticationKeyTests : public testing::TestWithParam<string> {
+  public:
+    virtual void SetUp() override {
+        string halInstanceName = GetParam();
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(halInstanceName.c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+        halApiVersion_ = credentialStore_->getInterfaceVersion();
+    }
+
+    sp<IIdentityCredentialStore> credentialStore_;
+    int halApiVersion_;
+};
+
+TEST_P(AuthenticationKeyTests, proofOfProvisionInAuthKeyCert) {
+    if (halApiVersion_ < 3) {
+        GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
+    }
+
+    string docType = "org.iso.18013-5.2019.mdl";
+    sp<IWritableIdentityCredential> wc;
+    ASSERT_TRUE(credentialStore_
+                        ->createCredential(docType,
+                                           true,  // testCredential
+                                           &wc)
+                        .isOk());
+
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge = {1};
+    vector<Certificate> certChain;
+    ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+                                              &certChain)
+                        .isOk());
+
+    optional<vector<uint8_t>> optCredentialPubKey =
+            support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+    ASSERT_TRUE(optCredentialPubKey);
+    vector<uint8_t> credentialPubKey;
+    credentialPubKey = optCredentialPubKey.value();
+
+    size_t proofOfProvisioningSize = 112;
+    // Not in v1 HAL, may fail
+    wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+    ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+                                         {1} /* numDataElementsPerNamespace */)
+                        .isOk());
+
+    // Access control profile 0: open access - don't care about the returned SACP
+    SecureAccessControlProfile sacp;
+    ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+    // Single entry - don't care about the returned encrypted data
+    vector<uint8_t> encryptedData;
+    vector<uint8_t> tstrLastName = cppbor::Tstr("Turing").encode();
+    ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
+    ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
+
+    vector<uint8_t> proofOfProvisioningSignature;
+    vector<uint8_t> credentialData;
+    Status status = wc->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+    EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+
+    optional<vector<uint8_t>> proofOfProvisioning =
+            support::coseSignGetPayload(proofOfProvisioningSignature);
+    ASSERT_TRUE(proofOfProvisioning);
+    string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
+    EXPECT_EQ(
+            "[\n"
+            "  'ProofOfProvisioning',\n"
+            "  'org.iso.18013-5.2019.mdl',\n"
+            "  [\n"
+            "    {\n"
+            "      'id' : 1,\n"
+            "    },\n"
+            "  ],\n"
+            "  {\n"
+            "    'ns' : [\n"
+            "      {\n"
+            "        'name' : 'Last name',\n"
+            "        'value' : 'Turing',\n"
+            "        'accessControlProfiles' : [1, ],\n"
+            "      },\n"
+            "    ],\n"
+            "  },\n"
+            "  true,\n"
+            "]",
+            cborPretty);
+    // Make sure it's signed by the CredentialKey in the returned cert chain.
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+                                                 {},  // Additional data
+                                                 credentialPubKey));
+
+    // Now get a credential and have it create AuthenticationKey so we can check
+    // the certificate.
+    sp<IIdentityCredential> credential;
+    ASSERT_TRUE(credentialStore_
+                        ->getCredential(
+                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+                                credentialData, &credential)
+                        .isOk());
+    vector<uint8_t> signingKeyBlob;
+    Certificate signingKeyCertificate;
+    ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+    optional<vector<uint8_t>> signingPubKey =
+            support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
+    EXPECT_TRUE(signingPubKey);
+
+    // SHA-256(ProofOfProvisioning) is embedded in CBOR with the following CDDL
+    //
+    //   ProofOfBinding = [
+    //     "ProofOfBinding",
+    //     bstr,                  // Contains the SHA-256 of ProofOfProvisioning
+    //   ]
+    //
+    // Check that.
+    //
+    optional<vector<uint8_t>> proofOfBinding = support::certificateGetExtension(
+            signingKeyCertificate.encodedCertificate, "1.3.6.1.4.1.11129.2.1.26");
+    ASSERT_TRUE(proofOfBinding);
+    auto [item, _, message] = cppbor::parse(proofOfBinding.value());
+    ASSERT_NE(item, nullptr) << message;
+    const cppbor::Array* arrayItem = item->asArray();
+    ASSERT_NE(arrayItem, nullptr);
+    ASSERT_EQ(arrayItem->size(), 2);
+    const cppbor::Tstr* strItem = (*arrayItem)[0]->asTstr();
+    ASSERT_NE(strItem, nullptr);
+    EXPECT_EQ(strItem->value(), "ProofOfBinding");
+    const cppbor::Bstr* popSha256Item = (*arrayItem)[1]->asBstr();
+    ASSERT_NE(popSha256Item, nullptr);
+    EXPECT_EQ(popSha256Item->value(), support::sha256(proofOfProvisioning.value()));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthenticationKeyTests);
+INSTANTIATE_TEST_SUITE_P(
+        Identity, AuthenticationKeyTests,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+        android::PrintInstanceNameToString);
+
+}  // namespace android::hardware::identity
diff --git a/identity/aidl/vts/DeleteCredentialTests.cpp b/identity/aidl/vts/DeleteCredentialTests.cpp
new file mode 100644
index 0000000..1d30067
--- /dev/null
+++ b/identity/aidl/vts/DeleteCredentialTests.cpp
@@ -0,0 +1,169 @@
+/*
+ * 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 "DeleteCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class DeleteCredentialTests : public testing::TestWithParam<string> {
+  public:
+    virtual void SetUp() override {
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(GetParam().c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+        halApiVersion_ = credentialStore_->getInterfaceVersion();
+    }
+
+    void provisionData();
+
+    // Set by provisionData
+    vector<uint8_t> credentialData_;
+    vector<uint8_t> credentialPubKey_;
+
+    sp<IIdentityCredentialStore> credentialStore_;
+    int halApiVersion_;
+};
+
+void DeleteCredentialTests::provisionData() {
+    string docType = "org.iso.18013-5.2019.mdl";
+    bool testCredential = true;
+    sp<IWritableIdentityCredential> wc;
+    ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge = {1};
+    vector<Certificate> certChain;
+    ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+                                              &certChain)
+                        .isOk());
+
+    optional<vector<uint8_t>> optCredentialPubKey =
+            support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+    ASSERT_TRUE(optCredentialPubKey);
+    credentialPubKey_ = optCredentialPubKey.value();
+
+    size_t proofOfProvisioningSize = 106;
+    // Not in v1 HAL, may fail
+    wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+    ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+                                         {1} /* numDataElementsPerNamespace */)
+                        .isOk());
+
+    // Access control profile 0: open access - don't care about the returned SACP
+    SecureAccessControlProfile sacp;
+    ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+    // Single entry - don't care about the returned encrypted data
+    ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Some Data", 1).isOk());
+    vector<uint8_t> encryptedData;
+    ASSERT_TRUE(wc->addEntryValue({9}, &encryptedData).isOk());
+
+    vector<uint8_t> proofOfProvisioningSignature;
+    Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+    EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+}
+
+TEST_P(DeleteCredentialTests, Delete) {
+    provisionData();
+
+    sp<IIdentityCredential> credential;
+    ASSERT_TRUE(credentialStore_
+                        ->getCredential(
+                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+                                credentialData_, &credential)
+                        .isOk());
+
+    vector<uint8_t> proofOfDeletionSignature;
+    ASSERT_TRUE(credential->deleteCredential(&proofOfDeletionSignature).isOk());
+    optional<vector<uint8_t>> proofOfDeletion =
+            support::coseSignGetPayload(proofOfDeletionSignature);
+    ASSERT_TRUE(proofOfDeletion);
+    string cborPretty = support::cborPrettyPrint(proofOfDeletion.value(), 32, {});
+    EXPECT_EQ("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', true, ]", cborPretty);
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfDeletionSignature, {},  // Additional data
+                                                 credentialPubKey_));
+}
+
+TEST_P(DeleteCredentialTests, DeleteWithChallenge) {
+    if (halApiVersion_ < 3) {
+        GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
+    }
+
+    provisionData();
+
+    sp<IIdentityCredential> credential;
+    ASSERT_TRUE(credentialStore_
+                        ->getCredential(
+                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+                                credentialData_, &credential)
+                        .isOk());
+
+    vector<uint8_t> challenge = {65, 66, 67};
+    vector<uint8_t> proofOfDeletionSignature;
+    ASSERT_TRUE(
+            credential->deleteCredentialWithChallenge(challenge, &proofOfDeletionSignature).isOk());
+    optional<vector<uint8_t>> proofOfDeletion =
+            support::coseSignGetPayload(proofOfDeletionSignature);
+    ASSERT_TRUE(proofOfDeletion);
+    string cborPretty = support::cborPrettyPrint(proofOfDeletion.value(), 32, {});
+    EXPECT_EQ("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', {0x41, 0x42, 0x43}, true, ]",
+              cborPretty);
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfDeletionSignature, {},  // Additional data
+                                                 credentialPubKey_));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DeleteCredentialTests);
+INSTANTIATE_TEST_SUITE_P(
+        Identity, DeleteCredentialTests,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+        android::PrintInstanceNameToString);
+
+}  // namespace android::hardware::identity
diff --git a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp b/identity/aidl/vts/EndToEndTests.cpp
similarity index 93%
rename from identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
rename to identity/aidl/vts/EndToEndTests.cpp
index cdecb97..5798b4c 100644
--- a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
+++ b/identity/aidl/vts/EndToEndTests.cpp
@@ -29,7 +29,7 @@
 #include <map>
 #include <tuple>
 
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
 
 namespace android::hardware::identity {
 
@@ -50,18 +50,20 @@
 
 using test_utils::validateAttestationCertificate;
 
-class IdentityAidl : public testing::TestWithParam<std::string> {
+class EndToEndTests : public testing::TestWithParam<std::string> {
   public:
     virtual void SetUp() override {
         credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
                 String16(GetParam().c_str()));
         ASSERT_NE(credentialStore_, nullptr);
+        halApiVersion_ = credentialStore_->getInterfaceVersion();
     }
 
     sp<IIdentityCredentialStore> credentialStore_;
+    int halApiVersion_;
 };
 
-TEST_P(IdentityAidl, hardwareInformation) {
+TEST_P(EndToEndTests, hardwareInformation) {
     HardwareInformation info;
     ASSERT_TRUE(credentialStore_->getHardwareInformation(&info).isOk());
     ASSERT_GT(info.credentialStoreName.size(), 0);
@@ -69,20 +71,21 @@
     ASSERT_GE(info.dataChunkSize, 256);
 }
 
-tuple<bool, string, vector<uint8_t>, vector<uint8_t>> extractFromTestCredentialData(
-        const vector<uint8_t>& credentialData) {
+tuple<bool, string, vector<uint8_t>, vector<uint8_t>, vector<uint8_t>>
+extractFromTestCredentialData(const vector<uint8_t>& credentialData) {
     string docType;
     vector<uint8_t> storageKey;
     vector<uint8_t> credentialPrivKey;
+    vector<uint8_t> sha256Pop;
 
     auto [item, _, message] = cppbor::parse(credentialData);
     if (item == nullptr) {
-        return make_tuple(false, docType, storageKey, credentialPrivKey);
+        return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
     }
 
     const cppbor::Array* arrayItem = item->asArray();
     if (arrayItem == nullptr || arrayItem->size() != 3) {
-        return make_tuple(false, docType, storageKey, credentialPrivKey);
+        return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
     }
 
     const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
@@ -92,7 +95,7 @@
     const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
     if (docTypeItem == nullptr || testCredentialItem == nullptr ||
         encryptedCredentialKeysItem == nullptr) {
-        return make_tuple(false, docType, storageKey, credentialPrivKey);
+        return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
     }
 
     docType = docTypeItem->value();
@@ -103,28 +106,38 @@
     optional<vector<uint8_t>> decryptedCredentialKeys =
             support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
     if (!decryptedCredentialKeys) {
-        return make_tuple(false, docType, storageKey, credentialPrivKey);
+        return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
     }
 
     auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
     if (dckItem == nullptr) {
-        return make_tuple(false, docType, storageKey, credentialPrivKey);
+        return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
     }
     const cppbor::Array* dckArrayItem = dckItem->asArray();
-    if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
-        return make_tuple(false, docType, storageKey, credentialPrivKey);
+    if (dckArrayItem == nullptr) {
+        return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
+    }
+    if (dckArrayItem->size() < 2) {
+        return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
     }
     const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
     const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
     if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
-        return make_tuple(false, docType, storageKey, credentialPrivKey);
+        return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
     }
     storageKey = storageKeyItem->value();
     credentialPrivKey = credentialPrivKeyItem->value();
-    return make_tuple(true, docType, storageKey, credentialPrivKey);
+    if (dckArrayItem->size() == 3) {
+        const cppbor::Bstr* sha256PopItem = (*dckArrayItem)[2]->asBstr();
+        if (sha256PopItem == nullptr) {
+            return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
+        }
+        sha256Pop = sha256PopItem->value();
+    }
+    return make_tuple(true, docType, storageKey, credentialPrivKey, sha256Pop);
 }
 
-TEST_P(IdentityAidl, createAndRetrieveCredential) {
+TEST_P(EndToEndTests, createAndRetrieveCredential) {
     // First, generate a key-pair for the reader since its public key will be
     // part of the request data.
     vector<uint8_t> readerKey;
@@ -277,8 +290,9 @@
 
     // Extract doctype, storage key, and credentialPrivKey from credentialData... this works
     // only because we asked for a test-credential meaning that the HBK is all zeroes.
-    auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey] =
+    auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey, exSha256Pop] =
             extractFromTestCredentialData(credentialData);
+
     ASSERT_TRUE(exSuccess);
     ASSERT_EQ(exDocType, "org.iso.18013-5.2019.mdl");
     // ... check that the public key derived from the private key matches what was
@@ -291,6 +305,13 @@
     ASSERT_TRUE(exCredentialPubKey);
     ASSERT_EQ(exCredentialPubKey.value(), credentialPubKey.value());
 
+    // Starting with API version 3 (feature version 202101) we require SHA-256(ProofOfProvisioning)
+    // to be in CredentialKeys (which is stored encrypted in CredentialData). Check
+    // that it's there with the expected value.
+    if (halApiVersion_ >= 3) {
+        ASSERT_EQ(exSha256Pop, support::sha256(proofOfProvisioning.value()));
+    }
+
     // Now that the credential has been provisioned, read it back and check the
     // correct data is returned.
     sp<IIdentityCredential> credential;
@@ -498,13 +519,11 @@
     EXPECT_EQ(mac, calculatedMac);
 }
 
-GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IdentityAidl);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EndToEndTests);
 INSTANTIATE_TEST_SUITE_P(
-        Identity, IdentityAidl,
+        Identity, EndToEndTests,
         testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
         android::PrintInstanceNameToString);
-// INSTANTIATE_TEST_SUITE_P(Identity, IdentityAidl,
-// testing::Values("android.hardware.identity.IIdentityCredentialStore/default"));
 
 }  // namespace android::hardware::identity
 
diff --git a/identity/aidl/vts/ProveOwnershipTests.cpp b/identity/aidl/vts/ProveOwnershipTests.cpp
new file mode 100644
index 0000000..d1a3d39
--- /dev/null
+++ b/identity/aidl/vts/ProveOwnershipTests.cpp
@@ -0,0 +1,146 @@
+/*
+ * 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 "ProveOwnershipTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class ProveOwnershipTests : public testing::TestWithParam<string> {
+  public:
+    virtual void SetUp() override {
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(GetParam().c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+        halApiVersion_ = credentialStore_->getInterfaceVersion();
+    }
+
+    void provisionData();
+
+    // Set by provisionData
+    vector<uint8_t> credentialData_;
+    vector<uint8_t> credentialPubKey_;
+
+    sp<IIdentityCredentialStore> credentialStore_;
+    int halApiVersion_;
+};
+
+void ProveOwnershipTests::provisionData() {
+    string docType = "org.iso.18013-5.2019.mdl";
+    bool testCredential = true;
+    sp<IWritableIdentityCredential> wc;
+    ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge = {1};
+    vector<Certificate> certChain;
+    ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+                                              &certChain)
+                        .isOk());
+
+    optional<vector<uint8_t>> optCredentialPubKey =
+            support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+    ASSERT_TRUE(optCredentialPubKey);
+    credentialPubKey_ = optCredentialPubKey.value();
+
+    size_t proofOfProvisioningSize = 106;
+    // Not in v1 HAL, may fail
+    wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+    ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+                                         {1} /* numDataElementsPerNamespace */)
+                        .isOk());
+
+    // Access control profile 0: open access - don't care about the returned SACP
+    SecureAccessControlProfile sacp;
+    ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+    // Single entry - don't care about the returned encrypted data
+    ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Some Data", 1).isOk());
+    vector<uint8_t> encryptedData;
+    ASSERT_TRUE(wc->addEntryValue({9}, &encryptedData).isOk());
+
+    vector<uint8_t> proofOfProvisioningSignature;
+    Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+    EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+}
+
+TEST_P(ProveOwnershipTests, proveOwnership) {
+    if (halApiVersion_ < 3) {
+        GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
+    }
+
+    provisionData();
+
+    sp<IIdentityCredential> credential;
+    ASSERT_TRUE(credentialStore_
+                        ->getCredential(
+                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+                                credentialData_, &credential)
+                        .isOk());
+
+    vector<uint8_t> challenge = {17, 18};
+    vector<uint8_t> proofOfOwnershipSignature;
+    ASSERT_TRUE(credential->proveOwnership(challenge, &proofOfOwnershipSignature).isOk());
+    optional<vector<uint8_t>> proofOfOwnership =
+            support::coseSignGetPayload(proofOfOwnershipSignature);
+    ASSERT_TRUE(proofOfOwnership);
+    string cborPretty = support::cborPrettyPrint(proofOfOwnership.value(), 32, {});
+    EXPECT_EQ("['ProofOfOwnership', 'org.iso.18013-5.2019.mdl', {0x11, 0x12}, true, ]", cborPretty);
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfOwnershipSignature, {},  // Additional data
+                                                 credentialPubKey_));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProveOwnershipTests);
+INSTANTIATE_TEST_SUITE_P(
+        Identity, ProveOwnershipTests,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+        android::PrintInstanceNameToString);
+
+}  // namespace android::hardware::identity
diff --git a/identity/aidl/vts/ReaderAuthTests.cpp b/identity/aidl/vts/ReaderAuthTests.cpp
index 0a9fdc0..7656c8e 100644
--- a/identity/aidl/vts/ReaderAuthTests.cpp
+++ b/identity/aidl/vts/ReaderAuthTests.cpp
@@ -32,7 +32,7 @@
 #include <map>
 #include <utility>
 
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
 
 namespace android::hardware::identity {
 
@@ -123,9 +123,9 @@
                                    const vector<uint8_t>& signingKey) {
     time_t validityNotBefore = 0;
     time_t validityNotAfter = 0xffffffff;
-    optional<vector<uint8_t>> cert =
-            support::ecPublicKeyGenerateCertificate(publicKey, signingKey, "24601", "Issuer",
-                                                    "Subject", validityNotBefore, validityNotAfter);
+    optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
+            publicKey, signingKey, "24601", "Issuer", "Subject", validityNotBefore,
+            validityNotAfter, {});
     return cert.value();
 }
 
diff --git a/identity/aidl/vts/TestCredentialTests.cpp b/identity/aidl/vts/TestCredentialTests.cpp
new file mode 100644
index 0000000..d53de3b
--- /dev/null
+++ b/identity/aidl/vts/TestCredentialTests.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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 "TestCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class TestCredentialTests : public testing::TestWithParam<string> {
+  public:
+    virtual void SetUp() override {
+        string halInstanceName = GetParam();
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(halInstanceName.c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+        halApiVersion_ = credentialStore_->getInterfaceVersion();
+    }
+
+    sp<IIdentityCredentialStore> credentialStore_;
+    int halApiVersion_;
+};
+
+TEST_P(TestCredentialTests, testCredential) {
+    string docType = "org.iso.18013-5.2019.mdl";
+    sp<IWritableIdentityCredential> wc;
+    ASSERT_TRUE(credentialStore_
+                        ->createCredential(docType,
+                                           true,  // testCredential
+                                           &wc)
+                        .isOk());
+
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge = {1};
+    vector<Certificate> certChain;
+    ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+                                              &certChain)
+                        .isOk());
+
+    optional<vector<uint8_t>> optCredentialPubKey =
+            support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+    ASSERT_TRUE(optCredentialPubKey);
+    vector<uint8_t> credentialPubKey;
+    credentialPubKey = optCredentialPubKey.value();
+
+    size_t proofOfProvisioningSize = 112;
+    // Not in v1 HAL, may fail
+    wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+    ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+                                         {1} /* numDataElementsPerNamespace */)
+                        .isOk());
+
+    // Access control profile 0: open access - don't care about the returned SACP
+    SecureAccessControlProfile sacp;
+    ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+    // Single entry - don't care about the returned encrypted data
+    vector<uint8_t> encryptedData;
+    vector<uint8_t> tstrLastName = cppbor::Tstr("Turing").encode();
+    ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
+    ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
+
+    vector<uint8_t> proofOfProvisioningSignature;
+    vector<uint8_t> credentialData;
+    Status status = wc->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+    EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+
+    optional<vector<uint8_t>> proofOfProvisioning =
+            support::coseSignGetPayload(proofOfProvisioningSignature);
+    ASSERT_TRUE(proofOfProvisioning);
+    string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
+    EXPECT_EQ(
+            "[\n"
+            "  'ProofOfProvisioning',\n"
+            "  'org.iso.18013-5.2019.mdl',\n"
+            "  [\n"
+            "    {\n"
+            "      'id' : 1,\n"
+            "    },\n"
+            "  ],\n"
+            "  {\n"
+            "    'ns' : [\n"
+            "      {\n"
+            "        'name' : 'Last name',\n"
+            "        'value' : 'Turing',\n"
+            "        'accessControlProfiles' : [1, ],\n"
+            "      },\n"
+            "    ],\n"
+            "  },\n"
+            "  true,\n"
+            "]",
+            cborPretty);
+    // Make sure it's signed by the CredentialKey in the returned cert chain.
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+                                                 {},  // Additional data
+                                                 credentialPubKey));
+
+    // Now analyze credentialData..
+    auto [item, _, message] = cppbor::parse(credentialData);
+    ASSERT_NE(item, nullptr);
+    const cppbor::Array* arrayItem = item->asArray();
+    ASSERT_NE(arrayItem, nullptr);
+    ASSERT_EQ(arrayItem->size(), 3);
+    const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
+    const cppbor::Bool* testCredentialItem =
+            ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
+                                                    : nullptr);
+    EXPECT_EQ(docTypeItem->value(), docType);
+    EXPECT_EQ(testCredentialItem->value(), true);
+
+    vector<uint8_t> hardwareBoundKey = support::getTestHardwareBoundKey();
+    const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
+    const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
+    const vector<uint8_t> docTypeVec(docType.begin(), docType.end());
+    optional<vector<uint8_t>> decryptedCredentialKeys =
+            support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
+    ASSERT_TRUE(decryptedCredentialKeys);
+    auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
+    ASSERT_NE(dckItem, nullptr) << dckMessage;
+    const cppbor::Array* dckArrayItem = dckItem->asArray();
+    ASSERT_NE(dckArrayItem, nullptr);
+    // In HAL API version 1 and 2 this array has two items, in version 3 and later it has three.
+    if (halApiVersion_ < 3) {
+        ASSERT_EQ(dckArrayItem->size(), 2);
+    } else {
+        ASSERT_EQ(dckArrayItem->size(), 3);
+    }
+    const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
+    const vector<uint8_t> storageKey = storageKeyItem->value();
+    // const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
+    // const vector<uint8_t> credentialPrivKey = credentialPrivKeyItem->value();
+
+    // Check storageKey can be used to decrypt |encryptedData| to |tstrLastName|
+    vector<uint8_t> additionalData = cppbor::Map()
+                                             .add("Namespace", "ns")
+                                             .add("Name", "Last name")
+                                             .add("AccessControlProfileIds", cppbor::Array().add(1))
+                                             .encode();
+    optional<vector<uint8_t>> decryptedDataItemValue =
+            support::decryptAes128Gcm(storageKey, encryptedData, additionalData);
+    ASSERT_TRUE(decryptedDataItemValue);
+    EXPECT_EQ(decryptedDataItemValue.value(), tstrLastName);
+
+    // Check that SHA-256(ProofOfProvisioning) matches (only in HAL API version 3)
+    if (halApiVersion_ >= 3) {
+        const cppbor::Bstr* popSha256Item = (*dckArrayItem)[2]->asBstr();
+        const vector<uint8_t> popSha256 = popSha256Item->value();
+        ASSERT_EQ(popSha256, support::sha256(proofOfProvisioning.value()));
+    }
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestCredentialTests);
+INSTANTIATE_TEST_SUITE_P(
+        Identity, TestCredentialTests,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+        android::PrintInstanceNameToString);
+
+}  // namespace android::hardware::identity
diff --git a/identity/aidl/vts/UpdateCredentialTests.cpp b/identity/aidl/vts/UpdateCredentialTests.cpp
new file mode 100644
index 0000000..9c5ca55
--- /dev/null
+++ b/identity/aidl/vts/UpdateCredentialTests.cpp
@@ -0,0 +1,232 @@
+/*
+ * 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 "UpdateCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class UpdateCredentialTests : public testing::TestWithParam<string> {
+  public:
+    virtual void SetUp() override {
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(GetParam().c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+        halApiVersion_ = credentialStore_->getInterfaceVersion();
+    }
+
+    void provisionData();
+
+    // Set by provisionData
+    vector<uint8_t> credentialData_;
+    vector<uint8_t> credentialPubKey_;
+
+    sp<IIdentityCredentialStore> credentialStore_;
+    int halApiVersion_;
+};
+
+void UpdateCredentialTests::provisionData() {
+    string docType = "org.iso.18013-5.2019.mdl";
+    bool testCredential = true;
+    sp<IWritableIdentityCredential> wc;
+    ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge = {1};
+    vector<Certificate> certChain;
+    ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+                                              &certChain)
+                        .isOk());
+
+    optional<vector<uint8_t>> optCredentialPubKey =
+            support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+    ASSERT_TRUE(optCredentialPubKey);
+    credentialPubKey_ = optCredentialPubKey.value();
+
+    size_t proofOfProvisioningSize = 112;
+    // Not in v1 HAL, may fail
+    wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+    ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+                                         {1} /* numDataElementsPerNamespace */)
+                        .isOk());
+
+    // Access control profile 0: open access - don't care about the returned SACP
+    SecureAccessControlProfile sacp;
+    ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+    // Single entry - don't care about the returned encrypted data
+    vector<uint8_t> encryptedData;
+    vector<uint8_t> tstrLastName = cppbor::Tstr("Prince").encode();
+    ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
+    ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
+
+    vector<uint8_t> proofOfProvisioningSignature;
+    Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+    EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+
+    optional<vector<uint8_t>> proofOfProvisioning =
+            support::coseSignGetPayload(proofOfProvisioningSignature);
+    ASSERT_TRUE(proofOfProvisioning);
+    string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
+    EXPECT_EQ(
+            "[\n"
+            "  'ProofOfProvisioning',\n"
+            "  'org.iso.18013-5.2019.mdl',\n"
+            "  [\n"
+            "    {\n"
+            "      'id' : 1,\n"
+            "    },\n"
+            "  ],\n"
+            "  {\n"
+            "    'ns' : [\n"
+            "      {\n"
+            "        'name' : 'Last name',\n"
+            "        'value' : 'Prince',\n"
+            "        'accessControlProfiles' : [1, ],\n"
+            "      },\n"
+            "    ],\n"
+            "  },\n"
+            "  true,\n"
+            "]",
+            cborPretty);
+    // Make sure it's signed by the CredentialKey in the returned cert chain.
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+                                                 {},  // Additional data
+                                                 credentialPubKey_));
+}
+
+TEST_P(UpdateCredentialTests, updateCredential) {
+    if (halApiVersion_ < 3) {
+        GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
+    }
+
+    provisionData();
+
+    sp<IIdentityCredential> credential;
+    ASSERT_TRUE(credentialStore_
+                        ->getCredential(
+                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+                                credentialData_, &credential)
+                        .isOk());
+
+    sp<IWritableIdentityCredential> wc;
+    ASSERT_TRUE(credential->updateCredential(&wc).isOk());
+
+    // Getting an attestation cert should fail (because it's an update).
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge = {1};
+    vector<Certificate> certChain;
+    Status result = wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+                                                  &certChain);
+    ASSERT_FALSE(result.isOk());
+    EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+    EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
+
+    // Now provision some new data...
+    //
+    size_t proofOfProvisioningSize = 117;
+    // Not in v1 HAL, may fail
+    wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+    ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+                                         {1} /* numDataElementsPerNamespace */)
+                        .isOk());
+
+    // Access control profile 0: open access - don't care about the returned SACP
+    SecureAccessControlProfile sacp;
+    ASSERT_TRUE(wc->addAccessControlProfile(2, {}, false, 0, 0, &sacp).isOk());
+
+    // Single entry - don't care about the returned encrypted data
+    vector<uint8_t> encryptedData;
+    vector<uint8_t> tstrLastName = cppbor::Tstr("T.A.F.K.A.P").encode();
+    ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Last name", tstrLastName.size()).isOk());
+    ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
+
+    vector<uint8_t> proofOfProvisioningSignature;
+    Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+    EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+    optional<vector<uint8_t>> proofOfProvisioning =
+            support::coseSignGetPayload(proofOfProvisioningSignature);
+    ASSERT_TRUE(proofOfProvisioning);
+    string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
+    EXPECT_EQ(
+            "[\n"
+            "  'ProofOfProvisioning',\n"
+            "  'org.iso.18013-5.2019.mdl',\n"
+            "  [\n"
+            "    {\n"
+            "      'id' : 2,\n"
+            "    },\n"
+            "  ],\n"
+            "  {\n"
+            "    'ns' : [\n"
+            "      {\n"
+            "        'name' : 'Last name',\n"
+            "        'value' : 'T.A.F.K.A.P',\n"
+            "        'accessControlProfiles' : [2, ],\n"
+            "      },\n"
+            "    ],\n"
+            "  },\n"
+            "  true,\n"
+            "]",
+            cborPretty);
+    // Make sure it's signed by the same CredentialKey we originally provisioned with.
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+                                                 {},  // Additional data
+                                                 credentialPubKey_));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(UpdateCredentialTests);
+INSTANTIATE_TEST_SUITE_P(
+        Identity, UpdateCredentialTests,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+        android::PrintInstanceNameToString);
+
+}  // namespace android::hardware::identity
diff --git a/identity/aidl/vts/UserAuthTests.cpp b/identity/aidl/vts/UserAuthTests.cpp
index 327493c..ef89d1c 100644
--- a/identity/aidl/vts/UserAuthTests.cpp
+++ b/identity/aidl/vts/UserAuthTests.cpp
@@ -32,7 +32,7 @@
 #include <map>
 #include <utility>
 
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
 
 namespace android::hardware::identity {
 
@@ -145,7 +145,7 @@
     EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
 }
 
-// From ReaderAuthTest.cpp - TODO: consolidate with VtsIdentityTestUtils.h
+// From ReaderAuthTest.cpp - TODO: consolidate with Util.h
 pair<vector<uint8_t>, vector<uint8_t>> generateReaderKey();
 vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
                                    const vector<uint8_t>& signingKey);
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.cpp b/identity/aidl/vts/Util.cpp
similarity index 98%
rename from identity/aidl/vts/VtsIdentityTestUtils.cpp
rename to identity/aidl/vts/Util.cpp
index 3b10651..1148cb0 100644
--- a/identity/aidl/vts/VtsIdentityTestUtils.cpp
+++ b/identity/aidl/vts/Util.cpp
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "VtsIdentityTestUtils"
+#define LOG_TAG "Util"
 
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
+
+#include <android-base/logging.h>
 
 #include <aidl/Gtest.h>
-#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 #include <keymaster/km_openssl/openssl_utils.h>
 #include <keymasterV4_1/attestation_record.h>
 #include <charconv>
+
 #include <map>
 
 namespace android::hardware::identity::test_utils {
@@ -35,6 +38,7 @@
 
 using ::android::sp;
 using ::android::String16;
+using ::android::base::StringPrintf;
 using ::android::binder::Status;
 using ::keymaster::X509_Ptr;
 
@@ -86,7 +90,7 @@
 
     return support::ecPublicKeyGenerateCertificate(readerPublicKey.value(), readerKey.value(),
                                                    serialDecimal, issuer, subject,
-                                                   validityNotBefore, validityNotAfter);
+                                                   validityNotBefore, validityNotAfter, {});
 }
 
 optional<vector<SecureAccessControlProfile>> addAccessControlProfiles(
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.h b/identity/aidl/vts/Util.h
similarity index 99%
rename from identity/aidl/vts/VtsIdentityTestUtils.h
rename to identity/aidl/vts/Util.h
index 85c24f8..80e52a2 100644
--- a/identity/aidl/vts/VtsIdentityTestUtils.h
+++ b/identity/aidl/vts/Util.h
@@ -21,6 +21,7 @@
 #include <android/hardware/identity/support/IdentityCredentialSupport.h>
 #include <cppbor.h>
 #include <cppbor_parse.h>
+#include <gtest/gtest.h>
 
 namespace android::hardware::identity::test_utils {
 
diff --git a/identity/aidl/vts/VtsAttestationTests.cpp b/identity/aidl/vts/VtsAttestationTests.cpp
index 5529853..e12fe05 100644
--- a/identity/aidl/vts/VtsAttestationTests.cpp
+++ b/identity/aidl/vts/VtsAttestationTests.cpp
@@ -29,7 +29,7 @@
 #include <future>
 #include <map>
 
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
 
 namespace android::hardware::identity {
 
diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
index 56e17ba..cc63c48 100644
--- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -29,7 +29,7 @@
 #include <future>
 #include <map>
 
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
 
 namespace android::hardware::identity {
 
@@ -182,7 +182,7 @@
                                                     false /* testCredential */));
 
     // Verify set a large number of profile count and entry count is ok
-    const vector<int32_t> entryCounts = {3000};
+    const vector<int32_t> entryCounts = {255};
     writableCredential->setExpectedProofOfProvisioningSize(123456);
     result = writableCredential->startPersonalization(25, entryCounts);
     EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
diff --git a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
index 3aa5bb6..3b91de6 100644
--- a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
+++ b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
@@ -18,6 +18,7 @@
 #define IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
 
 #include <cstdint>
+#include <map>
 #include <optional>
 #include <string>
 #include <tuple>
@@ -29,11 +30,12 @@
 namespace identity {
 namespace support {
 
+using ::std::map;
 using ::std::optional;
+using ::std::pair;
 using ::std::string;
 using ::std::tuple;
 using ::std::vector;
-using ::std::pair;
 
 // The semantic tag for a bstr which includes Encoded CBOR (RFC 7049, section 2.4)
 const int kSemanticTagEncodedCbor = 24;
@@ -221,6 +223,11 @@
 //
 optional<pair<time_t, time_t>> certificateGetValidity(const vector<uint8_t>& x509Certificate);
 
+// Looks for an extension with OID in |oidStr| which must be an stored as an OCTET STRING.
+//
+optional<vector<uint8_t>> certificateGetExtension(const vector<uint8_t>& x509Certificate,
+                                                  const string& oidStr);
+
 // Generates a X.509 certificate for |publicKey| (which must be in the format
 // returned by ecKeyPairGetPublicKey()).
 //
@@ -230,7 +237,8 @@
 optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
         const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
         const string& serialDecimal, const string& issuer, const string& subject,
-        time_t validityNotBefore, time_t validityNotAfter);
+        time_t validityNotBefore, time_t validityNotAfter,
+        const map<string, vector<uint8_t>>& extensions);
 
 // Performs Elliptic-curve Diffie-Helman using |publicKey| (which must be in the
 // format returned by ecKeyPairGetPublicKey()) and |privateKey| (which must be
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index 093120d..6418028 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -344,15 +344,22 @@
 // Crypto functionality / abstraction.
 // ---------------------------------------------------------------------------
 
-struct EVP_CIPHER_CTX_Deleter {
-    void operator()(EVP_CIPHER_CTX* ctx) const {
-        if (ctx != nullptr) {
-            EVP_CIPHER_CTX_free(ctx);
-        }
-    }
-};
-
-using EvpCipherCtxPtr = unique_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_Deleter>;
+using EvpCipherCtxPtr = bssl::UniquePtr<EVP_CIPHER_CTX>;
+using EC_KEY_Ptr = bssl::UniquePtr<EC_KEY>;
+using EVP_PKEY_Ptr = bssl::UniquePtr<EVP_PKEY>;
+using EVP_PKEY_CTX_Ptr = bssl::UniquePtr<EVP_PKEY_CTX>;
+using EC_GROUP_Ptr = bssl::UniquePtr<EC_GROUP>;
+using EC_POINT_Ptr = bssl::UniquePtr<EC_POINT>;
+using ECDSA_SIG_Ptr = bssl::UniquePtr<ECDSA_SIG>;
+using X509_Ptr = bssl::UniquePtr<X509>;
+using PKCS12_Ptr = bssl::UniquePtr<PKCS12>;
+using BIGNUM_Ptr = bssl::UniquePtr<BIGNUM>;
+using ASN1_INTEGER_Ptr = bssl::UniquePtr<ASN1_INTEGER>;
+using ASN1_TIME_Ptr = bssl::UniquePtr<ASN1_TIME>;
+using ASN1_OCTET_STRING_Ptr = bssl::UniquePtr<ASN1_OCTET_STRING>;
+using ASN1_OBJECT_Ptr = bssl::UniquePtr<ASN1_OBJECT>;
+using X509_NAME_Ptr = bssl::UniquePtr<X509_NAME>;
+using X509_EXTENSION_Ptr = bssl::UniquePtr<X509_EXTENSION>;
 
 // bool getRandom(size_t numBytes, vector<uint8_t>& output) {
 optional<vector<uint8_t>> getRandom(size_t numBytes) {
@@ -534,115 +541,6 @@
     return encryptedData;
 }
 
-struct EC_KEY_Deleter {
-    void operator()(EC_KEY* key) const {
-        if (key != nullptr) {
-            EC_KEY_free(key);
-        }
-    }
-};
-using EC_KEY_Ptr = unique_ptr<EC_KEY, EC_KEY_Deleter>;
-
-struct EVP_PKEY_Deleter {
-    void operator()(EVP_PKEY* key) const {
-        if (key != nullptr) {
-            EVP_PKEY_free(key);
-        }
-    }
-};
-using EVP_PKEY_Ptr = unique_ptr<EVP_PKEY, EVP_PKEY_Deleter>;
-
-struct EVP_PKEY_CTX_Deleter {
-    void operator()(EVP_PKEY_CTX* ctx) const {
-        if (ctx != nullptr) {
-            EVP_PKEY_CTX_free(ctx);
-        }
-    }
-};
-using EVP_PKEY_CTX_Ptr = unique_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_Deleter>;
-
-struct EC_GROUP_Deleter {
-    void operator()(EC_GROUP* group) const {
-        if (group != nullptr) {
-            EC_GROUP_free(group);
-        }
-    }
-};
-using EC_GROUP_Ptr = unique_ptr<EC_GROUP, EC_GROUP_Deleter>;
-
-struct EC_POINT_Deleter {
-    void operator()(EC_POINT* point) const {
-        if (point != nullptr) {
-            EC_POINT_free(point);
-        }
-    }
-};
-
-using EC_POINT_Ptr = unique_ptr<EC_POINT, EC_POINT_Deleter>;
-
-struct ECDSA_SIG_Deleter {
-    void operator()(ECDSA_SIG* sig) const {
-        if (sig != nullptr) {
-            ECDSA_SIG_free(sig);
-        }
-    }
-};
-using ECDSA_SIG_Ptr = unique_ptr<ECDSA_SIG, ECDSA_SIG_Deleter>;
-
-struct X509_Deleter {
-    void operator()(X509* x509) const {
-        if (x509 != nullptr) {
-            X509_free(x509);
-        }
-    }
-};
-using X509_Ptr = unique_ptr<X509, X509_Deleter>;
-
-struct PKCS12_Deleter {
-    void operator()(PKCS12* pkcs12) const {
-        if (pkcs12 != nullptr) {
-            PKCS12_free(pkcs12);
-        }
-    }
-};
-using PKCS12_Ptr = unique_ptr<PKCS12, PKCS12_Deleter>;
-
-struct BIGNUM_Deleter {
-    void operator()(BIGNUM* bignum) const {
-        if (bignum != nullptr) {
-            BN_free(bignum);
-        }
-    }
-};
-using BIGNUM_Ptr = unique_ptr<BIGNUM, BIGNUM_Deleter>;
-
-struct ASN1_INTEGER_Deleter {
-    void operator()(ASN1_INTEGER* value) const {
-        if (value != nullptr) {
-            ASN1_INTEGER_free(value);
-        }
-    }
-};
-using ASN1_INTEGER_Ptr = unique_ptr<ASN1_INTEGER, ASN1_INTEGER_Deleter>;
-
-struct ASN1_TIME_Deleter {
-    void operator()(ASN1_TIME* value) const {
-        if (value != nullptr) {
-            ASN1_TIME_free(value);
-        }
-    }
-};
-using ASN1_TIME_Ptr = unique_ptr<ASN1_TIME, ASN1_TIME_Deleter>;
-
-struct X509_NAME_Deleter {
-    void operator()(X509_NAME* value) const {
-        if (value != nullptr) {
-            X509_NAME_free(value);
-        }
-    }
-};
-using X509_NAME_Ptr = unique_ptr<X509_NAME, X509_NAME_Deleter>;
-
 vector<uint8_t> certificateChainJoin(const vector<vector<uint8_t>>& certificateChain) {
     vector<uint8_t> ret;
     for (const vector<uint8_t>& certificate : certificateChain) {
@@ -935,9 +833,16 @@
 optional<vector<vector<uint8_t>>> createAttestation(
         const EVP_PKEY* key, const vector<uint8_t>& applicationId, const vector<uint8_t>& challenge,
         uint64_t activeTimeMilliSeconds, uint64_t expireTimeMilliSeconds, bool isTestCredential) {
+    // Pretend to be implemented in a trusted environment just so we can pass
+    // the VTS tests. Of course, this is a pretend-only game since hopefully no
+    // relying party is ever going to trust our batch key and those keys above
+    // it.
+    ::keymaster::PureSoftKeymasterContext context(::keymaster::KmVersion::KEYMASTER_4_1,
+                                                  KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
+
     keymaster_error_t error;
     ::keymaster::CertificateChain attestation_chain =
-            ::keymaster::getAttestationChain(KM_ALGORITHM_EC, &error);
+            context.GetAttestationChain(KM_ALGORITHM_EC, &error);
     if (KM_ERROR_OK != error) {
         LOG(ERROR) << "Error getting attestation chain " << error;
         return {};
@@ -957,12 +862,6 @@
         }
         expireTimeMilliSeconds = bcNotAfter * 1000;
     }
-    const keymaster_key_blob_t* attestation_signing_key =
-            ::keymaster::getAttestationKey(KM_ALGORITHM_EC, nullptr);
-    if (attestation_signing_key == nullptr) {
-        LOG(ERROR) << "Error getting attestation key";
-        return {};
-    }
 
     ::keymaster::X509_NAME_Ptr subjectName;
     if (KM_ERROR_OK !=
@@ -976,8 +875,11 @@
 
     i2d_X509_NAME(subjectName.get(), &subjectPtr);
 
+    uint64_t nowMilliSeconds = time(nullptr) * 1000;
     ::keymaster::AuthorizationSet auth_set(
             ::keymaster::AuthorizationSetBuilder()
+                    .Authorization(::keymaster::TAG_CERTIFICATE_NOT_BEFORE, nowMilliSeconds)
+                    .Authorization(::keymaster::TAG_CERTIFICATE_NOT_AFTER, expireTimeMilliSeconds)
                     .Authorization(::keymaster::TAG_ATTESTATION_CHALLENGE, challenge.data(),
                                    challenge.size())
                     .Authorization(::keymaster::TAG_ACTIVE_DATETIME, activeTimeMilliSeconds)
@@ -1016,19 +918,11 @@
     }
     ::keymaster::AuthorizationSet hwEnforced(hwEnforcedBuilder);
 
-    // Pretend to be implemented in a trusted environment just so we can pass
-    // the VTS tests. Of course, this is a pretend-only game since hopefully no
-    // relying party is ever going to trust our batch key and those keys above
-    // it.
-    ::keymaster::PureSoftKeymasterContext context(::keymaster::KmVersion::KEYMASTER_4_1,
-                                                  KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
-
-    ::keymaster::CertificateChain cert_chain_out = generate_attestation_from_EVP(
-            key, swEnforced, hwEnforced, auth_set, context, move(attestation_chain),
-            *attestation_signing_key, &error);
+    ::keymaster::CertificateChain cert_chain_out = generate_attestation(
+            key, swEnforced, hwEnforced, auth_set, {} /* attest_key */, context, &error);
 
     if (KM_ERROR_OK != error) {
-        LOG(ERROR) << "Error generate attestation from EVP key" << error;
+        LOG(ERROR) << "Error generating attestation from EVP key: " << error;
         return {};
     }
 
@@ -1221,8 +1115,19 @@
         return {};
     }
     vector<uint8_t> privateKey;
-    privateKey.resize(BN_num_bytes(bignum));
-    BN_bn2bin(bignum, privateKey.data());
+
+    // Note that this may return fewer than 32 bytes so pad with zeroes since we
+    // want to always return 32 bytes.
+    size_t numBytes = BN_num_bytes(bignum);
+    if (numBytes > 32) {
+        LOG(ERROR) << "Size is " << numBytes << ", expected this to be 32 or less";
+        return {};
+    }
+    privateKey.resize(32);
+    for (size_t n = 0; n < 32 - numBytes; n++) {
+        privateKey[n] = 0x00;
+    }
+    BN_bn2bin(bignum, privateKey.data() + 32 - numBytes);
     return privateKey;
 }
 
@@ -1379,7 +1284,8 @@
 optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
         const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
         const string& serialDecimal, const string& issuer, const string& subject,
-        time_t validityNotBefore, time_t validityNotAfter) {
+        time_t validityNotBefore, time_t validityNotAfter,
+        const map<string, vector<uint8_t>>& extensions) {
     auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
     auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
     if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
@@ -1482,6 +1388,32 @@
         return {};
     }
 
+    for (auto const& [oidStr, blob] : extensions) {
+        ASN1_OBJECT_Ptr oid(
+                OBJ_txt2obj(oidStr.c_str(), 1));  // accept numerical dotted string form only
+        if (!oid.get()) {
+            LOG(ERROR) << "Error setting OID";
+            return {};
+        }
+        ASN1_OCTET_STRING_Ptr octetString(ASN1_OCTET_STRING_new());
+        if (!ASN1_OCTET_STRING_set(octetString.get(), blob.data(), blob.size())) {
+            LOG(ERROR) << "Error setting octet string for extension";
+            return {};
+        }
+
+        X509_EXTENSION_Ptr extension = X509_EXTENSION_Ptr(X509_EXTENSION_new());
+        extension.reset(X509_EXTENSION_create_by_OBJ(nullptr, oid.get(), 0 /* not critical */,
+                                                     octetString.get()));
+        if (!extension.get()) {
+            LOG(ERROR) << "Error setting extension";
+            return {};
+        }
+        if (!X509_add_ext(x509.get(), extension.get(), -1)) {
+            LOG(ERROR) << "Error adding extension";
+            return {};
+        }
+    }
+
     if (X509_sign(x509.get(), privPkey.get(), EVP_sha256()) == 0) {
         LOG(ERROR) << "Error signing X509 certificate";
         return {};
@@ -1650,6 +1582,44 @@
     return publicKey;
 }
 
+optional<vector<uint8_t>> certificateGetExtension(const vector<uint8_t>& x509Certificate,
+                                                  const string& oidStr) {
+    vector<X509_Ptr> certs;
+    if (!parseX509Certificates(x509Certificate, certs)) {
+        return {};
+    }
+    if (certs.size() < 1) {
+        LOG(ERROR) << "No certificates in chain";
+        return {};
+    }
+
+    ASN1_OBJECT_Ptr oid(
+            OBJ_txt2obj(oidStr.c_str(), 1));  // accept numerical dotted string form only
+    if (!oid.get()) {
+        LOG(ERROR) << "Error setting OID";
+        return {};
+    }
+
+    int location = X509_get_ext_by_OBJ(certs[0].get(), oid.get(), -1 /* search from beginning */);
+    if (location == -1) {
+        return {};
+    }
+
+    X509_EXTENSION* ext = X509_get_ext(certs[0].get(), location);
+    if (ext == nullptr) {
+        return {};
+    }
+
+    ASN1_OCTET_STRING* octetString = X509_EXTENSION_get_data(ext);
+    if (octetString == nullptr) {
+        return {};
+    }
+    vector<uint8_t> result;
+    result.resize(octetString->length);
+    memcpy(result.data(), octetString->data, octetString->length);
+    return result;
+}
+
 optional<pair<size_t, size_t>> certificateFindPublicKey(const vector<uint8_t>& x509Certificate) {
     vector<X509_Ptr> certs;
     if (!parseX509Certificates(x509Certificate, certs)) {
diff --git a/identity/support/tests/IdentityCredentialSupportTest.cpp b/identity/support/tests/IdentityCredentialSupportTest.cpp
index 266f263..509133c 100644
--- a/identity/support/tests/IdentityCredentialSupportTest.cpp
+++ b/identity/support/tests/IdentityCredentialSupportTest.cpp
@@ -271,7 +271,7 @@
         optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
 
         optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
-                pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+                pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
         certs.push_back(cert.value());
     }
     return support::certificateChainJoin(certs);
@@ -338,7 +338,7 @@
     ASSERT_TRUE(pubKey);
 
     optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
-            pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+            pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
 
     optional<vector<uint8_t>> extractedPubKey =
             support::certificateChainGetTopMostKey(cert.value());
@@ -358,7 +358,7 @@
     optional<vector<uint8_t>> otherPubKey = support::ecKeyPairGetPublicKey(keyPair.value());
     ASSERT_TRUE(otherPubKey);
     optional<vector<uint8_t>> otherCert = support::ecPublicKeyGenerateCertificate(
-            otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+            otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
 
     // Now both cert and otherCert are two distinct certificates. Let's make a
     // chain and check that certificateChainSplit() works as expected.
diff --git a/keymaster/4.1/default/OWNERS b/keymaster/4.1/default/OWNERS
index 335660d..2b2ad2a 100644
--- a/keymaster/4.1/default/OWNERS
+++ b/keymaster/4.1/default/OWNERS
@@ -1,2 +1,4 @@
+jbires@google.com
 jdanis@google.com
 swillden@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/keymaster/4.1/support/OWNERS b/keymaster/4.1/support/OWNERS
index a9efe66..2b2ad2a 100644
--- a/keymaster/4.1/support/OWNERS
+++ b/keymaster/4.1/support/OWNERS
@@ -1,3 +1,4 @@
+jbires@google.com
 jdanis@google.com
 swillden@google.com
-jbires@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/keymaster/4.1/vts/OWNERS b/keymaster/4.1/vts/OWNERS
index 335660d..2b2ad2a 100644
--- a/keymaster/4.1/vts/OWNERS
+++ b/keymaster/4.1/vts/OWNERS
@@ -1,2 +1,4 @@
+jbires@google.com
 jdanis@google.com
 swillden@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/light/aidl/default/Android.bp b/light/aidl/default/Android.bp
index ae3f463..4e43ba9 100644
--- a/light/aidl/default/Android.bp
+++ b/light/aidl/default/Android.bp
@@ -7,7 +7,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.light-ndk_platform",
+        "android.hardware.light-V1-ndk_platform",
     ],
     srcs: [
         "Lights.cpp",
diff --git a/light/aidl/vts/functional/Android.bp b/light/aidl/vts/functional/Android.bp
index aa4719b..4c9356c 100644
--- a/light/aidl/vts/functional/Android.bp
+++ b/light/aidl/vts/functional/Android.bp
@@ -27,7 +27,7 @@
         "libbinder",
     ],
     static_libs: [
-        "android.hardware.light-cpp",
+        "android.hardware.light-V1-cpp",
     ],
     test_suites: [
         "vts",
diff --git a/light/utils/Android.bp b/light/utils/Android.bp
index e901129..871f983 100644
--- a/light/utils/Android.bp
+++ b/light/utils/Android.bp
@@ -28,6 +28,6 @@
         "libutils",
     ],
     static_libs: [
-        "android.hardware.light-cpp",
+        "android.hardware.light-V1-cpp",
     ],
 }
diff --git a/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp b/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp
index 9184c56..ea29f03 100644
--- a/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp
+++ b/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp
@@ -215,6 +215,7 @@
     ASSERT_NE(handle, nullptr);
 
     *nStride = static_cast<int32_t>(stride);
+    buffer->handle = handle;
     buffer->omxBuffer.nativeHandle = handle;
     buffer->omxBuffer.attr.anwBuffer.width = nFrameWidth;
     buffer->omxBuffer.attr.anwBuffer.height = nFrameHeight;
@@ -335,6 +336,18 @@
     }
 }
 
+// free buffers needed on a component port
+void freePortBuffers(android::Vector<BufferInfo>* buffArray, PortMode portMode, bool allocGrap) {
+    for (size_t i = 0; i < buffArray->size(); i++) {
+        if (portMode == PortMode::PRESET_ANW_BUFFER ||
+            (allocGrap && portMode == PortMode::DYNAMIC_ANW_BUFFER)) {
+            android::GraphicBufferAllocator& allocator = android::GraphicBufferAllocator::get();
+            android::status_t error = allocator.free((*buffArray)[i].handle);
+            ASSERT_EQ(error, android::NO_ERROR);
+        }
+    }
+}
+
 // State Transition : Loaded -> Idle
 // Note: This function does not make any background checks for this transition.
 // The callee holds the reponsibility to ensure the legality of the transition.
@@ -399,11 +412,15 @@
 // The callee holds the reponsibility to ensure the legality of the transition.
 void changeStateIdletoLoaded(sp<IOmxNode> omxNode, sp<CodecObserver> observer,
                              android::Vector<BufferInfo>* iBuffer,
-                             android::Vector<BufferInfo>* oBuffer,
-                             OMX_U32 kPortIndexInput,
-                             OMX_U32 kPortIndexOutput) {
+                             android::Vector<BufferInfo>* oBuffer, OMX_U32 kPortIndexInput,
+                             OMX_U32 kPortIndexOutput, PortMode* portMode, bool allocGrap) {
     android::hardware::media::omx::V1_0::Status status;
     Message msg;
+    PortMode defaultPortMode[2], *pm;
+
+    defaultPortMode[0] = PortMode::PRESET_BYTE_BUFFER;
+    defaultPortMode[1] = PortMode::PRESET_BYTE_BUFFER;
+    pm = portMode ? portMode : defaultPortMode;
 
     // set state to Loaded
     status = omxNode->sendCommand(toRawCommandType(OMX_CommandStateSet),
@@ -446,6 +463,8 @@
     ASSERT_EQ(msg.data.eventData.data1, OMX_CommandStateSet);
     ASSERT_EQ(msg.data.eventData.data2, OMX_StateLoaded);
 
+    ASSERT_NO_FATAL_FAILURE(freePortBuffers(iBuffer, pm[0], allocGrap));
+    ASSERT_NO_FATAL_FAILURE(freePortBuffers(oBuffer, pm[1], allocGrap));
     return;
 }
 
diff --git a/media/omx/1.0/vts/functional/common/media_hidl_test_common.h b/media/omx/1.0/vts/functional/common/media_hidl_test_common.h
index b16c772..eddf83f 100644
--- a/media/omx/1.0/vts/functional/common/media_hidl_test_common.h
+++ b/media/omx/1.0/vts/functional/common/media_hidl_test_common.h
@@ -115,6 +115,7 @@
 struct BufferInfo {
     uint32_t id;
     bufferOwner owner;
+    buffer_handle_t handle;
     android::hardware::media::omx::V1_0::CodecBuffer omxBuffer;
     ::android::sp<IMemory> mMemory;
     int32_t slot;
@@ -329,6 +330,9 @@
                          PortMode portMode = PortMode::PRESET_BYTE_BUFFER,
                          bool allocGrap = false);
 
+void freePortBuffers(android::Vector<BufferInfo>* buffArray, PortMode portMode,
+                     bool allocGrap = false);
+
 void changeStateLoadedtoIdle(sp<IOmxNode> omxNode, sp<CodecObserver> observer,
                              android::Vector<BufferInfo>* iBuffer,
                              android::Vector<BufferInfo>* oBuffer,
@@ -338,8 +342,9 @@
 
 void changeStateIdletoLoaded(sp<IOmxNode> omxNode, sp<CodecObserver> observer,
                              android::Vector<BufferInfo>* iBuffer,
-                             android::Vector<BufferInfo>* oBuffer,
-                             OMX_U32 kPortIndexInput, OMX_U32 kPortIndexOutput);
+                             android::Vector<BufferInfo>* oBuffer, OMX_U32 kPortIndexInput,
+                             OMX_U32 kPortIndexOutput, PortMode* portMode = nullptr,
+                             bool allocGrap = false);
 
 void changeStateIdletoExecute(sp<IOmxNode> omxNode, sp<CodecObserver> observer);
 
diff --git a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp
index 67b9895..d35ce65 100644
--- a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp
+++ b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp
@@ -451,6 +451,7 @@
                     status,
                     android::hardware::media::omx::V1_0::Status::TIMED_OUT);
 
+                ASSERT_NO_FATAL_FAILURE(freePortBuffers(oBuffer, oPortMode, true));
                 ASSERT_NO_FATAL_FAILURE(allocatePortBuffers(
                     omxNode, oBuffer, kPortIndexOutput, oPortMode, true));
                 status = observer->dequeueMessage(&msg, DEFAULT_TIMEOUT,
@@ -853,9 +854,9 @@
     ASSERT_NO_FATAL_FAILURE(
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode,
+                                                    true));
 }
 
 // Test for adaptive playback support
@@ -1001,9 +1002,9 @@
     ASSERT_NO_FATAL_FAILURE(
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode,
+                                                    true));
 }
 
 // end of sequence test
@@ -1067,9 +1068,9 @@
     ASSERT_NO_FATAL_FAILURE(
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode,
+                                                    true));
 }
 
 // end of sequence test
@@ -1188,9 +1189,9 @@
     ASSERT_NO_FATAL_FAILURE(
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode,
+                                                    true));
 }
 
 // end of sequence test
@@ -1295,9 +1296,9 @@
     ASSERT_NO_FATAL_FAILURE(
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode,
+                                                    true));
 }
 
 // test input/output port flush
@@ -1414,9 +1415,9 @@
     ASSERT_NO_FATAL_FAILURE(
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode,
+                                                    true));
 }
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VideoDecHidlTest);
diff --git a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp
index 3c0734e..f24c6d1 100644
--- a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp
+++ b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp
@@ -1057,9 +1057,9 @@
     ASSERT_NO_FATAL_FAILURE(changeStateExecutetoIdle(
         omxNode, observer, &buffersource->iBuffer, &buffersource->oBuffer));
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(
-        omxNode, observer, &buffersource->iBuffer, &buffersource->oBuffer,
-        kPortIndexInput, kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &buffersource->iBuffer,
+                                                    &buffersource->oBuffer, kPortIndexInput,
+                                                    kPortIndexOutput, portMode));
     // test for callbacks
     EXPECT_EQ(buffersource->callback, 31);
 }
@@ -1174,9 +1174,8 @@
     ASSERT_NO_FATAL_FAILURE(
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode));
 }
 
 // test raw stream encode (input is ANW buffers)
@@ -1337,9 +1336,8 @@
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     EXPECT_EQ(portDef.nBufferCountActual, listener->freeBuffers);
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode));
 
     returnval = producer->disconnect(
         NATIVE_WINDOW_API_CPU, IGraphicBufferProducer::DisconnectMode::API);
@@ -1452,9 +1450,8 @@
         changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
     EXPECT_EQ(portDef.nBufferCountActual, listener->freeBuffers);
     // set state to executing
-    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
-                                                    &oBuffer, kPortIndexInput,
-                                                    kPortIndexOutput));
+    ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+                                                    kPortIndexInput, kPortIndexOutput, portMode));
 
     returnval = producer->disconnect(
         NATIVE_WINDOW_API_CPU, IGraphicBufferProducer::DisconnectMode::API);
diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
index 00abff9..3e25c56 100644
--- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
index 844a1bb..2e2b68e 100644
--- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
index 09ecefc..0e15ce3 100644
--- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
index 7f3f939..b19869e 100644
--- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -23,5 +38,4 @@
   GRAPHICS = 2,
   MULTIMEDIA = 3,
   CAMERA = 4,
-  NUM_TYPES = 5,
 }
diff --git a/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl b/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
index 715c6bf..5db735a 100644
--- a/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
+++ b/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
@@ -27,5 +27,4 @@
     GRAPHICS = 2,
     MULTIMEDIA = 3,
     CAMERA = 4,
-    NUM_TYPES,
 }
diff --git a/memtrack/aidl/default/Android.bp b/memtrack/aidl/default/Android.bp
index 52f88c8..8d97bfc 100644
--- a/memtrack/aidl/default/Android.bp
+++ b/memtrack/aidl/default/Android.bp
@@ -21,7 +21,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.memtrack-ndk_platform",
+        "android.hardware.memtrack-V1-ndk_platform",
     ],
     srcs: [
         "main.cpp",
diff --git a/memtrack/aidl/default/Memtrack.cpp b/memtrack/aidl/default/Memtrack.cpp
index 7361719..49a6582 100644
--- a/memtrack/aidl/default/Memtrack.cpp
+++ b/memtrack/aidl/default/Memtrack.cpp
@@ -26,7 +26,8 @@
     if (pid < 0) {
         return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT));
     }
-    if (type < MemtrackType::OTHER || type >= MemtrackType::NUM_TYPES) {
+    if (type != MemtrackType::OTHER && type != MemtrackType::GL && type != MemtrackType::GRAPHICS &&
+        type != MemtrackType::MULTIMEDIA && type != MemtrackType::CAMERA) {
         return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
     }
     _aidl_return->clear();
@@ -35,6 +36,8 @@
 
 ndk::ScopedAStatus Memtrack::getGpuDeviceInfo(std::vector<DeviceInfo>* _aidl_return) {
     _aidl_return->clear();
+    DeviceInfo dev_info = {.id = 0, .name = "virtio_gpu"};
+    _aidl_return->emplace_back(dev_info);
     return ndk::ScopedAStatus::ok();
 }
 
diff --git a/memtrack/aidl/vts/Android.bp b/memtrack/aidl/vts/Android.bp
index ea36677..eff2a56 100644
--- a/memtrack/aidl/vts/Android.bp
+++ b/memtrack/aidl/vts/Android.bp
@@ -7,11 +7,12 @@
     srcs: ["VtsHalMemtrackTargetTest.cpp"],
     shared_libs: [
         "libbinder_ndk",
+        "libvintf",
     ],
     static_libs: [
-        "android.hardware.memtrack-unstable-ndk_platform",
+        "android.hardware.memtrack-V1-ndk_platform",
     ],
     test_suites: [
-        "vts-core",
+        "vts",
     ],
 }
diff --git a/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp b/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp
index 4d33101..8905f50 100644
--- a/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp
+++ b/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp
@@ -21,11 +21,15 @@
 #include <aidl/android/hardware/memtrack/MemtrackType.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
+#include <vintf/VintfObject.h>
 
 using aidl::android::hardware::memtrack::DeviceInfo;
 using aidl::android::hardware::memtrack::IMemtrack;
 using aidl::android::hardware::memtrack::MemtrackRecord;
 using aidl::android::hardware::memtrack::MemtrackType;
+using android::vintf::KernelVersion;
+using android::vintf::RuntimeInfo;
+using android::vintf::VintfObject;
 
 class MemtrackAidlTest : public testing::TestWithParam<std::string> {
   public:
@@ -42,17 +46,19 @@
 
 TEST_P(MemtrackAidlTest, GetMemoryInvalidPid) {
     int pid = -1;
-    MemtrackType type = MemtrackType::OTHER;
-    std::vector<MemtrackRecord> records;
 
-    auto status = memtrack_->getMemory(pid, type, &records);
+    for (MemtrackType type : ndk::enum_range<MemtrackType>()) {
+        std::vector<MemtrackRecord> records;
 
-    EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT);
+        auto status = memtrack_->getMemory(pid, type, &records);
+
+        EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT);
+    }
 }
 
 TEST_P(MemtrackAidlTest, GetMemoryInvalidType) {
     int pid = 1;
-    MemtrackType type = MemtrackType::NUM_TYPES;
+    MemtrackType type = static_cast<MemtrackType>(-1);
     std::vector<MemtrackRecord> records;
 
     auto status = memtrack_->getMemory(pid, type, &records);
@@ -62,12 +68,13 @@
 
 TEST_P(MemtrackAidlTest, GetMemory) {
     int pid = 1;
-    MemtrackType type = MemtrackType::OTHER;
-    std::vector<MemtrackRecord> records;
+    for (MemtrackType type : ndk::enum_range<MemtrackType>()) {
+        std::vector<MemtrackRecord> records;
 
-    auto status = memtrack_->getMemory(pid, type, &records);
+        auto status = memtrack_->getMemory(pid, type, &records);
 
-    EXPECT_TRUE(status.isOk());
+        EXPECT_TRUE(status.isOk());
+    }
 }
 
 TEST_P(MemtrackAidlTest, GetGpuDeviceInfo) {
@@ -75,7 +82,23 @@
 
     auto status = memtrack_->getGpuDeviceInfo(&device_info);
 
+    // Devices with < 5.4 kernels aren't required to provide an implementation of
+    // getGpuDeviceInfo(), and can return EX_UNSUPPORTED_OPERATION
+    if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+        KernelVersion min_kernel_version = KernelVersion(5, 4, 0);
+        KernelVersion kernel_version = VintfObject::GetInstance()
+                                               ->getRuntimeInfo(RuntimeInfo::FetchFlag::CPU_VERSION)
+                                               ->kernelVersion();
+        EXPECT_LT(kernel_version, min_kernel_version)
+                << "Devices with 5.4 or later kernels must implement getGpuDeviceInfo()";
+        return;
+    }
+
     EXPECT_TRUE(status.isOk());
+    EXPECT_FALSE(device_info.empty());
+    for (auto device : device_info) {
+        EXPECT_FALSE(device.name.empty());
+    }
 }
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemtrackAidlTest);
diff --git a/neuralnetworks/1.0/utils/Android.bp b/neuralnetworks/1.0/utils/Android.bp
index 4d61fc0..d033617 100644
--- a/neuralnetworks/1.0/utils/Android.bp
+++ b/neuralnetworks/1.0/utils/Android.bp
@@ -32,3 +32,29 @@
         "neuralnetworks_utils_hal_common",
     ],
 }
+
+cc_test {
+    name: "neuralnetworks_utils_hal_1_0_test",
+    srcs: ["test/*.cpp"],
+    static_libs: [
+        "android.hardware.neuralnetworks@1.0",
+        "libgmock",
+        "libneuralnetworks_common",
+        "neuralnetworks_types",
+        "neuralnetworks_utils_hal_common",
+        "neuralnetworks_utils_hal_1_0",
+    ],
+    shared_libs: [
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libbase",
+        "libcutils",
+        "libfmq",
+        "libhidlbase",
+        "libhidlmemory",
+        "liblog",
+        "libnativewindow",
+        "libutils",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h
new file mode 100644
index 0000000..8329303
--- /dev/null
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H
+
+#include <nnapi/IBurst.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+// See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
+// lifetimes across processes and for protecting asynchronous calls across HIDL.
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+// Class that adapts nn::IPreparedModel to nn::IBurst.
+class Burst final : public nn::IBurst {
+    struct PrivateConstructorTag {};
+
+  public:
+    static nn::GeneralResult<std::shared_ptr<const Burst>> create(
+            nn::SharedPreparedModel preparedModel);
+
+    Burst(PrivateConstructorTag tag, nn::SharedPreparedModel preparedModel);
+
+    OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override;
+
+    nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
+            const nn::Request& request, nn::MeasureTiming measure) const override;
+
+  private:
+    const nn::SharedPreparedModel kPreparedModel;
+};
+
+}  // namespace android::hardware::neuralnetworks::V1_0::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H
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 d3d933b..5d4bdbc 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,7 +36,7 @@
 GeneralResult<Operation> unvalidatedConvert(const hal::V1_0::Operation& operation);
 GeneralResult<Model::OperandValues> unvalidatedConvert(
         const hardware::hidl_vec<uint8_t>& operandValues);
-GeneralResult<Memory> unvalidatedConvert(const hardware::hidl_memory& memory);
+GeneralResult<SharedMemory> unvalidatedConvert(const hardware::hidl_memory& memory);
 GeneralResult<Model> unvalidatedConvert(const hal::V1_0::Model& model);
 GeneralResult<Request::Argument> unvalidatedConvert(
         const hal::V1_0::RequestArgument& requestArgument);
@@ -65,7 +65,7 @@
 nn::GeneralResult<Operation> unvalidatedConvert(const nn::Operation& operation);
 nn::GeneralResult<hidl_vec<uint8_t>> unvalidatedConvert(
         const nn::Model::OperandValues& operandValues);
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory);
+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);
 nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Request::MemoryPool& memoryPool);
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..4681b9e 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
@@ -52,6 +52,7 @@
     const std::string& getVersionString() const override;
     nn::Version getFeatureLevel() const override;
     nn::DeviceType getType() const override;
+    bool isUpdatable() const override;
     const std::vector<nn::Extension>& getSupportedExtensions() const override;
     const nn::Capabilities& getCapabilities() const override;
     std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
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 2de1828..8853eea 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
@@ -35,7 +35,8 @@
 namespace android::hardware::neuralnetworks::V1_0::utils {
 
 // Class that adapts V1_0::IPreparedModel to nn::IPreparedModel.
-class PreparedModel final : public nn::IPreparedModel {
+class PreparedModel final : public nn::IPreparedModel,
+                            public std::enable_shared_from_this<PreparedModel> {
     struct PrivateConstructorTag {};
 
   public:
@@ -56,6 +57,8 @@
             const nn::OptionalDuration& loopTimeoutDuration,
             const nn::OptionalDuration& timeoutDurationAfterFence) const override;
 
+    nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
     std::any getUnderlyingResource() const override;
 
   private:
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 4cec545..b695f48 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
@@ -44,6 +44,12 @@
     return result.has_value();
 }
 
+template <typename Type>
+auto convertFromNonCanonical(const Type& nonCanonicalObject)
+        -> decltype(convert(nn::convert(nonCanonicalObject).value())) {
+    return convert(NN_TRY(nn::convert(nonCanonicalObject)));
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_0::utils
 
 #endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_H
diff --git a/neuralnetworks/1.0/utils/src/Burst.cpp b/neuralnetworks/1.0/utils/src/Burst.cpp
new file mode 100644
index 0000000..971ad08
--- /dev/null
+++ b/neuralnetworks/1.0/utils/src/Burst.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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 "Burst.h"
+
+#include <android-base/logging.h>
+#include <nnapi/IBurst.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+nn::GeneralResult<std::shared_ptr<const Burst>> Burst::create(
+        nn::SharedPreparedModel preparedModel) {
+    if (preparedModel == nullptr) {
+        return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
+               << "V1_0::utils::Burst::create must have non-null preparedModel";
+    }
+
+    return std::make_shared<const Burst>(PrivateConstructorTag{}, std::move(preparedModel));
+}
+
+Burst::Burst(PrivateConstructorTag /*tag*/, nn::SharedPreparedModel preparedModel)
+    : kPreparedModel(std::move(preparedModel)) {
+    CHECK(kPreparedModel != nullptr);
+}
+
+Burst::OptionalCacheHold Burst::cacheMemory(const nn::SharedMemory& /*memory*/) const {
+    return nullptr;
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Burst::execute(
+        const nn::Request& request, nn::MeasureTiming measure) const {
+    return kPreparedModel->execute(request, measure, {}, {});
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_0::utils
diff --git a/neuralnetworks/1.0/utils/src/Conversions.cpp b/neuralnetworks/1.0/utils/src/Conversions.cpp
index fde7346..7a099cf 100644
--- a/neuralnetworks/1.0/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.0/utils/src/Conversions.cpp
@@ -153,8 +153,8 @@
     return Model::OperandValues(operandValues.data(), operandValues.size());
 }
 
-GeneralResult<Memory> unvalidatedConvert(const hidl_memory& memory) {
-    return createSharedMemoryFromHidlMemory(memory);
+GeneralResult<SharedMemory> unvalidatedConvert(const hidl_memory& memory) {
+    return hal::utils::createSharedMemoryFromHidlMemory(memory);
 }
 
 GeneralResult<Model> unvalidatedConvert(const hal::V1_0::Model& model) {
@@ -346,9 +346,8 @@
     return hidl_vec<uint8_t>(operandValues.data(), operandValues.data() + operandValues.size());
 }
 
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) {
-    return hidl_memory(memory.name, NN_TRY(hal::utils::hidlHandleFromSharedHandle(memory.handle)),
-                       memory.size);
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
+    return hal::utils::createHidlMemoryFromSharedMemory(memory);
 }
 
 nn::GeneralResult<Model> unvalidatedConvert(const nn::Model& model) {
@@ -392,7 +391,7 @@
 }
 
 nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Request::MemoryPool& memoryPool) {
-    return unvalidatedConvert(std::get<nn::Memory>(memoryPool));
+    return unvalidatedConvert(std::get<nn::SharedMemory>(memoryPool));
 }
 
 nn::GeneralResult<Request> unvalidatedConvert(const nn::Request& request) {
diff --git a/neuralnetworks/1.0/utils/src/Device.cpp b/neuralnetworks/1.0/utils/src/Device.cpp
index 93bd81a..bb31a26 100644
--- a/neuralnetworks/1.0/utils/src/Device.cpp
+++ b/neuralnetworks/1.0/utils/src/Device.cpp
@@ -106,6 +106,10 @@
     return nn::DeviceType::OTHER;
 }
 
+bool Device::isUpdatable() const {
+    return false;
+}
+
 const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
     return kExtensions;
 }
diff --git a/neuralnetworks/1.0/utils/src/PreparedModel.cpp b/neuralnetworks/1.0/utils/src/PreparedModel.cpp
index c0c22fb..858571d 100644
--- a/neuralnetworks/1.0/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.0/utils/src/PreparedModel.cpp
@@ -16,6 +16,7 @@
 
 #include "PreparedModel.h"
 
+#include "Burst.h"
 #include "Callbacks.h"
 #include "Conversions.h"
 #include "Utils.h"
@@ -90,6 +91,10 @@
            << "IPreparedModel::executeFenced is not supported on 1.0 HAL service";
 }
 
+nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
+    return Burst::create(shared_from_this());
+}
+
 std::any PreparedModel::getUnderlyingResource() const {
     sp<V1_0::IPreparedModel> resource = kPreparedModel;
     return resource;
diff --git a/neuralnetworks/1.0/utils/test/DeviceTest.cpp b/neuralnetworks/1.0/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..e881da2
--- /dev/null
+++ b/neuralnetworks/1.0/utils/test/DeviceTest.cpp
@@ -0,0 +1,524 @@
+/*
+ * 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 "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.0/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+        .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+                               .dimensions = {1},
+                               .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+                              {.type = nn::OperandType::TENSOR_FLOAT32,
+                               .dimensions = {1},
+                               .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+                 .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+                 .inputIndexes = {0},
+                 .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_0::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+        .execTime = std::numeric_limits<float>::max(),
+        .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+    return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+        std::apply(cb, argPack);
+        return Void();
+    };
+}
+
+sp<MockDevice> createMockDevice() {
+    const auto mockDevice = MockDevice::create();
+
+    // Setup default actions for each relevant call.
+    const auto getCapabilities_ret = makeCallbackReturn(
+            V1_0::ErrorStatus::NONE, V1_0::Capabilities{
+                                             .float32Performance = kNoPerformanceInfo,
+                                             .quantized8Performance = kNoPerformanceInfo,
+                                     });
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, getCapabilities(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+                             const sp<V1_0::utils::MockPreparedModel>& preparedModel) {
+    return [launchStatus, returnStatus, preparedModel](const V1_0::Model& /*model*/,
+                                                       const sp<V1_0::IPreparedModelCallback>& cb)
+                   -> hardware::Return<V1_0::ErrorStatus> {
+        cb->notify(returnStatus, preparedModel).isOk();
+        return launchStatus;
+    };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+    return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+}  // namespace
+
+TEST(DeviceTest, invalidName) {
+    // run test
+    const auto device = MockDevice::create();
+    const auto result = Device::create(kInvalidName, device);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+    // run test
+    const auto result = Device::create(kName, kInvalidDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                        V1_0::Capabilities{
+                                                .float32Performance = kNoPerformanceInfo,
+                                                .quantized8Performance = kNoPerformanceInfo,
+                                        });
+    EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getCapabilities(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getCapabilities(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = []() -> Return<bool> { return false; };
+    EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto& name = device->getName();
+
+    // verify result
+    EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto featureLevel = device->getFeatureLevel();
+
+    // verify result
+    EXPECT_EQ(featureLevel, nn::Version::ANDROID_OC_MR1);
+}
+
+TEST(DeviceTest, getCachedData) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto result = Device::create(kName, mockDevice);
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& device = result.value();
+
+    // run test and verify results
+    EXPECT_EQ(device->getVersionString(), device->getVersionString());
+    EXPECT_EQ(device->getType(), device->getType());
+    EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+    EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+    EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = []() -> Return<void> { return {}; };
+    EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, ping())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [](const auto& model, const auto& cb) {
+        cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true));
+        return hardware::Void();
+    };
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& supportedOperations = result.value();
+    EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+    EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [](const auto& /*model*/, const auto& cb) {
+        cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
+        return hardware::Void();
+    };
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto mockPreparedModel = V1_0::utils::MockPreparedModel::create();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                                     V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::NONE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+        mockDevice->simulateCrash();
+        return V1_0::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNotSupported) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, allocateNotSupported) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_0::utils
diff --git a/neuralnetworks/1.0/utils/test/MockDevice.h b/neuralnetworks/1.0/utils/test/MockDevice.h
new file mode 100644
index 0000000..0fb59e3
--- /dev/null
+++ b/neuralnetworks/1.0/utils/test/MockDevice.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.0/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+class MockDevice final : public IDevice {
+  public:
+    static sp<MockDevice> create();
+
+    // IBase methods below.
+    MOCK_METHOD(Return<void>, ping, (), (override));
+    MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+    Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+    // V1_0 methods below.
+    MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations,
+                (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+                (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+    // Helper methods.
+    void simulateCrash();
+
+  private:
+    sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+    auto mockDevice = sp<MockDevice>::make();
+
+    // Setup default actions for each relevant call.
+    const auto ret = []() -> Return<bool> { return true; };
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+                                            uint64_t /*cookie*/) {
+    mDeathRecipient = recipient;
+    return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+    ASSERT_NE(nullptr, mDeathRecipient.get());
+
+    // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+    // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+    // to determine which object is dead. However, the utils::Device code only pairs a single death
+    // recipient with a single HIDL interface object, so these arguments are redundant.
+    mDeathRecipient->serviceDied(0, nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_0::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.0/utils/test/MockPreparedModel.h b/neuralnetworks/1.0/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..7a48a83
--- /dev/null
+++ b/neuralnetworks/1.0/utils/test/MockPreparedModel.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+  public:
+    static sp<MockPreparedModel> create();
+
+    // IBase methods below.
+    MOCK_METHOD(Return<void>, ping, (), (override));
+    MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+    Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient,
+                             uint64_t /*cookie*/) override;
+
+    // V1_0 methods below.
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+                (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+                (override));
+
+    // Helper methods.
+    void simulateCrash();
+
+  private:
+    sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+    auto mockPreparedModel = sp<MockPreparedModel>::make();
+
+    // Setup default actions for each relevant call.
+    const auto ret = []() -> Return<bool> { return true; };
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber());
+
+    return mockPreparedModel;
+}
+
+inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient,
+                                                   uint64_t /*cookie*/) {
+    mDeathRecipient = recipient;
+    return linkToDeathRet();
+}
+
+inline void MockPreparedModel::simulateCrash() {
+    ASSERT_NE(nullptr, mDeathRecipient.get());
+
+    // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass
+    // in 0 and nullptr for these arguments instead. Normally, they are used by the
+    // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel
+    // code only pairs a single death recipient with a single HIDL interface object, so these
+    // arguments are redundant.
+    mDeathRecipient->serviceDied(0, nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_0::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp
new file mode 100644
index 0000000..a5cbc72
--- /dev/null
+++ b/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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 "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.0/PreparedModel.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const sp<V1_0::IPreparedModel> kInvalidPreparedModel;
+
+sp<MockPreparedModel> createMockPreparedModel() {
+    return MockPreparedModel::create();
+}
+
+auto makeExecute(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus) {
+    return [launchStatus, returnStatus](
+                   const V1_0::Request& /*request*/,
+                   const sp<V1_0::IExecutionCallback>& cb) -> Return<V1_0::ErrorStatus> {
+        cb->notify(returnStatus);
+        return launchStatus;
+    };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+    return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+}  // namespace
+
+TEST(PreparedModelTest, invalidPreparedModel) {
+    // run test
+    const auto result = PreparedModel::create(kInvalidPreparedModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathError) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto ret = []() -> Return<bool> { return false; };
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathTransportFailure) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathDeadObject) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, execute) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecute(V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::NONE)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    EXPECT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeLaunchError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecute(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                         V1_0::ErrorStatus::GENERAL_FAILURE)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeReturnError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _))
+            .Times(1)
+            .WillOnce(Invoke(
+                    makeExecute(V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::GENERAL_FAILURE)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeTransportFailure) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeDeadObject) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeCrash) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+    const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_0::ErrorStatus> {
+        mockPreparedModel->simulateCrash();
+        return V1_0::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeFencedNotSupported) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+// TODO: test burst execution if/when it is added to nn::IPreparedModel.
+
+TEST(PreparedModelTest, getUnderlyingResource) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+
+    // run test
+    const auto resource = preparedModel->getUnderlyingResource();
+
+    // verify resource
+    const sp<V1_0::IPreparedModel>* maybeMock = std::any_cast<sp<V1_0::IPreparedModel>>(&resource);
+    ASSERT_NE(maybeMock, nullptr);
+    EXPECT_EQ(maybeMock->get(), mockPreparedModel.get());
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_0::utils
diff --git a/neuralnetworks/1.0/vts/functional/AndroidTest.xml b/neuralnetworks/1.0/vts/functional/AndroidTest.xml
index 13671f9..9dd85ae 100644
--- a/neuralnetworks/1.0/vts/functional/AndroidTest.xml
+++ b/neuralnetworks/1.0/vts/functional/AndroidTest.xml
@@ -28,5 +28,6 @@
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="VtsHalNeuralnetworksV1_0TargetTest" />
+        <option name="native-test-timeout" value="20m" />
     </test>
 </configuration>
diff --git a/neuralnetworks/1.1/utils/Android.bp b/neuralnetworks/1.1/utils/Android.bp
index 909575b..fe0c80a 100644
--- a/neuralnetworks/1.1/utils/Android.bp
+++ b/neuralnetworks/1.1/utils/Android.bp
@@ -34,3 +34,31 @@
         "neuralnetworks_utils_hal_common",
     ],
 }
+
+cc_test {
+    name: "neuralnetworks_utils_hal_1_1_test",
+    srcs: ["test/*.cpp"],
+    static_libs: [
+        "android.hardware.neuralnetworks@1.0",
+        "android.hardware.neuralnetworks@1.1",
+        "libgmock",
+        "libneuralnetworks_common",
+        "neuralnetworks_types",
+        "neuralnetworks_utils_hal_common",
+        "neuralnetworks_utils_hal_1_0",
+        "neuralnetworks_utils_hal_1_1",
+    ],
+    shared_libs: [
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libbase",
+        "libcutils",
+        "libfmq",
+        "libhidlbase",
+        "libhidlmemory",
+        "liblog",
+        "libnativewindow",
+        "libutils",
+    ],
+    test_suites: ["general-tests"],
+}
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..3aec8ee 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
@@ -52,6 +52,7 @@
     const std::string& getVersionString() const override;
     nn::Version getFeatureLevel() const override;
     nn::DeviceType getType() const override;
+    bool isUpdatable() const override;
     const std::vector<nn::Extension>& getSupportedExtensions() const override;
     const nn::Capabilities& getCapabilities() const override;
     std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
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 052d88e..09597a3 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
@@ -47,6 +47,12 @@
     return result.has_value();
 }
 
+template <typename Type>
+auto convertFromNonCanonical(const Type& nonCanonicalObject)
+        -> decltype(convert(nn::convert(nonCanonicalObject).value())) {
+    return convert(NN_TRY(nn::convert(nonCanonicalObject)));
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_1::utils
 
 #endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_H
diff --git a/neuralnetworks/1.1/utils/src/Conversions.cpp b/neuralnetworks/1.1/utils/src/Conversions.cpp
index b47f25a..07bf7bc 100644
--- a/neuralnetworks/1.1/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.1/utils/src/Conversions.cpp
@@ -175,7 +175,7 @@
     return V1_0::utils::unvalidatedConvert(operandValues);
 }
 
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) {
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
     return V1_0::utils::unvalidatedConvert(memory);
 }
 
diff --git a/neuralnetworks/1.1/utils/src/Device.cpp b/neuralnetworks/1.1/utils/src/Device.cpp
index 3197ef4..d2ef57f 100644
--- a/neuralnetworks/1.1/utils/src/Device.cpp
+++ b/neuralnetworks/1.1/utils/src/Device.cpp
@@ -106,6 +106,10 @@
     return nn::DeviceType::UNKNOWN;
 }
 
+bool Device::isUpdatable() const {
+    return false;
+}
+
 const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
     return kExtensions;
 }
diff --git a/neuralnetworks/1.1/utils/test/DeviceTest.cpp b/neuralnetworks/1.1/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..41e0e30
--- /dev/null
+++ b/neuralnetworks/1.1/utils/test/DeviceTest.cpp
@@ -0,0 +1,534 @@
+/*
+ * 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 "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.1/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.1/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_1::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+        .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+                               .dimensions = {1},
+                               .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+                              {.type = nn::OperandType::TENSOR_FLOAT32,
+                               .dimensions = {1},
+                               .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+                 .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+                 .inputIndexes = {0},
+                 .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_1::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+        .execTime = std::numeric_limits<float>::max(),
+        .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+    return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+        std::apply(cb, argPack);
+        return Void();
+    };
+}
+
+sp<MockDevice> createMockDevice() {
+    const auto mockDevice = MockDevice::create();
+
+    // Setup default actions for each relevant call.
+    const auto getCapabilities_ret =
+            makeCallbackReturn(V1_0::ErrorStatus::NONE,
+                               V1_1::Capabilities{
+                                       .float32Performance = kNoPerformanceInfo,
+                                       .quantized8Performance = kNoPerformanceInfo,
+                                       .relaxedFloat32toFloat16Performance = kNoPerformanceInfo,
+                               });
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, getCapabilities_1_1(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+    // Ensure that older calls are not used.
+    EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0);
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0);
+    EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0);
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+                             const sp<V1_0::utils::MockPreparedModel>& preparedModel) {
+    return [launchStatus, returnStatus, preparedModel](const V1_1::Model& /*model*/,
+                                                       V1_1::ExecutionPreference /*preference*/,
+                                                       const sp<V1_0::IPreparedModelCallback>& cb)
+                   -> hardware::Return<V1_0::ErrorStatus> {
+        cb->notify(returnStatus, preparedModel).isOk();
+        return launchStatus;
+    };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+    return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+}  // namespace
+
+TEST(DeviceTest, invalidName) {
+    // run test
+    const auto device = MockDevice::create();
+    const auto result = Device::create(kInvalidName, device);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+    // run test
+    const auto result = Device::create(kName, kInvalidDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret =
+            makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+                               V1_1::Capabilities{
+                                       .float32Performance = kNoPerformanceInfo,
+                                       .quantized8Performance = kNoPerformanceInfo,
+                                       .relaxedFloat32toFloat16Performance = kNoPerformanceInfo,
+                               });
+    EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getCapabilities_1_1(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getCapabilities_1_1(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = []() -> Return<bool> { return false; };
+    EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto& name = device->getName();
+
+    // verify result
+    EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto featureLevel = device->getFeatureLevel();
+
+    // verify result
+    EXPECT_EQ(featureLevel, nn::Version::ANDROID_P);
+}
+
+TEST(DeviceTest, getCachedData) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto result = Device::create(kName, mockDevice);
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& device = result.value();
+
+    // run test and verify results
+    EXPECT_EQ(device->getVersionString(), device->getVersionString());
+    EXPECT_EQ(device->getType(), device->getType());
+    EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+    EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+    EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = []() -> Return<void> { return {}; };
+    EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, ping())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [](const auto& model, const auto& cb) {
+        cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true));
+        return hardware::Void();
+    };
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& supportedOperations = result.value();
+    EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+    EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [](const auto& /*model*/, const auto& cb) {
+        cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
+        return hardware::Void();
+    };
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto mockPreparedModel = V1_0::utils::MockPreparedModel::create();
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                                     V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::NONE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+        mockDevice->simulateCrash();
+        return V1_0::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNotSupported) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, allocateNotSupported) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_1::utils
diff --git a/neuralnetworks/1.1/utils/test/MockDevice.h b/neuralnetworks/1.1/utils/test/MockDevice.h
new file mode 100644
index 0000000..3b92e58
--- /dev/null
+++ b/neuralnetworks/1.1/utils/test/MockDevice.h
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.1/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_1::utils {
+
+class MockDevice final : public IDevice {
+  public:
+    static sp<MockDevice> create();
+
+    // IBase methods below.
+    MOCK_METHOD(Return<void>, ping, (), (override));
+    MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+    Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+    // V1_0 methods below.
+    MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations,
+                (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+                (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+    // V1_1 methods below.
+    MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations_1_1,
+                (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1,
+                (const V1_1::Model& model, V1_1::ExecutionPreference preference,
+                 const sp<V1_0::IPreparedModelCallback>& callback),
+                (override));
+
+    // Helper methods.
+    void simulateCrash();
+
+  private:
+    sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+    auto mockDevice = sp<MockDevice>::make();
+
+    // Setup default actions for each relevant call.
+    const auto ret = []() -> Return<bool> { return true; };
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+                                            uint64_t /*cookie*/) {
+    mDeathRecipient = recipient;
+    return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+    ASSERT_NE(nullptr, mDeathRecipient.get());
+
+    // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+    // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+    // to determine which object is dead. However, the utils::Device code only pairs a single death
+    // recipient with a single HIDL interface object, so these arguments are redundant.
+    mDeathRecipient->serviceDied(0, nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_1::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.1/utils/test/MockPreparedModel.h b/neuralnetworks/1.1/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..aba731e
--- /dev/null
+++ b/neuralnetworks/1.1/utils/test/MockPreparedModel.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+  public:
+    static sp<MockPreparedModel> create();
+
+    // V1_0 methods below.
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+                (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+                (override));
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+    return sp<MockPreparedModel>::make();
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_0::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.1/vts/functional/AndroidTest.xml b/neuralnetworks/1.1/vts/functional/AndroidTest.xml
index cfde60c..74001f9 100644
--- a/neuralnetworks/1.1/vts/functional/AndroidTest.xml
+++ b/neuralnetworks/1.1/vts/functional/AndroidTest.xml
@@ -28,5 +28,6 @@
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="VtsHalNeuralnetworksV1_1TargetTest" />
+        <option name="native-test-timeout" value="20m" />
     </test>
 </configuration>
diff --git a/neuralnetworks/1.2/utils/Android.bp b/neuralnetworks/1.2/utils/Android.bp
index 22e8659..6959056 100644
--- a/neuralnetworks/1.2/utils/Android.bp
+++ b/neuralnetworks/1.2/utils/Android.bp
@@ -18,6 +18,7 @@
     name: "neuralnetworks_utils_hal_1_2",
     defaults: ["neuralnetworks_utils_defaults"],
     srcs: ["src/*"],
+    exclude_srcs: ["src/ExecutionBurst*"],
     local_include_dirs: ["include/nnapi/hal/1.2/"],
     export_include_dirs: ["include"],
     cflags: ["-Wthread-safety"],
@@ -36,3 +37,33 @@
         "neuralnetworks_utils_hal_common",
     ],
 }
+
+cc_test {
+    name: "neuralnetworks_utils_hal_1_2_test",
+    srcs: ["test/*.cpp"],
+    static_libs: [
+        "android.hardware.neuralnetworks@1.0",
+        "android.hardware.neuralnetworks@1.1",
+        "android.hardware.neuralnetworks@1.2",
+        "libgmock",
+        "libneuralnetworks_common",
+        "neuralnetworks_types",
+        "neuralnetworks_utils_hal_common",
+        "neuralnetworks_utils_hal_1_0",
+        "neuralnetworks_utils_hal_1_1",
+        "neuralnetworks_utils_hal_1_2",
+    ],
+    shared_libs: [
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libbase",
+        "libcutils",
+        "libfmq",
+        "libhidlbase",
+        "libhidlmemory",
+        "liblog",
+        "libnativewindow",
+        "libutils",
+    ],
+    test_suites: ["general-tests"],
+}
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..489f857 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
@@ -71,6 +71,7 @@
     const std::string& getVersionString() const override;
     nn::Version getFeatureLevel() const override;
     nn::DeviceType getType() const override;
+    bool isUpdatable() const override;
     const std::vector<nn::Extension>& getSupportedExtensions() const override;
     const nn::Capabilities& getCapabilities() const override;
     std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h
new file mode 100644
index 0000000..5356a91
--- /dev/null
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2019 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_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H
+
+#include "ExecutionBurstUtils.h"
+
+#include <android-base/macros.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/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>
+#include <hidl/MQDescriptor.h>
+
+#include <atomic>
+#include <chrono>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <stack>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace android::nn {
+
+/**
+ * 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.
+ */
+class ExecutionBurstController {
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ExecutionBurstController);
+
+  public:
+    /**
+     * NN runtime burst callback object and memory cache.
+     *
+     * ExecutionBurstCallback associates a hidl_memory object with a slot number
+     * to be passed across FMQ. The ExecutionBurstServer can use this callback
+     * to retrieve this hidl_memory corresponding to the slot via HIDL.
+     *
+     * Whenever a hidl_memory object is copied, it will duplicate the underlying
+     * file descriptor. Because the NN runtime currently copies the hidl_memory
+     * on each execution, it is difficult to associate hidl_memory objects with
+     * previously cached hidl_memory objects. For this reason, callers of this
+     * class must pair each hidl_memory object with an associated key. For
+     * efficiency, if two hidl_memory objects represent the same underlying
+     * buffer, they must use the same key.
+     */
+    class ExecutionBurstCallback : public hardware::neuralnetworks::V1_2::IBurstCallback {
+        DISALLOW_COPY_AND_ASSIGN(ExecutionBurstCallback);
+
+      public:
+        ExecutionBurstCallback() = default;
+
+        hardware::Return<void> getMemories(const hardware::hidl_vec<int32_t>& slots,
+                                           getMemories_cb cb) override;
+
+        /**
+         * This function performs one of two different actions:
+         * 1) If a key corresponding to a memory resource is unrecognized by the
+         *    ExecutionBurstCallback object, the ExecutionBurstCallback object
+         *    will allocate a slot, bind the memory to the slot, and return the
+         *    slot identifier.
+         * 2) If a key corresponding to a memory resource is recognized by the
+         *    ExecutionBurstCallback object, the ExecutionBurstCallback object
+         *    will return the existing slot identifier.
+         *
+         * @param memories Memory resources used in an inference.
+         * @param keys Unique identifiers where each element corresponds to a
+         *     memory resource element in "memories".
+         * @return Unique slot identifiers where each returned slot element
+         *     corresponds to a memory resource element in "memories".
+         */
+        std::vector<int32_t> getSlots(const hardware::hidl_vec<hardware::hidl_memory>& memories,
+                                      const std::vector<intptr_t>& keys);
+
+        /*
+         * This function performs two different actions:
+         * 1) Removes an entry from the cache (if present), including the local
+         *    storage of the hidl_memory object. Note that this call does not
+         *    free any corresponding hidl_memory object in ExecutionBurstServer,
+         *    which is separately freed via IBurstContext::freeMemory.
+         * 2) Return whether a cache entry was removed and which slot was removed if
+         *    found. If the key did not to correspond to any entry in the cache, a
+         *    slot number of 0 is returned. The slot number and whether the entry
+         *    existed is useful so the same slot can be freed in the
+         *    ExecutionBurstServer's cache via IBurstContext::freeMemory.
+         */
+        std::pair<bool, int32_t> freeMemory(intptr_t key);
+
+      private:
+        int32_t getSlotLocked(const hardware::hidl_memory& memory, intptr_t key);
+        int32_t allocateSlotLocked();
+
+        std::mutex mMutex;
+        std::stack<int32_t, std::vector<int32_t>> mFreeSlots;
+        std::map<intptr_t, int32_t> mMemoryIdToSlot;
+        std::vector<hardware::hidl_memory> mMemoryCache;
+    };
+
+    /**
+     * Creates a burst controller on a prepared model.
+     *
+     * Prefer this over ExecutionBurstController's constructor.
+     *
+     * @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.
+     */
+    static std::unique_ptr<ExecutionBurstController> create(
+            const sp<hardware::neuralnetworks::V1_2::IPreparedModel>& preparedModel,
+            std::chrono::microseconds pollingTimeWindow);
+
+    // prefer calling ExecutionBurstController::create
+    ExecutionBurstController(const std::shared_ptr<RequestChannelSender>& requestChannelSender,
+                             const std::shared_ptr<ResultChannelReceiver>& resultChannelReceiver,
+                             const sp<hardware::neuralnetworks::V1_2::IBurstContext>& burstContext,
+                             const sp<ExecutionBurstCallback>& callback,
+                             const sp<hardware::hidl_death_recipient>& deathHandler = nullptr);
+
+    // explicit destructor to unregister the death recipient
+    ~ExecutionBurstController();
+
+    /**
+     * Execute a request on a model.
+     *
+     * @param request Arguments to be executed on a model.
+     * @param measure Whether to collect timing measurements, either YES or NO
+     * @param memoryIds Identifiers corresponding to each memory object in the
+     *     request's pools.
+     * @return A tuple of:
+     *     - result code of the execution
+     *     - dynamic output shapes from the execution
+     *     - any execution time measurements of the execution
+     *     - whether or not a failed burst execution should be re-run using a
+     *       different path (e.g., IPreparedModel::executeSynchronously)
+     */
+    std::tuple<int, std::vector<hardware::neuralnetworks::V1_2::OutputShape>,
+               hardware::neuralnetworks::V1_2::Timing, bool>
+    compute(const hardware::neuralnetworks::V1_0::Request& request,
+            hardware::neuralnetworks::V1_2::MeasureTiming measure,
+            const std::vector<intptr_t>& memoryIds);
+
+    /**
+     * Propagate a user's freeing of memory to the service.
+     *
+     * @param key Key corresponding to the memory object.
+     */
+    void freeMemory(intptr_t key);
+
+  private:
+    std::mutex mMutex;
+    const std::shared_ptr<RequestChannelSender> mRequestChannelSender;
+    const std::shared_ptr<ResultChannelReceiver> mResultChannelReceiver;
+    const sp<hardware::neuralnetworks::V1_2::IBurstContext> mBurstContext;
+    const sp<ExecutionBurstCallback> mMemoryCache;
+    const sp<hardware::hidl_death_recipient> mDeathHandler;
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h
new file mode 100644
index 0000000..2e109b2
--- /dev/null
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2019 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_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_H
+
+#include "ExecutionBurstUtils.h"
+
+#include <android-base/macros.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.2/IBurstCallback.h>
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <optional>
+#include <thread>
+#include <tuple>
+#include <vector>
+
+namespace android::nn {
+
+/**
+ * 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.
+ */
+class ExecutionBurstServer : public hardware::neuralnetworks::V1_2::IBurstContext {
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ExecutionBurstServer);
+
+  public:
+    /**
+     * IBurstExecutorWithCache is a callback object passed to
+     * ExecutionBurstServer's factory function that is used to perform an
+     * execution. Because some memory resources are needed across multiple
+     * executions, this object also contains a local cache that can directly be
+     * used in the execution.
+     *
+     * ExecutionBurstServer will never access its IBurstExecutorWithCache object
+     * with concurrent calls.
+     */
+    class IBurstExecutorWithCache {
+        DISALLOW_COPY_AND_ASSIGN(IBurstExecutorWithCache);
+
+      public:
+        IBurstExecutorWithCache() = default;
+        virtual ~IBurstExecutorWithCache() = default;
+
+        /**
+         * Checks if a cache entry specified by a slot is present in the cache.
+         *
+         * @param slot Identifier of the cache entry.
+         * @return 'true' if the cache entry is present in the cache, 'false'
+         *     otherwise.
+         */
+        virtual bool isCacheEntryPresent(int32_t slot) const = 0;
+
+        /**
+         * Adds an entry specified by a slot to the cache.
+         *
+         * The caller of this function must ensure that the cache entry that is
+         * being added is not already present in the cache. This can be checked
+         * via isCacheEntryPresent.
+         *
+         * @param memory Memory resource to be cached.
+         * @param slot Slot identifier corresponding to the memory resource.
+         */
+        virtual void addCacheEntry(const hardware::hidl_memory& memory, int32_t slot) = 0;
+
+        /**
+         * Removes an entry specified by a slot from the cache.
+         *
+         * If the cache entry corresponding to the slot number does not exist,
+         * the call does nothing.
+         *
+         * @param slot Slot identifier corresponding to the memory resource.
+         */
+        virtual void removeCacheEntry(int32_t slot) = 0;
+
+        /**
+         * Perform an execution.
+         *
+         * @param request Request object with inputs and outputs specified.
+         *     Request::pools is empty, and DataLocation::poolIndex instead
+         *     refers to the 'slots' argument as if it were Request::pools.
+         * @param slots Slots corresponding to the cached memory entries to be
+         *     used.
+         * @param measure Whether timing information is requested for the
+         *     execution.
+         * @return Result of the execution, including the status of the
+         *     execution, dynamic output shapes, and any timing information.
+         */
+        virtual std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus,
+                           hardware::hidl_vec<hardware::neuralnetworks::V1_2::OutputShape>,
+                           hardware::neuralnetworks::V1_2::Timing>
+        execute(const hardware::neuralnetworks::V1_0::Request& request,
+                const std::vector<int32_t>& slots,
+                hardware::neuralnetworks::V1_2::MeasureTiming measure) = 0;
+    };
+
+    /**
+     * Create automated context to manage FMQ-based executions.
+     *
+     * This function is intended to be used by a service to automatically:
+     * 1) Receive data from a provided FMQ
+     * 2) Execute a model with the given information
+     * 3) Send the result to the created FMQ
+     *
+     * @param callback Callback used to retrieve memories corresponding to
+     *     unrecognized slots.
+     * @param requestChannel Input FMQ channel through which the client passes the
+     *     request to the service.
+     * @param resultChannel Output FMQ channel from which the client can retrieve
+     *     the result of the execution.
+     * @param executorWithCache 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.
+     * @result IBurstContext Handle to the burst context.
+     */
+    static sp<ExecutionBurstServer> create(
+            const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback,
+            const FmqRequestDescriptor& requestChannel, const FmqResultDescriptor& resultChannel,
+            std::shared_ptr<IBurstExecutorWithCache> executorWithCache,
+            std::chrono::microseconds pollingTimeWindow = std::chrono::microseconds{0});
+
+    /**
+     * Create automated context to manage FMQ-based executions.
+     *
+     * This function is intended to be used by a service to automatically:
+     * 1) Receive data from a provided FMQ
+     * 2) Execute a model with the given information
+     * 3) Send the result to the created FMQ
+     *
+     * @param callback Callback used to retrieve memories corresponding to
+     *     unrecognized slots.
+     * @param requestChannel Input FMQ channel through which the client passes the
+     *     request to the service.
+     * @param resultChannel Output FMQ channel from which the client can retrieve
+     *     the result of the execution.
+     * @param preparedModel PreparedModel that the burst object was created from.
+     *     IPreparedModel::executeSynchronously will be used to perform the
+     *     execution.
+     * @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.
+     * @result IBurstContext Handle to the burst context.
+     */
+    static sp<ExecutionBurstServer> create(
+            const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback,
+            const FmqRequestDescriptor& requestChannel, const FmqResultDescriptor& resultChannel,
+            hardware::neuralnetworks::V1_2::IPreparedModel* preparedModel,
+            std::chrono::microseconds pollingTimeWindow = std::chrono::microseconds{0});
+
+    ExecutionBurstServer(const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback,
+                         std::unique_ptr<RequestChannelReceiver> requestChannel,
+                         std::unique_ptr<ResultChannelSender> resultChannel,
+                         std::shared_ptr<IBurstExecutorWithCache> cachedExecutor);
+    ~ExecutionBurstServer();
+
+    // Used by the NN runtime to preemptively remove any stored memory.
+    hardware::Return<void> freeMemory(int32_t slot) override;
+
+  private:
+    // Ensures all cache entries contained in mExecutorWithCache are present in
+    // the cache. If they are not present, they are retrieved (via
+    // IBurstCallback::getMemories) and added to mExecutorWithCache.
+    //
+    // This method is locked via mMutex when it is called.
+    void ensureCacheEntriesArePresentLocked(const std::vector<int32_t>& slots);
+
+    // Work loop that will continue processing execution requests until the
+    // ExecutionBurstServer object is freed.
+    void task();
+
+    std::thread mWorker;
+    std::mutex mMutex;
+    std::atomic<bool> mTeardown{false};
+    const sp<hardware::neuralnetworks::V1_2::IBurstCallback> mCallback;
+    const std::unique_ptr<RequestChannelReceiver> mRequestChannelReceiver;
+    const std::unique_ptr<ResultChannelSender> mResultChannelSender;
+    const std::shared_ptr<IBurstExecutorWithCache> mExecutorWithCache;
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_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/ExecutionBurstUtils.h
new file mode 100644
index 0000000..8a41591
--- /dev/null
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2019 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_1_2_UTILS_EXECUTION_BURST_UTILS_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H
+
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <optional>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+
+/**
+ * Number of elements in the FMQ.
+ */
+constexpr const size_t kExecutionBurstChannelLength = 1024;
+
+using FmqRequestDescriptor = MQDescriptorSync<FmqRequestDatum>;
+using FmqResultDescriptor = MQDescriptorSync<FmqResultDatum>;
+
+/**
+ * Function to serialize a request.
+ *
+ * Prefer calling RequestChannelSender::send.
+ *
+ * @param request Request object without the pool information.
+ * @param measure Whether to collect timing information for the execution.
+ * @param memoryIds Slot identifiers corresponding to memory resources for the
+ *     request.
+ * @return Serialized FMQ request data.
+ */
+std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum> serialize(
+        const hardware::neuralnetworks::V1_0::Request& request,
+        hardware::neuralnetworks::V1_2::MeasureTiming measure, const std::vector<int32_t>& slots);
+
+/**
+ * Deserialize the FMQ request data.
+ *
+ * The three resulting fields are the Request object (where Request::pools is
+ * empty), slot identifiers (which are stand-ins for Request::pools), and
+ * whether timing information must be collected for the run.
+ *
+ * @param data Serialized FMQ request data.
+ * @return Request object if successfully deserialized, std::nullopt otherwise.
+ */
+std::optional<std::tuple<hardware::neuralnetworks::V1_0::Request, std::vector<int32_t>,
+                         hardware::neuralnetworks::V1_2::MeasureTiming>>
+deserialize(const std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>& data);
+
+/**
+ * Function to serialize results.
+ *
+ * Prefer calling ResultChannelSender::send.
+ *
+ * @param errorStatus Status of the execution.
+ * @param outputShapes Dynamic shapes of the output tensors.
+ * @param timing Timing information of the execution.
+ * @return Serialized FMQ result data.
+ */
+std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum> serialize(
+        hardware::neuralnetworks::V1_0::ErrorStatus errorStatus,
+        const std::vector<hardware::neuralnetworks::V1_2::OutputShape>& outputShapes,
+        hardware::neuralnetworks::V1_2::Timing timing);
+
+/**
+ * Deserialize the FMQ result data.
+ *
+ * The three resulting fields are the status of the execution, the dynamic
+ * shapes of the output tensors, and the timing information of the execution.
+ *
+ * @param data Serialized FMQ result data.
+ * @return Result object if successfully deserialized, std::nullopt otherwise.
+ */
+std::optional<std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus,
+                         std::vector<hardware::neuralnetworks::V1_2::OutputShape>,
+                         hardware::neuralnetworks::V1_2::Timing>>
+deserialize(const std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>& data);
+
+/**
+ * Convert result code to error status.
+ *
+ * @param resultCode Result code to be converted.
+ * @return ErrorStatus Resultant error status.
+ */
+hardware::neuralnetworks::V1_0::ErrorStatus legacyConvertResultCodeToErrorStatus(int resultCode);
+
+/**
+ * RequestChannelSender is responsible for serializing the result packet of
+ * information, sending it on the result channel, and signaling that the data is
+ * available.
+ */
+class RequestChannelSender {
+    using FmqRequestDescriptor =
+            hardware::MQDescriptorSync<hardware::neuralnetworks::V1_2::FmqRequestDatum>;
+    using FmqRequestChannel =
+            hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqRequestDatum,
+                                   hardware::kSynchronizedReadWrite>;
+
+  public:
+    /**
+     * Create the sending end of a request channel.
+     *
+     * Prefer this call over the constructor.
+     *
+     * @param channelLength Number of elements in the FMQ.
+     * @return A pair of ResultChannelReceiver and the FMQ descriptor on
+     *     successful creation, both nullptr otherwise.
+     */
+    static std::pair<std::unique_ptr<RequestChannelSender>, const FmqRequestDescriptor*> create(
+            size_t channelLength);
+
+    /**
+     * Send the request to the channel.
+     *
+     * @param request Request object without the pool information.
+     * @param measure Whether to collect timing information for the execution.
+     * @param memoryIds Slot identifiers corresponding to memory resources for
+     *     the request.
+     * @return 'true' on successful send, 'false' otherwise.
+     */
+    bool send(const hardware::neuralnetworks::V1_0::Request& request,
+              hardware::neuralnetworks::V1_2::MeasureTiming measure,
+              const std::vector<int32_t>& slots);
+
+    /**
+     * Method to mark the channel as invalid, causing all future calls to
+     * RequestChannelSender::send to immediately return false without attempting
+     * to send a message across the FMQ.
+     */
+    void invalidate();
+
+    // prefer calling RequestChannelSender::send
+    bool sendPacket(const std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>& packet);
+
+    RequestChannelSender(std::unique_ptr<FmqRequestChannel> fmqRequestChannel);
+
+  private:
+    const std::unique_ptr<FmqRequestChannel> mFmqRequestChannel;
+    std::atomic<bool> mValid{true};
+};
+
+/**
+ * RequestChannelReceiver is responsible for waiting on the channel until the
+ * packet is available, extracting the packet from the channel, and
+ * deserializing the packet.
+ *
+ * Because the receiver can wait on a packet that may never come (e.g., because
+ * the sending side of the packet has been closed), this object can be
+ * invalidated, unblocking the receiver.
+ */
+class RequestChannelReceiver {
+    using FmqRequestChannel =
+            hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqRequestDatum,
+                                   hardware::kSynchronizedReadWrite>;
+
+  public:
+    /**
+     * Create the receiving end of a request channel.
+     *
+     * Prefer this call over the constructor.
+     *
+     * @param requestChannel Descriptor for the request channel.
+     * @param pollingTimeWindow How much time (in microseconds) the
+     *     RequestChannelReceiver 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 RequestChannelReceiver on successful creation, nullptr otherwise.
+     */
+    static std::unique_ptr<RequestChannelReceiver> create(
+            const FmqRequestDescriptor& requestChannel,
+            std::chrono::microseconds pollingTimeWindow);
+
+    /**
+     * Get the request from the channel.
+     *
+     * This method will block until either:
+     * 1) The packet has been retrieved, or
+     * 2) The receiver has been invalidated
+     *
+     * @return Request object if successfully received, std::nullopt if error or
+     *     if the receiver object was invalidated.
+     */
+    std::optional<std::tuple<hardware::neuralnetworks::V1_0::Request, std::vector<int32_t>,
+                             hardware::neuralnetworks::V1_2::MeasureTiming>>
+    getBlocking();
+
+    /**
+     * Method to mark the channel as invalid, unblocking any current or future
+     * calls to RequestChannelReceiver::getBlocking.
+     */
+    void invalidate();
+
+    RequestChannelReceiver(std::unique_ptr<FmqRequestChannel> fmqRequestChannel,
+                           std::chrono::microseconds pollingTimeWindow);
+
+  private:
+    std::optional<std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>> getPacketBlocking();
+
+    const std::unique_ptr<FmqRequestChannel> mFmqRequestChannel;
+    std::atomic<bool> mTeardown{false};
+    const std::chrono::microseconds kPollingTimeWindow;
+};
+
+/**
+ * ResultChannelSender is responsible for serializing the result packet of
+ * information, sending it on the result channel, and signaling that the data is
+ * available.
+ */
+class ResultChannelSender {
+    using FmqResultChannel = hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqResultDatum,
+                                                    hardware::kSynchronizedReadWrite>;
+
+  public:
+    /**
+     * Create the sending end of a result channel.
+     *
+     * Prefer this call over the constructor.
+     *
+     * @param resultChannel Descriptor for the result channel.
+     * @return ResultChannelSender on successful creation, nullptr otherwise.
+     */
+    static std::unique_ptr<ResultChannelSender> create(const FmqResultDescriptor& resultChannel);
+
+    /**
+     * Send the result to the channel.
+     *
+     * @param errorStatus Status of the execution.
+     * @param outputShapes Dynamic shapes of the output tensors.
+     * @param timing Timing information of the execution.
+     * @return 'true' on successful send, 'false' otherwise.
+     */
+    bool send(hardware::neuralnetworks::V1_0::ErrorStatus errorStatus,
+              const std::vector<hardware::neuralnetworks::V1_2::OutputShape>& outputShapes,
+              hardware::neuralnetworks::V1_2::Timing timing);
+
+    // prefer calling ResultChannelSender::send
+    bool sendPacket(const std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>& packet);
+
+    ResultChannelSender(std::unique_ptr<FmqResultChannel> fmqResultChannel);
+
+  private:
+    const std::unique_ptr<FmqResultChannel> mFmqResultChannel;
+};
+
+/**
+ * ResultChannelReceiver is responsible for waiting on the channel until the
+ * packet is available, extracting the packet from the channel, and
+ * deserializing the packet.
+ *
+ * Because the receiver can wait on a packet that may never come (e.g., because
+ * the sending side of the packet has been closed), this object can be
+ * invalidated, unblocking the receiver.
+ */
+class ResultChannelReceiver {
+    using FmqResultDescriptor =
+            hardware::MQDescriptorSync<hardware::neuralnetworks::V1_2::FmqResultDatum>;
+    using FmqResultChannel = hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqResultDatum,
+                                                    hardware::kSynchronizedReadWrite>;
+
+  public:
+    /**
+     * Create the receiving end of a result channel.
+     *
+     * Prefer this call over the constructor.
+     *
+     * @param channelLength Number of elements in the FMQ.
+     * @param pollingTimeWindow How much time (in microseconds) the
+     *     ResultChannelReceiver 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 A pair of ResultChannelReceiver and the FMQ descriptor on
+     *     successful creation, both nullptr otherwise.
+     */
+    static std::pair<std::unique_ptr<ResultChannelReceiver>, const FmqResultDescriptor*> create(
+            size_t channelLength, std::chrono::microseconds pollingTimeWindow);
+
+    /**
+     * Get the result from the channel.
+     *
+     * This method will block until either:
+     * 1) The packet has been retrieved, or
+     * 2) The receiver has been invalidated
+     *
+     * @return Result object if successfully received, std::nullopt if error or
+     *     if the receiver object was invalidated.
+     */
+    std::optional<std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus,
+                             std::vector<hardware::neuralnetworks::V1_2::OutputShape>,
+                             hardware::neuralnetworks::V1_2::Timing>>
+    getBlocking();
+
+    /**
+     * Method to mark the channel as invalid, unblocking any current or future
+     * calls to ResultChannelReceiver::getBlocking.
+     */
+    void invalidate();
+
+    // prefer calling ResultChannelReceiver::getBlocking
+    std::optional<std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>> getPacketBlocking();
+
+    ResultChannelReceiver(std::unique_ptr<FmqResultChannel> fmqResultChannel,
+                          std::chrono::microseconds pollingTimeWindow);
+
+  private:
+    const std::unique_ptr<FmqResultChannel> mFmqResultChannel;
+    std::atomic<bool> mValid{true};
+    const std::chrono::microseconds kPollingTimeWindow;
+};
+
+}  // namespace android::hardware::neuralnetworks::V1_2::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_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 6a56a82..fb11130 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
@@ -36,7 +36,8 @@
 namespace android::hardware::neuralnetworks::V1_2::utils {
 
 // Class that adapts V1_2::IPreparedModel to nn::IPreparedModel.
-class PreparedModel final : public nn::IPreparedModel {
+class PreparedModel final : public nn::IPreparedModel,
+                            public std::enable_shared_from_this<PreparedModel> {
     struct PrivateConstructorTag {};
 
   public:
@@ -57,6 +58,8 @@
             const nn::OptionalDuration& loopTimeoutDuration,
             const nn::OptionalDuration& timeoutDurationAfterFence) const override;
 
+    nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
     std::any getUnderlyingResource() const override;
 
   private:
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 c289fc8..3233114 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
@@ -54,6 +54,12 @@
     return result.has_value();
 }
 
+template <typename Type>
+auto convertFromNonCanonical(const Type& nonCanonicalObject)
+        -> decltype(convert(nn::convert(nonCanonicalObject).value())) {
+    return convert(NN_TRY(nn::convert(nonCanonicalObject)));
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_2::utils
 
 #endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_H
diff --git a/neuralnetworks/1.2/utils/src/Callbacks.cpp b/neuralnetworks/1.2/utils/src/Callbacks.cpp
index fefa122..9f54bb1 100644
--- a/neuralnetworks/1.2/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.2/utils/src/Callbacks.cpp
@@ -43,6 +43,15 @@
 namespace android::hardware::neuralnetworks::V1_2::utils {
 namespace {
 
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+        V1_0::ErrorStatus status, const sp<V1_0::IPreparedModel>& preparedModel) {
+    if (const auto dynamicPreparedModel =
+                V1_2::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) {
+        return V1_2::utils::prepareModelCallback(status, dynamicPreparedModel);
+    }
+    return V1_0::utils::prepareModelCallback(status, preparedModel);
+}
+
 nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
 convertExecutionGeneralResultsHelper(const hidl_vec<OutputShape>& outputShapes,
                                      const Timing& timing) {
@@ -72,7 +81,7 @@
 
 Return<void> PreparedModelCallback::notify(V1_0::ErrorStatus status,
                                            const sp<V1_0::IPreparedModel>& preparedModel) {
-    mData.put(V1_0::utils::prepareModelCallback(status, preparedModel));
+    mData.put(prepareModelCallback(status, preparedModel));
     return Void();
 }
 
diff --git a/neuralnetworks/1.2/utils/src/Conversions.cpp b/neuralnetworks/1.2/utils/src/Conversions.cpp
index 062f6f7..7ae483e 100644
--- a/neuralnetworks/1.2/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.2/utils/src/Conversions.cpp
@@ -304,7 +304,11 @@
 }
 
 GeneralResult<SharedHandle> unvalidatedConvert(const hidl_handle& hidlHandle) {
-    return hal::utils::sharedHandleFromNativeHandle(hidlHandle.getNativeHandle());
+    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) {
@@ -365,7 +369,7 @@
     return V1_0::utils::unvalidatedConvert(operandValues);
 }
 
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) {
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
     return V1_0::utils::unvalidatedConvert(memory);
 }
 
@@ -588,7 +592,10 @@
 }
 
 nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle) {
-    return hal::utils::hidlHandleFromSharedHandle(handle);
+    if (handle == nullptr) {
+        return {};
+    }
+    return hal::utils::hidlHandleFromSharedHandle(*handle);
 }
 
 nn::GeneralResult<DeviceType> convert(const nn::DeviceType& deviceType) {
diff --git a/neuralnetworks/1.2/utils/src/Device.cpp b/neuralnetworks/1.2/utils/src/Device.cpp
index 9fe0de2..1954dfa 100644
--- a/neuralnetworks/1.2/utils/src/Device.cpp
+++ b/neuralnetworks/1.2/utils/src/Device.cpp
@@ -199,6 +199,10 @@
     return kDeviceType;
 }
 
+bool Device::isUpdatable() const {
+    return false;
+}
+
 const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
     return kExtensions;
 }
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp
new file mode 100644
index 0000000..2265861
--- /dev/null
+++ b/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2019 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 "ExecutionBurstController"
+
+#include "ExecutionBurstController.h"
+
+#include <android-base/logging.h>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "ExecutionBurstUtils.h"
+#include "HalInterfaces.h"
+#include "Tracing.h"
+#include "Utils.h"
+
+namespace android::nn {
+namespace {
+
+class BurstContextDeathHandler : public hardware::hidl_death_recipient {
+  public:
+    using Callback = std::function<void()>;
+
+    BurstContextDeathHandler(const Callback& onDeathCallback) : mOnDeathCallback(onDeathCallback) {
+        CHECK(onDeathCallback != nullptr);
+    }
+
+    void serviceDied(uint64_t /*cookie*/, const wp<hidl::base::V1_0::IBase>& /*who*/) override {
+        LOG(ERROR) << "BurstContextDeathHandler::serviceDied -- service unexpectedly died!";
+        mOnDeathCallback();
+    }
+
+  private:
+    const Callback mOnDeathCallback;
+};
+
+}  // anonymous namespace
+
+hardware::Return<void> ExecutionBurstController::ExecutionBurstCallback::getMemories(
+        const hardware::hidl_vec<int32_t>& slots, getMemories_cb cb) {
+    std::lock_guard<std::mutex> guard(mMutex);
+
+    // get all memories
+    hardware::hidl_vec<hardware::hidl_memory> memories(slots.size());
+    std::transform(slots.begin(), slots.end(), memories.begin(), [this](int32_t slot) {
+        return slot < mMemoryCache.size() ? mMemoryCache[slot] : hardware::hidl_memory{};
+    });
+
+    // ensure all memories are valid
+    if (!std::all_of(memories.begin(), memories.end(),
+                     [](const hardware::hidl_memory& memory) { return memory.valid(); })) {
+        cb(V1_0::ErrorStatus::INVALID_ARGUMENT, {});
+        return hardware::Void();
+    }
+
+    // return successful
+    cb(V1_0::ErrorStatus::NONE, std::move(memories));
+    return hardware::Void();
+}
+
+std::vector<int32_t> ExecutionBurstController::ExecutionBurstCallback::getSlots(
+        const hardware::hidl_vec<hardware::hidl_memory>& memories,
+        const std::vector<intptr_t>& keys) {
+    std::lock_guard<std::mutex> guard(mMutex);
+
+    // retrieve (or bind) all slots corresponding to memories
+    std::vector<int32_t> slots;
+    slots.reserve(memories.size());
+    for (size_t i = 0; i < memories.size(); ++i) {
+        slots.push_back(getSlotLocked(memories[i], keys[i]));
+    }
+    return slots;
+}
+
+std::pair<bool, int32_t> ExecutionBurstController::ExecutionBurstCallback::freeMemory(
+        intptr_t key) {
+    std::lock_guard<std::mutex> guard(mMutex);
+
+    auto iter = mMemoryIdToSlot.find(key);
+    if (iter == mMemoryIdToSlot.end()) {
+        return {false, 0};
+    }
+    const int32_t slot = iter->second;
+    mMemoryIdToSlot.erase(key);
+    mMemoryCache[slot] = {};
+    mFreeSlots.push(slot);
+    return {true, slot};
+}
+
+int32_t ExecutionBurstController::ExecutionBurstCallback::getSlotLocked(
+        const hardware::hidl_memory& memory, intptr_t key) {
+    auto iter = mMemoryIdToSlot.find(key);
+    if (iter == mMemoryIdToSlot.end()) {
+        const int32_t slot = allocateSlotLocked();
+        mMemoryIdToSlot[key] = slot;
+        mMemoryCache[slot] = memory;
+        return slot;
+    } else {
+        const int32_t slot = iter->second;
+        return slot;
+    }
+}
+
+int32_t ExecutionBurstController::ExecutionBurstCallback::allocateSlotLocked() {
+    constexpr size_t kMaxNumberOfSlots = std::numeric_limits<int32_t>::max();
+
+    // if there is a free slot, use it
+    if (mFreeSlots.size() > 0) {
+        const int32_t slot = mFreeSlots.top();
+        mFreeSlots.pop();
+        return slot;
+    }
+
+    // otherwise use a slot for the first time
+    CHECK(mMemoryCache.size() < kMaxNumberOfSlots) << "Exceeded maximum number of slots!";
+    const int32_t slot = static_cast<int32_t>(mMemoryCache.size());
+    mMemoryCache.emplace_back();
+
+    return slot;
+}
+
+std::unique_ptr<ExecutionBurstController> ExecutionBurstController::create(
+        const sp<V1_2::IPreparedModel>& preparedModel,
+        std::chrono::microseconds pollingTimeWindow) {
+    // check inputs
+    if (preparedModel == nullptr) {
+        LOG(ERROR) << "ExecutionBurstController::create passed a nullptr";
+        return nullptr;
+    }
+
+    // create callback object
+    sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback();
+
+    // create FMQ objects
+    auto [requestChannelSenderTemp, requestChannelDescriptor] =
+            RequestChannelSender::create(kExecutionBurstChannelLength);
+    auto [resultChannelReceiverTemp, resultChannelDescriptor] =
+            ResultChannelReceiver::create(kExecutionBurstChannelLength, pollingTimeWindow);
+    std::shared_ptr<RequestChannelSender> requestChannelSender =
+            std::move(requestChannelSenderTemp);
+    std::shared_ptr<ResultChannelReceiver> resultChannelReceiver =
+            std::move(resultChannelReceiverTemp);
+
+    // check FMQ objects
+    if (!requestChannelSender || !resultChannelReceiver || !requestChannelDescriptor ||
+        !resultChannelDescriptor) {
+        LOG(ERROR) << "ExecutionBurstController::create failed to create FastMessageQueue";
+        return nullptr;
+    }
+
+    // configure burst
+    V1_0::ErrorStatus errorStatus;
+    sp<IBurstContext> burstContext;
+    const hardware::Return<void> ret = preparedModel->configureExecutionBurst(
+            callback, *requestChannelDescriptor, *resultChannelDescriptor,
+            [&errorStatus, &burstContext](V1_0::ErrorStatus status,
+                                          const sp<IBurstContext>& context) {
+                errorStatus = status;
+                burstContext = context;
+            });
+
+    // check burst
+    if (!ret.isOk()) {
+        LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with description "
+                   << ret.description();
+        return nullptr;
+    }
+    if (errorStatus != V1_0::ErrorStatus::NONE) {
+        LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with status "
+                   << toString(errorStatus);
+        return nullptr;
+    }
+    if (burstContext == nullptr) {
+        LOG(ERROR) << "IPreparedModel::configureExecutionBurst returned nullptr for burst";
+        return nullptr;
+    }
+
+    // create death handler object
+    BurstContextDeathHandler::Callback onDeathCallback = [requestChannelSender,
+                                                          resultChannelReceiver] {
+        requestChannelSender->invalidate();
+        resultChannelReceiver->invalidate();
+    };
+    const sp<BurstContextDeathHandler> deathHandler = new BurstContextDeathHandler(onDeathCallback);
+
+    // linkToDeath registers a callback that will be invoked on service death to
+    // proactively handle service crashes. If the linkToDeath call fails,
+    // asynchronous calls are susceptible to hangs if the service crashes before
+    // providing the response.
+    const hardware::Return<bool> deathHandlerRet = burstContext->linkToDeath(deathHandler, 0);
+    if (!deathHandlerRet.isOk() || deathHandlerRet != true) {
+        LOG(ERROR) << "ExecutionBurstController::create -- Failed to register a death recipient "
+                      "for the IBurstContext object.";
+        return nullptr;
+    }
+
+    // make and return controller
+    return std::make_unique<ExecutionBurstController>(requestChannelSender, resultChannelReceiver,
+                                                      burstContext, callback, deathHandler);
+}
+
+ExecutionBurstController::ExecutionBurstController(
+        const std::shared_ptr<RequestChannelSender>& requestChannelSender,
+        const std::shared_ptr<ResultChannelReceiver>& resultChannelReceiver,
+        const sp<IBurstContext>& burstContext, const sp<ExecutionBurstCallback>& callback,
+        const sp<hardware::hidl_death_recipient>& deathHandler)
+    : mRequestChannelSender(requestChannelSender),
+      mResultChannelReceiver(resultChannelReceiver),
+      mBurstContext(burstContext),
+      mMemoryCache(callback),
+      mDeathHandler(deathHandler) {}
+
+ExecutionBurstController::~ExecutionBurstController() {
+    // It is safe to ignore any errors resulting from this unlinkToDeath call
+    // because the ExecutionBurstController object is already being destroyed
+    // and its underlying IBurstContext object is no longer being used by the NN
+    // runtime.
+    if (mDeathHandler) {
+        mBurstContext->unlinkToDeath(mDeathHandler).isOk();
+    }
+}
+
+static std::tuple<int, std::vector<V1_2::OutputShape>, V1_2::Timing, bool> getExecutionResult(
+        V1_0::ErrorStatus status, std::vector<V1_2::OutputShape> outputShapes, V1_2::Timing timing,
+        bool fallback) {
+    auto [n, checkedOutputShapes, checkedTiming] =
+            getExecutionResult(convertToV1_3(status), std::move(outputShapes), timing);
+    return {n, convertToV1_2(checkedOutputShapes), convertToV1_2(checkedTiming), fallback};
+}
+
+std::tuple<int, std::vector<V1_2::OutputShape>, V1_2::Timing, bool>
+ExecutionBurstController::compute(const V1_0::Request& request, V1_2::MeasureTiming measure,
+                                  const std::vector<intptr_t>& memoryIds) {
+    // 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_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "ExecutionBurstController::compute");
+
+    std::lock_guard<std::mutex> guard(mMutex);
+
+    // send request packet
+    const std::vector<int32_t> slots = mMemoryCache->getSlots(request.pools, memoryIds);
+    const bool success = mRequestChannelSender->send(request, measure, slots);
+    if (!success) {
+        LOG(ERROR) << "Error sending FMQ packet";
+        // only use fallback execution path if the packet could not be sent
+        return getExecutionResult(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming12,
+                                  /*fallback=*/true);
+    }
+
+    // get result packet
+    const auto result = mResultChannelReceiver->getBlocking();
+    if (!result) {
+        LOG(ERROR) << "Error retrieving FMQ packet";
+        // only use fallback execution path if the packet could not be sent
+        return getExecutionResult(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming12,
+                                  /*fallback=*/false);
+    }
+
+    // unpack results and return (only use fallback execution path if the
+    // packet could not be sent)
+    auto [status, outputShapes, timing] = std::move(*result);
+    return getExecutionResult(status, std::move(outputShapes), timing, /*fallback=*/false);
+}
+
+void ExecutionBurstController::freeMemory(intptr_t key) {
+    std::lock_guard<std::mutex> guard(mMutex);
+
+    bool valid;
+    int32_t slot;
+    std::tie(valid, slot) = mMemoryCache->freeMemory(key);
+    if (valid) {
+        mBurstContext->freeMemory(slot).isOk();
+    }
+}
+
+}  // namespace android::nn
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp
new file mode 100644
index 0000000..022548d
--- /dev/null
+++ b/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2019 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 "ExecutionBurstServer"
+
+#include "ExecutionBurstServer.h"
+
+#include <android-base/logging.h>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <map>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "ExecutionBurstUtils.h"
+#include "HalInterfaces.h"
+#include "Tracing.h"
+
+namespace android::nn {
+namespace {
+
+// DefaultBurstExecutorWithCache adapts an IPreparedModel so that it can be
+// used as an IBurstExecutorWithCache. Specifically, the cache simply stores the
+// hidl_memory object, and the execution forwards calls to the provided
+// IPreparedModel's "executeSynchronously" method. With this class, hidl_memory
+// must be mapped and unmapped for each execution.
+class DefaultBurstExecutorWithCache : public ExecutionBurstServer::IBurstExecutorWithCache {
+  public:
+    DefaultBurstExecutorWithCache(V1_2::IPreparedModel* preparedModel)
+        : mpPreparedModel(preparedModel) {}
+
+    bool isCacheEntryPresent(int32_t slot) const override {
+        const auto it = mMemoryCache.find(slot);
+        return (it != mMemoryCache.end()) && it->second.valid();
+    }
+
+    void addCacheEntry(const hardware::hidl_memory& memory, int32_t slot) override {
+        mMemoryCache[slot] = memory;
+    }
+
+    void removeCacheEntry(int32_t slot) override { mMemoryCache.erase(slot); }
+
+    std::tuple<V1_0::ErrorStatus, hardware::hidl_vec<V1_2::OutputShape>, V1_2::Timing> execute(
+            const V1_0::Request& request, const std::vector<int32_t>& slots,
+            V1_2::MeasureTiming measure) override {
+        // convert slots to pools
+        hardware::hidl_vec<hardware::hidl_memory> pools(slots.size());
+        std::transform(slots.begin(), slots.end(), pools.begin(),
+                       [this](int32_t slot) { return mMemoryCache[slot]; });
+
+        // create full request
+        V1_0::Request fullRequest = request;
+        fullRequest.pools = std::move(pools);
+
+        // setup execution
+        V1_0::ErrorStatus returnedStatus = V1_0::ErrorStatus::GENERAL_FAILURE;
+        hardware::hidl_vec<V1_2::OutputShape> returnedOutputShapes;
+        V1_2::Timing returnedTiming;
+        auto cb = [&returnedStatus, &returnedOutputShapes, &returnedTiming](
+                          V1_0::ErrorStatus status,
+                          const hardware::hidl_vec<V1_2::OutputShape>& outputShapes,
+                          const V1_2::Timing& timing) {
+            returnedStatus = status;
+            returnedOutputShapes = outputShapes;
+            returnedTiming = timing;
+        };
+
+        // execute
+        const hardware::Return<void> ret =
+                mpPreparedModel->executeSynchronously(fullRequest, measure, cb);
+        if (!ret.isOk() || returnedStatus != V1_0::ErrorStatus::NONE) {
+            LOG(ERROR) << "IPreparedModelAdapter::execute -- Error executing";
+            return {returnedStatus, std::move(returnedOutputShapes), kNoTiming};
+        }
+
+        return std::make_tuple(returnedStatus, std::move(returnedOutputShapes), returnedTiming);
+    }
+
+  private:
+    V1_2::IPreparedModel* const mpPreparedModel;
+    std::map<int32_t, hardware::hidl_memory> mMemoryCache;
+};
+
+}  // anonymous namespace
+
+// ExecutionBurstServer methods
+
+sp<ExecutionBurstServer> ExecutionBurstServer::create(
+        const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel,
+        const MQDescriptorSync<FmqResultDatum>& resultChannel,
+        std::shared_ptr<IBurstExecutorWithCache> executorWithCache,
+        std::chrono::microseconds pollingTimeWindow) {
+    // check inputs
+    if (callback == nullptr || executorWithCache == nullptr) {
+        LOG(ERROR) << "ExecutionBurstServer::create passed a nullptr";
+        return nullptr;
+    }
+
+    // create FMQ objects
+    std::unique_ptr<RequestChannelReceiver> requestChannelReceiver =
+            RequestChannelReceiver::create(requestChannel, pollingTimeWindow);
+    std::unique_ptr<ResultChannelSender> resultChannelSender =
+            ResultChannelSender::create(resultChannel);
+
+    // check FMQ objects
+    if (!requestChannelReceiver || !resultChannelSender) {
+        LOG(ERROR) << "ExecutionBurstServer::create failed to create FastMessageQueue";
+        return nullptr;
+    }
+
+    // make and return context
+    return new ExecutionBurstServer(callback, std::move(requestChannelReceiver),
+                                    std::move(resultChannelSender), std::move(executorWithCache));
+}
+
+sp<ExecutionBurstServer> ExecutionBurstServer::create(
+        const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel,
+        const MQDescriptorSync<FmqResultDatum>& resultChannel, V1_2::IPreparedModel* preparedModel,
+        std::chrono::microseconds pollingTimeWindow) {
+    // check relevant input
+    if (preparedModel == nullptr) {
+        LOG(ERROR) << "ExecutionBurstServer::create passed a nullptr";
+        return nullptr;
+    }
+
+    // adapt IPreparedModel to have caching
+    const std::shared_ptr<DefaultBurstExecutorWithCache> preparedModelAdapter =
+            std::make_shared<DefaultBurstExecutorWithCache>(preparedModel);
+
+    // make and return context
+    return ExecutionBurstServer::create(callback, requestChannel, resultChannel,
+                                        preparedModelAdapter, pollingTimeWindow);
+}
+
+ExecutionBurstServer::ExecutionBurstServer(
+        const sp<IBurstCallback>& callback, std::unique_ptr<RequestChannelReceiver> requestChannel,
+        std::unique_ptr<ResultChannelSender> resultChannel,
+        std::shared_ptr<IBurstExecutorWithCache> executorWithCache)
+    : mCallback(callback),
+      mRequestChannelReceiver(std::move(requestChannel)),
+      mResultChannelSender(std::move(resultChannel)),
+      mExecutorWithCache(std::move(executorWithCache)) {
+    // TODO: highly document the threading behavior of this class
+    mWorker = std::thread([this] { task(); });
+}
+
+ExecutionBurstServer::~ExecutionBurstServer() {
+    // set teardown flag
+    mTeardown = true;
+    mRequestChannelReceiver->invalidate();
+
+    // wait for task thread to end
+    mWorker.join();
+}
+
+hardware::Return<void> ExecutionBurstServer::freeMemory(int32_t slot) {
+    std::lock_guard<std::mutex> hold(mMutex);
+    mExecutorWithCache->removeCacheEntry(slot);
+    return hardware::Void();
+}
+
+void ExecutionBurstServer::ensureCacheEntriesArePresentLocked(const std::vector<int32_t>& slots) {
+    const auto slotIsKnown = [this](int32_t slot) {
+        return mExecutorWithCache->isCacheEntryPresent(slot);
+    };
+
+    // find unique unknown slots
+    std::vector<int32_t> unknownSlots = slots;
+    auto unknownSlotsEnd = unknownSlots.end();
+    std::sort(unknownSlots.begin(), unknownSlotsEnd);
+    unknownSlotsEnd = std::unique(unknownSlots.begin(), unknownSlotsEnd);
+    unknownSlotsEnd = std::remove_if(unknownSlots.begin(), unknownSlotsEnd, slotIsKnown);
+    unknownSlots.erase(unknownSlotsEnd, unknownSlots.end());
+
+    // quick-exit if all slots are known
+    if (unknownSlots.empty()) {
+        return;
+    }
+
+    V1_0::ErrorStatus errorStatus = V1_0::ErrorStatus::GENERAL_FAILURE;
+    std::vector<hardware::hidl_memory> returnedMemories;
+    auto cb = [&errorStatus, &returnedMemories](
+                      V1_0::ErrorStatus status,
+                      const hardware::hidl_vec<hardware::hidl_memory>& memories) {
+        errorStatus = status;
+        returnedMemories = memories;
+    };
+
+    const hardware::Return<void> ret = mCallback->getMemories(unknownSlots, cb);
+
+    if (!ret.isOk() || errorStatus != V1_0::ErrorStatus::NONE ||
+        returnedMemories.size() != unknownSlots.size()) {
+        LOG(ERROR) << "Error retrieving memories";
+        return;
+    }
+
+    // add memories to unknown slots
+    for (size_t i = 0; i < unknownSlots.size(); ++i) {
+        mExecutorWithCache->addCacheEntry(returnedMemories[i], unknownSlots[i]);
+    }
+}
+
+void ExecutionBurstServer::task() {
+    // loop until the burst object is being destroyed
+    while (!mTeardown) {
+        // receive request
+        auto arguments = mRequestChannelReceiver->getBlocking();
+
+        // if the request packet was not properly received, return a generic
+        // error and skip the execution
+        //
+        // if the burst is being torn down, skip the execution so the "task"
+        // function can end
+        if (!arguments) {
+            if (!mTeardown) {
+                mResultChannelSender->send(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+            }
+            continue;
+        }
+
+        // otherwise begin tracing execution
+        NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION,
+                     "ExecutionBurstServer getting memory, executing, and returning results");
+
+        // unpack the arguments; types are Request, std::vector<int32_t>, and
+        // MeasureTiming, respectively
+        const auto [requestWithoutPools, slotsOfPools, measure] = std::move(*arguments);
+
+        // ensure executor with cache has required memory
+        std::lock_guard<std::mutex> hold(mMutex);
+        ensureCacheEntriesArePresentLocked(slotsOfPools);
+
+        // perform computation; types are ErrorStatus, hidl_vec<OutputShape>,
+        // and Timing, respectively
+        const auto [errorStatus, outputShapes, returnedTiming] =
+                mExecutorWithCache->execute(requestWithoutPools, slotsOfPools, measure);
+
+        // return result
+        mResultChannelSender->send(errorStatus, outputShapes, returnedTiming);
+    }
+}
+
+}  // namespace android::nn
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp
new file mode 100644
index 0000000..f0275f9
--- /dev/null
+++ b/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 2019 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 "ExecutionBurstUtils"
+
+#include "ExecutionBurstUtils.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <thread>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace {
+
+constexpr V1_2::Timing kNoTiming = {std::numeric_limits<uint64_t>::max(),
+                                    std::numeric_limits<uint64_t>::max()};
+
+}
+
+// serialize a request into a packet
+std::vector<FmqRequestDatum> serialize(const V1_0::Request& request, V1_2::MeasureTiming measure,
+                                       const std::vector<int32_t>& slots) {
+    // count how many elements need to be sent for a request
+    size_t count = 2 + request.inputs.size() + request.outputs.size() + request.pools.size();
+    for (const auto& input : request.inputs) {
+        count += input.dimensions.size();
+    }
+    for (const auto& output : request.outputs) {
+        count += output.dimensions.size();
+    }
+
+    // create buffer to temporarily store elements
+    std::vector<FmqRequestDatum> data;
+    data.reserve(count);
+
+    // package packetInfo
+    {
+        FmqRequestDatum datum;
+        datum.packetInformation(
+                {/*.packetSize=*/static_cast<uint32_t>(count),
+                 /*.numberOfInputOperands=*/static_cast<uint32_t>(request.inputs.size()),
+                 /*.numberOfOutputOperands=*/static_cast<uint32_t>(request.outputs.size()),
+                 /*.numberOfPools=*/static_cast<uint32_t>(request.pools.size())});
+        data.push_back(datum);
+    }
+
+    // package input data
+    for (const auto& input : request.inputs) {
+        // package operand information
+        FmqRequestDatum datum;
+        datum.inputOperandInformation(
+                {/*.hasNoValue=*/input.hasNoValue,
+                 /*.location=*/input.location,
+                 /*.numberOfDimensions=*/static_cast<uint32_t>(input.dimensions.size())});
+        data.push_back(datum);
+
+        // package operand dimensions
+        for (uint32_t dimension : input.dimensions) {
+            FmqRequestDatum datum;
+            datum.inputOperandDimensionValue(dimension);
+            data.push_back(datum);
+        }
+    }
+
+    // package output data
+    for (const auto& output : request.outputs) {
+        // package operand information
+        FmqRequestDatum datum;
+        datum.outputOperandInformation(
+                {/*.hasNoValue=*/output.hasNoValue,
+                 /*.location=*/output.location,
+                 /*.numberOfDimensions=*/static_cast<uint32_t>(output.dimensions.size())});
+        data.push_back(datum);
+
+        // package operand dimensions
+        for (uint32_t dimension : output.dimensions) {
+            FmqRequestDatum datum;
+            datum.outputOperandDimensionValue(dimension);
+            data.push_back(datum);
+        }
+    }
+
+    // package pool identifier
+    for (int32_t slot : slots) {
+        FmqRequestDatum datum;
+        datum.poolIdentifier(slot);
+        data.push_back(datum);
+    }
+
+    // package measureTiming
+    {
+        FmqRequestDatum datum;
+        datum.measureTiming(measure);
+        data.push_back(datum);
+    }
+
+    // return packet
+    return data;
+}
+
+// serialize result
+std::vector<FmqResultDatum> serialize(V1_0::ErrorStatus errorStatus,
+                                      const std::vector<V1_2::OutputShape>& outputShapes,
+                                      V1_2::Timing timing) {
+    // count how many elements need to be sent for a request
+    size_t count = 2 + outputShapes.size();
+    for (const auto& outputShape : outputShapes) {
+        count += outputShape.dimensions.size();
+    }
+
+    // create buffer to temporarily store elements
+    std::vector<FmqResultDatum> data;
+    data.reserve(count);
+
+    // package packetInfo
+    {
+        FmqResultDatum datum;
+        datum.packetInformation({/*.packetSize=*/static_cast<uint32_t>(count),
+                                 /*.errorStatus=*/errorStatus,
+                                 /*.numberOfOperands=*/static_cast<uint32_t>(outputShapes.size())});
+        data.push_back(datum);
+    }
+
+    // package output shape data
+    for (const auto& operand : outputShapes) {
+        // package operand information
+        FmqResultDatum::OperandInformation info{};
+        info.isSufficient = operand.isSufficient;
+        info.numberOfDimensions = static_cast<uint32_t>(operand.dimensions.size());
+
+        FmqResultDatum datum;
+        datum.operandInformation(info);
+        data.push_back(datum);
+
+        // package operand dimensions
+        for (uint32_t dimension : operand.dimensions) {
+            FmqResultDatum datum;
+            datum.operandDimensionValue(dimension);
+            data.push_back(datum);
+        }
+    }
+
+    // package executionTiming
+    {
+        FmqResultDatum datum;
+        datum.executionTiming(timing);
+        data.push_back(datum);
+    }
+
+    // return result
+    return data;
+}
+
+// deserialize request
+std::optional<std::tuple<V1_0::Request, std::vector<int32_t>, V1_2::MeasureTiming>> deserialize(
+        const std::vector<FmqRequestDatum>& data) {
+    using discriminator = FmqRequestDatum::hidl_discriminator;
+
+    size_t index = 0;
+
+    // validate packet information
+    if (data.size() == 0 || data[index].getDiscriminator() != discriminator::packetInformation) {
+        LOG(ERROR) << "FMQ Request packet ill-formed";
+        return std::nullopt;
+    }
+
+    // unpackage packet information
+    const FmqRequestDatum::PacketInformation& packetInfo = data[index].packetInformation();
+    index++;
+    const uint32_t packetSize = packetInfo.packetSize;
+    const uint32_t numberOfInputOperands = packetInfo.numberOfInputOperands;
+    const uint32_t numberOfOutputOperands = packetInfo.numberOfOutputOperands;
+    const uint32_t numberOfPools = packetInfo.numberOfPools;
+
+    // verify packet size
+    if (data.size() != packetSize) {
+        LOG(ERROR) << "FMQ Request packet ill-formed";
+        return std::nullopt;
+    }
+
+    // unpackage input operands
+    std::vector<V1_0::RequestArgument> inputs;
+    inputs.reserve(numberOfInputOperands);
+    for (size_t operand = 0; operand < numberOfInputOperands; ++operand) {
+        // validate input operand information
+        if (data[index].getDiscriminator() != discriminator::inputOperandInformation) {
+            LOG(ERROR) << "FMQ Request packet ill-formed";
+            return std::nullopt;
+        }
+
+        // unpackage operand information
+        const FmqRequestDatum::OperandInformation& operandInfo =
+                data[index].inputOperandInformation();
+        index++;
+        const bool hasNoValue = operandInfo.hasNoValue;
+        const V1_0::DataLocation location = operandInfo.location;
+        const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
+
+        // unpackage operand dimensions
+        std::vector<uint32_t> dimensions;
+        dimensions.reserve(numberOfDimensions);
+        for (size_t i = 0; i < numberOfDimensions; ++i) {
+            // validate dimension
+            if (data[index].getDiscriminator() != discriminator::inputOperandDimensionValue) {
+                LOG(ERROR) << "FMQ Request packet ill-formed";
+                return std::nullopt;
+            }
+
+            // unpackage dimension
+            const uint32_t dimension = data[index].inputOperandDimensionValue();
+            index++;
+
+            // store result
+            dimensions.push_back(dimension);
+        }
+
+        // store result
+        inputs.push_back(
+                {/*.hasNoValue=*/hasNoValue, /*.location=*/location, /*.dimensions=*/dimensions});
+    }
+
+    // unpackage output operands
+    std::vector<V1_0::RequestArgument> outputs;
+    outputs.reserve(numberOfOutputOperands);
+    for (size_t operand = 0; operand < numberOfOutputOperands; ++operand) {
+        // validate output operand information
+        if (data[index].getDiscriminator() != discriminator::outputOperandInformation) {
+            LOG(ERROR) << "FMQ Request packet ill-formed";
+            return std::nullopt;
+        }
+
+        // unpackage operand information
+        const FmqRequestDatum::OperandInformation& operandInfo =
+                data[index].outputOperandInformation();
+        index++;
+        const bool hasNoValue = operandInfo.hasNoValue;
+        const V1_0::DataLocation location = operandInfo.location;
+        const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
+
+        // unpackage operand dimensions
+        std::vector<uint32_t> dimensions;
+        dimensions.reserve(numberOfDimensions);
+        for (size_t i = 0; i < numberOfDimensions; ++i) {
+            // validate dimension
+            if (data[index].getDiscriminator() != discriminator::outputOperandDimensionValue) {
+                LOG(ERROR) << "FMQ Request packet ill-formed";
+                return std::nullopt;
+            }
+
+            // unpackage dimension
+            const uint32_t dimension = data[index].outputOperandDimensionValue();
+            index++;
+
+            // store result
+            dimensions.push_back(dimension);
+        }
+
+        // store result
+        outputs.push_back(
+                {/*.hasNoValue=*/hasNoValue, /*.location=*/location, /*.dimensions=*/dimensions});
+    }
+
+    // unpackage pools
+    std::vector<int32_t> slots;
+    slots.reserve(numberOfPools);
+    for (size_t pool = 0; pool < numberOfPools; ++pool) {
+        // validate input operand information
+        if (data[index].getDiscriminator() != discriminator::poolIdentifier) {
+            LOG(ERROR) << "FMQ Request packet ill-formed";
+            return std::nullopt;
+        }
+
+        // unpackage operand information
+        const int32_t poolId = data[index].poolIdentifier();
+        index++;
+
+        // store result
+        slots.push_back(poolId);
+    }
+
+    // validate measureTiming
+    if (data[index].getDiscriminator() != discriminator::measureTiming) {
+        LOG(ERROR) << "FMQ Request packet ill-formed";
+        return std::nullopt;
+    }
+
+    // unpackage measureTiming
+    const V1_2::MeasureTiming measure = data[index].measureTiming();
+    index++;
+
+    // validate packet information
+    if (index != packetSize) {
+        LOG(ERROR) << "FMQ Result packet ill-formed";
+        return std::nullopt;
+    }
+
+    // return request
+    V1_0::Request request = {/*.inputs=*/inputs, /*.outputs=*/outputs, /*.pools=*/{}};
+    return std::make_tuple(std::move(request), std::move(slots), measure);
+}
+
+// deserialize a packet into the result
+std::optional<std::tuple<V1_0::ErrorStatus, std::vector<V1_2::OutputShape>, V1_2::Timing>>
+deserialize(const std::vector<FmqResultDatum>& data) {
+    using discriminator = FmqResultDatum::hidl_discriminator;
+
+    std::vector<V1_2::OutputShape> outputShapes;
+    size_t index = 0;
+
+    // validate packet information
+    if (data.size() == 0 || data[index].getDiscriminator() != discriminator::packetInformation) {
+        LOG(ERROR) << "FMQ Result packet ill-formed";
+        return std::nullopt;
+    }
+
+    // unpackage packet information
+    const FmqResultDatum::PacketInformation& packetInfo = data[index].packetInformation();
+    index++;
+    const uint32_t packetSize = packetInfo.packetSize;
+    const V1_0::ErrorStatus errorStatus = packetInfo.errorStatus;
+    const uint32_t numberOfOperands = packetInfo.numberOfOperands;
+
+    // verify packet size
+    if (data.size() != packetSize) {
+        LOG(ERROR) << "FMQ Result packet ill-formed";
+        return std::nullopt;
+    }
+
+    // unpackage operands
+    for (size_t operand = 0; operand < numberOfOperands; ++operand) {
+        // validate operand information
+        if (data[index].getDiscriminator() != discriminator::operandInformation) {
+            LOG(ERROR) << "FMQ Result packet ill-formed";
+            return std::nullopt;
+        }
+
+        // unpackage operand information
+        const FmqResultDatum::OperandInformation& operandInfo = data[index].operandInformation();
+        index++;
+        const bool isSufficient = operandInfo.isSufficient;
+        const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
+
+        // unpackage operand dimensions
+        std::vector<uint32_t> dimensions;
+        dimensions.reserve(numberOfDimensions);
+        for (size_t i = 0; i < numberOfDimensions; ++i) {
+            // validate dimension
+            if (data[index].getDiscriminator() != discriminator::operandDimensionValue) {
+                LOG(ERROR) << "FMQ Result packet ill-formed";
+                return std::nullopt;
+            }
+
+            // unpackage dimension
+            const uint32_t dimension = data[index].operandDimensionValue();
+            index++;
+
+            // store result
+            dimensions.push_back(dimension);
+        }
+
+        // store result
+        outputShapes.push_back({/*.dimensions=*/dimensions, /*.isSufficient=*/isSufficient});
+    }
+
+    // validate execution timing
+    if (data[index].getDiscriminator() != discriminator::executionTiming) {
+        LOG(ERROR) << "FMQ Result packet ill-formed";
+        return std::nullopt;
+    }
+
+    // unpackage execution timing
+    const V1_2::Timing timing = data[index].executionTiming();
+    index++;
+
+    // validate packet information
+    if (index != packetSize) {
+        LOG(ERROR) << "FMQ Result packet ill-formed";
+        return std::nullopt;
+    }
+
+    // return result
+    return std::make_tuple(errorStatus, std::move(outputShapes), timing);
+}
+
+V1_0::ErrorStatus legacyConvertResultCodeToErrorStatus(int resultCode) {
+    return convertToV1_0(convertResultCodeToErrorStatus(resultCode));
+}
+
+// RequestChannelSender methods
+
+std::pair<std::unique_ptr<RequestChannelSender>, const FmqRequestDescriptor*>
+RequestChannelSender::create(size_t channelLength) {
+    std::unique_ptr<FmqRequestChannel> fmqRequestChannel =
+            std::make_unique<FmqRequestChannel>(channelLength, /*confEventFlag=*/true);
+    if (!fmqRequestChannel->isValid()) {
+        LOG(ERROR) << "Unable to create RequestChannelSender";
+        return {nullptr, nullptr};
+    }
+
+    const FmqRequestDescriptor* descriptor = fmqRequestChannel->getDesc();
+    return std::make_pair(std::make_unique<RequestChannelSender>(std::move(fmqRequestChannel)),
+                          descriptor);
+}
+
+RequestChannelSender::RequestChannelSender(std::unique_ptr<FmqRequestChannel> fmqRequestChannel)
+    : mFmqRequestChannel(std::move(fmqRequestChannel)) {}
+
+bool RequestChannelSender::send(const V1_0::Request& request, V1_2::MeasureTiming measure,
+                                const std::vector<int32_t>& slots) {
+    const std::vector<FmqRequestDatum> serialized = serialize(request, measure, slots);
+    return sendPacket(serialized);
+}
+
+bool RequestChannelSender::sendPacket(const std::vector<FmqRequestDatum>& packet) {
+    if (!mValid) {
+        return false;
+    }
+
+    if (packet.size() > mFmqRequestChannel->availableToWrite()) {
+        LOG(ERROR)
+                << "RequestChannelSender::sendPacket -- packet size exceeds size available in FMQ";
+        return false;
+    }
+
+    // Always send the packet with "blocking" because this signals the futex and
+    // unblocks the consumer if it is waiting on the futex.
+    return mFmqRequestChannel->writeBlocking(packet.data(), packet.size());
+}
+
+void RequestChannelSender::invalidate() {
+    mValid = false;
+}
+
+// RequestChannelReceiver methods
+
+std::unique_ptr<RequestChannelReceiver> RequestChannelReceiver::create(
+        const FmqRequestDescriptor& requestChannel, std::chrono::microseconds pollingTimeWindow) {
+    std::unique_ptr<FmqRequestChannel> fmqRequestChannel =
+            std::make_unique<FmqRequestChannel>(requestChannel);
+
+    if (!fmqRequestChannel->isValid()) {
+        LOG(ERROR) << "Unable to create RequestChannelReceiver";
+        return nullptr;
+    }
+    if (fmqRequestChannel->getEventFlagWord() == nullptr) {
+        LOG(ERROR)
+                << "RequestChannelReceiver::create was passed an MQDescriptor without an EventFlag";
+        return nullptr;
+    }
+
+    return std::make_unique<RequestChannelReceiver>(std::move(fmqRequestChannel),
+                                                    pollingTimeWindow);
+}
+
+RequestChannelReceiver::RequestChannelReceiver(std::unique_ptr<FmqRequestChannel> fmqRequestChannel,
+                                               std::chrono::microseconds pollingTimeWindow)
+    : mFmqRequestChannel(std::move(fmqRequestChannel)), kPollingTimeWindow(pollingTimeWindow) {}
+
+std::optional<std::tuple<V1_0::Request, std::vector<int32_t>, V1_2::MeasureTiming>>
+RequestChannelReceiver::getBlocking() {
+    const auto packet = getPacketBlocking();
+    if (!packet) {
+        return std::nullopt;
+    }
+
+    return deserialize(*packet);
+}
+
+void RequestChannelReceiver::invalidate() {
+    mTeardown = true;
+
+    // force unblock
+    // ExecutionBurstServer is by default waiting on a request packet. If the
+    // client process destroys its burst object, the server may still be waiting
+    // on the futex. This force unblock wakes up any thread waiting on the
+    // futex.
+    // TODO: look for a different/better way to signal/notify the futex to wake
+    // up any thread waiting on it
+    FmqRequestDatum datum;
+    datum.packetInformation({/*.packetSize=*/0, /*.numberOfInputOperands=*/0,
+                             /*.numberOfOutputOperands=*/0, /*.numberOfPools=*/0});
+    mFmqRequestChannel->writeBlocking(&datum, 1);
+}
+
+std::optional<std::vector<FmqRequestDatum>> RequestChannelReceiver::getPacketBlocking() {
+    if (mTeardown) {
+        return std::nullopt;
+    }
+
+    // First spend time polling if results are available in FMQ instead of
+    // waiting on the futex. Polling is more responsive (yielding lower
+    // latencies), but can take up more power, so only poll for a limited period
+    // of time.
+
+    auto& getCurrentTime = std::chrono::high_resolution_clock::now;
+    const auto timeToStopPolling = getCurrentTime() + kPollingTimeWindow;
+
+    while (getCurrentTime() < timeToStopPolling) {
+        // if class is being torn down, immediately return
+        if (mTeardown.load(std::memory_order_relaxed)) {
+            return std::nullopt;
+        }
+
+        // Check if data is available. If it is, immediately retrieve it and
+        // return.
+        const size_t available = mFmqRequestChannel->availableToRead();
+        if (available > 0) {
+            // This is the first point when we know an execution is occurring,
+            // so begin to collect systraces. Note that a similar systrace does
+            // not exist at the corresponding point in
+            // ResultChannelReceiver::getPacketBlocking because the execution is
+            // already in flight.
+            NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION,
+                         "ExecutionBurstServer getting packet");
+            std::vector<FmqRequestDatum> packet(available);
+            const bool success = mFmqRequestChannel->read(packet.data(), available);
+            if (!success) {
+                LOG(ERROR) << "Error receiving packet";
+                return std::nullopt;
+            }
+            return std::make_optional(std::move(packet));
+        }
+    }
+
+    // If we get to this point, we either stopped polling because it was taking
+    // too long or polling was not allowed. Instead, perform a blocking call
+    // which uses a futex to save power.
+
+    // wait for request packet and read first element of request packet
+    FmqRequestDatum datum;
+    bool success = mFmqRequestChannel->readBlocking(&datum, 1);
+
+    // This is the first point when we know an execution is occurring, so begin
+    // to collect systraces. Note that a similar systrace does not exist at the
+    // corresponding point in ResultChannelReceiver::getPacketBlocking because
+    // the execution is already in flight.
+    NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "ExecutionBurstServer getting packet");
+
+    // retrieve remaining elements
+    // NOTE: all of the data is already available at this point, so there's no
+    // need to do a blocking wait to wait for more data. This is known because
+    // in FMQ, all writes are published (made available) atomically. Currently,
+    // the producer always publishes the entire packet in one function call, so
+    // if the first element of the packet is available, the remaining elements
+    // are also available.
+    const size_t count = mFmqRequestChannel->availableToRead();
+    std::vector<FmqRequestDatum> packet(count + 1);
+    std::memcpy(&packet.front(), &datum, sizeof(datum));
+    success &= mFmqRequestChannel->read(packet.data() + 1, count);
+
+    // terminate loop
+    if (mTeardown) {
+        return std::nullopt;
+    }
+
+    // ensure packet was successfully received
+    if (!success) {
+        LOG(ERROR) << "Error receiving packet";
+        return std::nullopt;
+    }
+
+    return std::make_optional(std::move(packet));
+}
+
+// ResultChannelSender methods
+
+std::unique_ptr<ResultChannelSender> ResultChannelSender::create(
+        const FmqResultDescriptor& resultChannel) {
+    std::unique_ptr<FmqResultChannel> fmqResultChannel =
+            std::make_unique<FmqResultChannel>(resultChannel);
+
+    if (!fmqResultChannel->isValid()) {
+        LOG(ERROR) << "Unable to create RequestChannelSender";
+        return nullptr;
+    }
+    if (fmqResultChannel->getEventFlagWord() == nullptr) {
+        LOG(ERROR) << "ResultChannelSender::create was passed an MQDescriptor without an EventFlag";
+        return nullptr;
+    }
+
+    return std::make_unique<ResultChannelSender>(std::move(fmqResultChannel));
+}
+
+ResultChannelSender::ResultChannelSender(std::unique_ptr<FmqResultChannel> fmqResultChannel)
+    : mFmqResultChannel(std::move(fmqResultChannel)) {}
+
+bool ResultChannelSender::send(V1_0::ErrorStatus errorStatus,
+                               const std::vector<V1_2::OutputShape>& outputShapes,
+                               V1_2::Timing timing) {
+    const std::vector<FmqResultDatum> serialized = serialize(errorStatus, outputShapes, timing);
+    return sendPacket(serialized);
+}
+
+bool ResultChannelSender::sendPacket(const std::vector<FmqResultDatum>& packet) {
+    if (packet.size() > mFmqResultChannel->availableToWrite()) {
+        LOG(ERROR)
+                << "ResultChannelSender::sendPacket -- packet size exceeds size available in FMQ";
+        const std::vector<FmqResultDatum> errorPacket =
+                serialize(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+
+        // Always send the packet with "blocking" because this signals the futex
+        // and unblocks the consumer if it is waiting on the futex.
+        return mFmqResultChannel->writeBlocking(errorPacket.data(), errorPacket.size());
+    }
+
+    // Always send the packet with "blocking" because this signals the futex and
+    // unblocks the consumer if it is waiting on the futex.
+    return mFmqResultChannel->writeBlocking(packet.data(), packet.size());
+}
+
+// ResultChannelReceiver methods
+
+std::pair<std::unique_ptr<ResultChannelReceiver>, const FmqResultDescriptor*>
+ResultChannelReceiver::create(size_t channelLength, std::chrono::microseconds pollingTimeWindow) {
+    std::unique_ptr<FmqResultChannel> fmqResultChannel =
+            std::make_unique<FmqResultChannel>(channelLength, /*confEventFlag=*/true);
+    if (!fmqResultChannel->isValid()) {
+        LOG(ERROR) << "Unable to create ResultChannelReceiver";
+        return {nullptr, nullptr};
+    }
+
+    const FmqResultDescriptor* descriptor = fmqResultChannel->getDesc();
+    return std::make_pair(
+            std::make_unique<ResultChannelReceiver>(std::move(fmqResultChannel), pollingTimeWindow),
+            descriptor);
+}
+
+ResultChannelReceiver::ResultChannelReceiver(std::unique_ptr<FmqResultChannel> fmqResultChannel,
+                                             std::chrono::microseconds pollingTimeWindow)
+    : mFmqResultChannel(std::move(fmqResultChannel)), kPollingTimeWindow(pollingTimeWindow) {}
+
+std::optional<std::tuple<V1_0::ErrorStatus, std::vector<V1_2::OutputShape>, V1_2::Timing>>
+ResultChannelReceiver::getBlocking() {
+    const auto packet = getPacketBlocking();
+    if (!packet) {
+        return std::nullopt;
+    }
+
+    return deserialize(*packet);
+}
+
+void ResultChannelReceiver::invalidate() {
+    mValid = false;
+
+    // force unblock
+    // ExecutionBurstController waits on a result packet after sending a
+    // request. If the driver containing ExecutionBurstServer crashes, the
+    // controller may be waiting on the futex. This force unblock wakes up any
+    // thread waiting on the futex.
+    // TODO: look for a different/better way to signal/notify the futex to
+    // wake up any thread waiting on it
+    FmqResultDatum datum;
+    datum.packetInformation({/*.packetSize=*/0,
+                             /*.errorStatus=*/V1_0::ErrorStatus::GENERAL_FAILURE,
+                             /*.numberOfOperands=*/0});
+    mFmqResultChannel->writeBlocking(&datum, 1);
+}
+
+std::optional<std::vector<FmqResultDatum>> ResultChannelReceiver::getPacketBlocking() {
+    if (!mValid) {
+        return std::nullopt;
+    }
+
+    // First spend time polling if results are available in FMQ instead of
+    // waiting on the futex. Polling is more responsive (yielding lower
+    // latencies), but can take up more power, so only poll for a limited period
+    // of time.
+
+    auto& getCurrentTime = std::chrono::high_resolution_clock::now;
+    const auto timeToStopPolling = getCurrentTime() + kPollingTimeWindow;
+
+    while (getCurrentTime() < timeToStopPolling) {
+        // if class is being torn down, immediately return
+        if (!mValid.load(std::memory_order_relaxed)) {
+            return std::nullopt;
+        }
+
+        // Check if data is available. If it is, immediately retrieve it and
+        // return.
+        const size_t available = mFmqResultChannel->availableToRead();
+        if (available > 0) {
+            std::vector<FmqResultDatum> packet(available);
+            const bool success = mFmqResultChannel->read(packet.data(), available);
+            if (!success) {
+                LOG(ERROR) << "Error receiving packet";
+                return std::nullopt;
+            }
+            return std::make_optional(std::move(packet));
+        }
+    }
+
+    // If we get to this point, we either stopped polling because it was taking
+    // too long or polling was not allowed. Instead, perform a blocking call
+    // which uses a futex to save power.
+
+    // wait for result packet and read first element of result packet
+    FmqResultDatum datum;
+    bool success = mFmqResultChannel->readBlocking(&datum, 1);
+
+    // retrieve remaining elements
+    // NOTE: all of the data is already available at this point, so there's no
+    // need to do a blocking wait to wait for more data. This is known because
+    // in FMQ, all writes are published (made available) atomically. Currently,
+    // the producer always publishes the entire packet in one function call, so
+    // if the first element of the packet is available, the remaining elements
+    // are also available.
+    const size_t count = mFmqResultChannel->availableToRead();
+    std::vector<FmqResultDatum> packet(count + 1);
+    std::memcpy(&packet.front(), &datum, sizeof(datum));
+    success &= mFmqResultChannel->read(packet.data() + 1, count);
+
+    if (!mValid) {
+        return std::nullopt;
+    }
+
+    // ensure packet was successfully received
+    if (!success) {
+        LOG(ERROR) << "Error receiving packet";
+        return std::nullopt;
+    }
+
+    return std::make_optional(std::move(packet));
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.2/utils/src/PreparedModel.cpp b/neuralnetworks/1.2/utils/src/PreparedModel.cpp
index 6d00082..6841c5e 100644
--- a/neuralnetworks/1.2/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.2/utils/src/PreparedModel.cpp
@@ -27,6 +27,7 @@
 #include <nnapi/IPreparedModel.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Burst.h>
 #include <nnapi/hal/1.0/Conversions.h>
 #include <nnapi/hal/CommonUtils.h>
 #include <nnapi/hal/HandleError.h>
@@ -117,6 +118,10 @@
            << "IPreparedModel::executeFenced is not supported on 1.2 HAL service";
 }
 
+nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
+    return V1_0::utils::Burst::create(shared_from_this());
+}
+
 std::any PreparedModel::getUnderlyingResource() const {
     sp<V1_2::IPreparedModel> resource = kPreparedModel;
     return resource;
diff --git a/neuralnetworks/1.2/utils/test/DeviceTest.cpp b/neuralnetworks/1.2/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..9c8adde
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/DeviceTest.cpp
@@ -0,0 +1,875 @@
+/*
+ * 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 "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.2/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.2/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+        .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+                               .dimensions = {1},
+                               .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+                              {.type = nn::OperandType::TENSOR_FLOAT32,
+                               .dimensions = {1},
+                               .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+                 .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+                 .inputIndexes = {0},
+                 .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_2::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+        .execTime = std::numeric_limits<float>::max(),
+        .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+    return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+        std::apply(cb, argPack);
+        return Void();
+    };
+}
+
+sp<MockDevice> createMockDevice() {
+    const auto mockDevice = MockDevice::create();
+
+    // Setup default actions for each relevant call.
+    const auto getVersionString_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, kName);
+    const auto getType_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, V1_2::DeviceType::OTHER);
+    const auto getSupportedExtensions_ret =
+            makeCallbackReturn(V1_0::ErrorStatus::NONE, hidl_vec<V1_2::Extension>{});
+    const auto getNumberOfCacheFilesNeeded_ret = makeCallbackReturn(
+            V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+    const auto getCapabilities_ret = makeCallbackReturn(
+            V1_0::ErrorStatus::NONE,
+            V1_2::Capabilities{
+                    .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+                    .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+            });
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, getVersionString(_)).WillByDefault(Invoke(getVersionString_ret));
+    ON_CALL(*mockDevice, getType(_)).WillByDefault(Invoke(getType_ret));
+    ON_CALL(*mockDevice, getSupportedExtensions(_))
+            .WillByDefault(Invoke(getSupportedExtensions_ret));
+    ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+            .WillByDefault(Invoke(getNumberOfCacheFilesNeeded_ret));
+    ON_CALL(*mockDevice, getCapabilities_1_2(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+    // Ensure that older calls are not used.
+    EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0);
+    EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(0);
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0);
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(0);
+    EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0);
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(0);
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, getVersionString(_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getType(_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+                             const sp<MockPreparedModel>& preparedModel) {
+    return [launchStatus, returnStatus, preparedModel](
+                   const V1_2::Model& /*model*/, V1_1::ExecutionPreference /*preference*/,
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+                   const CacheToken& /*token*/, const sp<V1_2::IPreparedModelCallback>& cb)
+                   -> hardware::Return<V1_0::ErrorStatus> {
+        cb->notify_1_2(returnStatus, preparedModel).isOk();
+        return launchStatus;
+    };
+}
+auto makePreparedModelFromCacheReturn(V1_0::ErrorStatus launchStatus,
+                                      V1_0::ErrorStatus returnStatus,
+                                      const sp<MockPreparedModel>& preparedModel) {
+    return [launchStatus, returnStatus, preparedModel](
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+                   const CacheToken& /*token*/, const sp<V1_2::IPreparedModelCallback>& cb)
+                   -> hardware::Return<V1_0::ErrorStatus> {
+        cb->notify_1_2(returnStatus, preparedModel).isOk();
+        return launchStatus;
+    };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+    return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+}  // namespace
+
+TEST(DeviceTest, invalidName) {
+    // run test
+    const auto device = MockDevice::create();
+    const auto result = Device::create(kInvalidName, device);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+    // run test
+    const auto result = Device::create(kName, kInvalidDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getVersionStringError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, "");
+    EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getVersionString(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getVersionString(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getTypeError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret =
+            makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, V1_2::DeviceType::OTHER);
+    EXPECT_CALL(*mockDevice, getType(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getType(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getType(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedExtensionsError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret =
+            makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, hidl_vec<V1_2::Extension>{});
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                        nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, dataCacheFilesExceedsSpecifiedMax) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles + 1,
+                                        nn::kMaxNumberOfCacheFiles);
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, modelCacheFilesExceedsSpecifiedMax) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles,
+                                        nn::kMaxNumberOfCacheFiles + 1);
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(
+            V1_0::ErrorStatus::GENERAL_FAILURE,
+            V1_2::Capabilities{
+                    .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+                    .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+            });
+    EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getCapabilities_1_2(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getCapabilities_1_2(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = []() -> Return<bool> { return false; };
+    EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto& name = device->getName();
+
+    // verify result
+    EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto featureLevel = device->getFeatureLevel();
+
+    // verify result
+    EXPECT_EQ(featureLevel, nn::Version::ANDROID_Q);
+}
+
+TEST(DeviceTest, getCachedData) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1);
+    EXPECT_CALL(*mockDevice, getType(_)).Times(1);
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1);
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1);
+    EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(1);
+
+    const auto result = Device::create(kName, mockDevice);
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& device = result.value();
+
+    // run test and verify results
+    EXPECT_EQ(device->getVersionString(), device->getVersionString());
+    EXPECT_EQ(device->getType(), device->getType());
+    EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+    EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+    EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = []() -> Return<void> { return {}; };
+    EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, ping())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [](const auto& model, const auto& cb) {
+        cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true));
+        return hardware::Void();
+    };
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& supportedOperations = result.value();
+    EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+    EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [](const auto& /*model*/, const auto& cb) {
+        cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
+        return hardware::Void();
+    };
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto mockPreparedModel = MockPreparedModel::create();
+    EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                                     V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+                                                     V1_0::ErrorStatus::NONE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+        mockDevice->simulateCrash();
+        return V1_0::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCache) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto mockPreparedModel = MockPreparedModel::create();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelFromCacheReturn(
+                    V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelFromCacheError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                                              V1_0::ErrorStatus::GENERAL_FAILURE,
+                                                              nullptr)));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNullptrError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_0::ErrorStatus::NONE,
+                                                              V1_0::ErrorStatus::NONE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheAsyncCrash) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+        mockDevice->simulateCrash();
+        return V1_0::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, allocateNotSupported) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.2/utils/test/MockDevice.h b/neuralnetworks/1.2/utils/test/MockDevice.h
new file mode 100644
index 0000000..b459943
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/MockDevice.h
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.2/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+
+using CacheToken =
+        hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+
+class MockDevice final : public IDevice {
+  public:
+    static sp<MockDevice> create();
+
+    // IBase methods below.
+    MOCK_METHOD(Return<void>, ping, (), (override));
+    MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+    Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+    // V1_0 methods below.
+    MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations,
+                (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+                (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+    // V1_1 methods below.
+    MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations_1_1,
+                (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1,
+                (const V1_1::Model& model, V1_1::ExecutionPreference preference,
+                 const sp<V1_0::IPreparedModelCallback>& callback),
+                (override));
+
+    // V1_2 methods below.
+    MOCK_METHOD(Return<void>, getVersionString, (getVersionString_cb cb), (override));
+    MOCK_METHOD(Return<void>, getType, (getType_cb cb), (override));
+    MOCK_METHOD(Return<void>, getCapabilities_1_2, (getCapabilities_1_2_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedExtensions, (getSupportedExtensions_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations_1_2,
+                (const V1_2::Model& model, getSupportedOperations_1_2_cb cb), (override));
+    MOCK_METHOD(Return<void>, getNumberOfCacheFilesNeeded, (getNumberOfCacheFilesNeeded_cb cb),
+                (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_2,
+                (const V1_2::Model& model, V1_1::ExecutionPreference preference,
+                 const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+                 const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModelFromCache,
+                (const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+                 const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+                (override));
+
+    // Helper methods.
+    void simulateCrash();
+
+  private:
+    sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+    auto mockDevice = sp<MockDevice>::make();
+
+    // Setup default actions for each relevant call.
+    const auto ret = []() -> Return<bool> { return true; };
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+                                            uint64_t /*cookie*/) {
+    mDeathRecipient = recipient;
+    return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+    ASSERT_NE(nullptr, mDeathRecipient.get());
+
+    // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+    // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+    // to determine which object is dead. However, the utils::Device code only pairs a single death
+    // recipient with a single HIDL interface object, so these arguments are redundant.
+    mDeathRecipient->serviceDied(0, nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_2::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.2/utils/test/MockPreparedModel.h b/neuralnetworks/1.2/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..f5fd1f3
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/MockPreparedModel.h
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+  public:
+    static sp<MockPreparedModel> create();
+
+    // IBase methods below.
+    MOCK_METHOD(Return<void>, ping, (), (override));
+    MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+    Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient,
+                             uint64_t /*cookie*/) override;
+
+    // V1_0 methods below.
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+                (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+                (override));
+
+    // V1_2 methods below.
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, execute_1_2,
+                (const V1_0::Request& request, V1_2::MeasureTiming measure,
+                 const sp<V1_2::IExecutionCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<void>, executeSynchronously,
+                (const V1_0::Request& request, V1_2::MeasureTiming measure,
+                 executeSynchronously_cb cb),
+                (override));
+    MOCK_METHOD(Return<void>, configureExecutionBurst,
+                (const sp<V1_2::IBurstCallback>& callback,
+                 const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
+                 const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
+                 configureExecutionBurst_cb cb),
+                (override));
+
+    // Helper methods.
+    void simulateCrash();
+
+  private:
+    sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+    auto mockPreparedModel = sp<MockPreparedModel>::make();
+
+    // Setup default actions for each relevant call.
+    const auto ret = []() -> Return<bool> { return true; };
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber());
+
+    return mockPreparedModel;
+}
+
+inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient,
+                                                   uint64_t /*cookie*/) {
+    mDeathRecipient = recipient;
+    return linkToDeathRet();
+}
+
+inline void MockPreparedModel::simulateCrash() {
+    ASSERT_NE(nullptr, mDeathRecipient.get());
+
+    // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass
+    // in 0 and nullptr for these arguments instead. Normally, they are used by the
+    // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel
+    // code only pairs a single death recipient with a single HIDL interface object, so these
+    // arguments are redundant.
+    mDeathRecipient->serviceDied(0, nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_2::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp
new file mode 100644
index 0000000..5062ac9
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp
@@ -0,0 +1,341 @@
+/*
+ * 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 "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.2/PreparedModel.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const sp<V1_2::IPreparedModel> kInvalidPreparedModel;
+constexpr auto kNoTiming = V1_2::Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(),
+                                        .timeInDriver = std::numeric_limits<uint64_t>::max()};
+
+sp<MockPreparedModel> createMockPreparedModel() {
+    const auto mockPreparedModel = MockPreparedModel::create();
+
+    // Ensure that older calls are not used.
+    EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(0);
+
+    return mockPreparedModel;
+}
+
+auto makeExecuteSynchronously(V1_0::ErrorStatus status,
+                              const std::vector<V1_2::OutputShape>& outputShapes,
+                              const V1_2::Timing& timing) {
+    return [status, outputShapes, timing](const V1_0::Request& /*request*/,
+                                          V1_2::MeasureTiming /*measureTiming*/,
+                                          const V1_2::IPreparedModel::executeSynchronously_cb& cb) {
+        cb(status, outputShapes, timing);
+        return hardware::Void();
+    };
+}
+auto makeExecuteAsynchronously(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+                               const std::vector<V1_2::OutputShape>& outputShapes,
+                               const V1_2::Timing& timing) {
+    return [launchStatus, returnStatus, outputShapes, timing](
+                   const V1_0::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/,
+                   const sp<V1_2::IExecutionCallback>& cb) -> Return<V1_0::ErrorStatus> {
+        cb->notify_1_2(returnStatus, outputShapes, timing);
+        return launchStatus;
+    };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+    return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+}  // namespace
+
+TEST(PreparedModelTest, invalidPreparedModel) {
+    // run test
+    const auto result = PreparedModel::create(kInvalidPreparedModel, /*executeSynchronously=*/true);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathError) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto ret = []() -> Return<bool> { return false; };
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathTransportFailure) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathDeadObject) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeSync) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteSynchronously(V1_0::ErrorStatus::NONE, {}, kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    EXPECT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeSyncError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(
+                    makeExecuteSynchronously(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncTransportFailure) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncDeadObject) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsync) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteAsynchronously(V1_0::ErrorStatus::NONE,
+                                                       V1_0::ErrorStatus::NONE, {}, kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    EXPECT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeAsyncLaunchError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteAsynchronously(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                                       V1_0::ErrorStatus::GENERAL_FAILURE, {},
+                                                       kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncReturnError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteAsynchronously(
+                    V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncTransportFailure) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncDeadObject) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsyncCrash) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_0::ErrorStatus> {
+        mockPreparedModel->simulateCrash();
+        return V1_0::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeFencedNotSupported) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+// TODO: test burst execution if/when it is added to nn::IPreparedModel.
+
+TEST(PreparedModelTest, getUnderlyingResource) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+
+    // run test
+    const auto resource = preparedModel->getUnderlyingResource();
+
+    // verify resource
+    const sp<V1_2::IPreparedModel>* maybeMock = std::any_cast<sp<V1_2::IPreparedModel>>(&resource);
+    ASSERT_NE(maybeMock, nullptr);
+    EXPECT_EQ(maybeMock->get(), mockPreparedModel.get());
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.3/utils/Android.bp b/neuralnetworks/1.3/utils/Android.bp
index d5d897d..41d9521 100644
--- a/neuralnetworks/1.3/utils/Android.bp
+++ b/neuralnetworks/1.3/utils/Android.bp
@@ -38,3 +38,35 @@
         "neuralnetworks_utils_hal_common",
     ],
 }
+
+cc_test {
+    name: "neuralnetworks_utils_hal_1_3_test",
+    srcs: ["test/*.cpp"],
+    static_libs: [
+        "android.hardware.neuralnetworks@1.0",
+        "android.hardware.neuralnetworks@1.1",
+        "android.hardware.neuralnetworks@1.2",
+        "android.hardware.neuralnetworks@1.3",
+        "libgmock",
+        "libneuralnetworks_common",
+        "neuralnetworks_types",
+        "neuralnetworks_utils_hal_common",
+        "neuralnetworks_utils_hal_1_0",
+        "neuralnetworks_utils_hal_1_1",
+        "neuralnetworks_utils_hal_1_2",
+        "neuralnetworks_utils_hal_1_3",
+    ],
+    shared_libs: [
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libbase",
+        "libcutils",
+        "libfmq",
+        "libhidlbase",
+        "libhidlmemory",
+        "liblog",
+        "libnativewindow",
+        "libutils",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
index fda79c8..69e87f7 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
@@ -42,8 +42,8 @@
 
     nn::Request::MemoryDomainToken getToken() const override;
 
-    nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override;
-    nn::GeneralResult<void> copyFrom(const nn::Memory& src,
+    nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override;
+    nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src,
                                      const nn::Dimensions& dimensions) const override;
 
   private:
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 74a6534..8e1cdb8 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
@@ -59,7 +59,7 @@
 GeneralResult<ErrorStatus> convert(const hal::V1_3::ErrorStatus& errorStatus);
 
 GeneralResult<SharedHandle> convert(const hardware::hidl_handle& handle);
-GeneralResult<Memory> convert(const hardware::hidl_memory& memory);
+GeneralResult<SharedMemory> convert(const hardware::hidl_memory& memory);
 GeneralResult<std::vector<BufferRole>> convert(
         const hardware::hidl_vec<hal::V1_3::BufferRole>& bufferRoles);
 
@@ -100,7 +100,7 @@
 nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus);
 
 nn::GeneralResult<hidl_handle> convert(const nn::SharedHandle& handle);
-nn::GeneralResult<hidl_memory> convert(const nn::Memory& memory);
+nn::GeneralResult<hidl_memory> convert(const nn::SharedMemory& memory);
 nn::GeneralResult<hidl_vec<BufferRole>> convert(const std::vector<nn::BufferRole>& bufferRoles);
 
 nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus);
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..f36b6c0 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
@@ -54,6 +54,7 @@
     const std::string& getVersionString() const override;
     nn::Version getFeatureLevel() const override;
     nn::DeviceType getType() const override;
+    bool isUpdatable() const override;
     const std::vector<nn::Extension>& getSupportedExtensions() const override;
     const nn::Capabilities& getCapabilities() const override;
     std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
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 664d87a..690fecc 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
@@ -35,7 +35,8 @@
 namespace android::hardware::neuralnetworks::V1_3::utils {
 
 // Class that adapts V1_3::IPreparedModel to nn::IPreparedModel.
-class PreparedModel final : public nn::IPreparedModel {
+class PreparedModel final : public nn::IPreparedModel,
+                            public std::enable_shared_from_this<PreparedModel> {
     struct PrivateConstructorTag {};
 
   public:
@@ -56,6 +57,8 @@
             const nn::OptionalDuration& loopTimeoutDuration,
             const nn::OptionalDuration& timeoutDurationAfterFence) const override;
 
+    nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
     std::any getUnderlyingResource() const override;
 
   private:
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 29b0c80..3ce412c 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
@@ -49,6 +49,12 @@
     return result.has_value();
 }
 
+template <typename Type>
+auto convertFromNonCanonical(const Type& nonCanonicalObject)
+        -> decltype(convert(nn::convert(nonCanonicalObject).value())) {
+    return convert(NN_TRY(nn::convert(nonCanonicalObject)));
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_3::utils
 
 #endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_H
diff --git a/neuralnetworks/1.3/utils/src/Buffer.cpp b/neuralnetworks/1.3/utils/src/Buffer.cpp
index 614033e..ada5265 100644
--- a/neuralnetworks/1.3/utils/src/Buffer.cpp
+++ b/neuralnetworks/1.3/utils/src/Buffer.cpp
@@ -61,7 +61,7 @@
     return kToken;
 }
 
-nn::GeneralResult<void> Buffer::copyTo(const nn::Memory& dst) const {
+nn::GeneralResult<void> Buffer::copyTo(const nn::SharedMemory& dst) const {
     const auto hidlDst = NN_TRY(convert(dst));
 
     const auto ret = kBuffer->copyTo(hidlDst);
@@ -71,7 +71,7 @@
     return {};
 }
 
-nn::GeneralResult<void> Buffer::copyFrom(const nn::Memory& src,
+nn::GeneralResult<void> Buffer::copyFrom(const nn::SharedMemory& src,
                                          const nn::Dimensions& dimensions) const {
     const auto hidlSrc = NN_TRY(convert(src));
     const auto hidlDimensions = hidl_vec<uint32_t>(dimensions);
diff --git a/neuralnetworks/1.3/utils/src/Callbacks.cpp b/neuralnetworks/1.3/utils/src/Callbacks.cpp
index af76e6a..8e9fb83 100644
--- a/neuralnetworks/1.3/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.3/utils/src/Callbacks.cpp
@@ -28,6 +28,7 @@
 #include <nnapi/IPreparedModel.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Callbacks.h>
 #include <nnapi/hal/1.0/Conversions.h>
 #include <nnapi/hal/1.0/PreparedModel.h>
 #include <nnapi/hal/1.2/Callbacks.h>
@@ -46,6 +47,20 @@
 namespace android::hardware::neuralnetworks::V1_3::utils {
 namespace {
 
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+        V1_0::ErrorStatus status, const sp<V1_0::IPreparedModel>& preparedModel) {
+    if (const auto dynamicPreparedModel =
+                V1_3::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) {
+        const auto currentVersionStatus = NN_TRY(convertFromNonCanonical(status));
+        return V1_3::utils::prepareModelCallback(currentVersionStatus, dynamicPreparedModel);
+    }
+    if (const auto dynamicPreparedModel =
+                V1_2::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) {
+        return V1_2::utils::prepareModelCallback(status, dynamicPreparedModel);
+    }
+    return V1_0::utils::prepareModelCallback(status, preparedModel);
+}
+
 nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
 convertExecutionGeneralResultsHelper(const hidl_vec<V1_2::OutputShape>& outputShapes,
                                      const V1_2::Timing& timing) {
@@ -82,13 +97,13 @@
 
 Return<void> PreparedModelCallback::notify(V1_0::ErrorStatus status,
                                            const sp<V1_0::IPreparedModel>& preparedModel) {
-    mData.put(V1_0::utils::prepareModelCallback(status, preparedModel));
+    mData.put(prepareModelCallback(status, preparedModel));
     return Void();
 }
 
 Return<void> PreparedModelCallback::notify_1_2(V1_0::ErrorStatus status,
                                                const sp<V1_2::IPreparedModel>& preparedModel) {
-    mData.put(V1_2::utils::prepareModelCallback(status, preparedModel));
+    mData.put(prepareModelCallback(status, preparedModel));
     return Void();
 }
 
diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp
index 8b7db2b..6e74a62 100644
--- a/neuralnetworks/1.3/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.3/utils/src/Conversions.cpp
@@ -261,7 +261,7 @@
     using Discriminator = hal::V1_3::Request::MemoryPool::hidl_discriminator;
     switch (memoryPool.getDiscriminator()) {
         case Discriminator::hidlMemory:
-            return createSharedMemoryFromHidlMemory(memoryPool.hidlMemory());
+            return hal::utils::createSharedMemoryFromHidlMemory(memoryPool.hidlMemory());
         case Discriminator::token:
             return static_cast<Request::MemoryDomainToken>(memoryPool.token());
     }
@@ -352,7 +352,7 @@
     return validatedConvert(handle);
 }
 
-GeneralResult<Memory> convert(const hardware::hidl_memory& memory) {
+GeneralResult<SharedMemory> convert(const hardware::hidl_memory& memory) {
     return validatedConvert(memory);
 }
 
@@ -386,7 +386,7 @@
     return V1_2::utils::unvalidatedConvert(handle);
 }
 
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) {
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
     return V1_0::utils::unvalidatedConvert(memory);
 }
 
@@ -424,7 +424,7 @@
     return unvalidatedConvertVec(arguments);
 }
 
-nn::GeneralResult<Request::MemoryPool> makeMemoryPool(const nn::Memory& memory) {
+nn::GeneralResult<Request::MemoryPool> makeMemoryPool(const nn::SharedMemory& memory) {
     Request::MemoryPool ret;
     ret.hidlMemory(NN_TRY(unvalidatedConvert(memory)));
     return ret;
@@ -677,7 +677,7 @@
     return validatedConvert(handle);
 }
 
-nn::GeneralResult<hidl_memory> convert(const nn::Memory& memory) {
+nn::GeneralResult<hidl_memory> convert(const nn::SharedMemory& memory) {
     return validatedConvert(memory);
 }
 
diff --git a/neuralnetworks/1.3/utils/src/Device.cpp b/neuralnetworks/1.3/utils/src/Device.cpp
index d710b85..87c9f32 100644
--- a/neuralnetworks/1.3/utils/src/Device.cpp
+++ b/neuralnetworks/1.3/utils/src/Device.cpp
@@ -150,6 +150,10 @@
     return kDeviceType;
 }
 
+bool Device::isUpdatable() const {
+    return false;
+}
+
 const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
     return kExtensions;
 }
diff --git a/neuralnetworks/1.3/utils/src/PreparedModel.cpp b/neuralnetworks/1.3/utils/src/PreparedModel.cpp
index 7b4b7ba..725e4f5 100644
--- a/neuralnetworks/1.3/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.3/utils/src/PreparedModel.cpp
@@ -29,6 +29,7 @@
 #include <nnapi/Result.h>
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Burst.h>
 #include <nnapi/hal/1.2/Conversions.h>
 #include <nnapi/hal/CommonUtils.h>
 #include <nnapi/hal/HandleError.h>
@@ -197,6 +198,10 @@
     return std::make_pair(std::move(syncFence), std::move(callback));
 }
 
+nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
+    return V1_0::utils::Burst::create(shared_from_this());
+}
+
 std::any PreparedModel::getUnderlyingResource() const {
     sp<V1_3::IPreparedModel> resource = kPreparedModel;
     return resource;
diff --git a/neuralnetworks/1.3/utils/test/BufferTest.cpp b/neuralnetworks/1.3/utils/test/BufferTest.cpp
new file mode 100644
index 0000000..d892b87
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/BufferTest.cpp
@@ -0,0 +1,208 @@
+/*
+ * 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 "MockBuffer.h"
+
+#include <android/hardware/neuralnetworks/1.3/IBuffer.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IBuffer.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.3/Buffer.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+const auto kMemory = nn::createSharedMemory(4).value();
+const sp<V1_3::IBuffer> kInvalidBuffer;
+constexpr auto kInvalidToken = nn::Request::MemoryDomainToken{0};
+constexpr auto kToken = nn::Request::MemoryDomainToken{1};
+
+std::function<hardware::Return<V1_3::ErrorStatus>()> makeFunctionReturn(V1_3::ErrorStatus status) {
+    return [status]() -> hardware::Return<V1_3::ErrorStatus> { return status; };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+    return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeSuccessful = makeFunctionReturn(V1_3::ErrorStatus::NONE);
+const auto makeGeneralError = makeFunctionReturn(V1_3::ErrorStatus::GENERAL_FAILURE);
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+}  // namespace
+
+TEST(BufferTest, invalidBuffer) {
+    // run test
+    const auto result = Buffer::create(kInvalidBuffer, kToken);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, invalidToken) {
+    // setup call
+    const auto mockBuffer = MockBuffer::create();
+
+    // run test
+    const auto result = Buffer::create(mockBuffer, kInvalidToken);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, create) {
+    // setup call
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+
+    // run test
+    const auto token = buffer->getToken();
+
+    // verify result
+    EXPECT_EQ(token, kToken);
+}
+
+TEST(BufferTest, copyTo) {
+    // setup call
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+    EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeSuccessful));
+
+    // run test
+    const auto result = buffer->copyTo(kMemory);
+
+    // verify result
+    EXPECT_TRUE(result.has_value()) << result.error().message;
+}
+
+TEST(BufferTest, copyToError) {
+    // setup test
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+    EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeGeneralError));
+
+    // run test
+    const auto result = buffer->copyTo(kMemory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, copyToTransportFailure) {
+    // setup test
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+    EXPECT_CALL(*mockBuffer, copyTo(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = buffer->copyTo(kMemory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, copyToDeadObject) {
+    // setup test
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+    EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = buffer->copyTo(kMemory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(BufferTest, copyFrom) {
+    // setup call
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+    EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(InvokeWithoutArgs(makeSuccessful));
+
+    // run test
+    const auto result = buffer->copyFrom(kMemory, {});
+
+    // verify result
+    EXPECT_TRUE(result.has_value());
+}
+
+TEST(BufferTest, copyFromError) {
+    // setup test
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+    EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(InvokeWithoutArgs(makeGeneralError));
+
+    // run test
+    const auto result = buffer->copyFrom(kMemory, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, copyFromTransportFailure) {
+    // setup test
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+    EXPECT_CALL(*mockBuffer, copyFrom(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = buffer->copyFrom(kMemory, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, copyFromDeadObject) {
+    // setup test
+    const auto mockBuffer = MockBuffer::create();
+    const auto buffer = Buffer::create(mockBuffer, kToken).value();
+    EXPECT_CALL(*mockBuffer, copyFrom(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = buffer->copyFrom(kMemory, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/utils/test/DeviceTest.cpp b/neuralnetworks/1.3/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..f260990
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/DeviceTest.cpp
@@ -0,0 +1,951 @@
+/*
+ * 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 "MockBuffer.h"
+#include "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.3/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.3/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+        .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+                               .dimensions = {1},
+                               .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+                              {.type = nn::OperandType::TENSOR_FLOAT32,
+                               .dimensions = {1},
+                               .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+                 .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+                 .inputIndexes = {0},
+                 .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_3::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+        .execTime = std::numeric_limits<float>::max(),
+        .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+    return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+        std::apply(cb, argPack);
+        return Void();
+    };
+}
+
+sp<MockDevice> createMockDevice() {
+    const auto mockDevice = MockDevice::create();
+
+    // Setup default actions for each relevant call.
+    const auto getVersionString_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, kName);
+    const auto getType_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, V1_2::DeviceType::OTHER);
+    const auto getSupportedExtensions_ret =
+            makeCallbackReturn(V1_0::ErrorStatus::NONE, hidl_vec<V1_2::Extension>{});
+    const auto getNumberOfCacheFilesNeeded_ret = makeCallbackReturn(
+            V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+    const auto getCapabilities_ret = makeCallbackReturn(
+            V1_3::ErrorStatus::NONE,
+            V1_3::Capabilities{
+                    .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+                    .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+                    .ifPerformance = kNoPerformanceInfo,
+                    .whilePerformance = kNoPerformanceInfo,
+            });
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, getVersionString(_)).WillByDefault(Invoke(getVersionString_ret));
+    ON_CALL(*mockDevice, getType(_)).WillByDefault(Invoke(getType_ret));
+    ON_CALL(*mockDevice, getSupportedExtensions(_))
+            .WillByDefault(Invoke(getSupportedExtensions_ret));
+    ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+            .WillByDefault(Invoke(getNumberOfCacheFilesNeeded_ret));
+    ON_CALL(*mockDevice, getCapabilities_1_3(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+    // Ensure that older calls are not used.
+    EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0);
+    EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(0);
+    EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(0);
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0);
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(0);
+    EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0);
+    EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(0);
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(0);
+    EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)).Times(0);
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)).Times(0);
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, getVersionString(_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getType(_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_3::ErrorStatus launchStatus, V1_3::ErrorStatus returnStatus,
+                             const sp<MockPreparedModel>& preparedModel) {
+    return [launchStatus, returnStatus, preparedModel](
+                   const V1_3::Model& /*model*/, V1_1::ExecutionPreference /*preference*/,
+                   V1_3::Priority /*priority*/, const V1_3::OptionalTimePoint& /*deadline*/,
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+                   const CacheToken& /*token*/, const sp<V1_3::IPreparedModelCallback>& cb)
+                   -> hardware::Return<V1_3::ErrorStatus> {
+        cb->notify_1_3(returnStatus, preparedModel).isOk();
+        return launchStatus;
+    };
+}
+auto makePreparedModelFromCacheReturn(V1_3::ErrorStatus launchStatus,
+                                      V1_3::ErrorStatus returnStatus,
+                                      const sp<MockPreparedModel>& preparedModel) {
+    return [launchStatus, returnStatus, preparedModel](
+                   const V1_3::OptionalTimePoint& /*deadline*/,
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+                   const CacheToken& /*token*/, const sp<V1_3::IPreparedModelCallback>& cb)
+                   -> hardware::Return<V1_3::ErrorStatus> {
+        cb->notify_1_3(returnStatus, preparedModel).isOk();
+        return launchStatus;
+    };
+}
+auto makeAllocateReturn(ErrorStatus status, const sp<MockBuffer>& buffer, uint32_t token) {
+    return [status, buffer, token](
+                   const V1_3::BufferDesc& /*desc*/,
+                   const hardware::hidl_vec<sp<V1_3::IPreparedModel>>& /*preparedModels*/,
+                   const hardware::hidl_vec<V1_3::BufferRole>& /*inputRoles*/,
+                   const hardware::hidl_vec<V1_3::BufferRole>& /*outputRoles*/,
+                   const V1_3::IDevice::allocate_cb& cb) -> hardware::Return<void> {
+        cb(status, buffer, token);
+        return hardware::Void();
+    };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+    return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+}  // namespace
+
+TEST(DeviceTest, invalidName) {
+    // run test
+    const auto device = MockDevice::create();
+    const auto result = Device::create(kInvalidName, device);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+    // run test
+    const auto result = Device::create(kName, kInvalidDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getVersionStringError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, "");
+    EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getVersionString(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getVersionString(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getTypeError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret =
+            makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, V1_2::DeviceType::OTHER);
+    EXPECT_CALL(*mockDevice, getType(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getType(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getType(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedExtensionsError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret =
+            makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, hidl_vec<V1_2::Extension>{});
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+                                        nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, dataCacheFilesExceedsSpecifiedMax) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles + 1,
+                                        nn::kMaxNumberOfCacheFiles);
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, modelCacheFilesExceedsSpecifiedMax) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles,
+                                        nn::kMaxNumberOfCacheFiles + 1);
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = makeCallbackReturn(
+            V1_3::ErrorStatus::GENERAL_FAILURE,
+            V1_3::Capabilities{
+                    .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+                    .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+                    .ifPerformance = kNoPerformanceInfo,
+                    .whilePerformance = kNoPerformanceInfo,
+            });
+    EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getCapabilities_1_3(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getCapabilities_1_3(_))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = []() -> Return<bool> { return false; };
+    EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = Device::create(kName, mockDevice);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto& name = device->getName();
+
+    // verify result
+    EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto featureLevel = device->getFeatureLevel();
+
+    // verify result
+    EXPECT_EQ(featureLevel, nn::Version::ANDROID_R);
+}
+
+TEST(DeviceTest, getCachedData) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1);
+    EXPECT_CALL(*mockDevice, getType(_)).Times(1);
+    EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1);
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1);
+    EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(1);
+
+    const auto result = Device::create(kName, mockDevice);
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& device = result.value();
+
+    // run test and verify results
+    EXPECT_EQ(device->getVersionString(), device->getVersionString());
+    EXPECT_EQ(device->getType(), device->getType());
+    EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+    EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+    EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto ret = []() -> Return<void> { return {}; };
+    EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, ping())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+    const auto device = Device::create(kName, mockDevice).value();
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [](const auto& model, const auto& cb) {
+        cb(V1_3::ErrorStatus::NONE, std::vector<bool>(model.main.operations.size(), true));
+        return hardware::Void();
+    };
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& supportedOperations = result.value();
+    EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+    EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [](const auto& /*model*/, const auto& cb) {
+        cb(V1_3::ErrorStatus::GENERAL_FAILURE, {});
+        return hardware::Void();
+    };
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _)).Times(1).WillOnce(Invoke(ret));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->getSupportedOperations(kSimpleModel);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto mockPreparedModel = MockPreparedModel::create();
+    EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE,
+                                                     V1_3::ErrorStatus::NONE, mockPreparedModel)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::GENERAL_FAILURE,
+                                                     V1_3::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE,
+                                                     V1_3::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE,
+                                                     V1_3::ErrorStatus::NONE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [&mockDevice]() -> hardware::Return<V1_3::ErrorStatus> {
+        mockDevice->simulateCrash();
+        return V1_3::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+                                             nn::Priority::DEFAULT, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCache) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto mockPreparedModel = MockPreparedModel::create();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelFromCacheReturn(
+                    V1_3::ErrorStatus::NONE, V1_3::ErrorStatus::NONE, mockPreparedModel)));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelFromCacheError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_3::ErrorStatus::GENERAL_FAILURE,
+                                                              V1_3::ErrorStatus::GENERAL_FAILURE,
+                                                              nullptr)));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNullptrError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_3::ErrorStatus::NONE,
+                                                              V1_3::ErrorStatus::NONE, nullptr)));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheAsyncCrash) {
+    // setup test
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto ret = [&mockDevice]() -> hardware::Return<V1_3::ErrorStatus> {
+        mockDevice->simulateCrash();
+        return V1_3::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, allocate) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    const auto mockBuffer = MockBuffer::create();
+    constexpr uint32_t token = 1;
+    EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeAllocateReturn(ErrorStatus::NONE, mockBuffer, token)));
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, allocateError) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeAllocateReturn(ErrorStatus::GENERAL_FAILURE, nullptr, 0)));
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, allocateTransportFailure) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, allocateDeadObject) {
+    // setup call
+    const auto mockDevice = createMockDevice();
+    const auto device = Device::create(kName, mockDevice).value();
+    EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/utils/test/MockBuffer.h b/neuralnetworks/1.3/utils/test/MockBuffer.h
new file mode 100644
index 0000000..fb31b51
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/MockBuffer.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER
+
+#include <android/hardware/neuralnetworks/1.3/IBuffer.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+
+class MockBuffer final : public IBuffer {
+  public:
+    static sp<MockBuffer> create();
+
+    // V1_3 methods below.
+    MOCK_METHOD(Return<V1_3::ErrorStatus>, copyTo, (const hidl_memory& dst), (override));
+    MOCK_METHOD(Return<V1_3::ErrorStatus>, copyFrom,
+                (const hidl_memory& src, const hidl_vec<uint32_t>& dimensions), (override));
+};
+
+inline sp<MockBuffer> MockBuffer::create() {
+    return sp<MockBuffer>::make();
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER
diff --git a/neuralnetworks/1.3/utils/test/MockDevice.h b/neuralnetworks/1.3/utils/test/MockDevice.h
new file mode 100644
index 0000000..85d3750
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/MockDevice.h
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.3/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+
+using CacheToken =
+        hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+
+class MockDevice final : public IDevice {
+  public:
+    static sp<MockDevice> create();
+
+    // IBase methods below.
+    MOCK_METHOD(Return<void>, ping, (), (override));
+    MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+    Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+    // V1_0 methods below.
+    MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations,
+                (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+                (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+    // V1_1 methods below.
+    MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations_1_1,
+                (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1,
+                (const V1_1::Model& model, V1_1::ExecutionPreference preference,
+                 const sp<V1_0::IPreparedModelCallback>& callback),
+                (override));
+
+    // V1_2 methods below.
+    MOCK_METHOD(Return<void>, getVersionString, (getVersionString_cb cb), (override));
+    MOCK_METHOD(Return<void>, getType, (getType_cb cb), (override));
+    MOCK_METHOD(Return<void>, getCapabilities_1_2, (getCapabilities_1_2_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedExtensions, (getSupportedExtensions_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations_1_2,
+                (const V1_2::Model& model, getSupportedOperations_1_2_cb cb), (override));
+    MOCK_METHOD(Return<void>, getNumberOfCacheFilesNeeded, (getNumberOfCacheFilesNeeded_cb cb),
+                (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_2,
+                (const V1_2::Model& model, V1_1::ExecutionPreference preference,
+                 const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+                 const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModelFromCache,
+                (const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+                 const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+                (override));
+
+    // V1_3 methods below.
+    MOCK_METHOD(Return<void>, getCapabilities_1_3, (getCapabilities_1_3_cb cb), (override));
+    MOCK_METHOD(Return<void>, getSupportedOperations_1_3,
+                (const V1_3::Model& model, getSupportedOperations_1_3_cb cb), (override));
+    MOCK_METHOD(Return<V1_3::ErrorStatus>, prepareModel_1_3,
+                (const V1_3::Model& model, V1_1::ExecutionPreference preference,
+                 V1_3::Priority priority, const V1_3::OptionalTimePoint& deadline,
+                 const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+                 const CacheToken& token, const sp<V1_3::IPreparedModelCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<V1_3::ErrorStatus>, prepareModelFromCache_1_3,
+                (const V1_3::OptionalTimePoint& deadline, const hidl_vec<hidl_handle>& modelCache,
+                 const hidl_vec<hidl_handle>& dataCache, const CacheToken& token,
+                 const sp<V1_3::IPreparedModelCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<void>, allocate,
+                (const V1_3::BufferDesc& desc,
+                 const hidl_vec<sp<V1_3::IPreparedModel>>& preparedModels,
+                 const hidl_vec<V1_3::BufferRole>& inputRoles,
+                 const hidl_vec<V1_3::BufferRole>& outputRoles, allocate_cb cb),
+                (override));
+
+    // Helper methods.
+    void simulateCrash();
+
+  private:
+    sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+    auto mockDevice = sp<MockDevice>::make();
+
+    // Setup default actions for each relevant call.
+    const auto ret = []() -> Return<bool> { return true; };
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+                                            uint64_t /*cookie*/) {
+    mDeathRecipient = recipient;
+    return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+    ASSERT_NE(nullptr, mDeathRecipient.get());
+
+    // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+    // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+    // to determine which object is dead. However, the utils::Device code only pairs a single death
+    // recipient with a single HIDL interface object, so these arguments are redundant.
+    mDeathRecipient->serviceDied(0, nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h b/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h
new file mode 100644
index 0000000..fc08a7f
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK
+
+#include <android/hardware/neuralnetworks/1.3/IFencedExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+
+class MockFencedExecutionCallback final : public IFencedExecutionCallback {
+  public:
+    static sp<MockFencedExecutionCallback> create();
+
+    // V1_3 methods below.
+    MOCK_METHOD(Return<void>, getExecutionInfo, (IFencedExecutionCallback::getExecutionInfo_cb cb),
+                (override));
+};
+
+inline sp<MockFencedExecutionCallback> MockFencedExecutionCallback::create() {
+    return sp<MockFencedExecutionCallback>::make();
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK
diff --git a/neuralnetworks/1.3/utils/test/MockPreparedModel.h b/neuralnetworks/1.3/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..e441524
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/MockPreparedModel.h
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.3/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+  public:
+    static sp<MockPreparedModel> create();
+
+    // IBase methods below.
+    MOCK_METHOD(Return<void>, ping, (), (override));
+    MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+    Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient,
+                             uint64_t /*cookie*/) override;
+
+    // V1_0 methods below.
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+                (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+                (override));
+
+    // V1_2 methods below.
+    MOCK_METHOD(Return<V1_0::ErrorStatus>, execute_1_2,
+                (const V1_0::Request& request, V1_2::MeasureTiming measure,
+                 const sp<V1_2::IExecutionCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<void>, executeSynchronously,
+                (const V1_0::Request& request, V1_2::MeasureTiming measure,
+                 executeSynchronously_cb cb),
+                (override));
+    MOCK_METHOD(Return<void>, configureExecutionBurst,
+                (const sp<V1_2::IBurstCallback>& callback,
+                 const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
+                 const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
+                 configureExecutionBurst_cb cb),
+                (override));
+
+    // V1_3 methods below.
+    MOCK_METHOD(Return<V1_3::ErrorStatus>, execute_1_3,
+                (const V1_3::Request& request, V1_2::MeasureTiming measure,
+                 const V1_3::OptionalTimePoint& deadline,
+                 const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
+                 const sp<V1_3::IExecutionCallback>& callback),
+                (override));
+    MOCK_METHOD(Return<void>, executeSynchronously_1_3,
+                (const V1_3::Request& request, V1_2::MeasureTiming measure,
+                 const V1_3::OptionalTimePoint& deadline,
+                 const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
+                 executeSynchronously_1_3_cb cb),
+                (override));
+    MOCK_METHOD(Return<void>, executeFenced,
+                (const V1_3::Request& request, const hidl_vec<hidl_handle>& waitFor,
+                 V1_2::MeasureTiming measure, const V1_3::OptionalTimePoint& deadline,
+                 const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
+                 const V1_3::OptionalTimeoutDuration& duration, executeFenced_cb cb),
+                (override));
+
+    // Helper methods.
+    void simulateCrash();
+
+  private:
+    sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+    auto mockPreparedModel = sp<MockPreparedModel>::make();
+
+    // Setup default actions for each relevant call.
+    const auto ret = []() -> Return<bool> { return true; };
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber());
+
+    return mockPreparedModel;
+}
+
+inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient,
+                                                   uint64_t /*cookie*/) {
+    mDeathRecipient = recipient;
+    return linkToDeathRet();
+}
+
+inline void MockPreparedModel::simulateCrash() {
+    ASSERT_NE(nullptr, mDeathRecipient.get());
+
+    // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass
+    // in 0 and nullptr for these arguments instead. Normally, they are used by the
+    // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel
+    // code only pairs a single death recipient with a single HIDL interface object, so these
+    // arguments are redundant.
+    mDeathRecipient->serviceDied(0, nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp
new file mode 100644
index 0000000..11796dd
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp
@@ -0,0 +1,470 @@
+/*
+ * 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 "MockFencedExecutionCallback.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.3/IExecutionCallback.h>
+#include <android/hardware/neuralnetworks/1.3/IFencedExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.3/PreparedModel.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const sp<V1_3::IPreparedModel> kInvalidPreparedModel;
+constexpr auto kNoTiming = V1_2::Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(),
+                                        .timeInDriver = std::numeric_limits<uint64_t>::max()};
+
+sp<MockPreparedModel> createMockPreparedModel() {
+    const auto mockPreparedModel = MockPreparedModel::create();
+
+    // Ensure that older calls are not used.
+    EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(0);
+    EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)).Times(0);
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _)).Times(0);
+
+    return mockPreparedModel;
+}
+
+auto makeExecuteSynchronously(V1_3::ErrorStatus status,
+                              const std::vector<V1_2::OutputShape>& outputShapes,
+                              const V1_2::Timing& timing) {
+    return [status, outputShapes, timing](
+                   const V1_3::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/,
+                   const V1_3::OptionalTimePoint& /*deadline*/,
+                   const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
+                   const V1_3::IPreparedModel::executeSynchronously_1_3_cb& cb) {
+        cb(status, outputShapes, timing);
+        return hardware::Void();
+    };
+}
+auto makeExecuteAsynchronously(V1_3::ErrorStatus launchStatus, V1_3::ErrorStatus returnStatus,
+                               const std::vector<V1_2::OutputShape>& outputShapes,
+                               const V1_2::Timing& timing) {
+    return [launchStatus, returnStatus, outputShapes, timing](
+                   const V1_3::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/,
+                   const V1_3::OptionalTimePoint& /*deadline*/,
+                   const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
+                   const sp<V1_3::IExecutionCallback>& cb) -> Return<V1_3::ErrorStatus> {
+        cb->notify_1_3(returnStatus, outputShapes, timing);
+        return launchStatus;
+    };
+}
+auto makeExecuteFencedReturn(V1_3::ErrorStatus status, const hardware::hidl_handle& syncFence,
+                             const sp<V1_3::IFencedExecutionCallback>& dispatchCallback) {
+    return [status, syncFence, dispatchCallback](
+                   const V1_3::Request& /*request*/,
+                   const hardware::hidl_vec<hardware::hidl_handle>& /*waitFor*/,
+                   V1_2::MeasureTiming /*measure*/, const V1_3::OptionalTimePoint& /*deadline*/,
+                   const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
+                   const V1_3::OptionalTimeoutDuration& /*duration*/,
+                   const V1_3::IPreparedModel::executeFenced_cb& cb) {
+        cb(status, syncFence, dispatchCallback);
+        return hardware::Void();
+    };
+}
+auto makeExecuteFencedCallbackReturn(V1_3::ErrorStatus status, const V1_2::Timing& timingA,
+                                     const V1_2::Timing& timingB) {
+    return [status, timingA,
+            timingB](const V1_3::IFencedExecutionCallback::getExecutionInfo_cb& cb) {
+        cb(status, timingA, timingB);
+        return hardware::Void();
+    };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+    return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+}  // namespace
+
+TEST(PreparedModelTest, invalidPreparedModel) {
+    // run test
+    const auto result = PreparedModel::create(kInvalidPreparedModel, /*executeSynchronously=*/true);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathError) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto ret = []() -> Return<bool> { return false; };
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathTransportFailure) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathDeadObject) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeSync) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteSynchronously(V1_3::ErrorStatus::NONE, {}, kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    EXPECT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeSyncError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(
+                    makeExecuteSynchronously(V1_3::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncTransportFailure) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncDeadObject) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsync) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteAsynchronously(V1_3::ErrorStatus::NONE,
+                                                       V1_3::ErrorStatus::NONE, {}, kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    EXPECT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeAsyncLaunchError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteAsynchronously(V1_3::ErrorStatus::GENERAL_FAILURE,
+                                                       V1_3::ErrorStatus::GENERAL_FAILURE, {},
+                                                       kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncReturnError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteAsynchronously(
+                    V1_3::ErrorStatus::NONE, V1_3::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncTransportFailure) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncDeadObject) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsyncCrash) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+    const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_3::ErrorStatus> {
+        mockPreparedModel->simulateCrash();
+        return V1_3::ErrorStatus::NONE;
+    };
+    EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(ret));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeFenced) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    const auto mockCallback = MockFencedExecutionCallback::create();
+    EXPECT_CALL(*mockCallback, getExecutionInfo(_))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteFencedCallbackReturn(V1_3::ErrorStatus::NONE, kNoTiming,
+                                                             kNoTiming)));
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteFencedReturn(V1_3::ErrorStatus::NONE, {}, mockCallback)));
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& [syncFence, callback] = result.value();
+    EXPECT_EQ(syncFence.syncWait({}), nn::SyncFence::FenceState::SIGNALED);
+    ASSERT_NE(callback, nullptr);
+
+    // get results from callback
+    const auto callbackResult = callback();
+    ASSERT_TRUE(callbackResult.has_value()) << "Failed with " << callbackResult.error().code << ": "
+                                            << callbackResult.error().message;
+}
+
+TEST(PreparedModelTest, executeFencedCallbackError) {
+    // setup call
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    const auto mockCallback = MockFencedExecutionCallback::create();
+    EXPECT_CALL(*mockCallback, getExecutionInfo(_))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteFencedCallbackReturn(V1_3::ErrorStatus::GENERAL_FAILURE,
+                                                             kNoTiming, kNoTiming)));
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(makeExecuteFencedReturn(V1_3::ErrorStatus::NONE, {}, mockCallback)));
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    const auto& [syncFence, callback] = result.value();
+    EXPECT_NE(syncFence.syncWait({}), nn::SyncFence::FenceState::ACTIVE);
+    ASSERT_NE(callback, nullptr);
+
+    // verify callback failure
+    const auto callbackResult = callback();
+    ASSERT_FALSE(callbackResult.has_value());
+    EXPECT_EQ(callbackResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeFencedError) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Invoke(
+                    makeExecuteFencedReturn(V1_3::ErrorStatus::GENERAL_FAILURE, {}, nullptr)));
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeFencedTransportFailure) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeFencedDeadObject) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+// TODO: test burst execution if/when it is added to nn::IPreparedModel.
+
+TEST(PreparedModelTest, getUnderlyingResource) {
+    // setup test
+    const auto mockPreparedModel = createMockPreparedModel();
+    const auto preparedModel =
+            PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+
+    // run test
+    const auto resource = preparedModel->getUnderlyingResource();
+
+    // verify resource
+    const sp<V1_3::IPreparedModel>* maybeMock = std::any_cast<sp<V1_3::IPreparedModel>>(&resource);
+    ASSERT_NE(maybeMock, nullptr);
+    EXPECT_EQ(maybeMock->get(), mockPreparedModel.get());
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp
index b17d445..ee753bb 100644
--- a/neuralnetworks/1.3/vts/functional/Android.bp
+++ b/neuralnetworks/1.3/vts/functional/Android.bp
@@ -57,6 +57,7 @@
         "VtsHalNeuralNetworksV1_0_utils",
         "VtsHalNeuralNetworksV1_2_utils",
         "VtsHalNeuralNetworksV1_3_utils",
+        "android.hardware.neuralnetworks-V1-ndk_platform",
         "android.hardware.neuralnetworks@1.0",
         "android.hardware.neuralnetworks@1.1",
         "android.hardware.neuralnetworks@1.2",
diff --git a/neuralnetworks/TEST_MAPPING b/neuralnetworks/TEST_MAPPING
index ca5041d..de84624 100644
--- a/neuralnetworks/TEST_MAPPING
+++ b/neuralnetworks/TEST_MAPPING
@@ -1,6 +1,21 @@
 {
   "presubmit": [
     {
+      "name": "neuralnetworks_utils_hal_common_test"
+    },
+    {
+      "name": "neuralnetworks_utils_hal_1_0_test"
+    },
+    {
+      "name": "neuralnetworks_utils_hal_1_1_test"
+    },
+    {
+      "name": "neuralnetworks_utils_hal_1_2_test"
+    },
+    {
+      "name": "neuralnetworks_utils_hal_1_3_test"
+    },
+    {
       "name": "VtsHalNeuralnetworksV1_0TargetTest",
       "options": [
         {
diff --git a/neuralnetworks/aidl/Android.bp b/neuralnetworks/aidl/Android.bp
new file mode 100644
index 0000000..0557e43
--- /dev/null
+++ b/neuralnetworks/aidl/Android.bp
@@ -0,0 +1,27 @@
+aidl_interface {
+    name: "android.hardware.neuralnetworks",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/neuralnetworks/*.aidl",
+    ],
+    stability: "vintf",
+    imports: [
+        "android.hardware.common",
+    ],
+    backend: {
+        java: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+        ndk: {
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.neuralnetworks",
+                "test_com.android.neuralnetworks",
+            ],
+            min_sdk_version: "30",
+        },
+    },
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl
new file mode 100644
index 0000000..71b7758
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable BufferDesc {
+  int[] dimensions;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl
new file mode 100644
index 0000000..c2d636c
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable BufferRole {
+  int modelIndex;
+  int ioIndex;
+  float frequency;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl
new file mode 100644
index 0000000..01cc753
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Capabilities {
+  android.hardware.neuralnetworks.PerformanceInfo relaxedFloat32toFloat16PerformanceScalar;
+  android.hardware.neuralnetworks.PerformanceInfo relaxedFloat32toFloat16PerformanceTensor;
+  android.hardware.neuralnetworks.OperandPerformance[] operandPerformance;
+  android.hardware.neuralnetworks.PerformanceInfo ifPerformance;
+  android.hardware.neuralnetworks.PerformanceInfo whilePerformance;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl
new file mode 100644
index 0000000..074cc09
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable DataLocation {
+  int poolIndex;
+  long offset;
+  long length;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl
new file mode 100644
index 0000000..7bc8aa7
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable DeviceBuffer {
+  android.hardware.neuralnetworks.IBuffer buffer;
+  int token;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl
new file mode 100644
index 0000000..1abacc8
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum DeviceType {
+  OTHER = 1,
+  CPU = 2,
+  GPU = 3,
+  ACCELERATOR = 4,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl
new file mode 100644
index 0000000..873c584
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum ErrorStatus {
+  NONE = 0,
+  DEVICE_UNAVAILABLE = 1,
+  GENERAL_FAILURE = 2,
+  OUTPUT_INSUFFICIENT_SIZE = 3,
+  INVALID_ARGUMENT = 4,
+  MISSED_DEADLINE_TRANSIENT = 5,
+  MISSED_DEADLINE_PERSISTENT = 6,
+  RESOURCE_EXHAUSTED_TRANSIENT = 7,
+  RESOURCE_EXHAUSTED_PERSISTENT = 8,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl
new file mode 100644
index 0000000..c4badc0
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum ExecutionPreference {
+  LOW_POWER = 0,
+  FAST_SINGLE_ANSWER = 1,
+  SUSTAINED_SPEED = 2,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl
new file mode 100644
index 0000000..b99bb31
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl
@@ -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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable ExecutionResult {
+  boolean outputSufficientSize;
+  android.hardware.neuralnetworks.OutputShape[] outputShapes;
+  android.hardware.neuralnetworks.Timing timing;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl
new file mode 100644
index 0000000..a7ae942
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Extension {
+  String name;
+  android.hardware.neuralnetworks.ExtensionOperandTypeInformation[] operandTypes;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl
new file mode 100644
index 0000000..4c25538
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable ExtensionNameAndPrefix {
+  String name;
+  char prefix;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl
new file mode 100644
index 0000000..b32b217
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable ExtensionOperandTypeInformation {
+  char type;
+  boolean isTensor;
+  int byteSize;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl
new file mode 100644
index 0000000..2fee136
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum FusedActivationFunc {
+  NONE = 0,
+  RELU = 1,
+  RELU1 = 2,
+  RELU6 = 3,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl
new file mode 100644
index 0000000..2860692
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+interface IBuffer {
+  void copyFrom(in android.hardware.neuralnetworks.Memory src, in int[] dimensions);
+  void copyTo(in android.hardware.neuralnetworks.Memory dst);
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl
new file mode 100644
index 0000000..4c5fd2f
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+interface IDevice {
+  android.hardware.neuralnetworks.DeviceBuffer allocate(in android.hardware.neuralnetworks.BufferDesc desc, in android.hardware.neuralnetworks.IPreparedModelParcel[] preparedModels, in android.hardware.neuralnetworks.BufferRole[] inputRoles, in android.hardware.neuralnetworks.BufferRole[] outputRoles);
+  android.hardware.neuralnetworks.Capabilities getCapabilities();
+  android.hardware.neuralnetworks.NumberOfCacheFiles getNumberOfCacheFilesNeeded();
+  android.hardware.neuralnetworks.Extension[] getSupportedExtensions();
+  boolean[] getSupportedOperations(in android.hardware.neuralnetworks.Model model);
+  android.hardware.neuralnetworks.DeviceType getType();
+  String getVersionString();
+  void prepareModel(in android.hardware.neuralnetworks.Model model, in android.hardware.neuralnetworks.ExecutionPreference preference, in android.hardware.neuralnetworks.Priority priority, in long deadline, in ParcelFileDescriptor[] modelCache, in ParcelFileDescriptor[] dataCache, in byte[] token, in android.hardware.neuralnetworks.IPreparedModelCallback callback);
+  void prepareModelFromCache(in long deadline, in ParcelFileDescriptor[] modelCache, in ParcelFileDescriptor[] dataCache, in byte[] token, in android.hardware.neuralnetworks.IPreparedModelCallback callback);
+  const int BYTE_SIZE_OF_CACHE_TOKEN = 32;
+  const int MAX_NUMBER_OF_CACHE_FILES = 32;
+  const int EXTENSION_TYPE_HIGH_BITS_PREFIX = 15;
+  const int EXTENSION_TYPE_LOW_BITS_TYPE = 16;
+  const int OPERAND_TYPE_BASE_MAX = 65535;
+  const int OPERATION_TYPE_BASE_MAX = 65535;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl
new file mode 100644
index 0000000..abe67b8
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+interface IFencedExecutionCallback {
+  android.hardware.neuralnetworks.ErrorStatus getExecutionInfo(out android.hardware.neuralnetworks.Timing timingLaunched, out android.hardware.neuralnetworks.Timing timingFenced);
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl
new file mode 100644
index 0000000..3ca1550
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+interface IPreparedModel {
+  android.hardware.neuralnetworks.ExecutionResult executeSynchronously(in android.hardware.neuralnetworks.Request request, in boolean measureTiming, in long deadline, in long loopTimeoutDuration);
+  android.hardware.neuralnetworks.IFencedExecutionCallback executeFenced(in android.hardware.neuralnetworks.Request request, in ParcelFileDescriptor[] waitFor, in boolean measureTiming, in long deadline, in long loopTimeoutDuration, in long duration, out @nullable ParcelFileDescriptor syncFence);
+  const long DEFAULT_LOOP_TIMEOUT_DURATION_NS = 2000000000;
+  const long MAXIMUM_LOOP_TIMEOUT_DURATION_NS = 15000000000;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl
new file mode 100644
index 0000000..8eaaab6
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+interface IPreparedModelCallback {
+  void notify(in android.hardware.neuralnetworks.ErrorStatus status, in android.hardware.neuralnetworks.IPreparedModel preparedModel);
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
new file mode 100644
index 0000000..8388fda
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable IPreparedModelParcel {
+  android.hardware.neuralnetworks.IPreparedModel preparedModel;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl
new file mode 100644
index 0000000..3b2f240
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Memory {
+  android.hardware.common.NativeHandle handle;
+  long size;
+  String name;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl
new file mode 100644
index 0000000..9d12e58
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Model {
+  android.hardware.neuralnetworks.Subgraph main;
+  android.hardware.neuralnetworks.Subgraph[] referenced;
+  byte[] operandValues;
+  android.hardware.neuralnetworks.Memory[] pools;
+  boolean relaxComputationFloat32toFloat16;
+  android.hardware.neuralnetworks.ExtensionNameAndPrefix[] extensionNameToPrefix;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
new file mode 100644
index 0000000..c1e87da
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable NumberOfCacheFiles {
+  int numModelCache;
+  int numDataCache;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl
new file mode 100644
index 0000000..bb78caa
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Operand {
+  android.hardware.neuralnetworks.OperandType type;
+  int[] dimensions;
+  float scale;
+  int zeroPoint;
+  android.hardware.neuralnetworks.OperandLifeTime lifetime;
+  android.hardware.neuralnetworks.DataLocation location;
+  @nullable android.hardware.neuralnetworks.OperandExtraParams extraParams;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl
new file mode 100644
index 0000000..3f6d93b
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+union OperandExtraParams {
+  android.hardware.neuralnetworks.SymmPerChannelQuantParams channelQuant;
+  byte[] extension;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl
new file mode 100644
index 0000000..d581ced
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum OperandLifeTime {
+  TEMPORARY_VARIABLE = 0,
+  SUBGRAPH_INPUT = 1,
+  SUBGRAPH_OUTPUT = 2,
+  CONSTANT_COPY = 3,
+  CONSTANT_POOL = 4,
+  NO_VALUE = 5,
+  SUBGRAPH = 6,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl
new file mode 100644
index 0000000..87fd3a6
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable OperandPerformance {
+  android.hardware.neuralnetworks.OperandType type;
+  android.hardware.neuralnetworks.PerformanceInfo info;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl
new file mode 100644
index 0000000..186c13d
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum OperandType {
+  FLOAT32 = 0,
+  INT32 = 1,
+  UINT32 = 2,
+  TENSOR_FLOAT32 = 3,
+  TENSOR_INT32 = 4,
+  TENSOR_QUANT8_ASYMM = 5,
+  BOOL = 6,
+  TENSOR_QUANT16_SYMM = 7,
+  TENSOR_FLOAT16 = 8,
+  TENSOR_BOOL8 = 9,
+  FLOAT16 = 10,
+  TENSOR_QUANT8_SYMM_PER_CHANNEL = 11,
+  TENSOR_QUANT16_ASYMM = 12,
+  TENSOR_QUANT8_SYMM = 13,
+  TENSOR_QUANT8_ASYMM_SIGNED = 14,
+  SUBGRAPH = 15,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl
new file mode 100644
index 0000000..fec83a8
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Operation {
+  android.hardware.neuralnetworks.OperationType type;
+  int[] inputs;
+  int[] outputs;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl
new file mode 100644
index 0000000..ad42b02
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum OperationType {
+  ADD = 0,
+  AVERAGE_POOL_2D = 1,
+  CONCATENATION = 2,
+  CONV_2D = 3,
+  DEPTHWISE_CONV_2D = 4,
+  DEPTH_TO_SPACE = 5,
+  DEQUANTIZE = 6,
+  EMBEDDING_LOOKUP = 7,
+  FLOOR = 8,
+  FULLY_CONNECTED = 9,
+  HASHTABLE_LOOKUP = 10,
+  L2_NORMALIZATION = 11,
+  L2_POOL_2D = 12,
+  LOCAL_RESPONSE_NORMALIZATION = 13,
+  LOGISTIC = 14,
+  LSH_PROJECTION = 15,
+  LSTM = 16,
+  MAX_POOL_2D = 17,
+  MUL = 18,
+  RELU = 19,
+  RELU1 = 20,
+  RELU6 = 21,
+  RESHAPE = 22,
+  RESIZE_BILINEAR = 23,
+  RNN = 24,
+  SOFTMAX = 25,
+  SPACE_TO_DEPTH = 26,
+  SVDF = 27,
+  TANH = 28,
+  BATCH_TO_SPACE_ND = 29,
+  DIV = 30,
+  MEAN = 31,
+  PAD = 32,
+  SPACE_TO_BATCH_ND = 33,
+  SQUEEZE = 34,
+  STRIDED_SLICE = 35,
+  SUB = 36,
+  TRANSPOSE = 37,
+  ABS = 38,
+  ARGMAX = 39,
+  ARGMIN = 40,
+  AXIS_ALIGNED_BBOX_TRANSFORM = 41,
+  BIDIRECTIONAL_SEQUENCE_LSTM = 42,
+  BIDIRECTIONAL_SEQUENCE_RNN = 43,
+  BOX_WITH_NMS_LIMIT = 44,
+  CAST = 45,
+  CHANNEL_SHUFFLE = 46,
+  DETECTION_POSTPROCESSING = 47,
+  EQUAL = 48,
+  EXP = 49,
+  EXPAND_DIMS = 50,
+  GATHER = 51,
+  GENERATE_PROPOSALS = 52,
+  GREATER = 53,
+  GREATER_EQUAL = 54,
+  GROUPED_CONV_2D = 55,
+  HEATMAP_MAX_KEYPOINT = 56,
+  INSTANCE_NORMALIZATION = 57,
+  LESS = 58,
+  LESS_EQUAL = 59,
+  LOG = 60,
+  LOGICAL_AND = 61,
+  LOGICAL_NOT = 62,
+  LOGICAL_OR = 63,
+  LOG_SOFTMAX = 64,
+  MAXIMUM = 65,
+  MINIMUM = 66,
+  NEG = 67,
+  NOT_EQUAL = 68,
+  PAD_V2 = 69,
+  POW = 70,
+  PRELU = 71,
+  QUANTIZE = 72,
+  QUANTIZED_16BIT_LSTM = 73,
+  RANDOM_MULTINOMIAL = 74,
+  REDUCE_ALL = 75,
+  REDUCE_ANY = 76,
+  REDUCE_MAX = 77,
+  REDUCE_MIN = 78,
+  REDUCE_PROD = 79,
+  REDUCE_SUM = 80,
+  ROI_ALIGN = 81,
+  ROI_POOLING = 82,
+  RSQRT = 83,
+  SELECT = 84,
+  SIN = 85,
+  SLICE = 86,
+  SPLIT = 87,
+  SQRT = 88,
+  TILE = 89,
+  TOPK_V2 = 90,
+  TRANSPOSE_CONV_2D = 91,
+  UNIDIRECTIONAL_SEQUENCE_LSTM = 92,
+  UNIDIRECTIONAL_SEQUENCE_RNN = 93,
+  RESIZE_NEAREST_NEIGHBOR = 94,
+  QUANTIZED_LSTM = 95,
+  IF = 96,
+  WHILE = 97,
+  ELU = 98,
+  HARD_SWISH = 99,
+  FILL = 100,
+  RANK = 101,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl
new file mode 100644
index 0000000..09a43f7
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable OutputShape {
+  int[] dimensions;
+  boolean isSufficient;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl
new file mode 100644
index 0000000..178946c
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable PerformanceInfo {
+  float execTime;
+  float powerUsage;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl
new file mode 100644
index 0000000..d9b77fa
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum Priority {
+  LOW = 0,
+  MEDIUM = 1,
+  HIGH = 2,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl
new file mode 100644
index 0000000..599b3f4
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Request {
+  android.hardware.neuralnetworks.RequestArgument[] inputs;
+  android.hardware.neuralnetworks.RequestArgument[] outputs;
+  android.hardware.neuralnetworks.RequestMemoryPool[] pools;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl
new file mode 100644
index 0000000..91b9aa7
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable RequestArgument {
+  boolean hasNoValue;
+  android.hardware.neuralnetworks.DataLocation location;
+  int[] dimensions;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl
new file mode 100644
index 0000000..3813b51
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+union RequestMemoryPool {
+  android.hardware.neuralnetworks.Memory pool;
+  int token;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl
new file mode 100644
index 0000000..dec976f
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Subgraph {
+  android.hardware.neuralnetworks.Operand[] operands;
+  android.hardware.neuralnetworks.Operation[] operations;
+  int[] inputIndexes;
+  int[] outputIndexes;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl
new file mode 100644
index 0000000..66fdfe7
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable SymmPerChannelQuantParams {
+  float[] scales;
+  int channelDim;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl
new file mode 100644
index 0000000..d0de34a
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.neuralnetworks;
+@VintfStability
+parcelable Timing {
+  long timeOnDevice;
+  long timeInDriver;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl
new file mode 100644
index 0000000..bec7e86
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * A buffer descriptor. Describes the properties of a buffer.
+ */
+@VintfStability
+parcelable BufferDesc {
+    /**
+     * Dimensions of the buffer. May have unknown dimensions or rank. A buffer with some number of
+     * unspecified dimensions is represented by setting each unspecified dimension to 0. A buffer
+     * with unspecified rank is represented by providing an empty dimensions vector.
+     */
+    int[] dimensions;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl
new file mode 100644
index 0000000..0d7f678
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Describes a role of an input or output to a prepared model.
+ */
+@VintfStability
+parcelable BufferRole {
+    /**
+     * The index of the IPreparedModel within the "preparedModel" argument passed in
+     * IDevice::allocate.
+     */
+    int modelIndex;
+    /**
+     * The index of the input or output operand.
+     */
+    int ioIndex;
+    /**
+     * A floating-point value within the range (0.0, 1.0]. Describes how likely the buffer is to be
+     * used in the specified role. This is provided as a hint to optimize the case when multiple
+     * roles prefer different buffer locations or data layouts.
+     */
+    float frequency;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl
new file mode 100644
index 0000000..3802f1f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.OperandPerformance;
+import android.hardware.neuralnetworks.PerformanceInfo;
+
+/**
+ * The capabilities of a driver.
+ *
+ * This represents performance of non-extension operations.
+ *
+ * Performance of an operation other than {@link OperationType::IF} and {@link OperationType::WHILE}
+ * comes from the type of its first operand.
+ */
+@VintfStability
+parcelable Capabilities {
+    /**
+     * Driver performance when operating on float32 data but performing calculations with range
+     * and/or precision as low as that of the IEEE 754 16-bit floating-point format.
+     */
+    PerformanceInfo relaxedFloat32toFloat16PerformanceScalar;
+    PerformanceInfo relaxedFloat32toFloat16PerformanceTensor;
+    /**
+     * Performance by operand type. Must be sorted by OperandType.
+     *
+     * If a particular {@link OperandType} is not present in operandPerformance, its performance is
+     * treated as { .execTime = FLT_MAX, .powerUsage = FLT_MAX }.
+     *
+     * Performance does not apply to {@link OperandType::SUBGRAPH}, and a driver must not report
+     * operand performance for {@link OperandType::SUBGRAPH}.
+     */
+    OperandPerformance[] operandPerformance;
+    /**
+     * Performance of an {@link OperationType::IF} operation is the sum of
+     * {@link Capabilities::ifPerformance} and the mean of performance for the two branch subgraphs,
+     * where performance for a subgraph is the sum of the performance of all operations within the
+     * subgraph.
+     */
+    PerformanceInfo ifPerformance;
+    /**
+     * Performance of a {@link OperationType::WHILE} operation is the sum of
+     * {@link Capabilities::whilePerformance}, performance for the condition subgraph and
+     * performance for the body subgraph, where performance for a subgraph is the sum of the
+     * performance of all operations within the subgraph.
+     */
+    PerformanceInfo whilePerformance;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl
new file mode 100644
index 0000000..f6b5e0d
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Describes the location of a data object.
+ */
+@VintfStability
+parcelable DataLocation {
+    /**
+     * The index of the memory pool where this location is found.
+     */
+    int poolIndex;
+    /**
+     * Offset in bytes from the start of the pool.
+     */
+    long offset;
+    /**
+     * The length of the data in bytes.
+     */
+    long length;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl
new file mode 100644
index 0000000..07930a6
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.IBuffer;
+
+/**
+ * A type that is used to represent a driver allocated buffer and token that corresponds to it.
+ */
+@VintfStability
+parcelable DeviceBuffer {
+    /**
+     * An IBuffer object used to interact with the device allocated buffer.
+     */
+    IBuffer buffer;
+    /**
+     * A positive token identifying the allocated buffer. The token is provided when referencing the
+     * buffer as one of the memory pools in the request of an execution. The token must not collide
+     * with the tokens of other IBuffer objects that are currently alive in the same driver service.
+     */
+    int token;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl
new file mode 100644
index 0000000..815be64
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Device types.
+ *
+ * The type of NNAPI device.
+ */
+@VintfStability
+@Backing(type="int")
+enum DeviceType {
+    /**
+     * The device does not fall into any category below.
+     */
+    OTHER = 1,
+    /**
+     * The device runs NNAPI models on single or multi-core CPU.
+     */
+    CPU = 2,
+    /**
+     * The device can run NNAPI models and also accelerate graphics APIs such as OpenGL ES and
+     * Vulkan.
+     */
+    GPU = 3,
+    /**
+     * Dedicated accelerator for Machine Learning workloads.
+     */
+    ACCELERATOR = 4,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl
new file mode 100644
index 0000000..c2752d9
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Calls to neural networks AIDL interfaces may return a ServiceSpecificException with the following
+ * error codes.
+ */
+@VintfStability
+@Backing(type="int")
+enum ErrorStatus {
+    NONE,
+    DEVICE_UNAVAILABLE,
+    GENERAL_FAILURE,
+    OUTPUT_INSUFFICIENT_SIZE,
+    INVALID_ARGUMENT,
+    /**
+     * Failure because a deadline could not be met for a task, but future deadlines may still be met
+     * for the same task after a short delay.
+     */
+    MISSED_DEADLINE_TRANSIENT,
+    /**
+     * Failure because a deadline could not be met for a task, and future deadlines will likely also
+     * not be met for the same task even after a short delay.
+     */
+    MISSED_DEADLINE_PERSISTENT,
+    /**
+     * Failure because of a resource limitation within the driver, but future calls for the same
+     * task may still succeed after a short delay.
+     */
+    RESOURCE_EXHAUSTED_TRANSIENT,
+    /**
+     * Failure because of a resource limitation within the driver, and future calls for the same
+     * task will likely also fail even after a short delay.
+     */
+    RESOURCE_EXHAUSTED_PERSISTENT,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl
new file mode 100644
index 0000000..a3e3ce3
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Execution preferences.
+ */
+@VintfStability
+@Backing(type="int")
+enum ExecutionPreference {
+    /**
+     * Prefer executing in a way that minimizes battery drain. This is desirable for compilations
+     * that will be executed often.
+     */
+    LOW_POWER,
+    /**
+     * Prefer returning a single answer as fast as possible, even if this causes more power
+     * consumption.
+     */
+    FAST_SINGLE_ANSWER,
+    /**
+     * Prefer maximizing the throughput of successive frames, for example when processing successive
+     * frames coming from the camera.
+     */
+    SUSTAINED_SPEED,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl
new file mode 100644
index 0000000..1f88207
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.neuralnetworks;
+
+import android.hardware.neuralnetworks.ErrorStatus;
+import android.hardware.neuralnetworks.OutputShape;
+import android.hardware.neuralnetworks.Timing;
+
+/**
+ * A result from running a synchronous execution of a prepared model.
+ */
+@VintfStability
+parcelable ExecutionResult {
+    /**
+     * A value of "true" indicates that the execution was successful. A value of "false" indicates
+     * the execution failed because at least one output operand buffer was not large enough to store
+     * the corresponding output.
+     */
+    boolean outputSufficientSize;
+    /**
+     * A list of shape information of model output operands. The index in "outputShapes" corresponds
+     * to the index of the output operand in the Request outputs vector.
+     */
+    OutputShape[] outputShapes;
+    /**
+     * Duration of execution. Unless measure is true and the execution is successful, all times must
+     * be reported as -1. A driver may choose to report any time as -1, indicating that measurement
+     * is not available.
+     */
+    Timing timing;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl
new file mode 100644
index 0000000..20109bd
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.ExtensionOperandTypeInformation;
+
+/**
+ * Information about an extension.
+ */
+@VintfStability
+parcelable Extension {
+    /**
+     * The extension name.
+     *
+     * The name must consist of lowercase latin letters, numbers, periods, and underscore signs. The
+     * name must contain at least one period.
+     *
+     * The name must start with the reverse domain name of the vendor.
+     *
+     * Example: com.google.test_extension
+     */
+    String name;
+    /**
+     * Information about operand types defined by the extension.
+     */
+    ExtensionOperandTypeInformation[] operandTypes;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl
new file mode 100644
index 0000000..29be93f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * The mapping between extension names and prefixes of operand and operation type values.
+ *
+ * An operand or operation whose numeric type value is above {@link IDevice::OPERAND_TYPE_BASE_MAX}
+ * or {@link IDevice::OPERATION_TYPE_BASE_MAX} respectively should be interpreted as an extension
+ * operand/operation. The low {@link IDevice::EXTENSION_TYPE_LOW_BITS_TYPE} bits of the value
+ * correspond to the type ID within the extension and the high
+ * {@link IDevice::EXTENSION_TYPE_HIGH_BITS_PREFIX} bits encode the "prefix", which maps uniquely to
+ * the extension name. The sign bit is always 0.
+ *
+ * For example, if a model contains an operation whose value is 0x7AAABBBB and extensionNameToPrefix
+ * contains an entry with prefix=0x7AAA and name="vendor.test.test_extension", then the operation
+ * should be interpreted as the operation 0xBBBB of the extension named vendor.test.test_extension.
+ *
+ * This is a one-to-one correspondence. That is, there must be at most one prefix corresponding to
+ * each extension name and at most one extension name corresponding to each prefix.
+ */
+@VintfStability
+parcelable ExtensionNameAndPrefix {
+    /**
+     * The extension name.
+     *
+     * See {@link Extension::name} for the format specification.
+     */
+    String name;
+    /**
+     * The extension prefix. Only the lowest 15 bits are used, so the value must be less than 32768.
+     */
+    char prefix;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl
new file mode 100644
index 0000000..b8e3449
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Information about an extension operand type.
+ */
+@VintfStability
+parcelable ExtensionOperandTypeInformation {
+    /**
+     * The extension operand type.
+     */
+    char type;
+    /**
+     * Indicates whether the extension operand type represents a tensor or a scalar.
+     */
+    boolean isTensor;
+    /**
+     * The byte size of the operand (if scalar) or of a single element (if tensor).
+     */
+    int byteSize;
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/neuralnetworks/aidl/android/hardware/neuralnetworks/FusedActivationFunc.aidl
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to neuralnetworks/aidl/android/hardware/neuralnetworks/FusedActivationFunc.aidl
index 127f98e..861b6f0 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/FusedActivationFunc.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.neuralnetworks;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
+/**
+ * Fused activation function types.
+ */
+@VintfStability
+@Backing(type="int")
+enum FusedActivationFunc {
+    NONE,
+    RELU,
+    RELU1,
+    RELU6,
 }
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl
new file mode 100644
index 0000000..2915a22
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.Memory;
+
+/**
+ * This interface represents a device memory buffer.
+ */
+@VintfStability
+interface IBuffer {
+    /**
+     * Sets the content of this buffer from a shared memory region.
+     *
+     * @param src The source shared memory region.
+     * @param dimensions Updated dimensional information. If the dimensions of the IBuffer object
+     *                   are not fully specified, then the dimensions must be fully specified here.
+     *                   If the dimensions of the IBuffer object are fully specified, then the
+     *                   dimensions may be empty here. If dimensions.size() > 0, then all dimensions
+     *                   must be specified here, and any dimension that was specified in the IBuffer
+     *                   object must have the same value here.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     *     - INVALID_ARGUMENT if provided memory is invalid, or if the dimensions is invalid
+     */
+    void copyFrom(in Memory src, in int[] dimensions);
+
+    /**
+     * Retrieves the content of this buffer to a shared memory region.
+     *
+     * The IBuffer object must have been initialized before the call to IBuffer::copyTo. For more
+     * information on the state of the IBuffer object, refer to IDevice::allocate.
+     *
+     * @param dst The destination shared memory region.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if the IBuffer object is uninitialized, or there is an unspecified
+     *       error
+     *     - INVALID_ARGUMENT if provided memory is invalid
+     */
+    void copyTo(in Memory dst);
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl
new file mode 100644
index 0000000..e17e0cd
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl
@@ -0,0 +1,432 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.BufferDesc;
+import android.hardware.neuralnetworks.BufferRole;
+import android.hardware.neuralnetworks.Capabilities;
+import android.hardware.neuralnetworks.DeviceBuffer;
+import android.hardware.neuralnetworks.DeviceType;
+import android.hardware.neuralnetworks.ExecutionPreference;
+import android.hardware.neuralnetworks.Extension;
+import android.hardware.neuralnetworks.IPreparedModel;
+import android.hardware.neuralnetworks.IPreparedModelCallback;
+import android.hardware.neuralnetworks.IPreparedModelParcel;
+import android.hardware.neuralnetworks.Model;
+import android.hardware.neuralnetworks.NumberOfCacheFiles;
+import android.hardware.neuralnetworks.Priority;
+
+/**
+ * This interface represents a device driver.
+ */
+@VintfStability
+interface IDevice {
+    /**
+     * The byte size of the cache token.
+     */
+    const int BYTE_SIZE_OF_CACHE_TOKEN = 32;
+    /**
+     * The maximum number of files for each type of cache in compilation caching.
+     */
+    const int MAX_NUMBER_OF_CACHE_FILES = 32;
+
+    /**
+     * Numeric values of extension operand and operation types have the following structure:
+     * - The sign bit is always 0.
+     * - 15 high bits represent the "prefix", which corresponds uniquely to the extension name.
+     * - 16 low bits represent the type ID within the extension.
+     */
+    const int EXTENSION_TYPE_HIGH_BITS_PREFIX = 15;
+    const int EXTENSION_TYPE_LOW_BITS_TYPE = 16;
+    /**
+     * OperandType with any value above {@link IDevice::OPERAND_TYPE_BASE_MAX} must be interpreted
+     * as an extension type according to {@link Model::extensionNameToPrefix}.
+     */
+    const int OPERAND_TYPE_BASE_MAX = 0xFFFF;
+    /**
+     * OperationType with any value above {@link IDevice::OPERATION_TYPE_BASE_MAX} must be
+     * interpreted as an extension type according to {@link Model::extensionNameToPrefix}.
+     */
+    const int OPERATION_TYPE_BASE_MAX = 0xFFFF;
+
+    /**
+     * Allocates a driver-managed buffer with the properties specified by the buffer descriptor as
+     * well as the input and output roles.
+     *
+     * The allocate function must verify its inputs are correct. If there is an error, or if a
+     * certain role or property is not supported by the driver, the allocate function must return a
+     * service specific exception with an appropriate ErrorStatus. If the allocation is successful,
+     * this method must return a DeviceBuffer object with the produced IBuffer and a positive token
+     * identifying the allocated buffer. A successful allocation must accommodate all of the
+     * specified roles and buffer properties.
+     *
+     * The buffer is allocated to an uninitialized state. An uninitialized buffer may only be used
+     * in ways that are specified by outputRoles. A buffer is initialized after it is used as an
+     * output in a successful execution, or after a successful invocation of IBuffer::copyFrom on
+     * the buffer. An initialized buffer may be used according to all roles specified in inputRoles
+     * and outputRoles. A buffer will return to the uninitialized state if it is used as an output
+     * in a failed execution, or after a failed invocation of IBuffer::copyFrom on the buffer.
+     *
+     * The dimensions of the buffer can be deduced from the buffer descriptor as well as the
+     * dimensions of the corresponding model operands of the input and output roles. The dimensions
+     * or rank of the buffer may be unknown at this stage. As such, some driver services may only
+     * create a placeholder and defer the actual allocation until execution time. Note that the same
+     * buffer may be used for different shapes of outputs on different executions. When the buffer
+     * is used as an input, the input shape must be the same as the output shape from the last
+     * execution using this buffer as an output.
+     *
+     * The driver must apply proper validatation upon every usage of the buffer, and must fail the
+     * execution immediately if the usage is illegal.
+     *
+     * @param desc A buffer descriptor specifying the properties of the buffer to allocate.
+     * @param preparedModels A vector of IPreparedModel objects. Must only contain IPreparedModel
+     *                       objects from the same IDevice as this method is being invoked on.
+     * @param inputRoles A vector of roles with each specifying an input to a prepared model.
+     * @param outputRoles A vector of roles with each specifying an output to a prepared model. Each
+     *                    role specified in inputRoles and outputRoles must be unique. The
+     *                    corresponding model operands of the roles must have the same OperandType,
+     *                    scale, zero point, and ExtraParams. The dimensions of the operands and the
+     *                    dimensions specified in the buffer descriptor must be compatible with each
+     *                    other. Two dimensions are incompatible if there is at least one axis that
+     *                    is fully specified in both but has different values.
+     * @return DeviceBuffer object containing the allocated IBuffer object and a positive token that
+     *     can be used to reference the buffer as one of the memory pools.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if a certain buffer property or a certain role is not supported,
+     *       or if there is an unspecified error
+     *     - INVALID_ARGUMENT if one of the input arguments is invalid
+     *     - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+     */
+    DeviceBuffer allocate(in BufferDesc desc, in IPreparedModelParcel[] preparedModels,
+            in BufferRole[] inputRoles, in BufferRole[] outputRoles);
+
+    /**
+     * Gets the capabilities of a driver.
+     *
+     * @return Capabilities of the driver.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     */
+    Capabilities getCapabilities();
+
+    /**
+     * Gets the caching requirements of the driver implementation.
+     *
+     * There are two types of cache file descriptors provided to the driver: model cache and data
+     * cache.
+     *
+     * The data cache is for caching constant data, possibly including preprocessed and transformed
+     * tensor buffers. Any modification to the data cache should have no worse effect than
+     * generating bad output values at execution time.
+     *
+     * The model cache is for caching security-sensitive data such as compiled executable machine
+     * code in the device's native binary format. A modification to the model cache may affect the
+     * driver's execution behavior, and a malicious client could make use of this to execute beyond
+     * the granted permission. Thus, the driver must always check whether the model cache is
+     * corrupted before preparing the model from cache.
+     *
+     * getNumberOfCacheFilesNeeded returns how many of each type of cache files the driver
+     * implementation needs to cache a single prepared model. Returning 0 for both types indicates
+     * compilation caching is not supported by this driver. The driver may still choose not to cache
+     * certain compiled models even if it reports that caching is supported.
+     *
+     * If the device reports that caching is not supported, the user may avoid calling
+     * IDevice::prepareModelFromCache or providing cache file descriptors to
+     * IDevice::prepareModel.
+     *
+     * @return NumberOfCacheFiles structure indicating how many files for model and data cache the
+     *     driver needs to cache a single prepared model. It must be less than or equal to
+     *     MAX_NUMBER_OF_CACHE_FILES.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     */
+    NumberOfCacheFiles getNumberOfCacheFilesNeeded();
+
+    /**
+     * Gets information about extensions supported by the driver implementation.
+     *
+     * All extension operations and operands must be fully supported for the extension to appear in
+     * the list of supported extensions.
+     *
+     * @return A list of supported extensions.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     */
+    Extension[] getSupportedExtensions();
+
+    /**
+     * Gets the supported operations in a model.
+     *
+     * getSupportedOperations indicates which operations of the top-level subgraph are fully
+     * supported by the vendor driver. If an operation may not be supported for any reason,
+     * getSupportedOperations must return false for that operation.
+     *
+     * The {@link OperationType::IF} and {@link OperationType::WHILE} operations may only be fully
+     * supported if the vendor driver fully supports all operations in the referenced subgraphs.
+     *
+     * @param model A model whose operations -- and their corresponding operands -- are to be
+     *              verified by the driver.
+     * @return A list of supported operations, where true indicates the operation is supported and
+     *     false indicates the operation is not supported. The index of "supported" corresponds with
+     *     the index of the operation it is describing in the main subgraph.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     *     - INVALID_ARGUMENT if provided model is invalid
+     */
+    boolean[] getSupportedOperations(in Model model);
+
+    /**
+     * Get the type of a given device.
+     *
+     * The device type can be used to help application developers to distribute Machine Learning
+     * workloads and other workloads such as graphical rendering. E.g., for an app which renders AR
+     * scenes based on real time object detection results, the developer could choose an ACCELERATOR
+     * type device for ML workloads, and reserve GPU for graphical rendering.
+     *
+     * @return The DeviceType of the device. Please note, this is not a bitfield of DeviceTypes.
+     *     Each device must only be of a single DeviceType.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if the query resulted in an unspecified error
+     */
+    DeviceType getType();
+
+    /**
+     * Get the version string of the driver implementation.
+     *
+     * The version string must be a unique token among the set of version strings of drivers of a
+     * specific device. The token identifies the device driver's implementation. The token must not
+     * be confused with the feature level which is solely defined by the interface version. This API
+     * is opaque to the Android framework, but the Android framework may use the information for
+     * debugging or to pass on to NNAPI applications.
+     *
+     * Application developers sometimes have specific requirements to ensure good user experiences,
+     * and they need more information to make intelligent decisions when the Android framework
+     * cannot. For example, combined with the device name and other information, the token can help
+     * NNAPI applications filter devices based on their needs:
+     *     - An application demands a certain level of performance, but a specific version of the
+     *       driver cannot meet that requirement because of a performance regression.
+     *       The application can disallow the driver based on the version provided.
+     *     - An application has a minimum precision requirement, but certain versions of
+     *       the driver cannot meet that requirement because of bugs or certain optimizations.
+     *       The application can filter out versions of these drivers.
+     *
+     * @return The version string of the device implementation. Must have nonzero length.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if the query resulted in an unspecified error
+     */
+    String getVersionString();
+
+    /**
+     * Asynchronously creates a prepared model for execution and optionally saves it into cache
+     * files.
+     *
+     * prepareModel is used to make any necessary transformations to or alternative representations
+     * to a model for execution, possibly including transformations on the constant data,
+     * optimization on the model's graph, or compilation into the device's native binary format. The
+     * model itself is not changed.
+     *
+     * Optionally, caching information may be provided for the driver to save the prepared model to
+     * cache files for faster model compilation time when the same model preparation is requested in
+     * the future. There are two types of cache file descriptors provided to the driver: model cache
+     * and data cache. For more information on the two types of cache, refer to
+     * getNumberOfCacheFilesNeeded.
+     *
+     * The file descriptors must be opened with read and write permission. A file may have any size,
+     * and the corresponding file descriptor may have any offset. The driver must truncate a file to
+     * zero size before writing to that file. The file descriptors may be closed by the client once
+     * the asynchronous preparation has finished. The driver must dup a file descriptor if it wants
+     * to get access to the cache file later.
+     *
+     * The model is prepared asynchronously with respect to the caller. The prepareModel function
+     * must verify the inputs to the preparedModel function related to preparing the model (as
+     * opposed to saving the prepared model to cache) are correct. If there is an error,
+     * prepareModel must immediately invoke the callback with the appropriate ErrorStatus value and
+     * nullptr for the IPreparedModel, then return a status with a service specific exception with
+     * the same ErrorStatus. If the inputs to the prepareModel function that are related to
+     * preparing the model are valid and there is no error, prepareModel must launch an asynchronous
+     * task to prepare the model in the background, and immediately return from prepareModel. If the
+     * asynchronous task fails to launch, prepareModel must immediately invoke the callback with
+     * ErrorStatus::GENERAL_FAILURE and nullptr for the IPreparedModel, then return a service
+     * specific exception with ErrorStatus::GENERAL_FAILURE.
+     *
+     * When the asynchronous task has finished preparing the model, it must immediately invoke the
+     * callback function provided as an input to prepareModel. If the model was prepared
+     * successfully, the callback object must be invoked with an error status of ErrorStatus::NONE
+     * and the produced IPreparedModel object. If an error occurred preparing the model, the
+     * callback object must be invoked with the appropriate ErrorStatus value and nullptr for the
+     * IPreparedModel.
+     *
+     * The model is prepared with a priority. This priority is relative to other prepared models
+     * owned by the same client. Higher priority executions may use more compute resources than
+     * lower priority executions, and may preempt or starve lower priority executions.
+     *
+     * prepareModel can be called with an optional deadline. If the model is not able to be prepared
+     * before the provided deadline, the model preparation may be aborted, and either
+     * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+     * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be
+     * sent the same way as other errors, described above.
+     *
+     * Optionally, the driver may save the prepared model to cache during the asynchronous
+     * preparation. Any error that occurs when saving to cache must not affect the status of
+     * preparing the model. Even if the input arguments related to the cache may be invalid, or the
+     * driver may fail to save to cache, the prepareModel function must finish preparing the model.
+     * The driver may choose not to save to cache even if the caching information is provided and
+     * valid.
+     *
+     * The only information that may be unknown to the model at this stage is the shape of the
+     * tensors, which may only be known at execution time. As such, some driver services may return
+     * partially prepared models, where the prepared model may only be finished when it is paired
+     * with a set of inputs to the model. Note that the same prepared model object may be used with
+     * different shapes of inputs on different (possibly concurrent) executions.
+     *
+     * Multiple threads may call prepareModel on the same model concurrently.
+     *
+     * @param model The model to be prepared for execution.
+     * @param preference Indicates the intended execution behavior of a prepared model.
+     * @param priority The priority of the prepared model relative to other prepared models owned by
+     *                 the client.
+     * @param deadline The time by which the model is expected to be prepared. The time is measured
+     *                 in nanoseconds since epoch of the steady clock (as from
+     *                 std::chrono::steady_clock). If the model cannot be prepared by the deadline,
+     *                 the preparation may be aborted. Passing -1 means the deadline is omitted.
+     *                 Other negative values are invalid.
+     * @param modelCache A vector of file descriptors for the security-sensitive cache. The length
+     *                   of the vector must either be 0 indicating that caching information is not
+     *                   provided, or match the numModelCache returned from
+     *                   getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in
+     *                   the same order when retrieving the preparedModel from cache files with
+     *                   prepareModelFromCache.
+     * @param dataCache A vector of file descriptors for the constants' cache. The length of the
+     *                  vector must either be 0 indicating that caching information is not provided,
+     *                  or match the numDataCache returned from getNumberOfCacheFilesNeeded. The
+     *                  cache file descriptors will be provided in the same order when retrieving
+     *                  the preparedModel from cache files with prepareModelFromCache.
+     * @param token A caching token of length BYTE_SIZE_OF_CACHE_TOKEN identifying the prepared
+     *              model. The same token will be provided when retrieving the prepared model from
+     *              the cache files with prepareModelFromCache.  Tokens should be chosen to have a
+     *              low rate of collision for a particular application. The driver cannot detect a
+     *              collision; a collision will result in a failed execution or in a successful
+     *              execution that produces incorrect output values. If both modelCache and
+     *              dataCache are empty indicating that caching information is not provided, this
+     *              token must be ignored.
+     * @param callback A callback object used to return the error status of preparing the model for
+     *                 execution and the prepared model if successful, nullptr otherwise. The
+     *                 callback object's notify function must be called exactly once, even if the
+     *                 model could not be prepared.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     *     - INVALID_ARGUMENT if one of the input arguments related to preparing the model is
+     *       invalid
+     *     - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be prepared by
+     *       the deadline
+     *     - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+     */
+    void prepareModel(in Model model, in ExecutionPreference preference, in Priority priority,
+            in long deadline, in ParcelFileDescriptor[] modelCache,
+            in ParcelFileDescriptor[] dataCache, in byte[] token,
+            in IPreparedModelCallback callback);
+
+    /**
+     * Creates a prepared model from cache files for execution.
+     *
+     * prepareModelFromCache is used to retrieve a prepared model directly from cache files to avoid
+     * slow model compilation time. There are two types of cache file descriptors provided to the
+     * driver: model cache and data cache. For more information on the two types of cache files,
+     * refer to getNumberOfCacheFilesNeeded.
+     *
+     * The file descriptors must be opened with read and write permission. A file may have any size,
+     * and the corresponding file descriptor may have any offset. The driver must truncate a file to
+     * zero size before writing to that file. The file descriptors may be closed by the client once
+     * the asynchronous preparation has finished. The driver must dup a file descriptor if it wants
+     * to get access to the cache file later.
+     *
+     * The model is prepared asynchronously with respect to the caller. The prepareModelFromCache
+     * function must verify the inputs to the prepareModelFromCache function are correct, and that
+     * the security-sensitive cache has not been modified since it was last written by the driver.
+     * If there is an error, or if compilation caching is not supported, or if the
+     * security-sensitive cache has been modified, prepareModelFromCache must immediately invoke the
+     * callback with the appropriate ErrorStatus value and nullptr for the IPreparedModel, then
+     * return a status with a service specific exception with the same ErrorStatus. If the inputs to
+     * the prepareModelFromCache function are valid, the security-sensitive cache is not modified,
+     * and there is no error, prepareModelFromCache must launch an asynchronous task to prepare the
+     * model in the background, and immediately return from prepareModelFromCache. If the
+     * asynchronous task fails to launch, prepareModelFromCache must immediately invoke the callback
+     * with ErrorStatus::GENERAL_FAILURE and nullptr for the IPreparedModel, then return a service
+     * specific exception with ErrorStatus::GENERAL_FAILURE.
+     *
+     * When the asynchronous task has finished preparing the model, it must immediately invoke the
+     * callback function provided as an input to prepareModelFromCache. If the model was prepared
+     * successfully, the callback object must be invoked with an error status of ErrorStatus::NONE
+     * and the produced IPreparedModel object. If an error occurred preparing the model, the
+     * callback object must be invoked with the appropriate ErrorStatus value and nullptr for the
+     * IPreparedModel.
+     *
+     * prepareModelFromCache can be called with an optional deadline. If the model is not able to
+     * prepared before the provided deadline, the model preparation may be aborted, and either
+     * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or
+     * {@link ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort
+     * must be sent the same way as other errors, described above.
+     *
+     * The only information that may be unknown to the model at this stage is the shape of the
+     * tensors, which may only be known at execution time. As such, some driver services may return
+     * partially prepared models, where the prepared model may only be finished when it is paired
+     * with a set of inputs to the model. Note that the same prepared model object may be used with
+     * different shapes of inputs on different (possibly concurrent) executions.
+     *
+     * @param deadline The time by which the model is expected to be prepared. The time is measured
+     *                 in nanoseconds since epoch of the steady clock (as from
+     *                 std::chrono::steady_clock). If the model cannot be prepared by the deadline,
+     *                 the preparation may be aborted. Passing -1 means the deadline is omitted.
+     *                 Other negative values are invalid.
+     * @param modelCache A vector of file descriptors for the security-sensitive cache. The length
+     *                   of the vector must match the numModelCache returned from
+     *                   getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in
+     *                   the same order as with prepareModel.
+     * @param dataCache A vector of file descriptors for the constants' cache. The length of the
+     *                  vector must match the numDataCache returned from
+     *                  getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in
+     *                  the same order as with prepareModel.
+     * @param token A caching token of length BYTE_SIZE_OF_CACHE_TOKEN identifying the prepared
+     *              model. It is the same token provided when saving the cache files with
+     *              prepareModel. Tokens should be chosen to have a low rate of collision for a
+     *              particular application. The driver cannot detect a collision; a collision will
+     *              result in a failed execution or in a successful execution that produces
+     *              incorrect output values.
+     * @param callback A callback object used to return the error status of preparing the model for
+     *                 execution and the prepared model if successful, nullptr otherwise. The
+     *                 callback object's notify function must be called exactly once, even if the
+     *                 model could not be prepared.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if caching is not supported or if there is an unspecified error
+     *     - INVALID_ARGUMENT if one of the input arguments is invalid
+     *     - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be prepared by
+     *       the deadline
+     *     - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+     */
+    void prepareModelFromCache(in long deadline, in ParcelFileDescriptor[] modelCache,
+            in ParcelFileDescriptor[] dataCache, in byte[] token,
+            in IPreparedModelCallback callback);
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl
new file mode 100644
index 0000000..cb6db11
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.ErrorStatus;
+import android.hardware.neuralnetworks.Timing;
+
+/**
+ * IFencedExecutionCallback can be used to query the error status result and duration information
+ * from an IPreparedModel::executeFenced call.
+ */
+@VintfStability
+interface IFencedExecutionCallback {
+    /**
+     * The getExecutionInfo method is used by the clients to query error status result and duration
+     * information. The method must only be called after the actual evaluation has finished or
+     * resulted in an runtime error, as indicated by the status of the sync fence returned by the
+     * IPreparedModel::executeFenced call, otherwise GENERAL_FAILURE must be returned.
+     *
+     * @param out timingLaunched The duration starts when executeFenced is called and ends when
+     *                           executeFenced signals the returned syncFence. Unless measureTiming
+     *                           was set to true when launching the execution and status is NONE,
+     *                           all times must be reported as -1. A driver may choose to report any
+     *                           time as -1, indicating that particular measurement is not
+     *                           available.
+     * @param out timingFenced The duration starts when all waitFor sync fences have been signaled
+     *                         and ends when executeFenced signals the returned syncFence. Unless
+     *                         measureTiming was set to true when launching the execution and status
+     *                         is NONE, all times must be reported as -1. A driver may choose to
+     *                         report any time as -1, indicating that particular measurement is not
+     *                         available.
+     * @return Error status returned from the asynchronously dispatched execution must be:
+     *     - NONE if the asynchronous execution was successful
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if the asynchronous task resulted in an unspecified error
+     *     - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the
+     *       deadline
+     *     - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+     */
+    ErrorStatus getExecutionInfo(out Timing timingLaunched, out Timing timingFenced);
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl
new file mode 100644
index 0000000..2414a4a
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.common.NativeHandle;
+import android.hardware.neuralnetworks.ErrorStatus;
+import android.hardware.neuralnetworks.ExecutionResult;
+import android.hardware.neuralnetworks.IFencedExecutionCallback;
+import android.hardware.neuralnetworks.Request;
+
+/**
+ * IPreparedModel describes a model that has been prepared for execution and is used to launch
+ * executions.
+ */
+@VintfStability
+interface IPreparedModel {
+    /**
+     * Each {@link OperationType::WHILE} operation in the model has an implicit execution timeout
+     * duration associated with it ("loop timeout duration"). This duration is configurable on a
+     * per-execution basis and must not exceed 15 seconds. The default value is 2 seconds. The units
+     * are nanoseconds.
+     */
+    const long DEFAULT_LOOP_TIMEOUT_DURATION_NS = 2000000000;
+    const long MAXIMUM_LOOP_TIMEOUT_DURATION_NS = 15000000000;
+
+    /**
+     * Performs a synchronous execution on a prepared model.
+     *
+     * The execution is performed synchronously with respect to the caller. executeSynchronously
+     * must verify the inputs to the function are correct, and the usages of memory pools allocated
+     * by IDevice::allocate are valid. If there is an error, executeSynchronously must immediately
+     * return a service specific exception with the appropriate ErrorStatus value. If the inputs to
+     * the function are valid and there is no error, executeSynchronously must perform the
+     * execution, and must not return until the execution is complete.
+     *
+     * The caller must not change the content of any data object referenced by 'request' (described
+     * by the {@link DataLocation} of a {@link RequestArgument}) until executeSynchronously returns.
+     * executeSynchronously must not change the content of any of the data objects corresponding to
+     * 'request' inputs.
+     *
+     * If the prepared model was prepared from a model wherein all tensor operands have fully
+     * specified dimensions, and the inputs to the function are valid, and at execution time every
+     * operation's input operands have legal values, then the execution should complete
+     * successfully: there must be no failure unless the device itself is in a bad state.
+     *
+     * executeSynchronously may be called with an optional deadline. If the execution is not able to
+     * be completed before the provided deadline, the execution may be aborted, and either
+     * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+     * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be
+     * sent the same way as other errors, described above.
+     *
+     * Any number of calls to the execute* functions, in any combination, may be made concurrently,
+     * even on the same IPreparedModel object.
+     *
+     * @param request The input and output information on which the prepared model is to be
+     *                executed.
+     * @param measure Specifies whether or not to measure duration of the execution. The duration
+     *                runs from the time the driver sees the call to the executeSynchronously
+     *                function to the time the driver returns from the function.
+     * @param deadline The time by which the execution is expected to complete. The time is measured
+     *                 in nanoseconds since epoch of the steady clock (as from
+     *                 std::chrono::steady_clock). If the execution cannot be finished by the
+     *                 deadline, the execution may be aborted. Passing -1 means the deadline is
+     *                 omitted. Other negative values are invalid.
+     * @param loopTimeoutDuration The maximum amount of time in nanoseconds that should be spent
+     *                            executing a {@link OperationType::WHILE} operation. If a loop
+     *                            condition model does not output false within this duration, the
+     *                            execution must be aborted. If -1 is provided, the maximum amount
+     *                            of time is {@link DEFAULT_LOOP_TIMEOUT_DURATION_NS}. Other
+     *                            negative values are invalid. When provided, the duration must not
+     *                            exceed {@link MAXIMUM_LOOP_TIMEOUT_DURATION_NS}.
+     * @return ExecutionResult parcelable, containing the status of the execution, output shapes and
+     *     timing information.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     *     - INVALID_ARGUMENT if one of the input arguments is invalid
+     *     - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the
+     *       deadline
+     *     - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+     */
+    ExecutionResult executeSynchronously(in Request request, in boolean measureTiming,
+            in long deadline, in long loopTimeoutDuration);
+
+    /**
+     * Launch a fenced asynchronous execution on a prepared model.
+     *
+     * The execution is performed asynchronously with respect to the caller. executeFenced must
+     * verify the inputs to the function are correct, and the usages of memory pools allocated by
+     * IDevice::allocate are valid. If there is an error, executeFenced must immediately return a
+     * service specific exception with the corresponding ErrorStatus. If the inputs to the function
+     * are valid and there is no error, executeFenced must dispatch an asynchronous task to perform
+     * the execution in the background, assign a sync fence that will be signaled once the execution
+     * is completed and immediately return a callback that can be used by the client to query the
+     * duration and runtime error status. If the task has finished before the call returns,
+     * syncFence file descriptor may be set to -1. The execution must wait for all the sync fences
+     * (if any) in waitFor to be signaled before starting the actual execution.
+     *
+     * When the asynchronous task has finished its execution, it must immediately signal the
+     * syncFence returned from the executeFenced call. After the syncFence is signaled, the task
+     * must not modify the content of any data object referenced by 'request' (described by the
+     * {@link DataLocation} of a {@link RequestArgument}).
+     *
+     * executeFenced may be called with an optional deadline and an optional duration. If the
+     * execution is not able to be completed before the provided deadline or within the timeout
+     * duration (measured from when all sync fences in waitFor are signaled), whichever comes
+     * earlier, the execution may be aborted, and either
+     * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+     * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be
+     * sent the same way as other errors, described above.
+     *
+     * If any of the sync fences in waitFor changes to error status after the executeFenced call
+     * succeeds, or the execution is aborted because it cannot finish before the deadline has been
+     * reached or the duration has elapsed, the driver must immediately set the returned syncFence
+     * to error status.
+     *
+     * Any number of calls to the execute* functions, in any combination, may be made concurrently,
+     * even on the same IPreparedModel object.
+     *
+     * @param request The input and output information on which the prepared model is to be
+     *                executed. The outputs in the request must have fully specified dimensions.
+     * @param waitFor A vector of sync fence file descriptors. Execution must not start until all
+     *                sync fences have been signaled.
+     * @param measure Specifies whether or not to measure duration of the execution.
+     * @param deadline The time by which the execution is expected to complete. The time is measured
+     *                 in nanoseconds since epoch of the steady clock (as from
+     *                 std::chrono::steady_clock).If the execution cannot be finished by the
+     *                 deadline, the execution may be aborted. Passing -1 means the deadline is
+     *                 omitted. Other negative values are invalid.
+     * @param loopTimeoutDuration The maximum amount of time in nanoseconds that should be spent
+     *                            executing a {@link OperationType::WHILE} operation. If a loop
+     *                            condition model does not output false within this duration, the
+     *                            execution must be aborted. If -1 is provided, the maximum amount
+     *                            of time is {@link DEFAULT_LOOP_TIMEOUT_DURATION_NS}. Other
+     *                            negative values are invalid. When provided, the duration must not
+     *                            exceed {@link MAXIMUM_LOOP_TIMEOUT_DURATION_NS}.
+     * @param duration The length of time in nanoseconds within which the execution is expected to
+     *                 complete after all sync fences in waitFor are signaled. If the execution
+     *                 cannot be finished within the duration, the execution may be aborted. Passing
+     *                 -1 means the duration is omitted. Other negative values are invalid.
+     * @param out syncFence The sync fence that will be signaled when the task is completed. The
+     *                      sync fence will be set to error if a critical error, e.g. hardware
+     *                      failure or kernel panic, occurs when doing execution.
+     * @return The IFencedExecutionCallback can be used to query information like duration and error
+     *     status when the execution is completed.
+     * @throws ServiceSpecificException with one of the following ErrorStatus values:
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     *     - INVALID_ARGUMENT if one of the input arguments is invalid, including fences in error
+     *       states.
+     *     - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the
+     *       deadline
+     *     - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+     */
+    IFencedExecutionCallback executeFenced(in Request request, in ParcelFileDescriptor[] waitFor,
+            in boolean measureTiming, in long deadline, in long loopTimeoutDuration,
+            in long duration, out @nullable ParcelFileDescriptor syncFence);
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl
new file mode 100644
index 0000000..29cca6d
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.ErrorStatus;
+import android.hardware.neuralnetworks.IPreparedModel;
+
+/**
+ * IPreparedModelCallback must be used to return a prepared model produced by an asynchronous task
+ * launched from IDevice::prepareModel*.
+ */
+@VintfStability
+interface IPreparedModelCallback {
+    /**
+     * Notify must be invoked immediately after the asynchronous task holding this callback has
+     * finished preparing the model. If the model was successfully prepared, the method must be
+     * invoked with ErrorStatus::NONE and the prepared model. If the model was not able to be
+     * successfully prepared, the method must be invoked with the appropriate ErrorStatus and
+     * nullptr as the IPreparedModel. If the asynchronous task holding this callback fails to launch
+     * or if the model provided to IDevice::prepareModel is invalid, notify method must be invoked
+     * with the appropriate error as well as nullptr for the IPreparedModel.
+     *
+     * @param status Error status returned from the asynchronous model preparation task; must be:
+     *               - NONE if the asynchronous task successfully prepared the model
+     *               - DEVICE_UNAVAILABLE if driver is offline or busy
+     *               - GENERAL_FAILURE if the asynchronous task resulted in an unspecified error
+     *               - INVALID_ARGUMENT if one of the input arguments to prepareModel is invalid
+     *               - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be
+     *                 prepared by the deadline
+     *               - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+     * @param preparedModel A model that has been asynchronously prepared for execution. If the
+     *                      model was unable to be prepared due to an error, nullptr must be passed
+     *                      in place of the IPreparedModel object.
+     */
+    void notify(in ErrorStatus status, in IPreparedModel preparedModel);
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
index 127f98e..878b0ad 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.neuralnetworks;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
-}
+import android.hardware.neuralnetworks.IPreparedModel;
 
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
+/**
+ * A parcelable for passing a vector of IPreparedModel objects.
+ */
+@VintfStability
+parcelable IPreparedModelParcel {
+    IPreparedModel preparedModel;
 }
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl
new file mode 100644
index 0000000..870f0ae
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+import android.hardware.common.NativeHandle;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * A type that is used to pass pieces of shared memory between processes.
+ * The type structure mimics hidl_memory type from HIDL.
+ */
+@VintfStability
+parcelable Memory {
+    NativeHandle handle;
+    long size;
+    String name;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl
new file mode 100644
index 0000000..2f11dec
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.ExtensionNameAndPrefix;
+import android.hardware.neuralnetworks.Memory;
+import android.hardware.neuralnetworks.Subgraph;
+
+/**
+ * A Neural Network Model.
+ *
+ * This includes not only the execution graph, but also constant data such as weights or scalars
+ * added at construction time. The only information that may not be known is the shape of the input
+ * tensors.
+ */
+@VintfStability
+parcelable Model {
+    /**
+     * The top-level subgraph.
+     */
+    Subgraph main;
+    /**
+     * Referenced subgraphs.
+     *
+     * Each subgraph is referenced by the main subgraph or at least one other referenced subgraph.
+     *
+     * There must be no reference cycles.
+     */
+    Subgraph[] referenced;
+    /**
+     * A byte buffer containing operand data that were copied into the model.
+     *
+     * An operand's value must be located here if and only if Operand::lifetime equals
+     * OperandLifeTime::CONSTANT_COPY.
+     */
+    byte[] operandValues;
+    /**
+     * A collection of shared memory pools containing operand values.
+     *
+     * An operand's value must be located here if and only if Operand::lifetime equals
+     * OperandLifeTime::CONSTANT_POOL.
+     */
+    Memory[] pools;
+    /**
+     * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or precision as low as that
+     * of the IEEE 754 16-bit floating-point format.
+     * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the range and precision of
+     * the IEEE 754 32-bit floating-point format.
+     */
+    boolean relaxComputationFloat32toFloat16;
+    /**
+     * The mapping between extension names and prefixes of operand and operation type values.
+     */
+    ExtensionNameAndPrefix[] extensionNameToPrefix;
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
similarity index 63%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
index 127f98e..1ca2676 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.neuralnetworks;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
+/**
+ * Structure indicating how many files for model and numDataCache cache the driver needs to cache a
+ * single prepared model.
+ */
+@VintfStability
+parcelable NumberOfCacheFiles {
+    int numModelCache;
+    int numDataCache;
 }
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl
new file mode 100644
index 0000000..4d2260f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.DataLocation;
+import android.hardware.neuralnetworks.OperandExtraParams;
+import android.hardware.neuralnetworks.OperandLifeTime;
+import android.hardware.neuralnetworks.OperandType;
+
+/**
+ * Describes one operand of the model's graph.
+ */
+@VintfStability
+parcelable Operand {
+    /**
+     * The data type.
+     *
+     * Besides the values listed in {@link OperandType}, any value above
+     * {@link IDevice::OPERAND_TYPE_BASE_MAX} is possible and should be interpreted as an extension
+     * type according to {@link Model::extensionNameToPrefix}.
+     */
+    OperandType type;
+    /**
+     * Dimensions of the operand.
+     *
+     * For a scalar operand, dimensions.size() must be 0.
+     *
+     * A tensor operand with all dimensions specified has "fully specified" dimensions. Whenever
+     * possible (i.e., whenever the dimensions are known at model construction time), a tensor
+     * operand should have (but is not required to have) fully specified dimensions, in order to
+     * enable the best possible performance.
+     *
+     * If a tensor operand's dimensions are not fully specified, the dimensions of the operand are
+     * deduced from the operand dimensions and values of the operation for which that operand is an
+     * output or from the corresponding {@link OperationType::IF} or {@link OperationType::WHILE}
+     * operation input operand dimensions in the case of referenced subgraph input operands.
+     *
+     * In the following situations, a tensor operand's dimensions must be fully specified:
+     *
+     *     . The operand has lifetime CONSTANT_COPY or CONSTANT_POOL.
+     *
+     *     . The operand has lifetime SUBGRAPH_INPUT and belongs to the main subgraph. Fully
+     *       specified dimensions must either be present in the Operand or they must be provided in
+     *       the corresponding RequestArgument.
+     *       EXCEPTION: If the input is optional and omitted (by setting the hasNoValue field of the
+     *       corresponding RequestArgument to true) then it need not have fully specified
+     *       dimensions.
+     *
+     * A tensor operand with some number of unspecified dimensions is represented by setting each
+     * unspecified dimension to 0.
+     *
+     * A tensor operand with unspecified rank is represented by providing an empty dimensions
+     * vector.
+     */
+    int[] dimensions;
+    /**
+     * Quantized scale of the operand.
+     *
+     * Must be 0 when not applicable to an operand type.
+     *
+     * See {@link OperandType}.
+     */
+    float scale;
+    /**
+     * Quantized zero-point offset of the operand.
+     *
+     * Must be 0 when not applicable to an operand type.
+     *
+     * See {@link OperandType}.
+     */
+    int zeroPoint;
+    /**
+     * How the operand is used.
+     */
+    OperandLifeTime lifetime;
+    /**
+     * Where to find the data for this operand.
+     * If the lifetime is TEMPORARY_VARIABLE, SUBGRAPH_INPUT, SUBGRAPH_OUTPUT, or NO_VALUE:
+     * - All the fields must be 0.
+     * If the lifetime is CONSTANT_COPY:
+     * - location.poolIndex is 0.
+     * - location.offset is the offset in bytes into Model.operandValues.
+     * - location.length is set.
+     * If the lifetime is CONSTANT_POOL:
+     * - location.poolIndex is set.
+     * - location.offset is the offset in bytes into the specified pool.
+     * - location.length is set.
+     * If the lifetime is SUBGRAPH:
+     * - location.poolIndex is 0.
+     * - location.offset is the index of the referenced subgraph in {@link Model::referenced}.
+     * - location.length is 0.
+     */
+    DataLocation location;
+    /**
+     * Additional parameters specific to a particular operand type.
+     */
+    @nullable OperandExtraParams extraParams;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl
new file mode 100644
index 0000000..229754a
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.SymmPerChannelQuantParams;
+
+/**
+ * Parameters specific to a particular operand type.
+ */
+@VintfStability
+union OperandExtraParams {
+    /**
+     * Symmetric per-channel quantization parameters.
+     *
+     * Only applicable to operands of type TENSOR_QUANT8_SYMM_PER_CHANNEL.
+     */
+    SymmPerChannelQuantParams channelQuant;
+    /**
+     * Extension operand parameters.
+     *
+     * The framework treats this as an opaque data blob.
+     * The format is up to individual extensions.
+     */
+    byte[] extension;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl
new file mode 100644
index 0000000..1d18149
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * How an operand is used.
+ */
+@VintfStability
+@Backing(type="int")
+enum OperandLifeTime {
+    /**
+     * The operand is internal to the model. It's created by an operation and consumed by other
+     * operations. It must be an output operand of exactly one operation.
+     */
+    TEMPORARY_VARIABLE,
+    /**
+     * The operand is an input of a subgraph. It must not be an output operand of any operation.
+     *
+     * An operand can't be both input and output of a subgraph.
+     */
+    SUBGRAPH_INPUT,
+    /**
+     * The operand is an output of a subgraph. It must be an output operand of exactly one
+     * operation.
+     *
+     * An operand can't be both input and output of a subgraph.
+     */
+    SUBGRAPH_OUTPUT,
+    /**
+     * The operand is a constant found in Model.operandValues. It must not be an output operand of
+     * any operation.
+     */
+    CONSTANT_COPY,
+    /**
+     * The operand is a constant that was specified via a Memory object. It must not be an output
+     * operand of any operation.
+     */
+    CONSTANT_POOL,
+    /**
+     * The operand does not have a value. This is valid only for optional arguments of operations.
+     */
+    NO_VALUE,
+    /**
+     * The operand is a reference to a subgraph. It must be an input to one or more
+     * {@link OperationType::IF} or {@link OperationType::WHILE} operations.
+     */
+    SUBGRAPH,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl
new file mode 100644
index 0000000..7fd86f9
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.OperandType;
+import android.hardware.neuralnetworks.PerformanceInfo;
+
+/**
+ * Driver performance when operating on a particular data type. In the case of float32 data, this is
+ * used when the calculations are not relaxed.
+ */
+@VintfStability
+parcelable OperandPerformance {
+    OperandType type;
+    PerformanceInfo info;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl
new file mode 100644
index 0000000..12edc0f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Operand types.
+ *
+ * The type of an operand in a model.
+ *
+ * Types prefaced with TENSOR_* must be used for tensor data (i.e., tensors
+ * with at least one dimension). Types not prefaced by TENSOR_* represent
+ * scalar values and must have no dimensions.
+ */
+@VintfStability
+@Backing(type="int")
+enum OperandType {
+    /**
+     * A 32 bit floating point scalar value.
+     */
+    FLOAT32 = 0,
+    /**
+     * A signed 32 bit integer scalar value.
+     */
+    INT32 = 1,
+    /**
+     * An unsigned 32 bit integer scalar value.
+     */
+    UINT32 = 2,
+    /**
+     * A tensor of 32 bit floating point values.
+     */
+    TENSOR_FLOAT32 = 3,
+    /**
+     * A tensor of 32 bit integer values.
+     */
+    TENSOR_INT32 = 4,
+    /**
+     * A tensor of 8 bit unsigned integers that represent real numbers.
+     *
+     * Attached to this tensor are two numbers that can be used to convert the 8 bit integer to the
+     * real value and vice versa. These two numbers are:
+     * - scale: a 32 bit floating point value greater than zero.
+     * - zeroPoint: a 32 bit integer, in range [0, 255].
+     *
+     * The formula is:
+     *   real_value = (integer_value - zeroPoint) * scale.
+     */
+    TENSOR_QUANT8_ASYMM = 5,
+    /**
+     * An 8 bit boolean scalar value.
+     *
+     * Values of this operand type are either true or false. A zero value represents false; any
+     * other value represents true.
+     */
+    BOOL = 6,
+    /**
+     * A tensor of 16 bit signed integers that represent real numbers.
+     *
+     * Attached to this tensor is a number representing real value scale that is used to convert the
+     * 16 bit number to a real value in the following way:
+     * realValue = integerValue * scale.
+     *
+     * scale is a 32 bit floating point with value greater than zero.
+     */
+    TENSOR_QUANT16_SYMM = 7,
+    /**
+     * A tensor of IEEE 754 16 bit floating point values.
+     */
+    TENSOR_FLOAT16 = 8,
+    /**
+     * A tensor of 8 bit boolean values.
+     *
+     * Values of this operand type are either true or false. A zero value represents false; any
+     * other value represents true.
+     */
+    TENSOR_BOOL8 = 9,
+    /**
+     * An IEEE 754 16 bit floating point scalar value.
+     */
+    FLOAT16 = 10,
+    /**
+     * A tensor of 8 bit signed integers that represent real numbers.
+     *
+     * This tensor is associated with additional fields that can be used to convert the 8 bit signed
+     * integer to the real value and vice versa. These fields are:
+     * - channelDim: a 32 bit unsigned integer indicating channel dimension.
+     * - scales: an array of positive 32 bit floating point values.
+     * The size of the scales array must be equal to dimensions[channelDim].
+     *
+     * {@link SymmPerChannelQuantParams} must hold the parameters for an Operand of this type.
+     * The channel dimension of this tensor must not be unknown (dimensions[channelDim] != 0).
+     *
+     * The formula is:
+     * realValue[..., C, ...] =
+     *     integerValue[..., C, ...] * scales[C]
+     * where C is an index in the Channel dimension.
+     */
+    TENSOR_QUANT8_SYMM_PER_CHANNEL = 11,
+    /**
+     * A tensor of 16 bit unsigned integers that represent real numbers.
+     *
+     * Attached to this tensor are two numbers that can be used to convert the 16 bit integer to the
+     * real value and vice versa. These two numbers are:
+     * - scale: a 32 bit floating point value greater than zero.
+     * - zeroPoint: a 32 bit integer, in range [0, 65535].
+     *
+     * The formula is:
+     * real_value = (integer_value - zeroPoint) * scale.
+     */
+    TENSOR_QUANT16_ASYMM = 12,
+    /**
+     * A tensor of 8 bit signed integers that represent real numbers.
+     *
+     * Attached to this tensor is a number representing real value scale that is used to convert the
+     * 8 bit number to a real value in the following way:
+     * realValue = integerValue * scale.
+     *
+     * scale is a 32 bit floating point with value greater than zero.
+     */
+    TENSOR_QUANT8_SYMM = 13,
+    /**
+     * A tensor of 8 bit signed integers that represent real numbers.
+     *
+     * Attached to this tensor are two numbers that can be used to convert the 8 bit integer to the
+     * real value and vice versa. These two numbers are:
+     * - scale: a 32 bit floating point value greater than zero.
+     * - zeroPoint: a 32 bit integer, in range [-128, 127].
+     *
+     * The formula is:
+     * real_value = (integer_value - zeroPoint) * scale.
+     */
+    TENSOR_QUANT8_ASYMM_SIGNED = 14,
+    /**
+     * A reference to a subgraph.
+     *
+     * Must have the lifetime {@link OperandLifeTime::SUBGRAPH}.
+     */
+    SUBGRAPH = 15,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl
new file mode 100644
index 0000000..0c6032f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.OperationType;
+
+/**
+ * Describes one operation of the model's graph.
+ */
+@VintfStability
+parcelable Operation {
+    /**
+     * The operation type.
+     *
+     * Besides the values listed in {@link OperationType}, any value above
+     * {@link IDevice::OPERATION_TYPE_BASE_MAX} is possible and should be interpreted as an
+     * extension type according to {@link Model::extensionNameToPrefix}.
+     */
+    OperationType type;
+    /**
+     * Describes the table that contains the indexes of the inputs of the operation. The offset is
+     * the index in the operandIndexes table.
+     */
+    int[] inputs;
+    /**
+     * Describes the table that contains the indexes of the outputs of the operation. The offset is
+     * the index in the operandIndexes table.
+     */
+    int[] outputs;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl
new file mode 100644
index 0000000..3f49154
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl
@@ -0,0 +1,5131 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Operation types.
+ *
+ * The type of an operation in a model.
+ */
+@VintfStability
+@Backing(type="int")
+enum OperationType {
+    /**
+     * Adds two tensors, element-wise.
+     *
+     * Takes two input tensors of identical {@link OperandType} and compatible
+     * dimensions. The output is the sum of both input tensors, optionally
+     * modified by an activation function.
+     *
+     * Two dimensions are compatible when:
+     *     1. they are equal, or
+     *     2. one of them is 1
+     *
+     * The size of the output is the maximum size along each dimension of the
+     * input operands. It starts with the trailing dimensions, and works its
+     * way forward.
+     *
+     * Example:
+     *
+     *     input1.dimension = {4, 1, 2}
+     *     input2.dimension = {5, 4, 3, 1}
+     *     output.dimension = {5, 4, 3, 2}
+     *
+     * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero
+     * dimension is only compatible with 0 or 1. The size of the output
+     * dimension is zero if either of corresponding input dimension is zero.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+     *      as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scales and zeroPoint can be different from input0 scale and zeroPoint.
+     * * 2: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     *      For a {@link OperandType::TENSOR_INT32} tensor,
+     *      the {@link FusedActivationFunc} must be "NONE".
+     *
+     * Outputs:
+     * * 0: The sum, a tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+     */
+    ADD = 0,
+    /**
+     * Performs a 2-D average pooling operation.
+     *
+     * The output dimensions are functions of the filter dimensions, stride, and
+     * padding.
+     *
+     * The values in the output tensor are computed as:
+     *
+     *     output[b, i, j, channel] =
+     *         sum_{di, dj}(
+     *             input[b, strides[1] * i + di, strides[2] * j + dj, channel]
+     *         ) / sum(1)
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Both explicit padding and implicit padding are supported.
+     *
+     * Inputs (explicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the left, in the ‘width’ dimension.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the right, in the ‘width’ dimension.
+     * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the top, in the ‘height’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the bottom, in the ‘height’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the filter
+     *      width.
+     * * 8: An {@link OperandType::INT32} scalar, specifying the filter
+     *      height.
+     * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 10: An optional {@link OperandType::BOOL} scalar, default to false.
+     *       Set to true to specify NCHW data layout for input0 and output0.
+     *       Available since HAL version 1.2.
+     *
+     * Inputs (implicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the implicit
+     *      padding scheme, has to be one of the
+     *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 3: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the filter
+     *      width.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the filter
+     *      height.
+     * * 6: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 7: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, out_height, out_width, depth].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    AVERAGE_POOL_2D = 1,
+    /**
+     * Concatenates the input tensors along the given dimension.
+     *
+     * The input tensors must have identical {@link OperandType} and the same
+     * dimensions except the dimension along the concatenation axis.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *   (full support since HAL version 1.2, see the input section)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0 ~ n-1: The list of n input tensors, of shape
+     *            [D0, D1, ..., Daxis(i), ..., Dm].
+     *            Before HAL version 1.2, all input tensors of
+     *            {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *            must have the same scale and zeroPoint as the output tensor.
+     *            Input tensors of
+     *            {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *            are allowed to have different scale and zeroPoint.
+     *            Since HAL version 1.2, zero-sized tensors are supported.
+     * * n: An {@link OperandType::INT32} scalar, specifying the
+     *      concatenation axis.
+     *
+     * Outputs:
+     * * 0: The output, a tensor of the same {@link OperandType} as the input
+     *      tensors. The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm].
+     *      Since HAL version 1.2, for a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint values can be different from
+     *      input tensors. Before HAL version 1.2 they have to be the same as for the input tensors.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint values can be different from input tensors.
+     */
+    CONCATENATION = 2,
+    /**
+     * Performs a 2-D convolution operation.
+     *
+     * The CONV_2D op sweeps a 2-D filter that can mix channels together over a
+     * batch of images, applying the filter to each window of each image of the
+     * appropriate size.
+     *
+     * The output dimensions are functions of the filter dimensions, stride, and
+     * padding.
+     *
+     * The values in the output tensor are computed as:
+     *
+     *     output[b, i, j, channel] =
+     *         sum_{di, dj, k} (
+     *             input[b, strides[1] * i + di, strides[2] * j + dj, k] *
+     *             filter[channel, di, dj, k]
+     *         ) + bias[channel]
+     *
+     * Supported tensor {@link OperandType} configurations:
+     * * 32 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
+     *
+     * * Quantized:
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output.
+     * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+     * * * input.scale * filter.scale).
+     *
+     * Available since HAL version 1.2:
+     * * 16 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
+     *
+     * * Quantized with symmetric per channel quantization for the filter:
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output.
+     * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+     * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+     * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+     *
+     * Available since HAL version 1.3:
+     * * Quantized signed (since HAL version 1.3):
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output.
+     * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+     * * * input.scale * filter.scale).
+     *
+     * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3):
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output.
+     * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+     * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+     * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Both explicit padding and implicit padding are supported.
+     *
+     * Inputs (explicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: A 4-D tensor, of shape
+     *      [depth_out, filter_height, filter_width, depth_in], specifying the
+     *      filter.
+     *      For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+     *      the channel dimension (SymmPerChannelQuantParams::channelDim)
+     *      must be set to 0.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32}
+     *      or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+     *      of 0 and bias_scale == input_scale * filter_scale.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+     *      and bias_scale of 0. The actual scale of each value 'i' is equal to
+     *      bias_scale[i] = input_scale * filter_scale[i].
+     * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the left, in the ‘width’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the right, in the ‘width’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the top, in the ‘height’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the bottom, in the ‘height’ dimension.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 8: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 10: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     * * 11: An optional {@link OperandType::INT32} scalar, specifying the dilation
+     *      factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped
+     *      cells between each filter element on width dimension. If this input is set,
+     *      input 12 (dilation factor for height) must be specified as well.
+     *      Available since HAL version 1.2.
+     * * 12: An optional {@link OperandType::INT32} scalar, specifying the dilation
+     *      factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped
+     *      cells between each filter element on height dimension. If this input is set,
+     *      input 11 (dilation factor for width) must be specified as well.
+     *      Available since HAL version 1.2.
+     *
+     * Inputs (implicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: A 4-D tensor, of shape
+     *      [depth_out, filter_height, filter_width, depth_in], specifying the
+     *      filter.
+     *      For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+     *      the channel dimension (SymmPerChannelQuantParams::channelDim)
+     *      must be set to 0.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32}
+     *      or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same
+     *      type.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+     *      of 0 and bias_scale == input_scale * filter_scale.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+     *      and bias_scale of 0. The actual scale of each value 'i' is equal to
+     *      bias_scale[i] = input_scale * filter_scale[i].
+     * * 3: An {@link OperandType::INT32} scalar, specifying the implicit
+     *      padding scheme, has to be one of the
+     *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 7: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     * * 8: An optional {@link OperandType::INT32} scalar, specifying the dilation
+     *      factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped
+     *      cells between each filter element on width dimension. If this input is set,
+     *      input 9 (dilation factor for height) must be specified as well.
+     *      Available since HAL version 1.2.
+     * * 9: An optional {@link OperandType::INT32} scalar, specifying the dilation
+     *      factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped
+     *      cells between each filter element on height dimension. If this input is set,
+     *      input 8 (dilation factor for width) must be specified as well.
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, out_height, out_width, depth_out].
+     *      Before HAL version 1.2, for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      the following condition must be satisfied: output_scale > input_scale * filter_scale
+     */
+    CONV_2D = 3,
+    /**
+     * Performs a depthwise 2-D convolution operation.
+     *
+     * Given an input tensor of shape [batches, height, width, depth_in] and a
+     * filter tensor of shape [1, filter_height, filter_width, depth_out]
+     * containing depth_out convolutional filters of depth 1, DEPTHWISE_CONV
+     * applies a different filter to each input channel (expanding from 1
+     * channel to channel_multiplier channels for each), then concatenates the
+     * results together.
+     *
+     * The output has depth_out = depth_in * depth_multiplier channels.
+     * The output dimensions are functions of the filter dimensions, stride, and
+     * padding.
+     *
+     * The values in the output tensor are computed as:
+     *
+     *     output[b, i, j, k * channel_multiplier + q] =
+     *         sum_{di, dj} (
+     *             input[b, strides[1] * i + di, strides[2] * j + dj, k] *
+     *             filter[1, di, dj, k * channel_multiplier + q]
+     *         ) + bias[k * channel_multiplier + q]
+     *
+     * Supported tensor {@link OperandType} configurations:
+     * * 32 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
+     *
+     * * Quantized:
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output.
+     * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+     * * * input.scale * filter.scale).
+     *
+     * Available since HAL version 1.2:
+     * * 16 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
+     *
+     * * Quantized with symmetric per channel quantization for the filter:
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output.
+     * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+     * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+     * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+     *
+     * Available since HAL version 1.3:
+     * * Quantized signed (since HAL version 1.3):
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output.
+     * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+     * * * input.scale * filter.scale).
+     *
+     * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3):
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output.
+     * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+     * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+     * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Both explicit padding and implicit padding are supported.
+     *
+     * Inputs (explicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input.
+     * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out],
+     *      specifying the filter.
+     *      For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+     *      the channel dimension (SymmPerChannelQuantParams::channelDim)
+     *      must be set to 3.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32}
+     *      or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+     *      of 0 and bias_scale == input_scale * filter_scale.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+     *      and bias_scale of 0. The actual scale of each value 'i' is equal to
+     *      bias_scale[i] = input_scale * filter_scale[i].
+     * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the left, in the ‘width’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the right, in the ‘width’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the top, in the ‘height’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the bottom, in the ‘height’ dimension.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 8: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 9: An {@link OperandType::INT32} scalar, specifying the depthwise
+     *      multiplier.
+     * * 10: An {@link OperandType::INT32} scalar, and has to be one of the
+     *       {@link FusedActivationFunc} values. Specifies the activation to
+     *       invoke on the result.
+     * * 11: An optional {@link OperandType::BOOL} scalar, default to false.
+     *       Set to true to specify NCHW data layout for input0 and output0.
+     *       Available since HAL version 1.2.
+     * * 12: An optional {@link OperandType::INT32} scalar, specifying the dilation
+     *      factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped
+     *      cells between each filter element on width dimension. If this input is set,
+     *      input 13 (dilation factor for height) must be specified as well.
+     *      Available since HAL version 1.2.
+     * * 13: An optional {@link OperandType::INT32} scalar, specifying the dilation
+     *      factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped
+     *      cells between each filter element on height dimension. If this input is set,
+     *      input 12 (dilation factor for width) must be specified as well.
+     *      Available since HAL version 1.2.
+     *
+     * Inputs (implicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input.
+     * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out],
+     *      specifying the filter.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32}
+     *      or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+     *      of 0 and bias_scale == input_scale * filter_scale.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+     *      and bias_scale of 0. The actual scale of each value 'i' is equal to
+     *      bias_scale[i] = input_scale * filter_scale[i].
+     * * 3: An {@link OperandType::INT32} scalar, specifying the implicit
+     *      padding scheme, has to be one of the
+     *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the depthwise
+     *      multiplier.
+     * * 7: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 8: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     * * 9: An optional {@link OperandType::INT32} scalar, specifying the dilation
+     *      factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped
+     *      cells between each filter element on width dimension. If this input is set,
+     *      input 10 (dilation factor for height) must be specified as well.
+     *      Available since HAL version 1.2.
+     * * 10: An optional {@link OperandType::INT32} scalar, specifying the dilation
+     *      factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped
+     *      cells between each filter element on height dimension. If this input is set,
+     *      input 9 (dilation factor for width) must be specified as well.
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, out_height, out_width, depth_out]. Before HAL version 1.2, for
+     *      output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      the following condition must be satisfied:
+     *      output_scale > input_scale * filter_scale
+     */
+    DEPTHWISE_CONV_2D = 4,
+    /**
+     * Rearranges data from depth into blocks of spatial data.
+     *
+     * More specifically, this op outputs a copy of the input tensor where
+     * values from the depth dimension are moved in spatial blocks to the height
+     * and width dimensions. The value block_size indicates the input block size
+     * and how the data is moved.
+     *
+     * Chunks of data of size block_size * block_size from depth are rearranged
+     * into non-overlapping blocks of size block_size x block_size.
+     *
+     * The width of the output tensor is input_depth * block_size, whereas the
+     * height is input_height * block_size. The depth of the input tensor must
+     * be divisible by block_size * block_size
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Inputs:
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the block_size.
+     *      block_size must be >=1 and block_size * block_size must be a divisor
+     *      of the input depth.
+     * * 2: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape [batch, height*block_size,
+     *      width*block_size, depth/(block_size*block_size)].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    DEPTH_TO_SPACE = 5,
+    /**
+     * Dequantizes the input tensor.
+     *
+     * The formula is:
+     *
+     *     output = (input - zeroPoint) * scale.
+     *
+     * Supported input tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_SYMM} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported output tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}.
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
+     *
+     * Outputs:
+     * * 0: A tensor with the same shape as input0.
+     */
+    DEQUANTIZE = 6,
+    /**
+     * Looks up sub-tensors in the input tensor.
+     *
+     * This operator takes for input a tensor of values (Values) and
+     * a one-dimensional tensor of selection indices (Lookups).
+     * The output tensor is the concatenation of sub-tensors of Values as
+     * selected by Lookups.
+     *
+     * Think of Values as being sliced along its first dimension:
+     * The entries in Lookups select which slices are concatenated together
+     * to create the output tensor.
+     *
+     * For example, if Values has shape of [40, 200, 300] and
+     * Lookups has shape of [3], all three values found in Lookups are
+     * expected to be between 0 and 39. The resulting tensor must
+     * have shape of [3, 200, 300].
+     *
+     * If a value in Lookups is out of bounds, the operation must fail
+     * and an error must be reported.
+     *
+     * Supported value tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.3)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported value tensor rank: from 2
+     *
+     * Inputs:
+     * * 0: Lookups. A 1-D tensor of {@link OperandType::TENSOR_INT32}.
+     *      The values are indices into the first dimension of Values.
+     * * 1: Values. An n-D tensor, where n >= 2, from which sub-tensors are
+     *      extracted.
+     *
+     * Output:
+     * * 0: A n-D tensor with the same rank and shape as the Values
+     *      tensor, except for the first dimension which has the same size
+     *      as Lookups' only dimension.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input1.
+     */
+    EMBEDDING_LOOKUP = 7,
+    /**
+     * Computes element-wise floor() on the input tensor.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor, of the same {@link OperandType} and dimensions as
+     *      the input tensor.
+     */
+    FLOOR = 8,
+    /**
+     * Denotes a fully (densely) connected layer, which connects all elements
+     * in the input tensor with each element in the output tensor.
+     *
+     * This layer implements the operation:
+     *
+     *     outputs = activation(inputs * weights’ + bias)
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4.
+     *
+     * Inputs:
+     * * 0: A tensor of at least rank 2, specifying the input. If rank is
+     *      greater than 2, then it gets flattened to a 2-D Tensor. The
+     *      (flattened) 2-D Tensor is reshaped (if necessary) to
+     *      [batch_size, input_size], where "input_size" corresponds to the
+     *      number of inputs to the layer, matching the second dimension of
+     *      weights, and "batch_size" is calculated by dividing the number of
+     *      elements by "input_size".
+     *      Since HAL version 1.2, zero batch_size is supported for this tensor.
+     * * 1: A 2-D tensor, specifying the weights, of shape
+     *      [num_units, input_size], where "num_units" corresponds to the number
+     *      of output nodes.
+     * * 2: A 1-D tensor, of shape [num_units], specifying the bias. For input
+     *      tensor of {@link OperandType::TENSOR_FLOAT32}, the bias should
+     *      also be of {@link OperandType::TENSOR_FLOAT32}.
+     *      For input tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the bias should be of {@link OperandType::TENSOR_INT32},
+     *      with zeroPoint of 0 and bias_scale == input_scale * filter_scale.
+     * * 3: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     *
+     * Outputs:
+     * * 0: The output tensor, of shape [batch_size, num_units]. Before HAL version 1.2, for
+     *      output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the following
+     *      condition must be satisfied: output_scale > input_scale * filter_scale.
+     */
+    FULLY_CONNECTED = 9,
+    /**
+     * Looks up sub-tensors in the input tensor using a key-value map.
+     *
+     * This operator takes for input a tensor of values (Values),
+     * a one-dimensional tensor of selection values (Lookups) and
+     * a one-dimensional tensor that maps these values to Values
+     * indexes. The output tensor is the concatenation of sub-tensors of
+     * Values as selected by Lookups via Keys.
+     *
+     * Think of Values as being sliced along its outer-most dimension.
+     * The output is a concatenation of selected slices, with one slice
+     * for each entry of Lookups. The slice selected is the one at the
+     * same index as the Maps entry that matches the value in Lookups.
+     *
+     * For a hit, the corresponding sub-tensor of Values is included
+     * in the Output tensor. For a miss, the corresponding sub-tensor in
+     * Output must have zero values.
+     *
+     * For example, if Values has shape of [40, 200, 300],
+     * Keys should have a shape of [40]. If Lookups tensor has shape
+     * of [3], three slices are being concatenated, so the resulting tensor
+     * must have the shape of [3, 200, 300]. If the first entry in Lookups
+     * has the value 123456, that value must be located in Keys tensor.
+     * If the sixth entry of Keys contains 123456, the sixth slice of Values
+     * must be selected. If no entry in Keys has 123456, a slice of zeroes
+     * must be concatenated.
+     *
+     * Supported value tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *
+     * Supported value tensor rank: from 2
+     *
+     * Inputs:
+     * * 0: Lookups. A 1-D {@link OperandType::TENSOR_INT32} tensor with
+     *      shape [ k ].
+     * * 1: Keys. A 1-D {@link OperandType::TENSOR_INT32} tensor with shape
+     *      [ n ]; Keys and Values pair represent a map, i.e., the ith element
+     *      in Keys (Keys[i]) is the key to select the ith sub-tensor in Values
+     *      (Values[i]), where 0 <= i <= n-1. Keys tensor *MUST* be sorted in
+     *      ascending order.
+     * * 2: Values. A tensor with shape of [ n, … ]; i.e., the first dimension
+     *      must be n.
+     *
+     * Outputs:
+     * * 0: Output. A tensor with shape [ k …].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input2.
+     * * 1: Hits. A boolean tensor with shape [ k ] indicates whether the lookup
+     *      hits (True) or not (False).
+     *      Stored as {@link OperandType::TENSOR_QUANT8_ASYMM} with offset 0
+     *      and scale 1.0f.
+     *      A non-zero byte represents True, a hit. A zero indicates otherwise.
+     */
+    HASHTABLE_LOOKUP = 10,
+    /**
+     * Applies L2 normalization along the axis dimension.
+     *
+     * The values in the output tensor are computed as:
+     *
+     *     output[batch, row, col, channel] =
+     *         input[batch, row, col, channel] /
+     *         sqrt(sum_{c} pow(input[batch, row, col, c], 2))
+     *
+     * By default the axis dimension is the last dimension of the input tensor.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     * Tensors with rank less than 4 are only supported since HAL version 1.2.
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the tensor to be normalized.
+     * * 1: An optional {@link OperandType::INT32} scalar, default to -1,
+     *      specifying the dimension normalization would be performed on.
+     *      Negative index is used to specify axis from the end (e.g. -1 for
+     *      the last axis). Must be in the range [-n, n).
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} and same shape as input0.
+     *      For {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      the scale must be 1.f / 128 and the zeroPoint must be 128.
+     *      For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the scale must be 1.f / 128 and the zeroPoint must be 0.
+     *
+     *      NOTE: Before HAL version 1.3, if the elements along an axis are all zeros,
+     *      the result is undefined. Since HAL version 1.3, if the elements along an axis
+     *      are all zeros, the result is logical zero.
+     */
+    L2_NORMALIZATION = 11,
+    /**
+     * Performs an 2-D L2 pooling operation.
+     *
+     * The output dimensions are functions of the filter dimensions, stride, and
+     * padding.
+     *
+     * The values in the output tensor are computed as:
+     *
+     *     output[b, i, j, c] =
+     *         sqrt(sum_{di, dj} pow(input[b, strides[1] * i + di, strides[2] * j + dj, c], 2) /
+     *              sum(1))
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Both explicit padding and implicit padding are supported.
+     *
+     * Inputs (explicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the left, in the ‘width’ dimension.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the right, in the ‘width’ dimension.
+     * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the top, in the ‘height’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the bottom, in the ‘height’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the filter
+     *      width.
+     * * 8: An {@link OperandType::INT32} scalar, specifying the filter
+     *      height.
+     * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 10: An optional {@link OperandType::BOOL} scalar, default to false.
+     *       Set to true to specify NCHW data layout for input0 and output0.
+     *       Available since HAL version 1.2.
+     *
+     * Inputs (implicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the implicit
+     *      padding scheme, has to be one of the
+     *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 3: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the filter
+     *      width.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the filter
+     *      height.
+     * * 6: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 7: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, out_height, out_width, depth].
+     */
+    L2_POOL_2D = 12,
+    /**
+     * Applies Local Response Normalization along the depth dimension.
+     *
+     * The 4-D input tensor is treated as a 3-D array of 1-D vectors (along the
+     * last dimension), and each vector is normalized independently. Within a
+     * given vector, each component is divided by the weighted, squared sum of
+     * inputs within depth_radius.
+     *
+     * The output is calculated using this formula:
+     *
+     *     sqr_sum[a, b, c, d] = sum(
+     *         pow(input[a, b, c, d - depth_radius : d + depth_radius + 1], 2))
+     *     output = input / pow((bias + alpha * sqr_sum), beta)
+     *
+     * For input tensor with rank less than 4, independently normalizes each
+     * 1-D slice along specified dimension.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: up to 4
+     * Tensors with rank less than 4 are only supported since HAL version 1.2.
+     *
+     * Inputs:
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the radius of
+     *      the normalization window.
+     * * 2: A scalar, specifying the bias, must not be zero.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT16}, the bias
+     *      value must be of {@link OperandType::FLOAT16}.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT32}, the bias
+     *      value must be of {@link OperandType::FLOAT32}.
+     * * 3: A scalar, specifying the scale factor, alpha.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT16}, the
+     *      alpha value must be of {@link OperandType::FLOAT16}.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT32}, the
+     *      alpha value must be of {@link OperandType::FLOAT32}.
+     * * 4: A scalar, specifying the exponent, beta.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta
+     *      value must be of {@link OperandType::FLOAT16}.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta
+     *      value must be of {@link OperandType::FLOAT32}.
+     * * 5: An optional {@link OperandType::INT32} scalar, default to -1,
+     *      specifying the dimension normalization would be performed on.
+     *      Negative index is used to specify axis from the end (e.g. -1 for
+     *      the last axis). Must be in the range [-n, n).
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    LOCAL_RESPONSE_NORMALIZATION = 13,
+    /**
+     * Computes sigmoid activation on the input tensor element-wise.
+     *
+     * The output is calculated using this formula:
+     *
+     *     output = 1 / (1 + exp(-input))
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4.
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     *      For {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      the scale must be 1.f / 256 and the zeroPoint must be 0.
+     *      For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the scale must be 1.f / 256 and the zeroPoint must be -128.
+     */
+    LOGISTIC = 14,
+    /**
+     * Projects an input to a bit vector via locality senstive hashing.
+     *
+     * Supported input tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *
+     * Supported input tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: Hash functions. Dim.size == 2, DataType: Float.
+     *      Tensor[0].Dim[0]: 15 of hash functions.
+     *      Tensor[0].Dim[1]: 16 of projected output bits generated by each
+     *      hash function.
+     *      If the projection type is Sparse:
+     *      Tensor[0].Dim[1] + ceil(log2(Tensor[0].Dim[0])) <= 32
+     *
+     * * 1: Input. Dim.size >= 1, no restriction on DataType.
+     * * 2: Weight. Optional. Dim.size == 1, DataType: Float.
+     *      If not set, each input element is considered to have the same weight
+     *      of 1.0.
+     *      Tensor[1].Dim[0] == Tensor[2].Dim[0]
+     * * 3: Type:
+     *        Sparse:
+     *          Value LSHProjectionType_SPARSE(=3) (since HAL version 1.2).
+     *          Computed bit vector is considered to be sparse.
+     *          Each output element is an int32 made up of multiple bits
+     *          computed from hash functions.
+     *
+     *          NOTE: To avoid collisions across hash functions, an offset value
+     *          of k * (1 << Tensor[0].Dim[1]) will be added to each signature,
+     *          where k is the index of the hash function.
+     *
+     *          Value LSHProjectionType_SPARSE_DEPRECATED(=1).
+     *          Legacy behavior that does not include the offset value.
+     *
+     *        Dense:
+     *          Value LSHProjectionType_DENSE(=2).
+     *          Computed bit vector is considered to be dense. Each output
+     *          element represents a bit and can take the value of either
+     *          0 or 1.
+     *
+     * Outputs:
+     * * 0: If the projection type is Sparse:
+     *      Output.Dim == { Tensor[0].Dim[0] }
+     *      A tensor of int32 that represents hash signatures.
+     *
+     *      If the projection type is Dense:
+     *      Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] }
+     *      A flattened tensor that represents projected bit vectors.
+     * The offset value for sparse projections was added in HAL version 1.2.
+     */
+    LSH_PROJECTION = 15,
+    /**
+     * Performs a single time step in a Long Short-Term Memory (LSTM) layer
+     *
+     * The LSTM operation is described by the following equations.
+     *
+     * \f{eqnarray*}{
+     * i_t =& \sigma(W_{xi}x_t+W_{hi}h_{t-1}+W_{ci}C_{t-1}+b_i) & \\
+     * f_t =& \sigma(W_{xf}x_t+W_{hf}h_{t-1}+W_{cf}C_{t-1}+b_f) & \\
+     * C_t =& clip(f_t \odot C_{t-1} + i_t \odot
+     *        g(W_{xc}x_t+W_{hc}h_{t-1}+b_c),\ t_{cell}) & \\
+     * o_t =& \sigma(W_{xo}x_t+W_{ho}h_{t-1}+W_{co}C_t+b_o) & \\
+     *      & & \\
+     *      & clip(W_{proj}(o_t \odot g(C_t))+b_{proj},\ t_{proj})
+     *      & if\ there\ is\ a\ projection; \\
+     * h_t =& & \\
+     *      & o_t \odot g(C_t) & otherwise. \\
+     * \f}
+     * Where:
+     * * \f$x_t\f$ is the input,
+     * * \f$i_t\f$ is the input gate,
+     * * \f$f_t\f$ is the forget gate,
+     * * \f$C_t\f$ is the cell state,
+     * * \f$o_t\f$ is the output,
+     * * \f$h_t\f$ is the output state,
+     * * \f$\sigma\f$ is the logistic sigmoid function,
+     * * \f$g\f$ is the cell input and cell output activation function, usually
+     *   \f$tahn\f$,
+     * * \f$W_{xi}\f$ is the input-to-input weight matrix,
+     * * \f$W_{hi}\f$ is the recurrent to input weight matrix,
+     * * \f$W_{ci}\f$ is the cell-to-input weight matrix,
+     * * \f$b_i\f$ is the input gate bias,
+     * * \f$W_{xf}\f$ is the input-to-forget weight matrix,
+     * * \f$W_{hf}\f$ is the recurrent-to-forget weight matrix,
+     * * \f$W_{cf}\f$ is the cell-to-forget weight matrix,
+     * * \f$b_f\f$ is the forget gate bias,
+     * * \f$W_{xc}\f$ is the input-to-cell weight matrix,
+     * * \f$W_{hc}\f$ is the recurrent-to-cell weight matrix,
+     * * \f$b_c\f$ is the cell bias,
+     * * \f$W_{xo}\f$ is the input-to-output weight matrix,
+     * * \f$W_{ho}\f$ is the recurrent-to-output weight matrix,
+     * * \f$W_{co}\f$ is the cell-to-output weight matrix,
+     * * \f$b_o\f$ is the output gate bias,
+     * * \f$W_{proj}\f$ is the projection weight matrix,
+     * * \f$b_{proj}\f$ is the projection bias,
+     * * \f$t_{cell}\f$ is the threshold for clipping the cell state, and
+     * * \f$t_{proj}\f$ is the threshold for clipping the projected output.
+     * * \f$\odot\f$ is the
+     *   <a href="https://en.wikipedia.org/wiki/Hadamard_product_(matrices)">
+     *   Hadamard product</a> that takes two matrices and produces another
+     *   matrix, each element of which is the product of the corresponding
+     *   elements of the input matrices.
+     *
+     * Since HAL version 1.2 LSTM supports layer normalization.
+     * In case layer normalization is used, the inputs to internal activation
+     * functions (sigmoid and \f$g\f$) are normalized, rescaled and recentered
+     * following an approach from section 3.1 from
+     * https://arxiv.org/pdf/1607.06450.pdf
+     *
+     * The operation has the following independently optional inputs:
+     * * The cell-to-input weights (\f$W_{ci}\f$), cell-to-forget weights
+     *   (\f$W_{cf}\f$) and cell-to-output weights (\f$W_{co}\f$) either all
+     *   have values or neither of them have values (i.e., all set to null). If
+     *   they have values, the peephole optimization is used.
+     * * The input-to-input weights (\f$W_{xi}\f$), recurrent-to-input weights
+     *   (\f$W_{hi}\f$) and input gate bias (\f$b_i\f$) either all have values,
+     *   or none of them have values. If they have no values, coupling of input
+     *   and forget gates (CIFG) is used, in which case the input gate
+     *   (\f$i_t\f$) is calculated using the following equation instead.
+     *   \f{eqnarray*}{
+     *   i_t = 1 - f_t
+     *   \f}
+     *   In case peephole optimization is used and CIFG is not used
+     *   cell-to-input (\f$W_{ci}\f$) weights must be present. Otherwise, the
+     *   cell-to-input weights must have no value.
+     * * The projection weights (\f$W_{proj}\f$) is required only for the
+     *   recurrent projection layer, and should otherwise have no value.
+     * * The projection bias (\f$b_{proj}\f$) may (but not required to) have a
+     *   value if the recurrent projection layer exists, and should otherwise
+     *   have no value.
+     * * (HAL version 1.2 or later) The four layer normalization weights either all have
+     *   values or none of them have values. Additionally, if CIFG is used,
+     *   input layer normalization weights tensor is omitted and the other layer
+     *   normalization weights either all have values or none of them have
+     *   values. Layer normalization is used when the values of all the layer
+     *   normalization weights are present.
+     *
+     * References:
+     *
+     * The default non-peephole non-CIFG implementation is based on:
+     * http://www.bioinf.jku.at/publications/older/2604.pdf
+     * S. Hochreiter and J. Schmidhuber. "Long Short-Term Memory". Neural
+     * Computation, 9(8):1735-1780, 1997.
+     *
+     * The peephole implementation and projection layer is based on:
+     * https://research.google.com/pubs/archive/43905.pdf
+     * Hasim Sak, Andrew Senior, and Francoise Beaufays. "Long short-term memory
+     * recurrent neural network architectures for large scale acoustic
+     * modeling." INTERSPEECH, 2014.
+     * (However, the concept of peephole optimization was introduced in work
+     * prior to this paper.)
+     *
+     * The coupling of input and forget gate (CIFG) is based on:
+     * http://arxiv.org/pdf/1503.04069.pdf
+     * Greff et al. "LSTM: A Search Space Odyssey"
+     *
+     * The layer normalization is based on:
+     * https://arxiv.org/pdf/1607.06450.pdf
+     * Jimmy Ba et al. "Layer Normalization"
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * All input and output tensors must be of the same type.
+     *
+     * Inputs:
+     * * 0: The input (\f$x_t\f$).
+     *      A 2-D tensor of shape [batch_size, input_size], where “batch_size”
+     *      corresponds to the batching dimension, and “input_size” is the size
+     *      of the input.
+     * * 1: The input-to-input weights (\f$W_{xi}\f$). Optional.
+     *      A 2-D tensor of shape [num_units, input_size], where “num_units”
+     *      corresponds to the number of cell units.
+     * * 2: The input-to-forget weights (\f$W_{xf}\f$).
+     *      A 2-D tensor of shape [num_units, input_size].
+     * * 3: The input-to-cell weights (\f$W_{xc}\f$).
+     *      A 2-D tensor of shape [num_units, input_size].
+     * * 4: The input-to-output weights (\f$W_{xo}\f$).
+     *      A 2-D tensor of shape [num_units, input_size].
+     * * 5: The recurrent-to-input weights (\f$W_{hi}\f$). Optional.
+     *      A 2-D tensor of shape [num_units, output_size], where “output_size”
+     *      corresponds to either the number of cell units (i.e., “num_units”),
+     *      or the second dimension of the “projection_weights”, if defined.
+     * * 6: The recurrent-to-forget weights (\f$W_{hf}\f$).
+     *      A 2-D tensor of shape [num_units, output_size].
+     * * 7: The recurrent-to-cell weights (\f$W_{hc}\f$).
+     *      A 2-D tensor of shape [num_units, output_size].
+     * * 8: The recurrent-to-output weights (\f$W_{ho}\f$).
+     *      A 2-D tensor of shape [num_units, output_size].
+     * * 9: The cell-to-input weights (\f$W_{ci}\f$). Optional.
+     *      A 1-D tensor of shape [num_units].
+     * * 10:The cell-to-forget weights (\f$W_{cf}\f$). Optional.
+     *      A 1-D tensor of shape [num_units].
+     * * 11:The cell-to-output weights (\f$W_{co}\f$). Optional.
+     *      A 1-D tensor of shape [num_units].
+     * * 12:The input gate bias (\f$b_i\f$). Optional.
+     *      A 1-D tensor of shape [num_units].
+     * * 13:The forget gate bias (\f$b_f\f$).
+     *      A 1-D tensor of shape [num_units].
+     * * 14:The cell bias (\f$b_c\f$).
+     *      A 1-D tensor of shape [num_units].
+     * * 15:The output gate bias (\f$b_o\f$).
+     *      A 1-D tensor of shape [num_units].
+     * * 16:The projection weights (\f$W_{proj}\f$). Optional.
+     *      A 2-D tensor of shape [output_size, num_units].
+     * * 17:The projection bias (\f$b_{proj}\f$). Optional.
+     *      A 1-D tensor of shape [output_size].
+     * * 18:The output state (in) (\f$h_{t-1}\f$).
+     *      A 2-D tensor of shape [batch_size, output_size].
+     * * 19:The cell state (in) (\f$C_{t-1}\f$).
+     *      A 2-D tensor of shape [batch_size, num_units].
+     * * 20:The activation function (\f$g\f$).
+     *      A value indicating the activation function:
+     *      <ul>
+     *      <li>0: None;
+     *      <li>1: Relu;
+     *      <li>3: Relu6;
+     *      <li>4: Tanh;
+     *      <li>6: Sigmoid.
+     *      </ul>
+     * * 21:The clipping threshold (\f$t_{cell}\f$) for the cell state, such
+     *      that values are bound within [-cell_clip, cell_clip]. If set to 0.0
+     *      then clipping is disabled.
+     *      Until HAL version 1.2 this scalar must be of type {@link
+     *      OperandType::FLOAT32}. Since HAL version 1.2, if all the input
+     *      tensors have type {@link OperandType::TENSOR_FLOAT32}, this
+     *      scalar must be of the type {@link OperandType::FLOAT32},
+     *      otherwise if all the input tensors have the type {@link
+     *      OperandType::TENSOR_FLOAT16}, this scalar must be of type {@link
+     *      OperandType::FLOAT16}.
+     * * 22:The clipping threshold (\f$t_{proj}\f$) for the output from the
+     *      projection layer, such that values are bound within
+     *      [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
+     *      Until HAL version 1.2 this scalar must be of type {@link
+     *      OperandType::FLOAT32}. Since HAL version 1.2, if all the input
+     *      tensors have type {@link OperandType::TENSOR_FLOAT32}, this
+     *      scalar must be of the type {@link OperandType::FLOAT32},
+     *      otherwise if all the input tensors have the type {@link
+     *      OperandType::TENSOR_FLOAT16}, this scalar must be of type {@link
+     *      OperandType::FLOAT16}.
+     * Since HAL version 1.2 there are additional inputs to this op:
+     * * 23:The input layer normalization weights.
+     *      A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+     *      to activation at input gate.
+     * * 24:The forget layer normalization weights.
+     *      A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+     *      to activation at forget gate.
+     * * 25:The cell layer normalization weights.
+     *      A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+     *      to activation at cell gate.
+     * * 26:The output layer normalization weights.
+     *      A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+     *      to activation at output gate.
+     *
+     * Outputs:
+     * * 0: The scratch buffer.
+     *      A 2-D tensor of shape [batch_size, num_units * 3] with CIFG, or
+     *      [batch_size, num_units * 4] without CIFG.
+     * * 1: The output state (out) (\f$h_t\f$).
+     *      A 2-D tensor of shape [batch_size, output_size].
+     * * 2: The cell state (out) (\f$C_t\f$).
+     *      A 2-D tensor of shape [batch_size, num_units].
+     * * 3: The output (\f$o_t\f$).
+     *      A 2-D tensor of shape [batch_size, output_size]. This is effectively
+     *      the same as the current “output state (out)” value.
+     */
+    LSTM = 16,
+    /**
+     * Performs an 2-D max pooling operation.
+     *
+     * The output dimensions are functions of the filter dimensions, stride, and
+     * padding.
+     *
+     * The values in the output tensor are computed as:
+     *
+     *     output[b, i, j, channel] =
+     *         max_{di, dj} (
+     *             input[b, strides[1] * i + di, strides[2] * j + dj, channel]
+     *         )
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Both explicit padding and implicit padding are supported.
+     *
+     * Inputs (explicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the left, in the ‘width’ dimension.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the right, in the ‘width’ dimension.
+     * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the top, in the ‘height’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the bottom, in the ‘height’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the filter
+     *      width.
+     * * 8: An {@link OperandType::INT32} scalar, specifying the filter
+     *      height.
+     * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 10: An optional {@link OperandType::BOOL} scalar, default to false.
+     *       Set to true to specify NCHW data layout for input0 and output0.
+     *       Available since HAL version 1.2.
+     *
+     * Inputs (implicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the implicit
+     *      padding scheme, has to be one of the
+     *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 3: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the filter
+     *      width.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the filter
+     *      height.
+     * * 6: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 7: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, out_height, out_width, depth].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    MAX_POOL_2D = 17,
+    /**
+     * Multiplies two tensors, element-wise.
+     *
+     * Takes two input tensors of identical {@link OperandType} and compatible
+     * dimensions. The output is the product of both input tensors, optionally
+     * modified by an activation function.
+     *
+     * Two dimensions are compatible when:
+     *     1. they are equal, or
+     *     2. one of them is 1
+     *
+     * The size of the resulting output is the maximum size along each dimension
+     * of the input operands. It starts with the trailing dimensions, and works
+     * its way forward.
+     *
+     * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero
+     * dimension is only compatible with 0 or 1. The size of the output
+     * dimension is zero if either of corresponding input dimension is zero.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+     *      as input0.
+     * * 2: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     *      For a {@link OperandType::TENSOR_INT32} tensor,
+     *      the {@link FusedActivationFunc} must be "NONE".
+     *
+     * Outputs:
+     * * 0: The product, a tensor of the same {@link OperandType} as input0.
+     *      For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the following condition must be satisfied:
+     *      output_scale > input1_scale * input2_scale.
+     */
+    MUL = 18,
+    /**
+     * Computes rectified linear activation on the input tensor element-wise.
+     *
+     * The output is calculated using this formula:
+     *
+     *     output = max(0, input)
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4.
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    RELU = 19,
+    /**
+     * Computes rectified linear 1 activation on the input tensor element-wise.
+     *
+     * The output is calculated using this formula:
+     *
+     *     output = min(1.f, max(-1.f, input))
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4.
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
+     *
+     * Outputs:
+     * * 0: The output tensor of the same shape as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    RELU1 = 20,
+    /**
+     * Computes rectified linear 6 activation on the input tensor element-wise.
+     *
+     * The output is calculated using this formula:
+     *
+     *     output = min(6, max(0, input))
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4.
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    RELU6 = 21,
+    /**
+     * Reshapes a tensor.
+     *
+     * Given tensor, this operation returns a tensor that has the same values as
+     * tensor, but with a newly specified shape.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4.
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the tensor to be reshaped.
+     * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}, defining the
+     *      shape of the output tensor. The number of elements implied by shape
+     *      must be the same as the number of elements in the input tensor.
+     *
+     *      If one component of shape is the special value -1, the size of that
+     *      dimension is computed so that the total size remains constant. In
+     *      particular, a shape of [-1] flattens into 1-D. At most one component
+     *      of shape can be -1.
+     *
+     * Outputs:
+     * * 0: The output tensor, of shape specified by the input shape.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    RESHAPE = 22,
+    /**
+     * Resizes images to given size using the bilinear interpretation.
+     *
+     * Resized images must be distorted if their output aspect ratio is not the
+     * same as input aspect ratio. The corner pixels of output may not be the
+     * same as corner pixels of input.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Both resizing by shape and resizing by scale are supported.
+     *
+     * Inputs (resizing by shape):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input.
+     *      Since HAL version 1.2, zero batches is supported for this tensor.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the output
+     *      width of the output tensor.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the output
+     *      height of the output tensor.
+     * * 3: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     * * 4: Align corners. An optional {@link OperandType::BOOL}
+     *      scalar, default to false.  If True, the centers of the 4 corner
+     *      pixels of the input and output tensors are aligned, preserving the
+     *      values at the corner pixels.
+     *      Available since HAL version 1.3.
+     * * 5: Half pixel centers. An optional {@link OperandType::BOOL}
+     *      scalar, default to false. If True, the pixel centers are assumed to
+     *      be at (0.5, 0.5). This is the default behavior of image.resize in
+     *      TF 2.0. If this parameter is True, then align_corners parameter
+     *      must be False.
+     *      Available since HAL version 1.3.
+     *
+     * Inputs (resizing by scale, since HAL version 1.2):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input. Zero batches is supported for this tensor.
+     * * 1: A scalar, specifying width_scale, the scaling factor of the width
+     *      dimension from the input tensor to the output tensor. The output
+     *      width is calculated as new_width = floor(width * width_scale).
+     *      The scalar must be of {@link OperandType::FLOAT16} if input0 is
+     *      of {@link OperandType::TENSOR_FLOAT16} and of
+     *      {@link OperandType::FLOAT32} otherwise.
+     * * 2: A scalar, specifying height_scale, the scaling factor of the height
+     *      dimension from the input tensor to the output tensor. The output
+     *      height is calculated as new_height = floor(height * height_scale).
+     *      The scalar must be of {@link OperandType::FLOAT16} if input0 is
+     *      of {@link OperandType::TENSOR_FLOAT16} and of
+     *      {@link OperandType::FLOAT32} otherwise.
+     * * 3: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     * * 4: Align corners. An optional {@link OperandType::BOOL}
+     *      scalar, default to false.  If True, the centers of the 4 corner
+     *      pixels of the input and output tensors are aligned, preserving the
+     *      values at the corner pixels.
+     *      Available since HAL version 1.3.
+     * * 5: Half pixel centers. An optional {@link OperandType::BOOL}
+     *      scalar, default to false. If True, the pixel centers are assumed to
+     *      be at (0.5, 0.5). This is the default behavior of image.resize in
+     *      TF 2.0. If this parameter is True, then align_corners parameter
+     *      must be False.
+     *      Available since HAL version 1.3.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, new_height, new_width, depth].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    RESIZE_BILINEAR = 23,
+    /**
+     * A basic recurrent neural network layer.
+     *
+     * This layer implements the operation:
+     * outputs = state = activation(inputs * input_weights +
+     *                              state * recurrent_weights + bias)
+     *
+     * Where:
+     * * “input_weights” is a weight matrix that multiplies the inputs;
+     * * “recurrent_weights” is a weight matrix that multiplies the current
+     *    “state” which itself is the output from the previous time step
+     *    computation;
+     * * “bias” is a bias vector (added to each output vector in the batch);
+     * * “activation” is the function passed as the “fused_activation_function”
+     *   argument (if not “NONE”).
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * The input tensors must all be the same type.
+     *
+     * Inputs:
+     * * 0: input.
+     *      A 2-D tensor of shape [batch_size, input_size], where “batch_size”
+     *      corresponds to the batching dimension, and “input_size” is the size
+     *      of the input.
+     * * 1: weights.
+     *      A 2-D tensor of shape [num_units, input_size], where “num_units”
+     *      corresponds to the number of units.
+     * * 2: recurrent_weights.
+     *      A 2-D tensor of shape [num_units, num_units], with columns
+     *      corresponding to the weights from each unit.
+     * * 3: bias.
+     *      A 1-D tensor of shape [num_units].
+     * * 4: hidden state (in).
+     *      A 2-D tensor of shape [batch_size, num_units].
+     * * 5: fused_activation_function.
+     *      An optional {@link FusedActivationFunc} value indicating the
+     *      activation function. If “NONE” is specified then it results in a
+     *      linear activation.
+     *
+     * Outputs:
+     * * 0: hidden state (out).
+     *      A 2-D tensor of shape [batch_size, num_units].
+     *
+     * * 1: output.
+     *      A 2-D tensor of shape [batch_size, num_units]. This is effectively
+     *      the same as the current state value.
+     */
+    RNN = 24,
+    /**
+     * Computes the softmax activation on the input tensor element-wise, per
+     * batch, by normalizing the input vector so the maximum coefficient is
+     * zero.
+     *
+     * The output is calculated using this formula:
+     *
+     *     output[batch, i] =
+     *         exp((input[batch, i] - max(input[batch, :])) * beta) /
+     *         sum_{k}{exp((input[batch, k] - max(input[batch, :])) * beta)}
+     *
+     * For input tensor with rank other than 2, the activation will be applied
+     * independently on each 1-D slice along specified dimension.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4.
+     * Tensors with rank other than 2 or 4 are only supported since HAL version 1.2.
+     *
+     * Inputs:
+     * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
+     * * 1: A scalar, specifying the positive scaling factor for the exponent,
+     *      beta. If input0 is of {@link OperandType::TENSOR_FLOAT32},
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM} or
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, the scalar
+     *      must be of {@link OperandType::FLOAT32}.
+     *      If input0 is of {@link OperandType::TENSOR_FLOAT16}, then the
+     *      scalar must be of {@link OperandType::FLOAT16}.
+     * * 2: An optional {@link OperandType::INT32} scalar, default to -1,
+     *      specifying the dimension the activation would be performed on.
+     *      Negative index is used to specify axis from the end (e.g. -1 for
+     *      the last axis). Must be in the range [-n, n).
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     *      For {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      the scale must be 1.f / 256 and the zeroPoint must be 0.
+     *      For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the scale must be 1.f / 256 and the zeroPoint must be -128.
+     */
+    SOFTMAX = 25,
+    /**
+     * Rearranges blocks of spatial data, into depth.
+     *
+     * More specifically, this op outputs a copy of the input tensor where
+     * values from the height and width dimensions are moved to the depth
+     * dimension. The value block_size indicates the input block size and how
+     * the data is moved.
+     *
+     * Chunks of data of size block_size * block_size from depth are rearranged
+     * into non-overlapping blocks of size block_size x block_size.
+     *
+     * The depth of the output tensor is input_depth * block_size * block_size.
+     * The input tensor's height and width must be divisible by block_size.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Inputs:
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the block_size.
+     *      block_size must be >=1 and block_size must be a divisor of both the
+     *      input height and width.
+     * * 2: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape [batches, height/block_size,
+     *      width/block_size, depth_in*block_size*block_size].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    SPACE_TO_DEPTH = 26,
+    /**
+     * SVDF op is a kind of stateful layer derived from the notion that a
+     * densely connected layer that's processing a sequence of input frames can
+     * be approximated by using a singular value decomposition of each of its
+     * nodes. The implementation is based on:
+     *
+     * https://research.google.com/pubs/archive/43813.pdf
+     *
+     * P. Nakkiran, R. Alvarez, R. Prabhavalkar, C. Parada.
+     * “Compressing Deep Neural Networks using a Rank-Constrained Topology”.
+     * INTERSPEECH, 2015.
+     *
+     * It processes the incoming input using a 2-stage filtering mechanism:
+     * * stage 1 performs filtering on the "features" dimension, whose outputs
+     *   get pushed into a memory of fixed-size memory_size.
+     * * stage 2 performs filtering on the "time" dimension of the memory_size
+     *   memoized outputs of stage 1.
+     *
+     * Specifically, for rank 1, this layer implements the operation:
+     *
+     *     memory = push(conv1d(inputs, weights_feature, feature_dim,
+     *                          "PADDING_VALID"));
+     *     outputs = activation(memory * weights_time + bias);
+     *
+     * Where:
+     * * “weights_feature” is a weights matrix that processes the inputs (by
+     *   convolving the input with every “feature filter”), and whose outputs
+     *   get pushed, stacked in order, into the fixed-size “memory” (the oldest
+     *   entry gets dropped);
+     * * “weights_time” is a weights matrix that processes the “memory” (by a
+     *   batched matrix multiplication on the num_units);
+     * * “bias” is an optional bias vector (added to each output vector in the
+     *   batch); and
+     * * “activation” is the function passed as the “fused_activation_function”
+     *   argument (if not “NONE”).
+     *
+     * Each rank adds a dimension to the weights matrices by means of stacking
+     * the filters.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * All input tensors must be the same type.
+     *
+     * Inputs:
+     * * 0: input.
+     *      A 2-D tensor of shape [batch_size, input_size], where “batch_size”
+     *      corresponds to the batching dimension, and “input_size” is the size
+     *      of the input.
+     * * 1: weights_feature.
+     *      A 2-D tensor of shape [num_units, input_size], where “num_units”
+     *      corresponds to the number of units.
+     * * 2: weights_time.
+     *      A 2-D tensor of shape [num_units, memory_size], where “memory_size”
+     *      corresponds to the fixed-size of the memory.
+     * * 3: bias.
+     *      An optional 1-D tensor of shape [num_units].
+     * * 4: state (in).
+     *      A 2-D tensor of shape [batch_size, (memory_size - 1) * num_units * rank].
+     * * 5: rank.
+     *      The rank of the SVD approximation.
+     * * 6: fused_activation_function.
+     *      An optional {@link FusedActivationFunc} value indicating the
+     *      activation function. If “NONE” is specified then it results in a
+     *      linear activation.
+     *
+     * Outputs:
+     * * 0: state (out).
+     *      A 2-D tensor of the same {@link OperandType} as the inputs, with shape
+     *      [batch_size, (memory_size - 1) * num_units * rank].
+     * * 1: output.
+     *      A 2-D tensor of the same {@link OperandType} as the inputs, with shape
+     *      [batch_size, num_units].
+     */
+    SVDF = 27,
+    /**
+     * Computes hyperbolic tangent of input tensor element-wise.
+     *
+     * The output is calculated using this formula:
+     *
+     *     output = tanh(input)
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4.
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     *      For {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      the scale must be 1.f / 128 and the zeroPoint must be 128.
+     *      For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the scale must be 1.f / 128 and the zeroPoint must be 0.
+     */
+    TANH = 28,
+    /**
+     * BatchToSpace for N-dimensional tensors.
+     *
+     * This operation reshapes the batch dimension (dimension 0) into M + 1
+     * dimensions of shape block_shape + [batch], interleaves these blocks back
+     * into the grid defined by the spatial dimensions [1, ..., M], to obtain a
+     * result with the same rank as the input.
+     *
+     * This is the reverse of SpaceToBatch.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the tensor to be reshaped
+     * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}, the block
+     *      sizes for each spatial dimension of the input tensor. All values
+     *      must be >= 1.
+     * * 2: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since API level 29.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    BATCH_TO_SPACE_ND = 29,
+    /**
+     * Element-wise division of two tensors.
+     *
+     * Takes two input tensors of identical {@link OperandType} and compatible
+     * dimensions. The output is the result of dividing the first input tensor
+     * by the second, optionally modified by an activation function.
+     *
+     * For inputs of {@link OperandType::TENSOR_INT32}, performs
+     * "floor division" ("//" in Python). For example,
+     *     5 // 2 = 2
+     *    -5 // 2 = -3
+     *
+     * Two dimensions are compatible when:
+     *     1. they are equal, or
+     *     2. one of them is 1
+     *
+     * The size of the output is the maximum size along each dimension of the
+     * input operands. It starts with the trailing dimensions, and works its way
+     * forward.
+     *
+     * Example:
+     *     input1.dimension =    {4, 1, 2}
+     *     input2.dimension = {5, 4, 3, 1}
+     *     output.dimension = {5, 4, 3, 2}
+     *
+     * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero
+     * dimension is only compatible with 0 or 1. The size of the output
+     * dimension is zero if either of corresponding input dimension is zero.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the first input.
+     * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+     *      as input0.
+     * * 2: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     *      For a {@link OperandType::TENSOR_INT32} tensor,
+     *      the {@link FusedActivationFunc} must be "NONE".
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     */
+    DIV = 30,
+    /**
+     * Computes the mean of elements across dimensions of a tensor.
+     *
+     * Reduces the input tensor along the given dimensions to reduce. Unless
+     * keep_dims is true, the rank of the tensor is reduced by 1 for each entry
+     * in axis. If keep_dims is true, the reduced dimensions are retained with
+     * length 1.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input.
+     * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+     *      to reduce. Must be in the range
+     *      [-rank(input_tensor), rank(input_tensor)).
+     *
+     *      NOTE: When the operation was introduced, the documentation
+     *      incorrectly stated that if dimensions were empty, the operation
+     *      would reduce across all dimensions. This behavior was never
+     *      implemented.
+     *
+     * * 2: An {@link OperandType::INT32} scalar, keep_dims. If positive,
+     *      retains reduced dimensions with length 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
+     */
+    MEAN = 31,
+    /**
+     * Pads a tensor.
+     *
+     * This operation pads a tensor according to the specified paddings.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *   (full support since HAL version 1.2, see the output section)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the tensor to be padded.
+     * * 1: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings
+     *      for each spatial dimension of the input tensor. The shape of the
+     *      tensor must be {rank(input0), 2}.
+     *      padding[i, 0] specifies the number of elements to be padded in the
+     *      front of dimension i.
+     *      padding[i, 1] specifies the number of elements to be padded after the
+     *      end of dimension i.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0. The
+     *      output tensor has the same rank as input0, and each
+     *      dimension of the output tensor has the same size as the
+     *      corresponding dimension of the input tensor plus the size
+     *      of the padding:
+     *          output0.dimension[i] =
+     *              padding[i, 0] + input0.dimension[i] + padding[i, 1]
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     *
+     *      NOTE: Before HAL version 1.2, the pad value for
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM} is undefined.
+     *      Since HAL version 1.2, the pad value is always the logical zero.
+     */
+    PAD = 32,
+    /**
+     * SpaceToBatch for N-Dimensional tensors.
+     *
+     * This operation divides "spatial" dimensions [1, ..., M] of the input into
+     * a grid of blocks of shape block_shape, and interleaves these blocks with
+     * the "batch" dimension (0) such that in the output, the spatial dimensions
+     * [1, ..., M] correspond to the position within the grid, and the batch
+     * dimension combines both the position within a spatial block and the
+     * original batch position. Prior to division into blocks, the spatial
+     * dimensions of the input are optionally zero padded according to paddings.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *   (full support since HAL version 1.2, see the output section)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     * NCHW is supported since HAL version 1.2.
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the input.
+     * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}, the block
+     *      sizes for each spatial dimension of the input tensor. All values
+     *      must be >= 1.
+     * * 2: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings
+     *      for each spatial dimension of the input tensor. All values must be
+     *      >= 0. The shape of the tensor must be {M, 2}, where M is the number
+     *      of spatial dimensions.
+     *      padding[i, 0] specifies the number of element to be padded in the
+     *      front of dimension i.
+     *      padding[i, 1] specifies the number of element to be padded after the
+     *      end of dimension i.
+     * * 3: An optional {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     *      Available since HAL version 1.2.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     *
+     *      NOTE: Before HAL version 1.2, the pad value for
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM} is undefined.
+     *      Since HAL version 1.2, the pad value is always the logical zero.
+     */
+    SPACE_TO_BATCH_ND = 33,
+    /**
+     * Removes dimensions of size 1 from the shape of a tensor.
+     *
+     * Given a tensor input, this operation returns a tensor of the same
+     * {@link OperandType} with all dimensions of size 1 removed. If you don't
+     * want to remove all size 1 dimensions, you can remove specific size 1
+     * dimensions by specifying the axes (input1).
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor, the tensor to be squeezed.
+     * * 1: An optional 1-D tensor of {@link OperandType::TENSOR_INT32}. The
+     *      dimensions to squeeze. If specified only squeezes the dimensions
+     *      listed. Otherwise, squeezes all dimensions. The dimension index
+     *      starts at 0. An error must be reported if squeezing a dimension that
+     *      is not 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0. Contains the
+     *      same data as input, but has one or more dimensions of size 1
+     *      removed.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     *      If all input dimensions are equal to 1 and are to be squeezed, the
+     *      output shape is [1].
+     */
+    SQUEEZE = 34,
+    /**
+     * Extracts a strided slice of a tensor.
+     *
+     * Roughly speaking, this op extracts a slice of size (end - begin) / stride
+     * from the given input tensor. Starting at the location specified by begin
+     * the slice continues by adding stride to the index until all dimensions
+     * are not less than end. Note that a stride can be negative, which causes a
+     * reverse slice.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the tensor to be sliced.
+     * * 1: begin, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The
+     *      starts of the dimensions of the input tensor to be sliced. The
+     *      length must be of rank(input0).
+     * * 2: end, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The
+     *      ends of the dimensions of the input tensor to be sliced. The length
+     *      must be of rank(input0).
+     * * 3: strides, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The
+     *      strides of the dimensions of the input tensor to be sliced. The
+     *      length must be of rank(input0). The entries must be non-zero.
+     * * 4: begin_mask, an {@link OperandType::INT32} scalar. If the ith bit
+     *      of begin_mask is set, begin[i] is ignored and the fullest possible
+     *      range in that dimension is used instead.
+     * * 5: end_mask, an {@link OperandType::INT32} scalar. If the ith bit of
+     *      end_mask is set, end[i] is ignored and the fullest possible range in
+     *      that dimension is used instead.
+     * * 6: shrink_axis_mask, an {@link OperandType::INT32} scalar. If the
+     *      ith bit of shrink_axis_mask is set, the ith dimension specification
+     *      shrinks the dimensionality by 1, taking on the value at index
+     *      begin[i]. In this case, the ith specification must define a
+     *      slice of size 1, e.g. begin[i] = x, end[i] = x + 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0 and rank (n - k),
+     *      where k is the number of bits set in shrink_axis_mask.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     *      If shrink_axis_mask is true for all input dimensions, the output
+     *      shape is [1].
+     */
+    STRIDED_SLICE = 35,
+    /**
+     * Element-wise subtraction of two tensors.
+     *
+     * Takes two input tensors of identical {@link OperandType} and compatible
+     * dimensions. The output is the result of subtracting the second input
+     * tensor from the first one, optionally modified by an activation function.
+     *
+     * Two dimensions are compatible when:
+     *     1. they are equal, or
+     *     2. one of them is 1
+     *
+     * The size of the output is the maximum size along each dimension of the
+     * input operands. It starts with the trailing dimensions, and works its way
+     * forward.
+     *
+     * Example:
+     *     input1.dimension =    {4, 1, 2}
+     *     input2.dimension = {5, 4, 3, 1}
+     *     output.dimension = {5, 4, 3, 2}
+     *
+     * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero
+     * dimension is only compatible with 0 or 1. The size of the output
+     * dimension is zero if either of corresponding input dimension is zero.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the first input.
+     * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+     *      as input0.
+     * * 2: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     *      For a {@link OperandType::TENSOR_INT32} tensor,
+     *      the {@link FusedActivationFunc} must be "NONE".
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+     */
+    SUB = 36,
+    /**
+     * Transposes the input tensor, permuting the dimensions according to the
+     * perm tensor.
+     *
+     * The returned tensor's dimension i corresponds to the input dimension
+     * perm[i]. If perm is not given, it is set to (n-1...0), where n is the
+     * rank of the input tensor. Hence by default, this operation performs a
+     * regular matrix transpose on 2-D input Tensors.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the tensor to be transposed.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
+     * * 1: An optional 1-D Tensor of {@link OperandType::TENSOR_INT32},
+     *      the permutation of the dimensions of the input tensor.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    TRANSPOSE = 37,
+    /**
+     * Computes the absolute value of a tensor, element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    ABS = 38,
+    /**
+     * Returns the index of the largest element along an axis.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: An n-D tensor specifying the input. Must be non-empty.
+     * * 1: An {@link OperandType::INT32} scalar specifying the axis to
+     *      reduce across. Negative index is used to specify axis from the
+     *      end (e.g. -1 for the last axis). Must be in the range [-n, n).
+     *
+     * Outputs:
+     * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor.
+     *      If input is 1-dimensional, the output shape is [1].
+     */
+    ARGMAX = 39,
+    /**
+     * Returns the index of the smallest element along an axis.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: An n-D tensor specifying the input. Must be non-empty.
+     * * 1: An {@link OperandType::INT32} scalar specifying the axis to
+     *      reduce across. Negative index is used to specify axis from the
+     *      end (e.g. -1 for the last axis). Must be in the range [-n, n).
+     *
+     * Outputs:
+     * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor.
+     *      If input is 1-dimensional, the output shape is [1].
+     */
+    ARGMIN = 40,
+    /**
+     * Transform axis-aligned bounding box proposals using bounding box deltas.
+     *
+     * Given the positions of bounding box proposals and the corresponding
+     * bounding box deltas for each class, return the refined bounding box
+     * regions. The resulting bounding boxes are cliped against the edges of
+     * the image.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT16_ASYMM}
+     *
+     * Inputs:
+     * * 0: A 2-D Tensor of shape [num_rois, 4], specifying the locations of the
+     *      bounding box proposals, each line with format [x1, y1, x2, y2].
+     *      For tensor of type {@link OperandType::TENSOR_QUANT16_ASYMM},
+     *      the zeroPoint must be 0 and the scale must be 0.125. Zero num_rois
+     *      is supported for this tensor.
+     * * 1: A 2-D Tensor of shape [num_rois, num_classes * 4], specifying the
+     *      bounding box delta for each region of interest and each class. The
+     *      bounding box deltas are organized in the following order
+     *      [dx, dy, dw, dh], where dx and dy is the relative correction factor
+     *      for the center position of the bounding box with respect to the width
+     *      and height, dw and dh is the log-scale relative correction factor
+     *      for the width and height. For input0 of type
+     *      {@link OperandType::TENSOR_QUANT16_ASYMM}, this tensor should be
+     *      of {@link OperandType::TENSOR_QUANT8_ASYMM} or
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}. Zero num_rois is
+     *      supported for this tensor.
+     * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+     *      [num_rois], specifying the batch index of each box. Boxes with
+     *      the same batch index are grouped together. Zero num_rois is
+     *      supported for this tensor.
+     * * 3: A 2-D Tensor of shape [batches, 2], specifying the information of
+     *      each image in the batch, each line with format
+     *      [image_height, image_width].
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0, with shape
+     *      [num_rois, num_classes * 4], specifying the coordinates of each
+     *      output bounding box for each class, with format [x1, y1, x2, y2].
+     *      For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the
+     *      scale must be 0.125 and the zero point must be 0.
+     */
+    AXIS_ALIGNED_BBOX_TRANSFORM = 41,
+    /**
+     * A recurrent neural network layer that applies an LSTM cell to a
+     * sequence of inputs in forward and backward directions.
+     *
+     * The op supports cross-linking via an auxiliary input. Regular cell feeds
+     * one input into the two RNN cells in the following way:
+     *
+     *       INPUT  (INPUT_REVERSED)
+     *         |         |
+     *    ---------------------
+     *    | FW_LSTM   BW_LSTM |
+     *    ---------------------
+     *         |         |
+     *      FW_OUT     BW_OUT
+     *
+     * An op with cross-linking takes two inputs and feeds them into the RNN
+     * cells in the following way:
+     *
+     *       AUX_INPUT   (AUX_INPUT_REVERSED)
+     *           |             |
+     *     INPUT | (INPUT_R'D.)|
+     *       |   |       |     |
+     *    -----------------------
+     *    |  \  /        \    / |
+     *    | FW_LSTM     BW_LSTM |
+     *    -----------------------
+     *         |           |
+     *      FW_OUT      BW_OUT
+     *
+     * The cross-linking mode is enabled iff auxiliary input and auxiliary
+     * weights are present. While stacking this op on top of itself, this
+     * allows to connect both forward and backward outputs from previous cell
+     * to the next cell's input.
+     *
+     * Since HAL version 1.3 parallel linking mode is supported. The mode is
+     * enabled if auxiliary input is present but auxiliary weights are omitted.
+     * In this case, the cell feeds inputs into the RNN in the following way:
+     *
+     *       INPUT (AUX_INPUT_REVERSED)
+     *         |         |
+     *    ---------------------
+     *    | FW_LSTM   BW_LSTM |
+     *    ---------------------
+     *         |         |
+     *      FW_OUT     BW_OUT
+     *
+     * While stacking this op on top of itself, this allows to connect both
+     * forward and backward outputs from previous cell to the next cell's
+     * corresponding inputs.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: 3, either time-major or batch-major.
+     *
+     * All input and output tensors must be of the same type.
+     *
+     * Inputs:
+     * * 0: The input.
+     *      A 3-D tensor of shape:
+     *        If time-major: [max_time, batch_size, input_size]
+     *        If batch-major: [batch_size, max_time, input_size]
+     *      where "max_time" is the number of timesteps (sequence length),
+     *      "batch_size" corresponds to the batching dimension, and
+     *      "input_size" is the size of the input.
+     * * 1: The forward input-to-input weights. Optional.
+     *      A 2-D tensor of shape [fw_num_units, input_size], where “fw_num_units”
+     *      corresponds to the number of forward cell units.
+     * * 2: The forward input-to-forget weights.
+     *      A 2-D tensor of shape [fw_num_units, input_size].
+     * * 3: The forward input-to-cell weights.
+     *      A 2-D tensor of shape [fw_num_units, input_size].
+     * * 4: The forward input-to-output weights.
+     *      A 2-D tensor of shape [fw_num_units, input_size].
+     * * 5: The forward recurrent-to-input weights. Optional.
+     *      A 2-D tensor of shape [fw_num_units, fw_output_size], where “fw_output_size”
+     *      corresponds to either the number of cell units (i.e., fw_num_units),
+     *      or the second dimension of the “fw_projection_weights”, if defined.
+     * * 6: The forward recurrent-to-forget weights.
+     *      A 2-D tensor of shape [fw_num_units, fw_output_size].
+     * * 7: The forward recurrent-to-cell weights.
+     *      A 2-D tensor of shape [fw_num_units, fw_output_size].
+     * * 8: The forward recurrent-to-output weights.
+     *      A 2-D tensor of shape [fw_num_units, fw_output_size].
+     * * 9: The forward cell-to-input weights. Optional.
+     *      A 1-D tensor of shape [fw_num_units].
+     * * 10: The forward cell-to-forget weights. Optional.
+     *       A 1-D tensor of shape [fw_num_units].
+     * * 11: The forward cell-to-output weights. Optional.
+     *       A 1-D tensor of shape [fw_num_units].
+     * * 12: The forward input gate bias. Optional.
+     *       A 1-D tensor of shape [fw_num_units].
+     * * 13: The forward forget gate bias.
+     *       A 1-D tensor of shape [fw_num_units].
+     * * 14: The forward cell gate bias.
+     *       A 1-D tensor of shape [fw_num_units].
+     * * 15: The forward output gate bias.
+     *       A 1-D tensor of shape [fw_num_units].
+     * * 16: The forward projection weights. Optional.
+     *       A 2-D tensor of shape [fw_output_size, fw_num_units].
+     * * 17: The forward projection bias. Optional.
+     *       A 1-D tensor of shape [fw_output_size].
+     * * 18: The backward input-to-input weights. Optional.
+     *       A 2-D tensor of shape [bw_num_units, input_size], where “bw_num_units”
+     *       corresponds to the number of backward cell units.
+     * * 19: The backward input-to-forget weights.
+     *       A 2-D tensor of shape [bw_num_units, input_size].
+     * * 20: The backward input-to-cell weights.
+     *       A 2-D tensor of shape [bw_num_units, input_size].
+     * * 21: The backward input-to-output weights.
+     *       A 2-D tensor of shape [bw_num_units, input_size].
+     * * 22: The backward recurrent-to-input weights. Optional.
+     *       A 2-D tensor of shape [bw_num_units, bw_output_size], where “bw_output_size”
+     *       corresponds to either the number of cell units (i.e., “bw_num_units”),
+     *       or the second dimension of the “bw_projection_weights”, if defined.
+     * * 23: The backward recurrent-to-forget weights.
+     *       A 2-D tensor of shape [bw_num_units, bw_output_size].
+     * * 24: The backward recurrent-to-cell weights.
+     *       A 2-D tensor of shape [bw_num_units, bw_output_size].
+     * * 25: The backward recurrent-to-output weights.
+     *       A 2-D tensor of shape [bw_num_units, bw_output_size].
+     * * 26: The backward cell-to-input weights. Optional.
+     *       A 1-D tensor of shape [bw_num_units].
+     * * 27: The backward cell-to-forget weights. Optional.
+     *       A 1-D tensor of shape [bw_num_units].
+     * * 28: The backward cell-to-output weights. Optional.
+     *       A 1-D tensor of shape [bw_num_units].
+     * * 29: The backward input gate bias. Optional.
+     *       A 1-D tensor of shape [bw_num_units].
+     * * 30: The backward forget gate bias.
+     *       A 1-D tensor of shape [bw_num_units].
+     * * 31: The backward cell gate bias.
+     *       A 1-D tensor of shape [bw_num_units].
+     * * 32: The backward output gate bias.
+     *       A 1-D tensor of shape [bw_num_units].
+     * * 33: The backward projection weights. Optional.
+     *       A 2-D tensor of shape [bw_output_size, bw_num_units].
+     * * 34: The backward projection bias. Optional.
+     *       A 1-D tensor of shape [bw_output_size].
+     * * 35: The forward input activation state.
+     *       A 2-D tensor of shape [batch_size, bw_output_size].
+     * * 36: The forward input cell state.
+     *       A 2-D tensor of shape [batch_size, bw_num_units].
+     * * 37: The backward input activation state.
+     *       A 2-D tensor of shape [batch_size, bw_output_size].
+     * * 38: The backward input cell state.
+     *       A 2-D tensor of shape [batch_size, bw_num_units].
+     * * 39: The auxiliary input. Optional.
+     *       A 3-D tensor of shape [max_time, batch_size, aux_input_size],
+     *       where “batch_size” corresponds to the batching dimension, and
+     *       “aux_input_size” is the size of the auxiliary input. Optional. See
+     *       the docs above for the usage modes explanation.
+     * * 40: The forward auxiliary input-to-input weights.
+     *       Optional. See the docs above for the usage modes explanation.
+     *       A 2-D tensor of shape [fw_num_units, aux_input_size].
+     * * 41: The forward auxiliary input-to-forget weights.
+     *       Optional. See the docs above for the usage modes explanation.
+     *       A 2-D tensor of shape [fw_num_units, aux_input_size].
+     * * 42: The forward auxiliary input-to-cell weights.
+     *       Optional. See the docs above for the usage modes explanation.
+     *       A 2-D tensor of shape [fw_num_units, aux_input_size].
+     * * 43: The forward auxiliary input-to-output weights.
+     *       Optional. See the docs above for the usage modes explanation.
+     *       A 2-D tensor of shape [fw_num_units, aux_input_size].
+     * * 44: The backward auxiliary input-to-input weights.
+     *       Optional. See the docs above for the usage modes explanation.
+     *       A 2-D tensor of shape [bw_num_units, aux_input_size].
+     * * 45: The backward auxiliary input-to-forget weights.
+     *       Optional. See the docs above for the usage modes explanation.
+     *       A 2-D tensor of shape [bw_num_units, aux_input_size].
+     * * 46: The backward auxiliary input-to-cell weights.
+     *       Optional. See the docs above for the usage modes explanation.
+     *       A 2-D tensor of shape [bw_num_units, aux_input_size].
+     * * 47: The backward auxiliary input-to-output weights.
+     *       Optional. See the docs above for the usage modes explanation.
+     *       A 2-D tensor of shape [bw_num_units, aux_input_size].
+     * * 48: The activation function.
+     *       A value indicating the activation function:
+     *       <ul>
+     *       <li>0: None;
+     *       <li>1: Relu;
+     *       <li>3: Relu6;
+     *       <li>4: Tanh;
+     *       <li>6: Sigmoid.
+     *       </ul>
+     * * 49: The clipping threshold for the cell state, such
+     *       that values are bound within [-cell_clip, cell_clip]. If set to 0.0
+     *       then clipping is disabled.
+     *       If all the input tensors have type {@link OperandType::TENSOR_FLOAT32},
+     *       this scalar must be of the type {@link OperandType::FLOAT32},
+     *       otherwise if all the input tensors have the type
+     *       {@link OperandType::TENSOR_FLOAT16}, this scalar must be
+     *       of type {@link OperandType::FLOAT16}.
+     * * 50: The clipping threshold for the output from the
+     *       projection layer, such that values are bound within
+     *       [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
+     *       If all the input tensors have type {@link OperandType::TENSOR_FLOAT32},
+     *       this scalar must be of the type {@link OperandType::FLOAT32},
+     *       otherwise if all the input tensors have the type
+     *       {@link OperandType::TENSOR_FLOAT16}, this scalar must be
+     *       of type {@link OperandType::FLOAT16}.
+     * * 51: merge_outputs
+     *       An {@link OperandType::BOOL} scalar specifying if the outputs
+     *       from forward and backward cells should be merged.
+     * * 52: time_major
+     *       An {@link OperandType::BOOL} scalar specifying the shape format
+     *       of input and output tensors.
+     * * 53: The forward input layer normalization weights. Optional.
+     *       A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs
+     *       to activation at input gate.
+     * * 54: The forward forget layer normalization weights. Optional.
+     *       A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs
+     *       to activation at forget gate.
+     * * 55: The forward cell layer normalization weights. Optional.
+     *       A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs
+     *       to activation at cell gate.
+     * * 56: The forward output layer normalization weights. Optional.
+     *       A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs
+     *       to activation at output gate.
+     * * 57: The backward input layer normalization weights. Optional.
+     *       A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs
+     *       to activation at input gate.
+     * * 58: The backward forget layer normalization weights. Optional.
+     *       A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs
+     *       to activation at forget gate.
+     * * 59: The backward cell layer normalization weights. Optional.
+     *       A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs
+     *       to activation at cell gate.
+     * * 60: The backward output layer normalization weights. Optional.
+     *       A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs
+     *       to activation at output gate.
+     *
+     * Outputs:
+     * * 0: The forward output.
+     *      A 3-D tensor of shape:
+     *        If time-major and not merge_outputs:
+     *          [max_time, batch_size, fw_output_size]
+     *        If time-major and merge_outputs:
+     *          [max_time, batch_size, fw_output_size + bw_output_size]
+     *        If batch-major and not merge_outputs:
+     *          [batch_size, max_time, fw_output_size]
+     *        If batch-major and merge_outputs:
+     *          [batch_size, max_time, fw_output_size + bw_output_size]
+     * * 1: The backward output.  Unused if merge_outputs is true.
+     *      A 3-D tensor of shape:
+     *        If time-major: [max_time, batch_size, bw_output_size]
+     *        If batch-major: [batch_size, max_time, bw_output_size]
+     * * 2: The forward activation state output.
+     *      A 2-D tensor of shape [batch_size, fw_output_size] containing an
+     *      activation state from the last time step in the sequence. This
+     *      output is optional and can be omitted. If this output is present
+     *      then outputs 3-5 must be present as well.
+     *      Available since HAL version 1.3.
+     * * 3: The forward cell state output.
+     *      A tensor of shape [batch_size, fw_cell_size] containing a cell state
+     *      from the last time step in the sequence. This output is optional
+     *      and can be omitted. If this output is present
+     *      then outputs 2, 4, 5 must be present as well.
+     *      Available since HAL version 1.3.
+     * * 4: The backward activation state output.
+     *      A 2-D tensor of shape [batch_size, bw_output_size] containing an
+     *      activation state from the last time step in the sequence. This
+     *      output is optional and can be omitted. If this output is present
+     *      then outputs 2, 3, 5 must be present as well.
+     *      Available since HAL version 1.3.
+     * * 5: The backward cell state output.
+     *      A tensor of shape [batch_size, bw_cell_size] containing a cell state
+     *      from the last time step in the sequence. This output is optional
+     *      and can be omitted. If this output is present
+     *      then outputs 2-4 must be present as well.
+     *      Available since HAL version 1.3.
+     */
+    BIDIRECTIONAL_SEQUENCE_LSTM = 42,
+    /**
+     * A recurrent neural network layer that applies a basic RNN cell to a
+     * sequence of inputs in forward and backward directions.
+     *
+     * This Op unrolls the input along the sequence dimension, and implements
+     * the following operation for each element in the sequence s =
+     * 1...sequence_length:
+     *   fw_outputs[s] = fw_state = activation(inputs[s] * fw_input_weights’ +
+     *          fw_state * fw_recurrent_weights’ + fw_bias)
+     *
+     * And for each element in sequence t = sequence_length : 1
+     *   bw_outputs[t] = bw_state = activation(inputs[t] * bw_input_weights’ +
+     *          bw_state * bw_recurrent_weights’ + bw_bias)
+     *
+     * Where:
+     * * “{fw,bw}_input_weights” is a weight matrix that multiplies the inputs;
+     * * “{fw,bw}_recurrent_weights” is a weight matrix that multiplies the
+     *    current “state” which itself is the output from the previous time step
+     *    computation;
+     * * “{fw,bw}_bias” is a bias vector (added to each output vector in the
+     *    batch);
+     * * “activation” is the function passed as the “fused_activation_function”
+     *   argument (if not “NONE”).
+     *
+     * The op supports cross-linking via an auxiliary input. Regular cell feeds
+     * one input into the two RNN cells in the following way:
+     *
+     *       INPUT  (INPUT_REVERSED)
+     *         |         |
+     *    ---------------------
+     *    | FW_RNN     BW_RNN |
+     *    ---------------------
+     *         |         |
+     *      FW_OUT     BW_OUT
+     *
+     * An op with cross-linking takes two inputs and feeds them into the RNN
+     * cells in the following way:
+     *
+     *       AUX_INPUT   (AUX_INPUT_REVERSED)
+     *           |             |
+     *     INPUT | (INPUT_R'D.)|
+     *       |   |       |     |
+     *    -----------------------
+     *    |  \  /        \    / |
+     *    | FW_RNN       BW_RNN |
+     *    -----------------------
+     *         |           |
+     *      FW_OUT      BW_OUT
+     *
+     * The cross-linking mode is enabled iff auxiliary input and auxiliary
+     * weights are present. While stacking this op on top of itself, this
+     * allows to connect both forward and backward outputs from previous cell
+     * to the next cell's input.
+     *
+     * Since HAL version 1.3 parallel linking mode is supported. The mode is
+     * enabled if auxiliary input is present but auxiliary weights are omitted.
+     * In this case, the cell feeds inputs into the RNN in the following way:
+     *
+     *       INPUT (AUX_INPUT_REVERSED)
+     *         |         |
+     *    ---------------------
+     *    | FW_RNN     BW_RNN |
+     *    ---------------------
+     *         |         |
+     *      FW_OUT     BW_OUT
+     *
+     * While stacking this op on top of itself, this allows to connect both
+     * forward and backward outputs from previous cell to the next cell's
+     * corresponding inputs.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * The input tensors must all be the same type.
+     *
+     * Inputs:
+     * * 0: input.
+     *      A 3-D tensor. The shape is defined by the input 6 (timeMajor). If
+     *      it is set to true, then the input has a shape [maxTime, batchSize,
+     *      inputSize], otherwise the input has a shape [batchSize, maxTime,
+     *      inputSize].
+     * * 1: fwWeights.
+     *      A 2-D tensor of shape [fwNumUnits, inputSize].
+     * * 2: fwRecurrentWeights.
+     *      A 2-D tensor of shape [fwNumUnits, fwNumUnits].
+     * * 3: fwBias.
+     *      A 1-D tensor of shape [fwNumUnits].
+     * * 4: fwHiddenState.
+     *      A 2-D tensor of shape [batchSize, fwNumUnits]. Specifies a hidden
+     *      state input for the first time step of the computation.
+     * * 5: bwWeights.
+     *      A 2-D tensor of shape [bwNumUnits, inputSize].
+     * * 6: bwRecurrentWeights.
+     *      A 2-D tensor of shape [bwNumUnits, bwNumUnits].
+     * * 7: bwBias.
+     *      A 1-D tensor of shape [bwNumUnits].
+     * * 8: bwHiddenState
+     *      A 2-D tensor of shape [batchSize, bwNumUnits]. Specifies a hidden
+     *      state input for the first time step of the computation.
+     * * 9: auxInput.
+     *      A 3-D tensor. The shape is defined by the input 6 (timeMajor). If
+     *      it is set to true, then the input has a shape [maxTime, batchSize,
+     *      auxInputSize], otherwise the input has a shape [batchSize, maxTime,
+     *      auxInputSize]. Can be omitted. See the docs above for the usage
+     *      modes explanation.
+     * * 10:fwAuxWeights.
+     *      A 2-D tensor of shape [fwNumUnits, auxInputSize]. Can be omitted.
+     *      See the docs above for the usage modes explanation.
+     * * 11:bwAuxWeights.
+     *      A 2-D tensor of shape [bwNumUnits, auxInputSize]. Can be omitted.
+     *      See the docs above for the usage modes explanation.
+     * * 12:fusedActivationFunction.
+     *      A {@link FusedActivationFunc} value indicating the activation function. If
+     *      “NONE” is specified then it results in a linear activation.
+     * * 13:timeMajor
+     *      An {@link OperandType::BOOL} scalar specifying the shape format
+     *      of input and output tensors.
+     * * 14:mergeOutputs
+     *      An {@link OperandType::BOOL} scalar specifying if the outputs
+     *      from forward and backward cells are separate (if set to false) or
+     *      concatenated (if set to true).
+     * Outputs:
+     * * 0: fwOutput.
+     *      A 3-D tensor. The first two dimensions of the shape are defined by
+     *      the input 6 (timeMajor) and the third dimension is defined by the
+     *      input 14 (mergeOutputs). If timeMajor is set to true, then the first
+     *      two dimensions are [maxTime, batchSize], otherwise they are set to
+     *      [batchSize, maxTime]. If mergeOutputs is set to true, then the third
+     *      dimension is equal to (fwNumUnits + bwNumUnits), otherwise it is set
+     *      to fwNumUnits.
+     * * 1: bwOutput.
+     *      A 3-D tensor. If the input 14 (mergeOutputs) is set to true, then
+     *      this tensor is not produced. The shape is defined by the input 6
+     *      (timeMajor). If it is set to true, then the shape is set to
+     *      [maxTime, batchSize, bwNumUnits], otherwise the shape is set to
+     *      [batchSize, maxTime, bwNumUnits].
+     * * 2: The forward hidden state output.
+     *      A 2-D tensor of shape [batchSize, fwNumUnits] containing a hidden
+     *      state from the last time step in the sequence. This output is
+     *      optional and can be omitted. If this output is present then output
+     *      3 must be present as well.
+     *      Available since HAL version 1.3.
+     * * 3: The backward hidden state output.
+     *      A 2-D tensor of shape [batchSize, bwNumUnits] containing a hidden
+     *      state from the last time step in the sequence. This output is
+     *      optional and can be omitted. If this output is present then output
+     *      2 must be present as well.
+     *      Available since HAL version 1.3.
+     */
+    BIDIRECTIONAL_SEQUENCE_RNN = 43,
+    /**
+     * Greedily selects a subset of bounding boxes in descending order of score.
+     *
+     * This op applies NMS algorithm to each class. In each loop of execution,
+     * the box with maximum score gets selected and removed from the pending set.
+     * The scores of the rest of boxes are lowered according to the
+     * intersection-over-union (IOU) overlapping with the previously selected
+     * boxes and a specified NMS kernel method. Any boxes with score less
+     * than a threshold are removed from the pending set.
+     *
+     * Three NMS kernels are supported:
+     * * Hard:     score_new = score_old * (1 if IoU < threshold else 0)
+     * * Linear:   score_new = score_old * (1 if IoU < threshold else 1 - IoU)
+     * * Gaussian: score_new = score_old * exp(- IoU^2 / sigma)
+     *
+     * Axis-aligned bounding boxes are represented by its upper-left corner
+     * coordinate (x1,y1) and lower-right corner coordinate (x2,y2). A valid
+     * bounding box should satisfy x1 <= x2 and y1 <= y2.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Inputs:
+     * * 0: A 2-D Tensor of shape [num_rois, num_classes], specifying the score
+     *      of each bounding box proposal. The boxes are grouped by batches in the
+     *      first dimension. Zero num_rois is supported for this tensor.
+     * * 1: A 2-D Tensor specifying the bounding boxes of shape
+     *      [num_rois, num_classes * 4], organized in the order [x1, y1, x2, y2].
+     *      The boxes are grouped by batches in the first dimension. The sequential
+     *      order of the boxes corresponds with input0. For input0 of type
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM}, this tensor should be of
+     *      {@link OperandType::TENSOR_QUANT16_ASYMM}, with zeroPoint of 0 and
+     *      scale of 0.125.
+     *      For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM},
+     *      with zeroPoint of -128 and scale of 0.125.
+     *      Zero num_rois is supported for this tensor.
+     * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+     *      [num_rois], specifying the batch index of each box. Boxes with
+     *      the same batch index are grouped together.
+     * * 3: An {@link OperandType::FLOAT32} scalar, score_threshold. Boxes
+     *      with scores lower than the threshold are filtered before sending
+     *      to the NMS algorithm.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the maximum
+     *      number of selected bounding boxes for each image. Set to a negative
+     *      value for unlimited number of output bounding boxes.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the NMS
+     *      kernel method, options are 0:hard, 1:linear, 2:gaussian.
+     * * 6: An {@link OperandType::FLOAT32} scalar, specifying the IoU
+     *      threshold in hard and linear NMS kernel. This field is ignored if
+     *      gaussian kernel is selected.
+     * * 7: An {@link OperandType::FLOAT32} scalar, specifying the sigma in
+     *      gaussian NMS kernel. This field is ignored if gaussian kernel is
+     *      not selected.
+     * * 8: An {@link OperandType::FLOAT32} scalar, nms_score_threshold.
+     *      Boxes with scores lower than the threshold are dropped during the
+     *      score updating phase in soft NMS.
+     *
+     * Outputs:
+     * * 0: A 1-D Tensor of the same {@link OperandType} as input0, with shape
+     *      [num_output_rois], specifying the score of each output box. The boxes
+     *      are grouped by batches, but the sequential order in each batch is not
+     *      guaranteed. For type of {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      guaranteed. For type of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      or {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the scale and zero point must be the same as input0.
+     * * 1: A 2-D Tensor of the same {@link OperandType} as input1, with shape
+     *      [num_output_rois, 4], specifying the coordinates of each
+     *      output bounding box with the same format as input1. The sequential
+     *      order of the boxes corresponds with output0. For type of
+     *      {@link OperandType::TENSOR_QUANT16_ASYMM}, the scale must be
+     *      0.125 and the zero point must be 0.
+     * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+     *      [num_output_rois], specifying the class of each output box. The
+     *      sequential order of the boxes corresponds with output0.
+     * * 3: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+     *      [num_output_rois], specifying the batch index of each box. Boxes
+     *      with the same batch index are grouped together.
+     */
+    BOX_WITH_NMS_LIMIT = 44,
+    /**
+     * Casts a tensor to a type.
+     *
+     * This operation ignores the scale and zeroPoint of quanized tensors,
+     * e.g. it treats a {@link OperandType::TENSOR_QUANT8_ASYMM} input
+     * as a tensor of uint8 values.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * Since HAL version 1.3, casting tensors of the following
+     * {@link OperandType} to the same {@link OperandType} is supported:
+     * * {@link OperandType::TENSOR_BOOL8}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT16_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT16_SYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     * * {@link OperandType::TENSOR_QUANT8_SYMM}
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: A tensor with the same shape as input0.
+     */
+    CAST = 45,
+    /**
+     * Shuffle the channels of the input tensor.
+     *
+     * Given an input tensor and a integer value of num_groups, CHANNEL_SHUFFLE
+     * divide the channel dimension into num_groups groups, and reorganize the
+     * channels by grouping channels with the same index in each group.
+     *
+     * Along the channel dimension, the output is calculated using this formula:
+     *
+     *     output_channel[k * num_groups + g] = input_channel[g * group_size + k]
+     *
+     * where group_size = num_channels / num_groups
+     *
+     * The number of channels must be divisible by num_groups.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the tensor to be shuffled.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the number of
+     *      groups.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the dimension
+     *      channel shuffle would be performed on. Negative index is used to
+     *      specify axis from the end (e.g. -1 for the last axis). Must be in
+     *      the range [-n, n).
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} and same shape as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    CHANNEL_SHUFFLE = 46,
+    /**
+     * Apply postprocessing steps to bounding box detections.
+     *
+     * Bounding box detections are generated by applying transformation on a set
+     * of predefined anchors with the bounding box deltas from bounding box
+     * regression. A final step of hard NMS is applied to limit the number of
+     * returned boxes.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Inputs:
+     * * 0: A 3-D Tensor of shape [batches, num_anchors, num_classes], specifying
+     *      the score of each anchor with each class. Class 0 for each
+     *      [batches, num_anchors, 0] is background and will be ignored.
+     * * 1: A 3-D Tensor of shape [batches, num_anchors, length_box_encoding], with
+     *      the first four values in length_box_encoding specifying the bounding
+     *      box deltas. The box deltas are encoded in the order of [dy, dx, dh, dw],
+     *      where dy and dx is the linear-scale relative correction factor for the
+     *      center position of the bounding box with respect to the width and height,
+     *      dh and dw is the log-scale relative correction factor for the width and
+     *      height. All the entries in length_box_encoding beyond the first four
+     *      values are ignored in this operation.
+     * * 2: A 2-D Tensor of shape [num_anchors, 4], specifying the shape of each
+     *      predefined anchor, with format [ctr_y, ctr_x, h, w], where ctr_y and
+     *      ctr_x are the center position of the box, and h and w are the height
+     *      and the width.
+     * * 3: An {@link OperandType::FLOAT32} scalar, specifying the scaling
+     *      factor for dy in bounding box deltas.
+     * * 4: An {@link OperandType::FLOAT32} scalar, specifying the scaling
+     *      factor for dx in bounding box deltas.
+     * * 5: An {@link OperandType::FLOAT32} scalar, specifying the scaling
+     *      factor for dh in bounding box deltas.
+     * * 6: An {@link OperandType::FLOAT32} scalar, specifying the scaling
+     *      factor for dw in bounding box deltas.
+     * * 7: An {@link OperandType::BOOL} scalar, set to true to use regular
+     *      multi-class NMS algorithm that do NMS separately for each class,
+     *      set to false for a faster algorithm that only do one single NMS
+     *      using the highest class score..
+     * * 8: An {@link OperandType::INT32} scalar, max_num_detections, specifying
+     *      the maximum number of boxes for the output. Boxes with the lowest
+     *      scores are discarded to meet the limit.
+     * * 9: An {@link OperandType::INT32} scalar, only used when input7 is
+     *      set to false, specifying the maximum number of classes per detection.
+     * * 10: An {@link OperandType::INT32} scalar, only used when input7 is
+     *       set to true, specifying the maximum number of detections when
+     *       applying NMS algorithm for each single class.
+     * * 11: A scalar, score_threshold. Boxes with scores lower than the
+     *       threshold are filtered before sending to the NMS algorithm. The
+     *       scalar must be of {@link OperandType::FLOAT16} if input0 is of
+     *       {@link OperandType::TENSOR_FLOAT16} and of
+     *       {@link OperandType::FLOAT32} if input0 is of
+     *       {@link OperandType::TENSOR_FLOAT32}.
+     * * 12: A scalar, specifying the IoU threshold for hard NMS. The scalar
+     *       must be of {@link OperandType::FLOAT16} if input0 is of
+     *       {@link OperandType::TENSOR_FLOAT16} and of
+     *       {@link OperandType::FLOAT32} if input0 is of
+     *       {@link OperandType::TENSOR_FLOAT32}.
+     * * 13: An {@link OperandType::BOOL} scalar, set to true to include
+     *       background class in the list of label map for the output, set
+     *       to false to not include the background. When the background
+     *       class is included, it has label 0 and the output classes start
+     *       at 1 in the label map, otherwise, the output classes start at 0.
+     *
+     * Outputs:
+     * * 0: A 2-D tensor of the same {@link OperandType} as input0, with shape
+     *      [batches, max_num_detections], specifying the score of each output
+     *      detections.
+     * * 1: A 3-D tensor of shape [batches, max_num_detections, 4], specifying the
+     *      coordinates of each output bounding box, with format
+     *      [y1, x1, y2, x2].
+     * * 2: A 2-D {@link OperandType::TENSOR_INT32} tensor, of shape
+     *      [batches, max_num_detections], specifying the class label for each
+     *      output detection.
+     * * 3: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape [batches],
+     *      specifying the number of valid output detections for each batch.
+     */
+    DETECTION_POSTPROCESSING = 47,
+    /**
+     * For input tensors x and y, computes x == y elementwise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * This operation supports broadcasting.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+     *      with input0.
+     *
+     * Outputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     */
+    EQUAL = 48,
+    /**
+     * Computes exponential of x element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    EXP = 49,
+    /**
+     * Inserts a dimension of 1 into a tensor's shape.
+     *
+     * Given a tensor input, this operation inserts a dimension of 1 at the
+     * given dimension index of input's shape. The dimension index starts at
+     * zero; if you specify a negative dimension index, it is counted backward
+     * from the end.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: An n-D tensor.
+     * * 1: An {@link OperandType::INT32} scalar specifying the dimension
+     *      index to expand. Must be in the range [-(n + 1), (n + 1)).
+     *
+     * Outputs:
+     * * 0: An (n + 1)-D tensor with the same {@link OperandType} and data as
+     *      input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    EXPAND_DIMS = 50,
+    /**
+     * Gathers values along an axis.
+     *
+     * Produces an output tensor with shape
+     *     input0.dimension[:axis] + indices.dimension + input0.dimension[axis + 1:]
+     * where:
+     *     # Vector indices (output is rank(input0)).
+     *     output[a_0, ..., a_n, i, b_0, ..., b_n] =
+     *       input0[a_0, ..., a_n, indices[i], b_0, ..., b_n]
+     *
+     *     # Higher rank indices (output is rank(input0) + rank(indices) - 1).
+     *     output[a_0, ..., a_n, i, ..., j, b_0, ... b_n] =
+     *       input0[a_0, ..., a_n, indices[i, ..., j], b_0, ..., b_n]
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: An n-D tensor from which to gather values.
+     * * 1: An {@link OperandType::INT32} scalar specifying the axis.
+     *      Negative index is used to specify axis from the end
+     *      (e.g. -1 for the last axis). Must be in the range [-n, n).
+     * * 2: A k-D tensor {@link OperandType::TENSOR_INT32} of indices.
+     *      The values must be in the bounds of the corresponding dimensions
+     *      of input0.
+     *
+     * Outputs:
+     * * 0: An (n + k - 1)-D tensor with the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    GATHER = 51,
+    /**
+     * Generate aixs-aligned bounding box proposals.
+     *
+     * Bounding box proposals are generated by applying transformation on a set
+     * of predefined anchors with the bounding box deltas from bounding box
+     * regression. A final step of hard NMS is applied to limit the number of
+     * returned boxes.
+     *
+     * Axis-aligned bounding boxes are represented by its upper-left corner
+     * coordinate (x1,y1) and lower-right corner coordinate (x2,y2). A valid
+     * bounding box should satisfy x1 <= x2 and y1 <= y2.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Inputs:
+     * * 0: A 4-D Tensor specifying the score of each anchor at each
+     *      location. With "NHWC" data layout, the tensor shape is
+     *      [batches, height, width, num_anchors]. With "NCHW" data layout,
+     *      the tensor shape is [batches, num_anchors, height, width].
+     * * 1: A 4-D Tensor specifying the bounding box deltas. With "NHWC" data
+     *      layout, the tensor shape is [batches, height, width, num_anchors * 4].
+     *      With "NCHW" data layout, the tensor shape is
+     *      [batches, num_anchors * 4, height, width]. The box deltas are encoded
+     *      in the order of [dx, dy, dw, dh], where dx and dy is the linear-scale
+     *      relative correction factor for the center position of the bounding box
+     *      with respect to the width and height, dw and dh is the log-scale
+     *      relative correction factor for the width and height. The last
+     *      dimensions is the channel dimension.
+     * * 2: A 2-D Tensor of shape [num_anchors, 4], specifying the shape of each
+     *      predefined anchor, with format [x1, y1, x2, y2]. For input0 of type
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM} or
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this tensor should be of
+     *      {@link OperandType::TENSOR_QUANT16_SYMM}, with scale of 0.125.
+     * * 3: A 2-D Tensor of shape [batches, 2], specifying the size of
+     *      each image in the batch, with format [image_height, image_width].
+     *      For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} or
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this
+     *      tensor should be of {@link OperandType::TENSOR_QUANT16_SYMM}, with
+     *      scale of 0.125.
+     * * 4: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+     *      from the height of original image to the height of feature map.
+     * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+     *      from the width of original image to the width of feature map.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the maximum
+     *      number of boxes before going into the hard NMS algorithm. Boxes
+     *      with the lowest scores are discarded to meet the limit. Set to
+     *      a non-positive value for unlimited number.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the maximum
+     *      number of boxes returning from the hard NMS algorithm. Boxes
+     *      with the lowest scores are discarded to meet the limit. Set to
+     *      a non-positive value for unlimited number.
+     * * 8: An {@link OperandType::FLOAT32} scalar, specifying the IoU
+     *      threshold for hard NMS.
+     * * 9: An {@link OperandType::FLOAT32} scalar, min_size. Boxes with
+     *      height or width lower than the absolute threshold are filtered out.
+     * * 10: An {@link OperandType::BOOL} scalar, set to true to specify
+     *       NCHW data layout for input0 and input1. Set to false for NHWC.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0, of shape
+     *      [num_output_rois], specifying the score of each output box.
+     *      The boxes are grouped by batches, but the sequential order in
+     *      each batch is not guaranteed. For type of
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM} or
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, the scale and zero
+     *      point must be the same as input0.
+     * * 1: A tensor of the same {@link OperandType} as input3, of shape
+     *      [num_output_rois, 4], specifying the coordinates of each output
+     *      bounding box for each class, with format [x1, y1, x2, y2].
+     *      The sequential order of the boxes corresponds with output0.
+     *      For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the
+     *      scale must be 0.125 and the zero point must be 0.
+     * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+     *      [num_output_rois], specifying the batch index of each box. Boxes
+     *      with the same batch index are grouped together.
+     */
+    GENERATE_PROPOSALS = 52,
+    /**
+     * For input tensors x and y, computes x > y elementwise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * This operation supports broadcasting.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+     *      with input0.
+     *
+     * Outputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     */
+    GREATER = 53,
+    /**
+     * For input tensors x and y, computes x >= y elementwise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * This operation supports broadcasting.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+     *      with input0.
+     *
+     * Outputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     */
+    GREATER_EQUAL = 54,
+    /**
+     * Performs a grouped 2-D convolution operation.
+     *
+     * Given an input tensor of shape [batches, height, width, depth_in] and a
+     * filter tensor of shape [depth_out, filter_height, filter_width, depth_group]
+     * containing depth_out convolutional filters of depth depth_group, GROUPED_CONV
+     * applies a group of different filters to each input channel group, then
+     * concatenates the results together.
+     *
+     * Specifically, the input channels are divided into num_groups groups, each with
+     * depth depth_group, i.e. depth_in = num_groups * depth_group. The convolutional
+     * filters are also divided into num_groups groups, i.e. depth_out is divisible
+     * by num_groups. GROUPED_CONV applies each group of filters to the corresponding
+     * input channel group, and the result are concatenated together.
+     *
+     * The output dimensions are functions of the filter dimensions, stride, and
+     * padding.
+     *
+     * The values in the output tensor are computed as:
+     *
+     *     output[b, i, j, g * channel_multiplier + q] =
+     *         sum_{di, dj, dk} (
+     *             input[b, strides[1] * i + di, strides[2] * j + dj,
+     *                   g * depth_group + dk] *
+     *             filter[g * channel_multiplier + q, di, dj, dk]
+     *         ) + bias[channel]
+     *
+     * where channel_multiplier = depth_out / num_groups
+     *
+     * Supported tensor {@link OperandType} configurations:
+     * * 16 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
+     *
+     * * 32 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
+     *
+     * * Quantized:
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output.
+     * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+     * * * input.scale * filter.scale).
+     *
+     * * Quantized signed (since HAL version 1.3):
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output.
+     * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+     * * * input.scale * filter.scale).
+     *
+     * * Quantized with symmetric per channel quantization for the filter:
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output.
+     * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+     * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+     * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+     *
+     * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3):
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output.
+     * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+     * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+     * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     *
+     * Both explicit padding and implicit padding are supported.
+     *
+     * Inputs (explicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input, where depth_in = num_groups * depth_group.
+     * * 1: A 4-D tensor, of shape
+     *      [depth_out, filter_height, filter_width, depth_group], specifying
+     *      the filter, where depth_out must be divisible by num_groups.  For
+     *      tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+     *      the channel dimension (channelDim at
+     *      {@link SymmPerChannelQuantParams}) must be set to 0.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32} or
+     *      {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same type.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+     *      of 0 and bias_scale == input_scale * filter_scale. For filter tensor
+     *      of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias
+     *      should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of
+     *      0 and bias_scale of 0. The actual scale of each value 'i' is equal to
+     *      bias_scale[i] = input_scale * filter_scale[i].
+     * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the left, in the ‘width’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the right, in the ‘width’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the top, in the ‘height’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the bottom, in the ‘height’ dimension.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 8: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 9: An {@link OperandType::INT32} scalar, specifying the number of
+     *      groups.
+     * * 10: An {@link OperandType::INT32} scalar, and has to be one of the
+     *       {@link FusedActivationFunc} values. Specifies the activation to
+     *       invoke on the result.
+     * * 11: An {@link OperandType::BOOL} scalar, set to true to specify
+     *       NCHW data layout for input0 and output0. Set to false for NHWC.
+     *
+     * Inputs (implicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input, where depth_in = num_groups * depth_group.
+     * * 1: A 4-D tensor, of shape
+     *      [depth_out, filter_height, filter_width, depth_group], specifying
+     *      the filter, where depth_out must be divisible by num_groups.  For
+     *      tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+     *      the channel dimension (SymmPerChannelQuantParams::channelDim)
+     *      must be set to 0.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32} or
+     *      {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same
+     *      {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same type.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *      the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+     *      of 0 and bias_scale == input_scale * filter_scale. For filter tensor
+     *      of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias
+     *      should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of
+     *      0 and bias_scale of 0. The actual scale of each value 'i' is equal to
+     *      bias_scale[i] = input_scale * filter_scale[i].
+     * * 3: An {@link OperandType::INT32} scalar, specifying the implicit
+     *      padding scheme, has to be one of the
+     *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the number of
+     *      groups.
+     * * 7: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 8: An {@link OperandType::BOOL} scalar, set to true to specify
+     *      NCHW data layout for input0 and output0. Set to false for NHWC.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, out_height, out_width, depth_out].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+     */
+    GROUPED_CONV_2D = 55,
+    /**
+     * Localize the maximum keypoints from heatmaps.
+     *
+     * This operation approximates the accurate maximum keypoint scores and
+     * indices after bicubic upscaling by using Taylor expansion up to the
+     * quadratic term.
+     *
+     * The bounding box is represented by its upper-left corner coordinate
+     * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image.
+     * A valid bounding box should satisfy x1 <= x2 and y1 <= y2.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     *
+     * Inputs:
+     * * 0: A 4-D Tensor of shape
+     *      [num_boxes, heatmap_size, heatmap_size, num_keypoints],
+     *      specifying the heatmaps, the height and width of heatmaps should
+     *      be the same, and must be greater than or equal to 2.
+     * * 1: A 2-D Tensor of shape [num_boxes, 4], specifying the bounding boxes,
+     *      each with format [x1, y1, x2, y2]. For input0 of type
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM}, this tensor should
+     *      be of {@link OperandType::TENSOR_QUANT16_ASYMM}, with zeroPoint
+     *      of 0 and scale of 0.125.
+     *      For input0 of type
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this tensor
+     *      should be of {@link OperandType::TENSOR_QUANT16_ASYMM}, with
+     *      zeroPoint of -128 and scale of 0.125.
+     * * 2: An {@link OperandType::BOOL} scalar, set to true to specify
+     *      NCHW data layout for input0. Set to false for NHWC.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0, with shape
+     *      [num_boxes, num_keypoints], specifying score of the keypoints.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} or
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint can be different from input0 scale and zeroPoint.
+     * * 1: A tensor of the same {@link OperandType} as input1, with shape
+     *      [num_boxes, num_keypoints, 2], specifying the location of
+     *      the keypoints, the second dimension is organized as
+     *      [keypoint_x, keypoint_y].
+     *      For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the
+     *      scale must be 0.125 and the zero point must be 0.
+     */
+    HEATMAP_MAX_KEYPOINT = 56,
+    /**
+     * Applies instance normalization to the input tensor.
+     *
+     * The values in the output tensor are computed as:
+     *
+     *     output[b, h, w, c] =
+     *         (input[b, h, w, c] - mean[b, c]) * gamma /
+     *         sqrt(var[b, c] + epsilon) + beta
+     *
+     * Where the mean and variance are computed across the spatial dimensions:
+     *
+     *     mean[b, c] =
+     *         sum_{h, w}(input[b, h, w, c]) / sum(1)
+     *
+     *     var[b, c] =
+     *         sum_{h, w}(pow(input[b, h, w, c] - mean[b, c], 2)) / sum(1)
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the tensor to be normalized.
+     * * 1: A scalar, specifying gamma, the scale applied to the normalized
+     *      tensor. The scalar must be of {@link OperandType::FLOAT16} if
+     *      input0 is of {@link OperandType::TENSOR_FLOAT16} and of
+     *      {@link OperandType::FLOAT32} if input0 is of
+     *      {@link OperandType::TENSOR_FLOAT32}.
+     * * 2: A scalar, specifying beta, the offset applied to the normalized
+     *      tensor. The scalar must be of {@link OperandType::FLOAT16} if
+     *      input0 is of {@link OperandType::TENSOR_FLOAT16} and of
+     *      {@link OperandType::FLOAT32} if input0 is of
+     *      {@link OperandType::TENSOR_FLOAT32}.
+     * * 3: A scalar, specifying epsilon, the small value added to variance to
+     *      avoid dividing by zero. The scalar must be of {@link OperandType::FLOAT16} if
+     *      input0 is of {@link OperandType::TENSOR_FLOAT16} and of
+     *      {@link OperandType::FLOAT32} if input0 is of
+     *      {@link OperandType::TENSOR_FLOAT32}.
+     * * 4: An {@link OperandType::BOOL} scalar, set to true to specify
+     *      NCHW data layout for input0 and output0. Set to false for NHWC.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} and same shape as input0.
+     */
+    INSTANCE_NORMALIZATION = 57,
+    /**
+     * For input tensors x and y, computes x < y elementwise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * This operation supports broadcasting.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+     *      with input0.
+     *
+     * Outputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     */
+    LESS = 58,
+    /**
+     * For input tensors x and y, computes x <= y elementwise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * This operation supports broadcasting.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+     *      with input0.
+     *
+     * Outputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     */
+    LESS_EQUAL = 59,
+    /**
+     * Computes natural logarithm of x element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    LOG = 60,
+    /**
+     * Returns the truth value of x AND y element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     *
+     * Supported tensor rank: from 1
+     *
+     * This operation supports broadcasting.
+     *
+     * Inputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     * * 1: A tensor of {@link OperandType::TENSOR_BOOL8} and dimensions
+     *      compatible with input0.
+     *
+     * Outputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     */
+    LOGICAL_AND = 61,
+    /**
+     * Computes the truth value of NOT x element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    LOGICAL_NOT = 62,
+    /**
+     * Returns the truth value of x OR y element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     *
+     * Supported tensor rank: from 1
+     *
+     * This operation supports broadcasting.
+     *
+     * Inputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     * * 1: A tensor of {@link OperandType::TENSOR_BOOL8} and dimensions
+     *      compatible with input0.
+     *
+     * Outputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     */
+    LOGICAL_OR = 63,
+    /**
+     * Computes the log softmax activations given logits.
+     *
+     * The output is calculated using this formula:
+     *
+     *     output = logits * beta - log(reduce_sum(exp(logits * beta), axis))
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor specifying the input logits.
+     * * 1: A scalar, specifying the positive scaling factor for the exponent,
+     *      beta.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta
+     *      value must be of {@link OperandType::FLOAT16}.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta
+     *      value must be of {@link OperandType::FLOAT32}.
+     * * 2: An {@link OperandType::INT32} scalar specifying the axis to
+     *      reduce across. Negative index is used to specify axis from the
+     *      end (e.g. -1 for the last axis). Must be in the range [-n, n).
+     *
+     * Outputs:
+     * * 0: The output tensor of the same {@link OperandType} and shape as
+     *      input0.
+     */
+    LOG_SOFTMAX = 64,
+    /**
+     * Returns the element-wise maximum of two tensors.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType} and compatible dimensions
+     *      with input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scales and zeroPoint can be different from input0 scale and zeroPoint.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+     */
+    MAXIMUM = 65,
+    /**
+     * Returns the element-wise minimum of two tensors.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType} and compatible dimensions
+     *      with input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scales and zeroPoint can be different from input0 scale and zeroPoint.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+     */
+    MINIMUM = 66,
+    /**
+     * Computes numerical negative value element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    NEG = 67,
+    /**
+     * For input tensors x and y, computes x != y elementwise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * This operation supports broadcasting.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+     *      with input0.
+     *
+     * Outputs:
+     * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+     */
+    NOT_EQUAL = 68,
+    /**
+     * Pads a tensor with the given constant value according to the specified
+     * paddings.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor, specifying the tensor to be padded.
+     * * 1: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings
+     *      for each spatial dimension of the input tensor. The shape of the
+     *      tensor must be {rank(input0), 2}.
+     *      padding[i, 0] specifies the number of elements to be padded in the
+     *      front of dimension i.
+     *      padding[i, 1] specifies the number of elements to be padded after
+     *      the end of dimension i.
+     * * 2: An scalar specifying the value to use for padding input0.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT16}, the
+     *      pad value must be of {@link OperandType::FLOAT16}.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT32}, the
+     *      pad value must be of {@link OperandType::FLOAT32}.
+     *      For input tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the pad value must be of {@link OperandType::INT32}. The
+     *      scale and zeroPoint are assumed to be the same as in input0.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0. The
+     *      output tensor has the same rank as input0, and each
+     *      dimension of the output tensor has the same size as the
+     *      corresponding dimension of the input tensor plus the size
+     *      of the padding:
+     *          output0.dimension[i] =
+     *              padding[i, 0] + input0.dimension[i] + padding[i, 1]
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    PAD_V2 = 69,
+    /**
+     * Computes the power of one value to another.
+     *
+     * Given a tensor base and a tensor exponent, this operation computes
+     * base^exponent elementwise.
+     *
+     * This operations supports broadcasting. The size of the output is the
+     * maximum size along each dimension of the input operands. It starts with
+     * the trailing dimensions, and works its way forward.
+     *
+     * For example:
+     *     base.dimension     =    {4, 1, 2}
+     *     exponent.dimension = {5, 4, 3, 1}
+     *     output.dimension   = {5, 4, 3, 2}
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: A tensor specifying the base.
+     * * 1: A tensor specifying the exponent.
+     *
+     * Outputs:
+     * * 0: An output tensor.
+     */
+    POW = 70,
+    /**
+     * Parametric Rectified Linear Unit.
+     *
+     * It follows: f(x) = alpha * x for x < 0, f(x) = x for x >= 0, where alpha
+     * is a learned array with the same {@link OperandType} and compatible
+     * dimensions as input x.
+     *
+     * Two dimensions are compatible when:
+     *     1. they are equal, or
+     *     2. one of them is 1
+     *
+     * The size of the output is the maximum size along each dimension of the
+     * input operands. It starts with the trailing dimensions, and works its way
+     * forward.
+     *
+     * Example:
+     *     input.dimension  =    {4, 1, 2}
+     *     alpha.dimension  = {5, 4, 3, 1}
+     *     output.dimension = {5, 4, 3, 2}
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input.
+     * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+     *      as input0, specifying the alpha.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scales and zeroPoint can be different from input0 scale and zeroPoint.
+     */
+    PRELU = 71,
+    /**
+     * Quantizes the input tensor.
+     *
+     * The formula for {@link OperandType::TENSOR_QUANT8_ASYMM} output tensor is:
+     *
+     *     output = max(0, min(255, round(input / scale) + zeroPoint)
+     *
+     * The formula for {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} output
+     * tensor is:
+     *
+     *     output = max(-128, min(127, round(input / scale) + zeroPoint)
+     *
+     * Supported input tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported output tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: A tensor, may be zero-sized.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0, but with
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM} or.
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}.
+     */
+    QUANTIZE = 72,
+    /**
+     * A version of quantized LSTM, using 16 bit quantization for internal
+     * state.
+     *
+     * There is no projection layer, so cell state size is equal to the output
+     * size.
+     *
+     * Inputs:
+     * * 0: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [numBatches, inputSize] specifying the input to the LSTM
+     *      cell. Tensor is quantized with a fixed quantization range of
+     *      [-1, 127/128] (scale = 1/128, zeroPoint = 128).
+     * * 1: The input-to-input weights.
+     *      A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [outputSize, inputSize] specifying input-to-input part of
+     *      weights for fully-connected layer inside the LSTM cell.
+     *      Quantization zero point and scale must be the same across all the
+     *      weights.
+     * * 2: The input-to-forget weights.
+     *      A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [outputSize, inputSize] specifying input-to-forget part of
+     *      weights for fully-connected layer inside the LSTM cell.
+     *      Quantization zero point and scale must be the same across all the
+     *      weights.
+     * * 3: The input-to-cell weights.
+     *      A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [outputSize, inputSize] specifying input-to-cell part of
+     *      weights for fully-connected layer inside the LSTM cell.
+     *      Quantization zero point and scale must be the same across all the
+     *      weights.
+     * * 4: The input-to-output weights.
+     *      A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [outputSize, inputSize] specifying input-to-output part of
+     *      weights for fully-connected layer inside the LSTM cell.
+     *      Quantization zero point and scale must be the same across all the
+     *      weights.
+     * * 5: The recurrent-to-input weights.
+     *      A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [outputSize, outputSize] specifying recurrent-to-input part
+     *      of weights for fully-connected layer inside the LSTM cell.
+     *      Quantization zero point and scale must be the same across all the
+     *      weights.
+     * * 6: The recurrent-to-forget weights.
+     *      A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [outputSize, outputSize] specifying recurrent-to-forget
+     *      part of weights for fully-connected layer inside the LSTM cell.
+     *      Quantization zero point and scale must be the same across all the
+     *      weights.
+     * * 7: The recurrent-to-cell weights.
+     *      A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [outputSize, outputSize] specifying recurrent-to-cell part
+     *      of weights for fully-connected layer inside the LSTM cell.
+     *      Quantization zero point and scale must be the same across all the
+     *      weights.
+     * * 8: The recurrent-to-output weights.
+     *      A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [outputSize, outputSize] specifying recurrent-to-output
+     *      part of weights for fully-connected layer inside the LSTM cell.
+     *      Quantization zero point and scale must be the same across all the
+     *      weights.
+     * * 9: The input gate bias.
+     *      A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape
+     *      [outputSize] specifying the bias for the fully-connected layer
+     *      inside the LSTM cell. Bias is quantized with scale being a product
+     *      of input and weights scales and zeroPoint equal to 0.
+     * * 10:The forget gate bias.
+     *      A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape
+     *      [outputSize] specifying the bias for the fully-connected layer
+     *      inside the LSTM cell. Bias is quantized with scale being a product
+     *      of input and weights scales and zeroPoint equal to 0.
+     * * 11:The cell bias.
+     *      A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape
+     *      [outputSize] specifying the bias for the fully-connected layer
+     *      inside the LSTM cell. Bias is quantized with scale being a product
+     *      of input and weights scales and zeroPoint equal to 0.
+     * * 12:The output gate bias.
+     *      A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape
+     *      [outputSize] specifying the bias for the fully-connected layer
+     *      inside the LSTM cell. Bias is quantized with scale being a product
+     *      of input and weights scales and zeroPoint equal to 0.
+     * * 13: A 2-D tensor of type {@link OperandType::TENSOR_QUANT16_SYMM}
+     *       and shape [numBatches, outputSize] specifying the cell state from the
+     *       previous time step of the LSTM cell. It is quantized using a
+     *       quantization range of [-2^4, 2^4 * 32767/32768] (scale = 2^4 /
+     *       32768, zeroPoint = 0).
+     * * 14: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *       and shape [numBathes, outputSize] specifying the output of the LSTM
+     *       cell from previous time-step. Tensor is quantized with a fixed
+     *       quantization range of [-1, 127/128] (scale = 1/128, zeroPoint =
+     *       128).
+     *
+     *
+     * Outputs:
+     * * 0: A 2-D tensor of type {@link OperandType::TENSOR_QUANT16_SYMM}
+     *      and shape [numBatches, outputSize] which contains a cell state from
+     *      the current time step. Tensor is quantized using a quantization
+     *      range of [-2^4, 2^4 * 32767/32768] (scale = 2^4 / 32768, zeroPoint =
+     *      0).
+     * * 1: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and shape [numBathes, outputSize] which contains the output value.
+     *      Tensor is quantized with a fixed quantization range of [-1, 127/128]
+     *      (scale = 1/128, zeroPoint = 128).
+     */
+    QUANTIZED_16BIT_LSTM = 73,
+    /**
+     * Draws samples from a multinomial distribution.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Inputs:
+     * * 0: A 2-D tensor with shape [batches, classes], specifying the
+     *      unnormalized log-probabilities for all classes.
+     * * 1: A scalar {@link OperandType::INT32}, specifying the number of
+     *      independent samples to draw for each row slice.
+     * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor with shape [2],
+     *      specifying seeds used to initialize the random distribution. If both
+     *      provided seeds are 0, both will be randomly generated.
+     * Outputs:
+     * * 0: A 2-D {@link OperandType::TENSOR_INT32} tensor with shape
+     *      [batches, samples], containing the drawn samples.
+     */
+    RANDOM_MULTINOMIAL = 74,
+    /**
+     * Reduces a tensor by computing the "logical and" of elements along given
+     * dimensions.
+     *
+     * If keep_dims is true, the reduced dimensions are
+     * retained with length 1. Otherwise, the rank of the tensor is reduced by
+     * 1 for each entry in dimensions.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor.
+     * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+     *      to reduce. Dimension values must be in the range [-n, n).
+     * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+     *      retains reduced dimensions with length 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
+     */
+    REDUCE_ALL = 75,
+    /**
+     * Reduces a tensor by computing the "logical or" of elements along given
+     * dimensions.
+     *
+     * If keep_dims is true, the reduced dimensions are
+     * retained with length 1. Otherwise, the rank of the tensor is reduced by
+     * 1 for each entry in dimensions.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_BOOL8}
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor.
+     * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+     *      to reduce. Dimension values must be in the range [-n, n).
+     * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+     *      retains reduced dimensions with length 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
+     */
+    REDUCE_ANY = 76,
+    /**
+     * Reduces a tensor by computing the maximum of elements along given
+     * dimensions.
+     *
+     * If keep_dims is true, the reduced dimensions are
+     * retained with length 1. Otherwise, the rank of the tensor is reduced by
+     * 1 for each entry in dimensions.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor.
+     * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+     *      to reduce. Dimension values must be in the range [-n, n).
+     * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+     *      retains reduced dimensions with length 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    REDUCE_MAX = 77,
+    /**
+     * Reduces a tensor by computing the minimum of elements along given
+     * dimensions.
+     *
+     * If keep_dims is true, the reduced dimensions are
+     * retained with length 1. Otherwise, the rank of the tensor is reduced by
+     * 1 for each entry in dimensions.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor.
+     * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+     *      to reduce. Dimension values must be in the range [-n, n).
+     * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+     *      retains reduced dimensions with length 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    REDUCE_MIN = 78,
+    /**
+     * Reduces a tensor by multiplying elements along given dimensions.
+     *
+     * If keep_dims is true, the reduced dimensions are
+     * retained with length 1. Otherwise, the rank of the tensor is reduced by
+     * 1 for each entry in dimensions.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor.
+     * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+     *      to reduce. Dimension values must be in the range [-n, n).
+     * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+     *      retains reduced dimensions with length 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
+     */
+    REDUCE_PROD = 79,
+    /**
+     * Reduces a tensor by summing elements along given dimensions.
+     *
+     * If keep_dims is true, the reduced dimensions are
+     * retained with length 1. Otherwise, the rank of the tensor is reduced by
+     * 1 for each entry in dimensions.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: up to 4
+     *
+     * Inputs:
+     * * 0: An n-D tensor.
+     * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+     *      to reduce. Dimension values must be in the range [-n, n).
+     * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+     *      retains reduced dimensions with length 1.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
+     */
+    REDUCE_SUM = 80,
+    /**
+     * Select and scale the feature map of each region of interest to a unified
+     * output size by average pooling sampling points from bilinear interpolation.
+     *
+     * The region of interest is represented by its upper-left corner coordinate
+     * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image.
+     * A spatial scaling factor is applied to map into feature map coordinate.
+     * A valid region of interest should satisfy x1 <= x2 and y1 <= y2.
+     *
+     * No rounding is applied in this operation. The sampling points are unified
+     * distributed in the pooling bin and their values are calculated by bilinear
+     * interpolation.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     *
+     * Inputs:
+     * * 0: A 4-D tensor, specifying the feature map.
+     * * 1: A 2-D Tensor of shape [num_rois, 4], specifying the locations of
+     *      the regions of interest, each line with format [x1, y1, x2, y2].
+     *      For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM},
+     *      with zeroPoint of 0 and scale of 0.125. Zero num_rois is
+     *      supported for this tensor.
+     * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+     *      [num_rois], specifying the batch index of each box. Boxes with
+     *      the same batch index are grouped together. Zero num_rois is
+     *      supported for this tensor.
+     * * 3: An {@link OperandType::INT32} scalar, specifying the output
+     *      height of the output tensor.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the output
+     *      width of the output tensor.
+     * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+     *      from the height of original image to the height of feature map.
+     * * 6: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+     *      from the width of original image to the width of feature map.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the number of
+     *      sampling points in height dimension used to compute the output.
+     *      Set to 0 for adaptive value of ceil(roi_height/out_height).
+     * * 8: An {@link OperandType::INT32} scalar, specifying the number of
+     *      sampling points in width dimension used to compute the output.
+     *      Set to 0 for adaptive value of ceil(roi_width/out_width).
+     * * 9: An {@link OperandType::BOOL} scalar, set to true to specify
+     *      NCHW data layout for input0 and output0. Set to false for NHWC.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0. The output
+     *      shape is [num_rois, out_height, out_width, depth].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint can be different from the input0 scale and zeroPoint.
+     */
+    ROI_ALIGN = 81,
+    /**
+     * Select and scale the feature map of each region of interest to a unified
+     * output size by max-pooling.
+     *
+     * The region of interest is represented by its upper-left corner coordinate
+     * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image.
+     * A spatial scaling factor is applied to map into feature map coordinate.
+     * A valid region of interest should satisfy x1 <= x2 and y1 <= y2.
+     *
+     * Rounding is applied in this operation to ensure integer boundary for
+     * regions of interest and pooling bins.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     *
+     * Inputs:
+     * * 0: A 4-D tensor, specifying the feature map.
+     * * 1: A 2-D Tensor of shape [num_rois, 4], specifying the locations of
+     *      the regions of interest, each line with format [x1, y1, x2, y2].
+     *      For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM},
+     *      with zeroPoint of 0 and scale of 0.125.
+     * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+     *      [num_rois], specifying the batch index of each box. Boxes with
+     *      the same batch index are grouped together.
+     * * 3: An {@link OperandType::INT32} scalar, specifying the output
+     *      height of the output tensor.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the output
+     *      width of the output tensor.
+     * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+     *      from the height of original image to the height of feature map.
+     * * 6: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+     *      from the width of original image to the width of feature map.
+     * * 7: An {@link OperandType::BOOL} scalar, set to true to specify
+     *      NCHW data layout for input0 and output0. Set to false for NHWC.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandType} as input0. The output
+     *      shape is [num_rois, out_height, out_width, depth].
+     *      For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    ROI_POOLING = 82,
+    /**
+     * Computes reciprocal of square root of x element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    RSQRT = 83,
+    /**
+     * Using a tensor of booleans c and input tensors x and y select values
+     * elementwise from both input tensors:
+     *
+     * O[i] = C[i] ? x[i] : y[i].
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: A tensor of type {@link OperandType::TENSOR_BOOL8} acting as a
+     *      mask that chooses, based on the value at each element, whether the
+     *      corresponding element in the output should be taken from input1 (if
+     *      true) or input2 (if false).
+     * * 1: An input tensor of the same shape as input0.
+     * * 2: An input tensor of the same shape and type as input1.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scales and zeroPoint can be different from input1 scale and zeroPoint.
+     *
+     * Outputs:
+     * * 0: A tensor of the same type and shape as input1 and input2.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+     */
+    SELECT = 84,
+    /**
+     * Computes sin of x element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    SIN = 85,
+    /**
+     * Extracts a slice of specified size from the input tensor starting at a
+     * specified location.
+     *
+     * The starting location is specified as a 1-D tensor containing offsets
+     * for each dimension. The size is specified as a 1-D tensor containing
+     * either size of a slice along corresponding dimension or -1. In the latter
+     * case, all the remaining elements in dimension are included in the slice.
+     *
+     * A sum of begin offset and a size of a slice must not exceed size of a
+     * corresponding dimension.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: An n-D tensor to take slice from, may be zero-sized.
+     * * 1: A 1-D tensor of type {@link OperandType::TENSOR_INT32} specifying
+     *      the beginning indices of the slice in each dimension.
+     * * 2: A 1-D tensor of type {@link OperandType::TENSOR_INT32} specifying
+     *      the size of the slice in each dimension.
+     *
+     * Outputs:
+     * * 0: An n-D tensor of the same type as the input containing the slice.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      its scale and zeroPoint has to be same as the input0 scale and zeroPoint.
+     */
+    SLICE = 86,
+    /**
+     * Splits a tensor along a given axis into num_splits subtensors.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: An n-D tensor to split.
+     * * 1: An {@link OperandType::INT32} scalar specifying the axis along
+     *      which to split.
+     * * 2: An {@link OperandType::INT32} scalar indicating the number of
+     *      splits along given axis. Must evenly divide axis size.
+     *
+     * Outputs:
+     * * 0 ~ (num_splits - 1): Resulting subtensors.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    SPLIT = 87,
+    /**
+     * Computes square root of x element-wise.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape as input0.
+     */
+    SQRT = 88,
+    /**
+     * Constructs a tensor by tiling a given tensor.
+     *
+     * This operation creates a new tensor by replicating `input` `multiples`
+     * times. The output tensor's i-th dimension has `input.dims(i) * multiples[i]`
+     * elements, and the values of `input` are replicated `multiples[i]` times
+     * along the i-th dimension.
+     * For example, tiling `[a b c d]` by `[2]` produces `[a b c d a b c d]`.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: input, an n-D tensor specifying the input.
+     * * 1: multiples, a 1-D tensor of {@link OperandType::TENSOR_INT32}.
+     *      The length of multiples must be n.
+     *
+     * Outputs:
+     * * 0: A tiled tensor of the same {@link OperandType} and rank as `input`.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    TILE = 89,
+    /**
+     * Finds values and indices of the k largest entries for the last dimension.
+     *
+     * Resulting values in each dimensions are sorted in descending order. If
+     * two values are equal, the one with larger index appears first.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: from 1
+     *
+     * Inputs:
+     * * 0: input, an n-D tensor specifying the input.
+     * * 1: k, an {@link OperandType::INT32} scalar, specifying the number of
+     *      top elements to look for along the last dimension.
+     *
+     * Outputs:
+     * * 0: An n-D tensor of the same type as the input, containing the k
+     *      largest elements along each last dimensional slice.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     * * 1: An n-D tensor of type {@link OperandType::TENSOR_INT32}
+     *      containing the indices of values within the last dimension of input.
+     */
+    TOPK_V2 = 90,
+    /**
+     * Performs the transpose of 2-D convolution operation.
+     *
+     * This operation is sometimes called "deconvolution" after Deconvolutional
+     * Networks, but is actually the transpose (gradient) of
+     * {@link OperandType::CONV_2D} rather than an actual deconvolution.
+     *
+     * The output dimensions are functions of the filter dimensions, stride, and
+     * padding.
+     *
+     * Supported tensor {@link OperandType} configurations:
+     * * 16 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
+     *
+     * * 32 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
+     *
+     * * Quantized:
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output.
+     * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+     * * * input.scale * filter.scale).
+     *
+     * * Quantized with symmetric per channel quantization for the filter:
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output.
+     * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+     * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+     * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+     *
+     * Available since HAL version 1.3:
+     * * Quantized signed (since HAL version 1.3):
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output.
+     * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+     * * * input.scale * filter.scale).
+     *
+     * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3):
+     * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output.
+     * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+     * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+     * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     *
+     * Both explicit padding and implicit padding are supported.
+     *
+     * Inputs (explicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input.
+     * * 1: A 4-D tensor, of shape
+     *      [depth_out, filter_height, filter_width, depth_in], specifying the
+     *      filter. For tensor of type
+     *      {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel
+     *      dimension (SymmPerChannelQuantParams::channelDim) must be set to 0.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32} or
+     *      {@link OperandType::TENSOR_FLOAT16}, the bias must be of the
+     *      same type.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the bias should be of {@link OperandType::TENSOR_INT32},
+     *      with zeroPoint of 0 and bias_scale == input_scale * filter_scale.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+     *      the bias must be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+     *      and bias_scale of 0. The actual scale of each value 'i' is equal to
+     *      bias_scale[i] = input_scale * filter_scale[i].
+     * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the left, in the ‘width’ dimension.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the right, in the ‘width’ dimension.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the top, in the ‘height’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the padding on
+     *      the bottom, in the ‘height’ dimension.
+     * * 7: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 8: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 10: An {@link OperandType::BOOL} scalar, set to true to specify
+     *       NCHW data layout for input0 and output0. Set to false for NHWC.
+     *
+     * Inputs (implicit padding):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+     *      specifying the input.
+     * * 1: A 4-D tensor, of shape
+     *      [depth_out, filter_height, filter_width, depth_in], specifying the
+     *      filter. For tensor of type
+     *      {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel
+     *      dimension (SymmPerChannelQuantParams::channelDim) must be set to 0.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32} or
+     *      {@link OperandType::TENSOR_FLOAT16}, the bias should be of the
+     *      same type.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *      and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+     *      the bias should be of {@link OperandType::TENSOR_INT32},
+     *      with zeroPoint of 0 and bias_scale == input_scale * filter_scale.
+     *      For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+     *      the bias must be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+     *      and bias_scale of 0. The actual scale of each value 'i' is equal to
+     *      bias_scale[i] = input_scale * filter_scale[i].
+     * * 3: An {@link OperandType::TENSOR_INT32} tensor, specifying the output
+     *      tensor shape.
+     * * 4: An {@link OperandType::INT32} scalar, specifying the implicit
+     *      padding scheme, has to be one of the
+     *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+     * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘width’ dimension.
+     * * 6: An {@link OperandType::INT32} scalar, specifying the stride when
+     *      walking through input in the ‘height’ dimension.
+     * * 7: An {@link OperandType::INT32} scalar, and has to be one of the
+     *      {@link FusedActivationFunc} values. Specifies the activation to
+     *      invoke on the result.
+     * * 8: An {@link OperandType::BOOL} scalar, set to true to specify
+     *      NCHW data layout for input0 and output0. Set to false for NHWC.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, out_height, out_width, depth_out].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+     */
+    TRANSPOSE_CONV_2D = 91,
+    /**
+     * A recurrent neural network specified by an LSTM cell.
+     *
+     * Performs (fully) dynamic unrolling of input.
+     *
+     * This Op unrolls the input along the time dimension, and implements the
+     * following operation for each element in the sequence
+     * s = 1...sequence_length:
+     *   outputs[s] = projection(state = activation(LSTMOp(inputs[s])))
+     *
+     * Where LSTMOp is the LSTM op as in {@link OperandType::LSTM},
+     * the "projection" is an optional projection layer from state and output
+     * and the “activation” is the function passed as the
+     * “fused_activation_function” argument (if not “NONE”).
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: 3, either time-major or batch-major.
+     *
+     * All input and output tensors must be of the same type.
+     *
+     * Inputs:
+     * * 0: The input (\f$x_t\f$).
+     *      A 3-D tensor of shape:
+     *        If time-major: [max_time, batch_size, input_size]
+     *        If batch-major: [batch_size, max_time, input_size]
+     *      where “max_time” is the number of timesteps (sequence length),
+     *      “batch_size” corresponds to the batching dimension, and
+     *      “input_size” is the size of the input.
+     * * 1: The input-to-input weights (\f$W_{xi}\f$). Optional.
+     *      A 2-D tensor of shape [num_units, input_size], where “num_units”
+     *      corresponds to the number of cell units.
+     * * 2: The input-to-forget weights (\f$W_{xf}\f$).
+     *      A 2-D tensor of shape [num_units, input_size].
+     * * 3: The input-to-cell weights (\f$W_{xc}\f$).
+     *      A 2-D tensor of shape [num_units, input_size].
+     * * 4: The input-to-output weights (\f$W_{xo}\f$).
+     *      A 2-D tensor of shape [num_units, input_size].
+     * * 5: The recurrent-to-input weights (\f$W_{hi}\f$). Optional.
+     *      A 2-D tensor of shape [num_units, output_size], where “output_size”
+     *      corresponds to either the number of cell units (i.e., “num_units”),
+     *      or the second dimension of the “projection_weights”, if defined.
+     * * 6: The recurrent-to-forget weights (\f$W_{hf}\f$).
+     *      A 2-D tensor of shape [num_units, output_size].
+     * * 7: The recurrent-to-cell weights (\f$W_{hc}\f$).
+     *      A 2-D tensor of shape [num_units, output_size].
+     * * 8: The recurrent-to-output weights (\f$W_{ho}\f$).
+     *      A 2-D tensor of shape [num_units, output_size].
+     * * 9: The cell-to-input weights (\f$W_{ci}\f$). Optional.
+     *      A 1-D tensor of shape [num_units].
+     * * 10:The cell-to-forget weights (\f$W_{cf}\f$). Optional.
+     *      A 1-D tensor of shape [num_units].
+     * * 11:The cell-to-output weights (\f$W_{co}\f$). Optional.
+     *      A 1-D tensor of shape [num_units].
+     * * 12:The input gate bias (\f$b_i\f$). Optional.
+     *      A 1-D tensor of shape [num_units].
+     * * 13:The forget gate bias (\f$b_f\f$).
+     *      A 1-D tensor of shape [num_units].
+     * * 14:The cell bias (\f$b_c\f$).
+     *      A 1-D tensor of shape [num_units].
+     * * 15:The output gate bias (\f$b_o\f$).
+     *      A 1-D tensor of shape [num_units].
+     * * 16:The projection weights (\f$W_{proj}\f$). Optional.
+     *      A 2-D tensor of shape [output_size, num_units].
+     * * 17:The projection bias (\f$b_{proj}\f$). Optional.
+     *      A 1-D tensor of shape [output_size].
+     * * 18:The output state (in) (\f$h_{t-1}\f$).
+     *      A 2-D tensor of shape [batch_size, output_size].
+     * * 19:The cell state (in) (\f$C_{t-1}\f$).
+     *      A 2-D tensor of shape [batch_size, num_units].
+     * * 20:The activation function (\f$g\f$).
+     *      A value indicating the activation function:
+     *      <ul>
+     *      <li>0: None;
+     *      <li>1: Relu;
+     *      <li>3: Relu6;
+     *      <li>4: Tanh;
+     *      <li>6: Sigmoid.
+     *      </ul>
+     * * 21:The clipping threshold (\f$t_{cell}\f$) for the cell state, such
+     *      that values are bound within [-cell_clip, cell_clip]. If set to 0.0
+     *      then clipping is disabled.
+     * * 22:The clipping threshold (\f$t_{proj}\f$) for the output from the
+     *      projection layer, such that values are bound within
+     *      [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
+     * * 23:Time-major if true, batch-major if false.
+     * * 24:The input layer normalization weights. Optional.
+     *      A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+     *      to activation at input gate.
+     * * 25:The forget layer normalization weights. Optional.
+     *      A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+     *      to activation at forget gate.
+     * * 26:The cell layer normalization weights. Optional.
+     *      A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+     *      to activation at cell gate.
+     * * 27:The output layer normalization weights. Optional.
+     *      A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+     *      to activation at output gate.
+     *
+     * Outputs:
+     * * 0: The output (\f$o_t\f$).
+     *      A 3-D tensor of shape:
+     *        If time-major: [max_time, batch_size, output_size]
+     *        If batch-major: [batch_size, max_time, output_size]
+     * * 1: A tensor of shape [batch_size, output_size] containing a hidden
+     *      state from the last time step in the sequence. This output is
+     *      optional and can be omitted. If this output is present then
+     *      output #2 must be present as well.
+     *      Available since HAL version 1.3.
+     * * 2: A tensor of shape [batch_size, cell_size] containing a cell state
+     *      from the last time step in the sequence. This output is optional
+     *      and can be omitted.
+     *      Available since HAL version 1.3.
+     */
+    UNIDIRECTIONAL_SEQUENCE_LSTM = 92,
+    /**
+     * A recurrent neural network layer that applies a basic RNN cell to a
+     * sequence of inputs.
+     *
+     * This layer unrolls the input along the sequence dimension, and implements
+     * the following operation
+     * for each element in the sequence s = 1...sequence_length:
+     *   outputs[s] = state = activation(inputs[s] * input_weights’ + state *
+     *   recurrent_weights’ + bias)
+     *
+     * Where:
+     * * “input_weights” is a weight matrix that multiplies the inputs;
+     * * “recurrent_weights” is a weight matrix that multiplies the current
+     *    “state” which itself is the output from the previous time step
+     *    computation;
+     * * “bias” is a bias vector (added to each output vector in the batch);
+     * * “activation” is the function passed as the “fused_activation_function”
+     *   argument (if not “NONE”).
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * The input tensors must all be the same type.
+     *
+     * Inputs:
+     * * 0: input.
+     *      A 3-D tensor. The shape is defined by the input 6 (timeMajor). If
+     *      it is set to 1, then the input has a shape [maxTime, batchSize,
+     *      inputSize], otherwise the input has a shape [batchSize, maxTime,
+     *      inputSize].
+     * * 1: weights.
+     *      A 2-D tensor of shape [numUnits, inputSize].
+     * * 2: recurrent_weights.
+     *      A 2-D tensor of shape [numUnits, numUnits].
+     * * 3: bias.
+     *      A 1-D tensor of shape [numUnits].
+     * * 4: hidden state
+     *      A 2-D tensor of shape [batchSize, numUnits]. Specifies a hidden
+     *      state input for the first time step of the computation.
+     * * 5: fusedActivationFunction.
+     *      A {@link FusedActivationFunc} value indicating the activation function. If
+     *      “NONE” is specified then it results in a linear activation.
+     * * 6: timeMajor
+     *      An {@link OperandType::INT32} scalar specifying the shape format
+     *      of input and output tensors. Must be set to either 0 or 1.
+     * Outputs:
+     * * 0: output.
+     *      A 3-D tensor. The shape is defined by the input 6 (timeMajor). If
+     *      it is set to 1, then the output has a shape [maxTime, batchSize,
+     *      numUnits], otherwise the output has a shape [batchSize, maxTime,
+     *      numUnits].
+     * * 1: A tensor of shape [batchSize, numUnits] containing hidden state
+     *      from the last time step in the sequence. This output is optional
+     *      and can be omitted.
+     *      Available since HAL version 1.3.
+     */
+    UNIDIRECTIONAL_SEQUENCE_RNN = 93,
+    /**
+     * Resizes images to given size using the nearest neighbor interpretation.
+     *
+     * Resized images must be distorted if their output aspect ratio is not the
+     * same as input aspect ratio. The corner pixels of output may not be the
+     * same as corner pixels of input.
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     *
+     * Both resizing by shape and resizing by scale are supported.
+     *
+     * Inputs (resizing by shape):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input. Zero batches is supported for this tensor.
+     * * 1: An {@link OperandType::INT32} scalar, specifying the output
+     *      width of the output tensor.
+     * * 2: An {@link OperandType::INT32} scalar, specifying the output
+     *      height of the output tensor.
+     * * 3: An {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     * * 4: Align corners. An optional {@link OperandType::BOOL}
+     *      scalar, default to false.  If True, the centers of the 4 corner
+     *      pixels of the input and output tensors are aligned, preserving the
+     *      values at the corner pixels.
+     *      Available since HAL version 1.3.
+     * * 5: Half pixel centers. An optional {@link OperandType::BOOL}
+     *      scalar, default to false. If True, the pixel centers are assumed to
+     *      be at (0.5, 0.5). This is the default behavior of image.resize in
+     *      TF 2.0. If this parameter is True, then align_corners parameter
+     *      must be False.
+     *      Available since HAL version 1.3.
+     *
+     * Inputs (resizing by scale):
+     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+     *      the input. Zero batches is supported for this tensor.
+     * * 1: A scalar, specifying width_scale, the scaling factor of the width
+     *      dimension from the input tensor to the output tensor. The output
+     *      width is calculated as new_width = floor(width * width_scale).
+     *      The scalar must be of {@link OperandType::FLOAT16} if input0 is
+     *      of {@link OperandType::TENSOR_FLOAT16} and of
+     *      {@link OperandType::FLOAT32} otherwise.
+     * * 2: A scalar, specifying height_scale, the scaling factor of the height
+     *      dimension from the input tensor to the output tensor. The output
+     *      height is calculated as new_height = floor(height * height_scale).
+     *      The scalar must be of {@link OperandType::FLOAT16} if input0 is
+     *      of {@link OperandType::TENSOR_FLOAT16} and of
+     *      {@link OperandType::FLOAT32} otherwise.
+     * * 3: An {@link OperandType::BOOL} scalar, default to false.
+     *      Set to true to specify NCHW data layout for input0 and output0.
+     * * 4: Align corners. An optional {@link OperandType::BOOL}
+     *      scalar, default to false.  If True, the centers of the 4 corner
+     *      pixels of the input and output tensors are aligned, preserving the
+     *      values at the corner pixels.
+     *      Available since HAL version 1.3.
+     * * 5: Half pixel centers. An optional {@link OperandType::BOOL}
+     *      scalar, default to false. If True, the pixel centers are assumed to
+     *      be at (0.5, 0.5). This is the default behavior of image.resize in
+     *      TF 2.0. If this parameter is True, then align_corners parameter
+     *      must be False.
+     *      Available since HAL version 1.3.
+     *
+     * Outputs:
+     * * 0: The output 4-D tensor, of shape
+     *      [batches, new_height, new_width, depth].
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+     *      the scale and zeroPoint must be the same as input0.
+     */
+    RESIZE_NEAREST_NEIGHBOR = 94,
+    /**
+     * Quantized version of {@link OperationType::LSTM}.
+     *
+     * The input and the output use asymmetric quantized types, while the rest
+     * use symmetric ones.
+     *
+     * Inputs:
+     * * 0: The input to the LSTM cell.
+     *      Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *      Shape: [batchSize, inputSize]
+     * * 1: The input-to-input weights. Optional.
+     *      Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *      Shape: [numUnits, inputSize]
+     * * 2: The input-to-forget weights.
+     *      Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *      Shape: [numUnits, inputSize]
+     * * 3: The input-to-cell weights.
+     *      Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *      Shape: [numUnits, inputSize]
+     * * 4: The input-to-output weights.
+     *      Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *      Shape: [numUnits, inputSize]
+     * * 5: The recurrent-to-input weights. Optional.
+     *      Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *      Shape: [numUnits, outputSize]
+     * * 6: The recurrent-to-forget weights.
+     *      Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *      Shape: [numUnits, outputSize]
+     * * 7: The recurrent-to-cell weights.
+     *      Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *      Shape: [numUnits, outputSize]
+     * * 8: The recurrent-to-output weights.
+     *      Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *      Shape: [numUnits, outputSize]
+     * * 9: The cell-to-input weights (for peephole). Optional.
+     *      Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *      Shape: [numUnits]
+     * * 10: The cell-to-forget weights (for peephole). Optional.
+     *       Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *       Shape: [numUnits]
+     * * 11: The cell-to-output weights (for peephole). Optional.
+     *       Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *       Shape: [numUnits]
+     * * 12: The input gate bias. Quantized with scale being the
+     *       product of input and weights scales and zeroPoint equal to 0.
+     *       Optional.
+     *       Type: {@link OperandType::TENSOR_INT32}
+     *       Shape: [numUnits]
+     * * 13: The forget gate bias. Quantized with scale being the
+     *       product of input and weights scales and zeroPoint equal to 0.
+     *       Type: {@link OperandType::TENSOR_INT32}
+     *       Shape: [numUnits]
+     * * 14: The cell bias. Quantized with scale being the
+     *       product of input and weights scales and zeroPoint equal to 0.
+     *       Type: {@link OperandType::TENSOR_INT32}
+     *       Shape: [numUnits]
+     * * 15: The output gate bias. Quantized with scale being the
+     *       product of input and weights scales and zeroPoint equal to 0.
+     *       Type: {@link OperandType::TENSOR_INT32}
+     *       Shape: [numUnits]
+     * * 16: The projection weights. Optional.
+     *       Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+     *       Shape: [outputSize, numUnits]
+     * * 17: The projection bias. Quantized with scale being the
+     *       product of input and weights scales and zeroPoint equal to 0.
+     *       Optional.
+     *       Type: {@link OperandType::TENSOR_INT32}
+     *       Shape: [outputSize]
+     * * 18: The output from the previous time step.
+     *       Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *       Shape: [batchSize, outputSize]
+     * * 19: The cell state from the previous time step.
+     *       Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *       Shape: [batchSize, numUnits]
+     * * 20: The input layer normalization weights. Used to rescale
+     *       normalized inputs to activation at input gate. Optional.
+     *       Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *       Shape: [numUnits]
+     * * 21: The forget layer normalization weights. Used to
+     *       rescale normalized inputs to activation at forget gate. Optional.
+     *       Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *       Shape: [numUnits]
+     * * 22: The cell layer normalization weights. Used to rescale
+     *       normalized inputs to activation at cell gate. Optional.
+     *       Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *       Shape: [numUnits]
+     * * 23: The output layer normalization weights. Used to
+     *       rescale normalized inputs to activation at output gate. Optional.
+     *       Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *       Shape: [numUnits]
+     * * 24: The cell clip. If provided the cell state is clipped
+     *       by this value prior to the cell output activation. Optional.
+     *       Type: {@link OperandType::FLOAT32}.
+     * * 25: The projection clip. If provided and projection is enabled,
+     *       this is used for clipping the projected values. Optional.
+     *       Type: {@link OperandType::FLOAT32}.
+     * * 26: The scale of the intermediate result of matmul,
+     *       i.e. input to layer normalization, at input gate.
+     *       Type: {@link OperandType::FLOAT32}.
+     * * 27: The scale of the intermediate result of matmul,
+     *       i.e. input to layer normalization, at forget gate.
+     *       Type: {@link OperandType::FLOAT32}.
+     * * 28: The scale of the intermediate result of matmul,
+     *       i.e. input to layer normalization, at cell gate.
+     *       Type: {@link OperandType::FLOAT32}.
+     * * 29: The scale of the intermediate result of matmul,
+     *       i.e. input to layer normalization, at output gate.
+     *       Type: {@link OperandType::FLOAT32}.
+     * * 30: The zero point of the hidden state, i.e. input to
+     *       projection.
+     *       Type: {@link OperandType::INT32}.
+     * * 31: The scale of the hidden state, i.e. input to
+     *       projection.
+     *       Type: {@link OperandType::FLOAT32}.
+     *
+     * Outputs:
+     * * 0: The output state (out).
+     *      Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *      Shape: [batchSize, outputSize]
+     * * 1: The cell state (out).
+     *      Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+     *      Shape: [batchSize, numUnits]
+     * * 2: The output. This is effectively the same as the current
+     *      "output state (out)" value.
+     *      Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *      Shape: [batchSize, outputSize]
+     */
+    QUANTIZED_LSTM = 95,
+    /**
+     * Executes one of the two referenced subgraphs as determined by a boolean
+     * value.
+     *
+     * The inputs and outputs of the two referenced subgraphs must agree with the
+     * signature of this operation. That is, if the operation has (3 + n) inputs
+     * and m outputs, both subgraphs must have n inputs and m outputs with the same
+     * types, ranks, dimensions, scales,
+     * zeroPoints, and extraParams as the corresponding operation
+     * inputs and outputs.
+     * All of the operands mentioned must have fully specified dimensions.
+     *
+     * Inputs:
+     * * 0: A value of type {@link OperandType::TENSOR_BOOL8} and shape [1]
+     *      that determines which of the two referenced subgraphs to execute.
+     *      The operand must have fully specified dimensions.
+     * * 1: A {@link OperandType::SUBGRAPH} reference to the subgraph to be
+     *      executed if the condition is true.
+     * * 2: A {@link OperandType::SUBGRAPH} reference to the subgraph to be
+     *      executed if the condition is false.
+     * * 3 ~ (n + 2): Inputs to be passed to the subgraph selected for execution.
+     *
+     * Outputs:
+     * * 0 ~ (m - 1): Outputs produced by the selected subgraph.
+     */
+    IF = 96,
+    /**
+     * Executes the body subgraph until the condition subgraph outputs false.
+     *
+     * The inputs to this operation are the condition subgraph, the body subgraph,
+     * and operand values for the first iteration of the loop. The values are
+     * implicitly split into three groups of input-output, state-only, and
+     * input-only values, as described below.
+     *
+     * The outputs of this operation are the final values of input-output
+     * operands.
+     *
+     * Both the condition and body subgraph receive (m + k + n) inputs.
+     * * The first m (m >= 1) inputs are input-output operands. For the first
+     *   iteration, these are initialized from the corresponding inputs of the
+     *   WHILE operation. In subsequent iterations, their values come from the
+     *   corresponding outputs of the body subgraph produced during the previous
+     *   iteration.
+     * * The next k (k >= 0) inputs are state-only operands. They are similar to
+     *   the input-output operands, except that their values are no longer
+     *   available after the loop terminates.
+     * * The last n (n >= 0) inputs are input-only operands. Their values come
+     *   from the corresponding inputs of the WHILE operation.
+     *
+     * The body subgraph produces (m + k) outputs.
+     * * The first m outputs are input-output operands. They become the outputs
+     *   of the WHILE operation when a termination condition is reached.
+     * * The last k outputs are state-only operands. Their values are no longer
+     *   available after the loop terminates.
+     *
+     * The numbers m, k, and n are inferred by the driver as follows:
+     *     m = (WHILE operation output count)
+     *     k = (body subgraph output count) - m
+     *     n = (body subgraph input count) - m - k
+     *
+     * The pseudo-code below illustrates the flow of a WHILE operation with
+     * inputs condition, body, initial_input_output, initial_state, input_only
+     * (m = 1, k = 1, n = 1):
+     *
+     *     input_output = initial_input_output
+     *     state = initial_state
+     *     while condition(input_output, state, input_only):
+     *         input_output, state = body(input_output, state, input_only)
+     *     return input_output
+     *
+     * Inputs:
+     * * 0: A {@link OperandType::SUBGRAPH} reference to the condition
+     *      subgraph. The subgraph must have (m + k + n) inputs with
+     *      the same types, ranks, dimensions,
+     *      scales, zeroPoints, and extraParams as the
+     *      corresponding inputs of the WHILE operation and exactly one output
+     *      of {@link OperandType::TENSOR_BOOL8} and shape [1].
+     *      All of the operands mentioned must have fully specified dimensions.
+     * * 1: A {@link OperandType::SUBGRAPH} reference to the body subgraph.
+     *      The subgraph must have (m + k + n) inputs and (m + k) outputs with
+     *      the same types, ranks, dimensions,
+     *      scales, zeroPoints, and extraParams as the
+     *      corresponding inputs and outputs of the WHILE operation.
+     *      All of the operands mentioned must have fully specified dimensions.
+     * * (m inputs): Initial values for input-output operands.
+     * * (k inputs): Initial values for state-only operands.
+     * * (n inputs): Values for input-only operands.
+     *
+     * Outputs:
+     * * 0 ~ (m - 1): Outputs produced by the loop.
+     */
+    WHILE = 97,
+    /**
+     * Computes exponential linear activation on the input tensor element-wise.
+     *
+     * The output is calculated using the following formula:
+     *
+     *     ELU(x) = max(0, x) + min(0, alpha * (exp(x) - 1))
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input. May be zero-sized.
+     * * 1: A scalar, specifying the alpha parameter.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT16},
+     *      the alpha value must be of {@link OperandType::FLOAT16}.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT32},
+     *      the alpha value must be of {@link OperandType::FLOAT32}.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape and type as input0.
+     */
+    ELU = 98,
+    /**
+     * Computes hard-swish activation on the input tensor element-wise.
+     *
+     * Hard swish activation is introduced in
+     * https://arxiv.org/pdf/1905.02244.pdf
+     *
+     * The output is calculated using the following formula:
+     *
+     *     h-swish(x) = x * max(0, min(6, (x + 3))) / 6
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A tensor, specifying the input. May be zero-sized.
+     *
+     * Outputs:
+     * * 0: The output tensor of same shape and type as input0.
+     *      Scale and zero point of this tensor may be different from the input
+     *      tensor's parameters.
+     */
+    HARD_SWISH = 99,
+    /**
+     * Creates a tensor filled with a scalar value.
+     *
+     * Supported output tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: A 1-D tensor, specifying the desired output tensor shape.
+     * * 1: A scalar, specifying the value to fill the output tensors with.
+     *      For output tensor of {@link OperandType::TENSOR_FLOAT16},
+     *      the scalar must be of {@link OperandType::FLOAT16}.
+     *      For output tensor of {@link OperandType::TENSOR_FLOAT32},
+     *      the scalar must be of {@link OperandType::FLOAT32}.
+     *      For output tensor of {@link OperandType::TENSOR_INT32},
+     *      the scalar must be of {@link OperandType::INT32}.
+     *
+     * Outputs:
+     * * 0: The output tensor.
+     */
+    FILL = 100,
+    /**
+     * Returns the rank of a tensor.
+     *
+     * The rank of a tensor is the number of dimensions in it. Also known as
+     * "order", "degree", "ndims".
+     *
+     * Supported tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT16}
+     * * {@link OperandType::TENSOR_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT16_SYMM}
+     * * {@link OperandType::TENSOR_BOOL8}
+     * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+     * * {@link OperandType::TENSOR_QUANT16_ASYMM}
+     * * {@link OperandType::TENSOR_QUANT8_SYMM}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+     *
+     * Supported tensor rank: from 1.
+     *
+     * Inputs:
+     * * 0: The input tensor.
+     *
+     * Outputs:
+     * * 0: A scalar of {@link OperandType::INT32}, specifying the rank
+     *      of the input tensor.
+     */
+    RANK = 101,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl
new file mode 100644
index 0000000..f90a613
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Describes the shape information of an output operand after execution.
+ */
+@VintfStability
+parcelable OutputShape {
+    /**
+     * Dimensions of the operand.
+     */
+    int[] dimensions;
+    /**
+     * Whether the provided buffer size is sufficient for the output.
+     */
+    boolean isSufficient;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl
new file mode 100644
index 0000000..6915c67
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Performance information for the reference workload.
+ *
+ * Used by a driver to report its performance characteristics.
+ */
+@VintfStability
+parcelable PerformanceInfo {
+    /**
+     * Ratio of the time taken by the driver to execute the workload compared to the time the CPU
+     * would take for the same workload. A lower number is better.
+     */
+    float execTime;
+    /**
+     * Ratio of the energy used by the driver compared to what the CPU would use for doing the same
+     * workload. A lower number is better.
+     */
+    float powerUsage;
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl
index 127f98e..7dbf8e9 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.neuralnetworks;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
+/**
+ * Priority given to a prepared model for execution.
+ */
+@VintfStability
+@Backing(type="int")
+enum Priority {
+    LOW,
+    MEDIUM,
+    HIGH,
 }
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl
new file mode 100644
index 0000000..dc138ba
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.RequestArgument;
+import android.hardware.neuralnetworks.RequestMemoryPool;
+
+/**
+ * Inputs to be sent to and outputs to be retrieved from a prepared model.
+ *
+ * A Request serves two primary tasks:
+ * 1) Provides the input and output data to be used when executing the model.
+ * 2) Specifies any updates to the input operand metadata that were left unspecified at model
+ *    preparation time.
+ *
+ * An output must not overlap with any other output, with an input, or with an operand of lifetime
+ * CONSTANT_POOL.
+ */
+@VintfStability
+parcelable Request {
+    /**
+     * Input data and information to be used in the execution of a prepared model.
+     *
+     * The index of the input corresponds to the index in Model.main.inputIndexes.
+     *   E.g., input[i] corresponds to Model.main.inputIndexes[i].
+     */
+    RequestArgument[] inputs;
+    /**
+     * Output data and information to be used in the execution of a prepared model.
+     *
+     * The index of the output corresponds to the index in Model.main.outputIndexes.
+     *   E.g., output[i] corresponds to Model.main.outputIndexes[i].
+     */
+    RequestArgument[] outputs;
+    /**
+     * A collection of memory pools containing operand data for both the inputs and the outputs to a
+     * model.
+     */
+    RequestMemoryPool[] pools;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl
new file mode 100644
index 0000000..8dc9252
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.DataLocation;
+
+/**
+ * Metadata information specifying the location of the input or output data and any updates to the
+ * input or output operand.
+ */
+@VintfStability
+parcelable RequestArgument {
+    /**
+     * If true, the argument does not have a value. This can be used for operations that take
+     * optional arguments. If true, the fields of location are set to 0 and the dimensions vector is
+     * left empty.
+     */
+    boolean hasNoValue;
+    /**
+     * The location within one of the memory pools passed in the Request.
+     */
+    DataLocation location;
+    /**
+     * Updated dimension information.
+     *
+     * If dimensions.size() > 0, dimension information was provided along with the argument. This
+     * can be the case for models that accept inputs of varying size. This can't change the rank,
+     * just the value of the dimensions that were unspecified in the model. If dimensions.size() >
+     * 0, then all dimensions must be specified here; and any dimension that was specified in the
+     * model must have the same value here.
+     *
+     * If the dimensions in the model are not fully specified, then they must be fully specified
+     * here, unless hasNoValue is set to true. If the dimensions in the model are fully specified,
+     * then either dimensions.size() may be 0, or the dimensions in the model must be identical to
+     * the dimensions here.
+     */
+    int[] dimensions;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl
new file mode 100644
index 0000000..faca2fe
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.Memory;
+
+/**
+ * A memory pool.
+ */
+@VintfStability
+union RequestMemoryPool {
+    /**
+     * Specifies a client-managed shared memory pool.
+     */
+    Memory pool;
+    /**
+     * Specifies a driver-managed buffer. It is the token returned from IDevice::allocate, and is
+     * specific to the IDevice object.
+     */
+    int token;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl
new file mode 100644
index 0000000..2e9c450
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.Operand;
+import android.hardware.neuralnetworks.Operation;
+
+/**
+ * An excerpt of the execution graph.
+ */
+@VintfStability
+parcelable Subgraph {
+    /**
+     * All operands included in the subgraph.
+     */
+    Operand[] operands;
+    /**
+     * All operations included in the subgraph.
+     *
+     * The operations are sorted into execution order. Every operand with lifetime SUBGRAPH_OUTPUT
+     * or TEMPORARY_VARIABLE must be written before it is read.
+     */
+    Operation[] operations;
+    /**
+     * Input indexes of the subgraph. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    int[] inputIndexes;
+    /**
+     * Output indexes of the subgraph. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    int[] outputIndexes;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl
new file mode 100644
index 0000000..eb47df0
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Parameters for TENSOR_QUANT8_SYMM_PER_CHANNEL operand.
+ */
+@VintfStability
+parcelable SymmPerChannelQuantParams {
+    /**
+     * Array of scaling values for each channel. Each value must be greater than zero.
+     */
+    float[] scales;
+    /**
+     * Index of the channel dimension
+     */
+    int channelDim;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl
new file mode 100644
index 0000000..8130e08
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Timing information measured during execution. Each time is a duration from the beginning of some
+ * task to the end of that task, including time when that task is not active (for example, preempted
+ * by some other task, or waiting for some resource to become available).
+ *
+ * Times are measured in nanoseconds. When a time is not available, it must be reported as -1.
+ */
+@VintfStability
+parcelable Timing {
+    /**
+     * Execution time on device (not driver, which runs on host processor).
+     */
+    long timeOnDevice;
+    /**
+     * Execution time in driver (including time on device).
+     */
+    long timeInDriver;
+}
diff --git a/neuralnetworks/aidl/utils/Android.bp b/neuralnetworks/aidl/utils/Android.bp
new file mode 100644
index 0000000..147d401
--- /dev/null
+++ b/neuralnetworks/aidl/utils/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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.
+//
+
+cc_library_static {
+    name: "neuralnetworks_utils_hal_aidl",
+    defaults: ["neuralnetworks_utils_defaults"],
+    srcs: ["src/*"],
+    local_include_dirs: ["include/nnapi/hal/aidl/"],
+    export_include_dirs: ["include"],
+    static_libs: [
+        "libarect",
+        "neuralnetworks_types",
+        "neuralnetworks_utils_hal_common",
+    ],
+    shared_libs: [
+        "android.hardware.neuralnetworks-V1-ndk_platform",
+        "libbinder_ndk",
+        "libhidlbase",
+        "libnativewindow",
+    ],
+}
diff --git a/neuralnetworks/aidl/utils/OWNERS b/neuralnetworks/aidl/utils/OWNERS
new file mode 100644
index 0000000..e4feee3
--- /dev/null
+++ b/neuralnetworks/aidl/utils/OWNERS
@@ -0,0 +1,11 @@
+# Neuralnetworks team
+butlermichael@google.com
+dgross@google.com
+galarragas@google.com
+jeanluc@google.com
+levp@google.com
+miaowang@google.com
+pszczepaniak@google.com
+slavash@google.com
+vddang@google.com
+xusongw@google.com
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h
new file mode 100644
index 0000000..1b2f69c
--- /dev/null
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h
@@ -0,0 +1,134 @@
+/*
+ * 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_CONVERSIONS_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_CONVERSIONS_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/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/Memory.h>
+#include <aidl/android/hardware/neuralnetworks/Model.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>
+
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/CommonUtils.h>
+
+#include <vector>
+
+namespace android::nn {
+
+GeneralResult<OperandType> unvalidatedConvert(const aidl_hal::OperandType& operandType);
+GeneralResult<OperationType> unvalidatedConvert(const aidl_hal::OperationType& operationType);
+GeneralResult<DeviceType> unvalidatedConvert(const aidl_hal::DeviceType& deviceType);
+GeneralResult<Priority> unvalidatedConvert(const aidl_hal::Priority& priority);
+GeneralResult<Capabilities> unvalidatedConvert(const aidl_hal::Capabilities& capabilities);
+GeneralResult<Capabilities::OperandPerformance> unvalidatedConvert(
+        const aidl_hal::OperandPerformance& operandPerformance);
+GeneralResult<Capabilities::PerformanceInfo> unvalidatedConvert(
+        const aidl_hal::PerformanceInfo& performanceInfo);
+GeneralResult<DataLocation> unvalidatedConvert(const aidl_hal::DataLocation& location);
+GeneralResult<Operand> unvalidatedConvert(const aidl_hal::Operand& operand);
+GeneralResult<Operand::ExtraParams> unvalidatedConvert(
+        const std::optional<aidl_hal::OperandExtraParams>& optionalExtraParams);
+GeneralResult<Operand::LifeTime> unvalidatedConvert(
+        const aidl_hal::OperandLifeTime& operandLifeTime);
+GeneralResult<Operand::SymmPerChannelQuantParams> unvalidatedConvert(
+        const aidl_hal::SymmPerChannelQuantParams& symmPerChannelQuantParams);
+GeneralResult<Operation> unvalidatedConvert(const aidl_hal::Operation& operation);
+GeneralResult<Model> unvalidatedConvert(const aidl_hal::Model& model);
+GeneralResult<Model::ExtensionNameAndPrefix> unvalidatedConvert(
+        const aidl_hal::ExtensionNameAndPrefix& extensionNameAndPrefix);
+GeneralResult<Model::OperandValues> unvalidatedConvert(const std::vector<uint8_t>& operandValues);
+GeneralResult<Model::Subgraph> unvalidatedConvert(const aidl_hal::Subgraph& subgraph);
+GeneralResult<OutputShape> unvalidatedConvert(const aidl_hal::OutputShape& outputShape);
+GeneralResult<MeasureTiming> unvalidatedConvert(bool measureTiming);
+GeneralResult<SharedMemory> unvalidatedConvert(const aidl_hal::Memory& memory);
+GeneralResult<Timing> unvalidatedConvert(const aidl_hal::Timing& timing);
+GeneralResult<BufferDesc> unvalidatedConvert(const aidl_hal::BufferDesc& bufferDesc);
+GeneralResult<BufferRole> unvalidatedConvert(const aidl_hal::BufferRole& bufferRole);
+GeneralResult<Request> unvalidatedConvert(const aidl_hal::Request& request);
+GeneralResult<Request::Argument> unvalidatedConvert(
+        const aidl_hal::RequestArgument& requestArgument);
+GeneralResult<Request::MemoryPool> unvalidatedConvert(
+        const aidl_hal::RequestMemoryPool& memoryPool);
+GeneralResult<ErrorStatus> unvalidatedConvert(const aidl_hal::ErrorStatus& errorStatus);
+GeneralResult<ExecutionPreference> unvalidatedConvert(
+        const aidl_hal::ExecutionPreference& executionPreference);
+GeneralResult<Extension> unvalidatedConvert(const aidl_hal::Extension& extension);
+GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert(
+        const aidl_hal::ExtensionOperandTypeInformation& operandTypeInformation);
+GeneralResult<SharedHandle> unvalidatedConvert(
+        const ::aidl::android::hardware::common::NativeHandle& handle);
+
+GeneralResult<ExecutionPreference> convert(
+        const aidl_hal::ExecutionPreference& executionPreference);
+GeneralResult<SharedMemory> convert(const aidl_hal::Memory& memory);
+GeneralResult<Model> convert(const aidl_hal::Model& model);
+GeneralResult<Operand> convert(const aidl_hal::Operand& operand);
+GeneralResult<OperandType> convert(const aidl_hal::OperandType& operandType);
+GeneralResult<Priority> convert(const aidl_hal::Priority& priority);
+GeneralResult<Request::MemoryPool> convert(const aidl_hal::RequestMemoryPool& memoryPool);
+GeneralResult<Request> convert(const aidl_hal::Request& request);
+
+GeneralResult<std::vector<Operation>> convert(const std::vector<aidl_hal::Operation>& outputShapes);
+GeneralResult<std::vector<SharedMemory>> convert(const std::vector<aidl_hal::Memory>& memories);
+
+GeneralResult<std::vector<uint32_t>> toUnsigned(const std::vector<int32_t>& vec);
+
+}  // namespace android::nn
+
+namespace aidl::android::hardware::neuralnetworks::utils {
+
+namespace nn = ::android::nn;
+
+nn::GeneralResult<Memory> unvalidatedConvert(const nn::SharedMemory& memory);
+nn::GeneralResult<OutputShape> unvalidatedConvert(const nn::OutputShape& outputShape);
+nn::GeneralResult<ErrorStatus> unvalidatedConvert(const nn::ErrorStatus& errorStatus);
+
+nn::GeneralResult<Memory> convert(const nn::SharedMemory& memory);
+nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus);
+nn::GeneralResult<std::vector<OutputShape>> convert(
+        const std::vector<nn::OutputShape>& outputShapes);
+
+nn::GeneralResult<std::vector<int32_t>> toSigned(const std::vector<uint32_t>& vec);
+
+}  // namespace aidl::android::hardware::neuralnetworks::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_CONVERSIONS_H
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
new file mode 100644
index 0000000..79b511d
--- /dev/null
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
@@ -0,0 +1,57 @@
+/*
+ * 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_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H
+
+#include "nnapi/hal/aidl/Conversions.h"
+
+#include <android-base/logging.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+#include <nnapi/Validation.h>
+
+namespace aidl::android::hardware::neuralnetworks::utils {
+
+constexpr auto kDefaultPriority = Priority::MEDIUM;
+constexpr auto kVersion = nn::Version::ANDROID_S;
+
+template <typename Type>
+nn::Result<void> validate(const Type& halObject) {
+    const auto maybeCanonical = nn::convert(halObject);
+    if (!maybeCanonical.has_value()) {
+        return nn::error() << maybeCanonical.error().message;
+    }
+    return {};
+}
+
+template <typename Type>
+bool valid(const Type& halObject) {
+    const auto result = utils::validate(halObject);
+    if (!result.has_value()) {
+        LOG(ERROR) << result.error();
+    }
+    return result.has_value();
+}
+
+nn::GeneralResult<Memory> clone(const Memory& memory);
+nn::GeneralResult<Request> clone(const Request& request);
+nn::GeneralResult<RequestMemoryPool> clone(const RequestMemoryPool& requestPool);
+nn::GeneralResult<Model> clone(const Model& model);
+
+}  // namespace aidl::android::hardware::neuralnetworks::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H
diff --git a/neuralnetworks/aidl/utils/src/Assertions.cpp b/neuralnetworks/aidl/utils/src/Assertions.cpp
new file mode 100644
index 0000000..0e88091
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/Assertions.cpp
@@ -0,0 +1,269 @@
+/*
+ * 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 <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/FusedActivationFunc.h>
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+#include <aidl/android/hardware/neuralnetworks/OperandLifeTime.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/OperationType.h>
+#include <aidl/android/hardware/neuralnetworks/Priority.h>
+
+#include <ControlFlow.h>
+#include <nnapi/OperandTypes.h>
+#include <nnapi/OperationTypes.h>
+#include <nnapi/Types.h>
+#include <type_traits>
+
+namespace {
+
+#define COMPARE_ENUMS_TYPES(lhsType, rhsType)                                                   \
+    static_assert(                                                                              \
+            std::is_same_v<                                                                     \
+                    std::underlying_type_t<::aidl::android::hardware::neuralnetworks::lhsType>, \
+                    std::underlying_type_t<::android::nn::rhsType>>,                            \
+            "::aidl::android::hardware::neuralnetworks::" #lhsType                              \
+            " does not have the same underlying type as ::android::nn::" #rhsType)
+
+COMPARE_ENUMS_TYPES(OperandType, OperandType);
+COMPARE_ENUMS_TYPES(OperationType, OperationType);
+COMPARE_ENUMS_TYPES(Priority, Priority);
+COMPARE_ENUMS_TYPES(OperandLifeTime, Operand::LifeTime);
+COMPARE_ENUMS_TYPES(ErrorStatus, ErrorStatus);
+
+#undef COMPARE_ENUMS_TYPES
+
+#define COMPARE_ENUMS_FULL(lhsSymbol, rhsSymbol, lhsType, rhsType)                               \
+    static_assert(                                                                               \
+            static_cast<                                                                         \
+                    std::underlying_type_t<::aidl::android::hardware::neuralnetworks::lhsType>>( \
+                    ::aidl::android::hardware::neuralnetworks::lhsType::lhsSymbol) ==            \
+                    static_cast<std::underlying_type_t<::android::nn::rhsType>>(                 \
+                            ::android::nn::rhsType::rhsSymbol),                                  \
+            "::aidl::android::hardware::neuralnetworks::" #lhsType "::" #lhsSymbol               \
+            " does not match ::android::nn::" #rhsType "::" #rhsSymbol)
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, OperandType, OperandType)
+
+COMPARE_ENUMS(FLOAT32);
+COMPARE_ENUMS(INT32);
+COMPARE_ENUMS(UINT32);
+COMPARE_ENUMS(TENSOR_FLOAT32);
+COMPARE_ENUMS(TENSOR_INT32);
+COMPARE_ENUMS(TENSOR_QUANT8_ASYMM);
+COMPARE_ENUMS(BOOL);
+COMPARE_ENUMS(TENSOR_QUANT16_SYMM);
+COMPARE_ENUMS(TENSOR_FLOAT16);
+COMPARE_ENUMS(TENSOR_BOOL8);
+COMPARE_ENUMS(FLOAT16);
+COMPARE_ENUMS(TENSOR_QUANT8_SYMM_PER_CHANNEL);
+COMPARE_ENUMS(TENSOR_QUANT16_ASYMM);
+COMPARE_ENUMS(TENSOR_QUANT8_SYMM);
+COMPARE_ENUMS(TENSOR_QUANT8_ASYMM_SIGNED);
+COMPARE_ENUMS(SUBGRAPH);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, OperationType, OperationType)
+
+COMPARE_ENUMS(ADD);
+COMPARE_ENUMS(AVERAGE_POOL_2D);
+COMPARE_ENUMS(CONCATENATION);
+COMPARE_ENUMS(CONV_2D);
+COMPARE_ENUMS(DEPTHWISE_CONV_2D);
+COMPARE_ENUMS(DEPTH_TO_SPACE);
+COMPARE_ENUMS(DEQUANTIZE);
+COMPARE_ENUMS(EMBEDDING_LOOKUP);
+COMPARE_ENUMS(FLOOR);
+COMPARE_ENUMS(FULLY_CONNECTED);
+COMPARE_ENUMS(HASHTABLE_LOOKUP);
+COMPARE_ENUMS(L2_NORMALIZATION);
+COMPARE_ENUMS(L2_POOL_2D);
+COMPARE_ENUMS(LOCAL_RESPONSE_NORMALIZATION);
+COMPARE_ENUMS(LOGISTIC);
+COMPARE_ENUMS(LSH_PROJECTION);
+COMPARE_ENUMS(LSTM);
+COMPARE_ENUMS(MAX_POOL_2D);
+COMPARE_ENUMS(MUL);
+COMPARE_ENUMS(RELU);
+COMPARE_ENUMS(RELU1);
+COMPARE_ENUMS(RELU6);
+COMPARE_ENUMS(RESHAPE);
+COMPARE_ENUMS(RESIZE_BILINEAR);
+COMPARE_ENUMS(RNN);
+COMPARE_ENUMS(SOFTMAX);
+COMPARE_ENUMS(SPACE_TO_DEPTH);
+COMPARE_ENUMS(SVDF);
+COMPARE_ENUMS(TANH);
+COMPARE_ENUMS(BATCH_TO_SPACE_ND);
+COMPARE_ENUMS(DIV);
+COMPARE_ENUMS(MEAN);
+COMPARE_ENUMS(PAD);
+COMPARE_ENUMS(SPACE_TO_BATCH_ND);
+COMPARE_ENUMS(SQUEEZE);
+COMPARE_ENUMS(STRIDED_SLICE);
+COMPARE_ENUMS(SUB);
+COMPARE_ENUMS(TRANSPOSE);
+COMPARE_ENUMS(ABS);
+COMPARE_ENUMS(ARGMAX);
+COMPARE_ENUMS(ARGMIN);
+COMPARE_ENUMS(AXIS_ALIGNED_BBOX_TRANSFORM);
+COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_LSTM);
+COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_RNN);
+COMPARE_ENUMS(BOX_WITH_NMS_LIMIT);
+COMPARE_ENUMS(CAST);
+COMPARE_ENUMS(CHANNEL_SHUFFLE);
+COMPARE_ENUMS(DETECTION_POSTPROCESSING);
+COMPARE_ENUMS(EQUAL);
+COMPARE_ENUMS(EXP);
+COMPARE_ENUMS(EXPAND_DIMS);
+COMPARE_ENUMS(GATHER);
+COMPARE_ENUMS(GENERATE_PROPOSALS);
+COMPARE_ENUMS(GREATER);
+COMPARE_ENUMS(GREATER_EQUAL);
+COMPARE_ENUMS(GROUPED_CONV_2D);
+COMPARE_ENUMS(HEATMAP_MAX_KEYPOINT);
+COMPARE_ENUMS(INSTANCE_NORMALIZATION);
+COMPARE_ENUMS(LESS);
+COMPARE_ENUMS(LESS_EQUAL);
+COMPARE_ENUMS(LOG);
+COMPARE_ENUMS(LOGICAL_AND);
+COMPARE_ENUMS(LOGICAL_NOT);
+COMPARE_ENUMS(LOGICAL_OR);
+COMPARE_ENUMS(LOG_SOFTMAX);
+COMPARE_ENUMS(MAXIMUM);
+COMPARE_ENUMS(MINIMUM);
+COMPARE_ENUMS(NEG);
+COMPARE_ENUMS(NOT_EQUAL);
+COMPARE_ENUMS(PAD_V2);
+COMPARE_ENUMS(POW);
+COMPARE_ENUMS(PRELU);
+COMPARE_ENUMS(QUANTIZE);
+COMPARE_ENUMS(QUANTIZED_16BIT_LSTM);
+COMPARE_ENUMS(RANDOM_MULTINOMIAL);
+COMPARE_ENUMS(REDUCE_ALL);
+COMPARE_ENUMS(REDUCE_ANY);
+COMPARE_ENUMS(REDUCE_MAX);
+COMPARE_ENUMS(REDUCE_MIN);
+COMPARE_ENUMS(REDUCE_PROD);
+COMPARE_ENUMS(REDUCE_SUM);
+COMPARE_ENUMS(ROI_ALIGN);
+COMPARE_ENUMS(ROI_POOLING);
+COMPARE_ENUMS(RSQRT);
+COMPARE_ENUMS(SELECT);
+COMPARE_ENUMS(SIN);
+COMPARE_ENUMS(SLICE);
+COMPARE_ENUMS(SPLIT);
+COMPARE_ENUMS(SQRT);
+COMPARE_ENUMS(TILE);
+COMPARE_ENUMS(TOPK_V2);
+COMPARE_ENUMS(TRANSPOSE_CONV_2D);
+COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_LSTM);
+COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_RNN);
+COMPARE_ENUMS(RESIZE_NEAREST_NEIGHBOR);
+COMPARE_ENUMS(QUANTIZED_LSTM);
+COMPARE_ENUMS(IF);
+COMPARE_ENUMS(WHILE);
+COMPARE_ENUMS(ELU);
+COMPARE_ENUMS(HARD_SWISH);
+COMPARE_ENUMS(FILL);
+COMPARE_ENUMS(RANK);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, Priority, Priority)
+
+COMPARE_ENUMS(LOW);
+COMPARE_ENUMS(MEDIUM);
+COMPARE_ENUMS(HIGH);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(lhsSymbol, rhsSymbol) \
+    COMPARE_ENUMS_FULL(lhsSymbol, rhsSymbol, OperandLifeTime, Operand::LifeTime)
+
+COMPARE_ENUMS(TEMPORARY_VARIABLE, TEMPORARY_VARIABLE);
+COMPARE_ENUMS(SUBGRAPH_INPUT, SUBGRAPH_INPUT);
+COMPARE_ENUMS(SUBGRAPH_OUTPUT, SUBGRAPH_OUTPUT);
+COMPARE_ENUMS(CONSTANT_COPY, CONSTANT_COPY);
+COMPARE_ENUMS(CONSTANT_POOL, CONSTANT_REFERENCE);
+COMPARE_ENUMS(NO_VALUE, NO_VALUE);
+COMPARE_ENUMS(SUBGRAPH, SUBGRAPH);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, ErrorStatus, ErrorStatus)
+
+COMPARE_ENUMS(NONE);
+COMPARE_ENUMS(DEVICE_UNAVAILABLE);
+COMPARE_ENUMS(GENERAL_FAILURE);
+COMPARE_ENUMS(OUTPUT_INSUFFICIENT_SIZE);
+COMPARE_ENUMS(INVALID_ARGUMENT);
+COMPARE_ENUMS(MISSED_DEADLINE_TRANSIENT);
+COMPARE_ENUMS(MISSED_DEADLINE_PERSISTENT);
+COMPARE_ENUMS(RESOURCE_EXHAUSTED_TRANSIENT);
+COMPARE_ENUMS(RESOURCE_EXHAUSTED_PERSISTENT);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) \
+    COMPARE_ENUMS_FULL(symbol, symbol, ExecutionPreference, ExecutionPreference)
+
+COMPARE_ENUMS(LOW_POWER);
+COMPARE_ENUMS(FAST_SINGLE_ANSWER);
+COMPARE_ENUMS(SUSTAINED_SPEED);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, DeviceType, DeviceType)
+
+COMPARE_ENUMS(OTHER);
+COMPARE_ENUMS(CPU);
+COMPARE_ENUMS(GPU);
+COMPARE_ENUMS(ACCELERATOR);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) \
+    COMPARE_ENUMS_FULL(symbol, symbol, FusedActivationFunc, FusedActivationFunc)
+
+COMPARE_ENUMS(NONE);
+COMPARE_ENUMS(RELU);
+COMPARE_ENUMS(RELU1);
+COMPARE_ENUMS(RELU6);
+
+#undef COMPARE_ENUMS
+
+#undef COMPARE_ENUMS_FULL
+
+#define COMPARE_CONSTANTS(halSymbol, canonicalSymbol)                     \
+    static_assert(::aidl::android::hardware::neuralnetworks::halSymbol == \
+                  ::android::nn::canonicalSymbol);
+
+COMPARE_CONSTANTS(IDevice::BYTE_SIZE_OF_CACHE_TOKEN, kByteSizeOfCacheToken);
+COMPARE_CONSTANTS(IDevice::MAX_NUMBER_OF_CACHE_FILES, kMaxNumberOfCacheFiles);
+COMPARE_CONSTANTS(IDevice::EXTENSION_TYPE_HIGH_BITS_PREFIX, kExtensionPrefixBits - 1);
+COMPARE_CONSTANTS(IDevice::EXTENSION_TYPE_LOW_BITS_TYPE, kExtensionTypeBits);
+COMPARE_CONSTANTS(IPreparedModel::DEFAULT_LOOP_TIMEOUT_DURATION_NS,
+                  operation_while::kTimeoutNsDefault);
+COMPARE_CONSTANTS(IPreparedModel::MAXIMUM_LOOP_TIMEOUT_DURATION_NS,
+                  operation_while::kTimeoutNsMaximum);
+
+#undef COMPARE_CONSTANTS
+
+}  // anonymous namespace
diff --git a/neuralnetworks/aidl/utils/src/Conversions.cpp b/neuralnetworks/aidl/utils/src/Conversions.cpp
new file mode 100644
index 0000000..db3504b
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/Conversions.cpp
@@ -0,0 +1,731 @@
+/*
+ * 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 "Conversions.h"
+
+#include <aidl/android/hardware/common/NativeHandle.h>
+#include <android-base/logging.h>
+#include <android/hardware_buffer.h>
+#include <cutils/native_handle.h>
+#include <nnapi/OperandTypes.h>
+#include <nnapi/OperationTypes.h>
+#include <nnapi/Result.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/Validation.h>
+#include <nnapi/hal/CommonUtils.h>
+#include <nnapi/hal/HandleError.h>
+#include <vndk/hardware_buffer.h>
+
+#include <algorithm>
+#include <chrono>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <type_traits>
+#include <utility>
+
+#define VERIFY_NON_NEGATIVE(value) \
+    while (UNLIKELY(value < 0)) return NN_ERROR()
+
+namespace {
+
+template <typename Type>
+constexpr std::underlying_type_t<Type> underlyingType(Type value) {
+    return static_cast<std::underlying_type_t<Type>>(value);
+}
+
+constexpr auto kVersion = android::nn::Version::ANDROID_S;
+
+}  // namespace
+
+namespace android::nn {
+namespace {
+
+using ::aidl::android::hardware::common::NativeHandle;
+
+constexpr auto validOperandType(nn::OperandType operandType) {
+    switch (operandType) {
+        case nn::OperandType::FLOAT32:
+        case nn::OperandType::INT32:
+        case nn::OperandType::UINT32:
+        case nn::OperandType::TENSOR_FLOAT32:
+        case nn::OperandType::TENSOR_INT32:
+        case nn::OperandType::TENSOR_QUANT8_ASYMM:
+        case nn::OperandType::BOOL:
+        case nn::OperandType::TENSOR_QUANT16_SYMM:
+        case nn::OperandType::TENSOR_FLOAT16:
+        case nn::OperandType::TENSOR_BOOL8:
+        case nn::OperandType::FLOAT16:
+        case nn::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case nn::OperandType::TENSOR_QUANT16_ASYMM:
+        case nn::OperandType::TENSOR_QUANT8_SYMM:
+        case nn::OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+        case nn::OperandType::SUBGRAPH:
+            return true;
+        case nn::OperandType::OEM:
+        case nn::OperandType::TENSOR_OEM_BYTE:
+            return false;
+    }
+    return nn::isExtension(operandType);
+}
+
+template <typename Input>
+using UnvalidatedConvertOutput =
+        std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>;
+
+template <typename Type>
+GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvertVec(
+        const std::vector<Type>& arguments) {
+    std::vector<UnvalidatedConvertOutput<Type>> canonical;
+    canonical.reserve(arguments.size());
+    for (const auto& argument : arguments) {
+        canonical.push_back(NN_TRY(nn::unvalidatedConvert(argument)));
+    }
+    return canonical;
+}
+
+template <typename Type>
+GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvert(
+        const std::vector<Type>& arguments) {
+    return unvalidatedConvertVec(arguments);
+}
+
+template <typename Type>
+GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& halObject) {
+    auto canonical = NN_TRY(nn::unvalidatedConvert(halObject));
+    const auto maybeVersion = validate(canonical);
+    if (!maybeVersion.has_value()) {
+        return error() << maybeVersion.error();
+    }
+    const auto version = maybeVersion.value();
+    if (version > kVersion) {
+        return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
+    }
+    return canonical;
+}
+
+template <typename Type>
+GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> validatedConvert(
+        const std::vector<Type>& arguments) {
+    std::vector<UnvalidatedConvertOutput<Type>> canonical;
+    canonical.reserve(arguments.size());
+    for (const auto& argument : arguments) {
+        canonical.push_back(NN_TRY(validatedConvert(argument)));
+    }
+    return canonical;
+}
+
+GeneralResult<Handle> unvalidatedConvertHelper(const NativeHandle& aidlNativeHandle) {
+    std::vector<base::unique_fd> fds;
+    fds.reserve(aidlNativeHandle.fds.size());
+    for (const auto& fd : aidlNativeHandle.fds) {
+        const int dupFd = dup(fd.get());
+        if (dupFd == -1) {
+            // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return
+            // here?
+            return NN_ERROR() << "Failed to dup the fd";
+        }
+        fds.emplace_back(dupFd);
+    }
+
+    return Handle{.fds = std::move(fds), .ints = aidlNativeHandle.ints};
+}
+
+struct NativeHandleDeleter {
+    void operator()(native_handle_t* handle) const {
+        if (handle) {
+            native_handle_close(handle);
+            native_handle_delete(handle);
+        }
+    }
+};
+
+using UniqueNativeHandle = std::unique_ptr<native_handle_t, NativeHandleDeleter>;
+
+static nn::GeneralResult<UniqueNativeHandle> nativeHandleFromAidlHandle(
+        const NativeHandle& handle) {
+    std::vector<base::unique_fd> fds;
+    fds.reserve(handle.fds.size());
+    for (const auto& fd : handle.fds) {
+        const int dupFd = dup(fd.get());
+        if (dupFd == -1) {
+            return NN_ERROR() << "Failed to dup the fd";
+        }
+        fds.emplace_back(dupFd);
+    }
+
+    constexpr size_t kIntMax = std::numeric_limits<int>::max();
+    CHECK_LE(handle.fds.size(), kIntMax);
+    CHECK_LE(handle.ints.size(), kIntMax);
+    native_handle_t* nativeHandle = native_handle_create(static_cast<int>(handle.fds.size()),
+                                                         static_cast<int>(handle.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(handle.ints.begin(), handle.ints.end(), &nativeHandle->data[nativeHandle->numFds]);
+
+    return UniqueNativeHandle(nativeHandle);
+}
+
+}  // anonymous namespace
+
+GeneralResult<OperandType> unvalidatedConvert(const aidl_hal::OperandType& operandType) {
+    VERIFY_NON_NEGATIVE(underlyingType(operandType)) << "Negative operand types are not allowed.";
+    return static_cast<OperandType>(operandType);
+}
+
+GeneralResult<OperationType> unvalidatedConvert(const aidl_hal::OperationType& operationType) {
+    VERIFY_NON_NEGATIVE(underlyingType(operationType))
+            << "Negative operation types are not allowed.";
+    return static_cast<OperationType>(operationType);
+}
+
+GeneralResult<DeviceType> unvalidatedConvert(const aidl_hal::DeviceType& deviceType) {
+    return static_cast<DeviceType>(deviceType);
+}
+
+GeneralResult<Priority> unvalidatedConvert(const aidl_hal::Priority& priority) {
+    return static_cast<Priority>(priority);
+}
+
+GeneralResult<Capabilities> unvalidatedConvert(const aidl_hal::Capabilities& capabilities) {
+    const bool validOperandTypes = std::all_of(
+            capabilities.operandPerformance.begin(), capabilities.operandPerformance.end(),
+            [](const aidl_hal::OperandPerformance& operandPerformance) {
+                const auto maybeType = unvalidatedConvert(operandPerformance.type);
+                return !maybeType.has_value() ? false : validOperandType(maybeType.value());
+            });
+    if (!validOperandTypes) {
+        return NN_ERROR() << "Invalid OperandType when unvalidatedConverting OperandPerformance in "
+                             "Capabilities";
+    }
+
+    auto operandPerformance = NN_TRY(unvalidatedConvert(capabilities.operandPerformance));
+    auto table = NN_TRY(hal::utils::makeGeneralFailure(
+            Capabilities::OperandPerformanceTable::create(std::move(operandPerformance)),
+            nn::ErrorStatus::GENERAL_FAILURE));
+
+    return Capabilities{
+            .relaxedFloat32toFloat16PerformanceScalar = NN_TRY(
+                    unvalidatedConvert(capabilities.relaxedFloat32toFloat16PerformanceScalar)),
+            .relaxedFloat32toFloat16PerformanceTensor = NN_TRY(
+                    unvalidatedConvert(capabilities.relaxedFloat32toFloat16PerformanceTensor)),
+            .operandPerformance = std::move(table),
+            .ifPerformance = NN_TRY(unvalidatedConvert(capabilities.ifPerformance)),
+            .whilePerformance = NN_TRY(unvalidatedConvert(capabilities.whilePerformance)),
+    };
+}
+
+GeneralResult<Capabilities::OperandPerformance> unvalidatedConvert(
+        const aidl_hal::OperandPerformance& operandPerformance) {
+    return Capabilities::OperandPerformance{
+            .type = NN_TRY(unvalidatedConvert(operandPerformance.type)),
+            .info = NN_TRY(unvalidatedConvert(operandPerformance.info)),
+    };
+}
+
+GeneralResult<Capabilities::PerformanceInfo> unvalidatedConvert(
+        const aidl_hal::PerformanceInfo& performanceInfo) {
+    return Capabilities::PerformanceInfo{
+            .execTime = performanceInfo.execTime,
+            .powerUsage = performanceInfo.powerUsage,
+    };
+}
+
+GeneralResult<DataLocation> unvalidatedConvert(const aidl_hal::DataLocation& location) {
+    VERIFY_NON_NEGATIVE(location.poolIndex) << "DataLocation: pool index must not be negative";
+    VERIFY_NON_NEGATIVE(location.offset) << "DataLocation: offset must not be negative";
+    VERIFY_NON_NEGATIVE(location.length) << "DataLocation: length must not be negative";
+    if (location.offset > std::numeric_limits<uint32_t>::max()) {
+        return NN_ERROR() << "DataLocation: offset must be <= std::numeric_limits<uint32_t>::max()";
+    }
+    if (location.length > std::numeric_limits<uint32_t>::max()) {
+        return NN_ERROR() << "DataLocation: length must be <= std::numeric_limits<uint32_t>::max()";
+    }
+    return DataLocation{
+            .poolIndex = static_cast<uint32_t>(location.poolIndex),
+            .offset = static_cast<uint32_t>(location.offset),
+            .length = static_cast<uint32_t>(location.length),
+    };
+}
+
+GeneralResult<Operation> unvalidatedConvert(const aidl_hal::Operation& operation) {
+    return Operation{
+            .type = NN_TRY(unvalidatedConvert(operation.type)),
+            .inputs = NN_TRY(toUnsigned(operation.inputs)),
+            .outputs = NN_TRY(toUnsigned(operation.outputs)),
+    };
+}
+
+GeneralResult<Operand::LifeTime> unvalidatedConvert(
+        const aidl_hal::OperandLifeTime& operandLifeTime) {
+    return static_cast<Operand::LifeTime>(operandLifeTime);
+}
+
+GeneralResult<Operand> unvalidatedConvert(const aidl_hal::Operand& operand) {
+    return Operand{
+            .type = NN_TRY(unvalidatedConvert(operand.type)),
+            .dimensions = NN_TRY(toUnsigned(operand.dimensions)),
+            .scale = operand.scale,
+            .zeroPoint = operand.zeroPoint,
+            .lifetime = NN_TRY(unvalidatedConvert(operand.lifetime)),
+            .location = NN_TRY(unvalidatedConvert(operand.location)),
+            .extraParams = NN_TRY(unvalidatedConvert(operand.extraParams)),
+    };
+}
+
+GeneralResult<Operand::ExtraParams> unvalidatedConvert(
+        const std::optional<aidl_hal::OperandExtraParams>& optionalExtraParams) {
+    if (!optionalExtraParams.has_value()) {
+        return Operand::NoParams{};
+    }
+    const auto& extraParams = optionalExtraParams.value();
+    using Tag = aidl_hal::OperandExtraParams::Tag;
+    switch (extraParams.getTag()) {
+        case Tag::channelQuant:
+            return unvalidatedConvert(extraParams.get<Tag::channelQuant>());
+        case Tag::extension:
+            return extraParams.get<Tag::extension>();
+    }
+    return NN_ERROR() << "Unrecognized Operand::ExtraParams tag: "
+                      << underlyingType(extraParams.getTag());
+}
+
+GeneralResult<Operand::SymmPerChannelQuantParams> unvalidatedConvert(
+        const aidl_hal::SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+    VERIFY_NON_NEGATIVE(symmPerChannelQuantParams.channelDim)
+            << "Per-channel quantization channel dimension must not be negative.";
+    return Operand::SymmPerChannelQuantParams{
+            .scales = symmPerChannelQuantParams.scales,
+            .channelDim = static_cast<uint32_t>(symmPerChannelQuantParams.channelDim),
+    };
+}
+
+GeneralResult<Model> unvalidatedConvert(const aidl_hal::Model& model) {
+    return Model{
+            .main = NN_TRY(unvalidatedConvert(model.main)),
+            .referenced = NN_TRY(unvalidatedConvert(model.referenced)),
+            .operandValues = NN_TRY(unvalidatedConvert(model.operandValues)),
+            .pools = NN_TRY(unvalidatedConvert(model.pools)),
+            .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16,
+            .extensionNameToPrefix = NN_TRY(unvalidatedConvert(model.extensionNameToPrefix)),
+    };
+}
+
+GeneralResult<Model::Subgraph> unvalidatedConvert(const aidl_hal::Subgraph& subgraph) {
+    return Model::Subgraph{
+            .operands = NN_TRY(unvalidatedConvert(subgraph.operands)),
+            .operations = NN_TRY(unvalidatedConvert(subgraph.operations)),
+            .inputIndexes = NN_TRY(toUnsigned(subgraph.inputIndexes)),
+            .outputIndexes = NN_TRY(toUnsigned(subgraph.outputIndexes)),
+    };
+}
+
+GeneralResult<Model::ExtensionNameAndPrefix> unvalidatedConvert(
+        const aidl_hal::ExtensionNameAndPrefix& extensionNameAndPrefix) {
+    return Model::ExtensionNameAndPrefix{
+            .name = extensionNameAndPrefix.name,
+            .prefix = extensionNameAndPrefix.prefix,
+    };
+}
+
+GeneralResult<Extension> unvalidatedConvert(const aidl_hal::Extension& extension) {
+    return Extension{
+            .name = extension.name,
+            .operandTypes = NN_TRY(unvalidatedConvert(extension.operandTypes)),
+    };
+}
+
+GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert(
+        const aidl_hal::ExtensionOperandTypeInformation& operandTypeInformation) {
+    VERIFY_NON_NEGATIVE(operandTypeInformation.byteSize)
+            << "Extension operand type byte size must not be negative";
+    return Extension::OperandTypeInformation{
+            .type = operandTypeInformation.type,
+            .isTensor = operandTypeInformation.isTensor,
+            .byteSize = static_cast<uint32_t>(operandTypeInformation.byteSize),
+    };
+}
+
+GeneralResult<OutputShape> unvalidatedConvert(const aidl_hal::OutputShape& outputShape) {
+    return OutputShape{
+            .dimensions = NN_TRY(toUnsigned(outputShape.dimensions)),
+            .isSufficient = outputShape.isSufficient,
+    };
+}
+
+GeneralResult<MeasureTiming> unvalidatedConvert(bool measureTiming) {
+    return measureTiming ? MeasureTiming::YES : MeasureTiming::NO;
+}
+
+static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
+    return (value + multiple - 1) / multiple * multiple;
+}
+
+GeneralResult<SharedMemory> unvalidatedConvert(const aidl_hal::Memory& memory) {
+    VERIFY_NON_NEGATIVE(memory.size) << "Memory size must not be negative";
+    if (memory.size > std::numeric_limits<uint32_t>::max()) {
+        return NN_ERROR() << "Memory: size must be <= std::numeric_limits<size_t>::max()";
+    }
+
+    if (memory.name != "hardware_buffer_blob") {
+        return std::make_shared<const Memory>(Memory{
+                .handle = NN_TRY(unvalidatedConvertHelper(memory.handle)),
+                .size = static_cast<uint32_t>(memory.size),
+                .name = memory.name,
+        });
+    }
+
+    const auto size = static_cast<uint32_t>(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.
+
+    const UniqueNativeHandle handle = NN_TRY(nativeHandleFromAidlHandle(memory.handle));
+    const native_handle_t* nativeHandle = handle.get();
+
+    // 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, nativeHandle,
+                                                  AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE,
+                                                  &hardwareBuffer);
+        if (status == NO_ERROR) {
+            break;
+        }
+    }
+    if (status != NO_ERROR) {
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
+               << "Can't create AHardwareBuffer from handle. Error: " << status;
+    }
+
+    return std::make_shared<const Memory>(Memory{
+            .handle = HardwareBufferHandle(hardwareBuffer, /*takeOwnership=*/true),
+            .size = static_cast<uint32_t>(memory.size),
+            .name = memory.name,
+    });
+}
+
+GeneralResult<Model::OperandValues> unvalidatedConvert(const std::vector<uint8_t>& operandValues) {
+    return Model::OperandValues(operandValues.data(), operandValues.size());
+}
+
+GeneralResult<BufferDesc> unvalidatedConvert(const aidl_hal::BufferDesc& bufferDesc) {
+    return BufferDesc{.dimensions = NN_TRY(toUnsigned(bufferDesc.dimensions))};
+}
+
+GeneralResult<BufferRole> unvalidatedConvert(const aidl_hal::BufferRole& bufferRole) {
+    VERIFY_NON_NEGATIVE(bufferRole.modelIndex) << "BufferRole: modelIndex must not be negative";
+    VERIFY_NON_NEGATIVE(bufferRole.ioIndex) << "BufferRole: ioIndex must not be negative";
+    return BufferRole{
+            .modelIndex = static_cast<uint32_t>(bufferRole.modelIndex),
+            .ioIndex = static_cast<uint32_t>(bufferRole.ioIndex),
+            .frequency = bufferRole.frequency,
+    };
+}
+
+GeneralResult<Request> unvalidatedConvert(const aidl_hal::Request& request) {
+    return Request{
+            .inputs = NN_TRY(unvalidatedConvert(request.inputs)),
+            .outputs = NN_TRY(unvalidatedConvert(request.outputs)),
+            .pools = NN_TRY(unvalidatedConvert(request.pools)),
+    };
+}
+
+GeneralResult<Request::Argument> unvalidatedConvert(const aidl_hal::RequestArgument& argument) {
+    const auto lifetime = argument.hasNoValue ? Request::Argument::LifeTime::NO_VALUE
+                                              : Request::Argument::LifeTime::POOL;
+    return Request::Argument{
+            .lifetime = lifetime,
+            .location = NN_TRY(unvalidatedConvert(argument.location)),
+            .dimensions = NN_TRY(toUnsigned(argument.dimensions)),
+    };
+}
+
+GeneralResult<Request::MemoryPool> unvalidatedConvert(
+        const aidl_hal::RequestMemoryPool& memoryPool) {
+    using Tag = aidl_hal::RequestMemoryPool::Tag;
+    switch (memoryPool.getTag()) {
+        case Tag::pool:
+            return unvalidatedConvert(memoryPool.get<Tag::pool>());
+        case Tag::token: {
+            const auto token = memoryPool.get<Tag::token>();
+            VERIFY_NON_NEGATIVE(token) << "Memory pool token must not be negative";
+            return static_cast<Request::MemoryDomainToken>(token);
+        }
+    }
+    return NN_ERROR() << "Invalid Request::MemoryPool tag " << underlyingType(memoryPool.getTag());
+}
+
+GeneralResult<ErrorStatus> unvalidatedConvert(const aidl_hal::ErrorStatus& status) {
+    switch (status) {
+        case aidl_hal::ErrorStatus::NONE:
+        case aidl_hal::ErrorStatus::DEVICE_UNAVAILABLE:
+        case aidl_hal::ErrorStatus::GENERAL_FAILURE:
+        case aidl_hal::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE:
+        case aidl_hal::ErrorStatus::INVALID_ARGUMENT:
+        case aidl_hal::ErrorStatus::MISSED_DEADLINE_TRANSIENT:
+        case aidl_hal::ErrorStatus::MISSED_DEADLINE_PERSISTENT:
+        case aidl_hal::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT:
+        case aidl_hal::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT:
+            return static_cast<ErrorStatus>(status);
+    }
+    return NN_ERROR() << "Invalid ErrorStatus " << underlyingType(status);
+}
+
+GeneralResult<ExecutionPreference> unvalidatedConvert(
+        const aidl_hal::ExecutionPreference& executionPreference) {
+    return static_cast<ExecutionPreference>(executionPreference);
+}
+
+GeneralResult<SharedHandle> unvalidatedConvert(const NativeHandle& aidlNativeHandle) {
+    return std::make_shared<const Handle>(NN_TRY(unvalidatedConvertHelper(aidlNativeHandle)));
+}
+
+GeneralResult<ExecutionPreference> convert(
+        const aidl_hal::ExecutionPreference& executionPreference) {
+    return validatedConvert(executionPreference);
+}
+
+GeneralResult<SharedMemory> convert(const aidl_hal::Memory& operand) {
+    return validatedConvert(operand);
+}
+
+GeneralResult<Model> convert(const aidl_hal::Model& model) {
+    return validatedConvert(model);
+}
+
+GeneralResult<Operand> convert(const aidl_hal::Operand& operand) {
+    return unvalidatedConvert(operand);
+}
+
+GeneralResult<OperandType> convert(const aidl_hal::OperandType& operandType) {
+    return unvalidatedConvert(operandType);
+}
+
+GeneralResult<Priority> convert(const aidl_hal::Priority& priority) {
+    return validatedConvert(priority);
+}
+
+GeneralResult<Request::MemoryPool> convert(const aidl_hal::RequestMemoryPool& memoryPool) {
+    return unvalidatedConvert(memoryPool);
+}
+
+GeneralResult<Request> convert(const aidl_hal::Request& request) {
+    return validatedConvert(request);
+}
+
+GeneralResult<std::vector<Operation>> convert(const std::vector<aidl_hal::Operation>& operations) {
+    return unvalidatedConvert(operations);
+}
+
+GeneralResult<std::vector<SharedMemory>> convert(const std::vector<aidl_hal::Memory>& memories) {
+    return validatedConvert(memories);
+}
+
+GeneralResult<std::vector<uint32_t>> toUnsigned(const std::vector<int32_t>& vec) {
+    if (!std::all_of(vec.begin(), vec.end(), [](int32_t v) { return v >= 0; })) {
+        return NN_ERROR() << "Negative value passed to conversion from signed to unsigned";
+    }
+    return std::vector<uint32_t>(vec.begin(), vec.end());
+}
+
+}  // namespace android::nn
+
+namespace aidl::android::hardware::neuralnetworks::utils {
+namespace {
+
+template <typename Input>
+using UnvalidatedConvertOutput =
+        std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>;
+
+template <typename Type>
+nn::GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvertVec(
+        const std::vector<Type>& arguments) {
+    std::vector<UnvalidatedConvertOutput<Type>> halObject(arguments.size());
+    for (size_t i = 0; i < arguments.size(); ++i) {
+        halObject[i] = NN_TRY(unvalidatedConvert(arguments[i]));
+    }
+    return halObject;
+}
+
+template <typename Type>
+nn::GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& canonical) {
+    const auto maybeVersion = nn::validate(canonical);
+    if (!maybeVersion.has_value()) {
+        return nn::error() << maybeVersion.error();
+    }
+    const auto version = maybeVersion.value();
+    if (version > kVersion) {
+        return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
+    }
+    return utils::unvalidatedConvert(canonical);
+}
+
+template <typename Type>
+nn::GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> validatedConvert(
+        const std::vector<Type>& arguments) {
+    std::vector<UnvalidatedConvertOutput<Type>> halObject(arguments.size());
+    for (size_t i = 0; i < arguments.size(); ++i) {
+        halObject[i] = NN_TRY(validatedConvert(arguments[i]));
+    }
+    return halObject;
+}
+
+nn::GeneralResult<common::NativeHandle> unvalidatedConvert(const nn::Handle& handle) {
+    common::NativeHandle aidlNativeHandle;
+    aidlNativeHandle.fds.reserve(handle.fds.size());
+    for (const auto& fd : handle.fds) {
+        const int dupFd = dup(fd.get());
+        if (dupFd == -1) {
+            // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return
+            // here?
+            return NN_ERROR() << "Failed to dup the fd";
+        }
+        aidlNativeHandle.fds.emplace_back(dupFd);
+    }
+    aidlNativeHandle.ints = handle.ints;
+    return aidlNativeHandle;
+}
+
+static nn::GeneralResult<common::NativeHandle> aidlHandleFromNativeHandle(
+        const native_handle_t& handle) {
+    common::NativeHandle aidlNativeHandle;
+
+    aidlNativeHandle.fds.reserve(handle.numFds);
+    for (int i = 0; i < handle.numFds; ++i) {
+        const int dupFd = dup(handle.data[i]);
+        if (dupFd == -1) {
+            return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
+        }
+        aidlNativeHandle.fds.emplace_back(dupFd);
+    }
+
+    aidlNativeHandle.ints = std::vector<int>(&handle.data[handle.numFds],
+                                             &handle.data[handle.numFds + handle.numInts]);
+
+    return aidlNativeHandle;
+}
+
+}  // namespace
+
+nn::GeneralResult<common::NativeHandle> unvalidatedConvert(const nn::SharedHandle& sharedHandle) {
+    CHECK(sharedHandle != nullptr);
+    return unvalidatedConvert(*sharedHandle);
+}
+
+nn::GeneralResult<Memory> unvalidatedConvert(const nn::SharedMemory& memory) {
+    CHECK(memory != nullptr);
+    if (memory->size > std::numeric_limits<int64_t>::max()) {
+        return NN_ERROR() << "Memory size doesn't fit into int64_t.";
+    }
+    if (const auto* handle = std::get_if<nn::Handle>(&memory->handle)) {
+        return Memory{
+                .handle = NN_TRY(unvalidatedConvert(*handle)),
+                .size = static_cast<int64_t>(memory->size),
+                .name = memory->name,
+        };
+    }
+
+    const auto* ahwb = std::get<nn::HardwareBufferHandle>(memory->handle).get();
+    AHardwareBuffer_Desc bufferDesc;
+    AHardwareBuffer_describe(ahwb, &bufferDesc);
+
+    if (bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB) {
+        CHECK_EQ(memory->size, bufferDesc.width);
+        CHECK_EQ(memory->name, "hardware_buffer_blob");
+    } else {
+        CHECK_EQ(memory->size, 0u);
+        CHECK_EQ(memory->name, "hardware_buffer");
+    }
+
+    const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
+    if (nativeHandle == nullptr) {
+        return NN_ERROR() << "unvalidatedConvert failed because AHardwareBuffer_getNativeHandle "
+                             "returned nullptr";
+    }
+
+    return Memory{
+            .handle = NN_TRY(aidlHandleFromNativeHandle(*nativeHandle)),
+            .size = static_cast<int64_t>(memory->size),
+            .name = memory->name,
+    };
+}
+
+nn::GeneralResult<ErrorStatus> unvalidatedConvert(const nn::ErrorStatus& errorStatus) {
+    switch (errorStatus) {
+        case nn::ErrorStatus::NONE:
+        case nn::ErrorStatus::DEVICE_UNAVAILABLE:
+        case nn::ErrorStatus::GENERAL_FAILURE:
+        case nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE:
+        case nn::ErrorStatus::INVALID_ARGUMENT:
+        case nn::ErrorStatus::MISSED_DEADLINE_TRANSIENT:
+        case nn::ErrorStatus::MISSED_DEADLINE_PERSISTENT:
+        case nn::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT:
+        case nn::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT:
+            return static_cast<ErrorStatus>(errorStatus);
+        default:
+            return ErrorStatus::GENERAL_FAILURE;
+    }
+}
+
+nn::GeneralResult<OutputShape> unvalidatedConvert(const nn::OutputShape& outputShape) {
+    return OutputShape{.dimensions = NN_TRY(toSigned(outputShape.dimensions)),
+                       .isSufficient = outputShape.isSufficient};
+}
+
+nn::GeneralResult<Memory> convert(const nn::SharedMemory& memory) {
+    return validatedConvert(memory);
+}
+
+nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus) {
+    return validatedConvert(errorStatus);
+}
+
+nn::GeneralResult<std::vector<OutputShape>> convert(
+        const std::vector<nn::OutputShape>& outputShapes) {
+    return validatedConvert(outputShapes);
+}
+
+nn::GeneralResult<std::vector<int32_t>> toSigned(const std::vector<uint32_t>& vec) {
+    if (!std::all_of(vec.begin(), vec.end(),
+                     [](uint32_t v) { return v <= std::numeric_limits<int32_t>::max(); })) {
+        return NN_ERROR() << "Vector contains a value that doesn't fit into int32_t.";
+    }
+    return std::vector<int32_t>(vec.begin(), vec.end());
+}
+
+}  // namespace aidl::android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/aidl/utils/src/Utils.cpp b/neuralnetworks/aidl/utils/src/Utils.cpp
new file mode 100644
index 0000000..8d00e59
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/Utils.cpp
@@ -0,0 +1,95 @@
+/*
+ * 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 "Utils.h"
+
+#include <nnapi/Result.h>
+
+namespace aidl::android::hardware::neuralnetworks::utils {
+namespace {
+
+using ::android::nn::GeneralResult;
+
+template <typename Type>
+nn::GeneralResult<std::vector<Type>> cloneVec(const std::vector<Type>& arguments) {
+    std::vector<Type> clonedObjects;
+    clonedObjects.reserve(arguments.size());
+    for (const auto& argument : arguments) {
+        clonedObjects.push_back(NN_TRY(clone(argument)));
+    }
+    return clonedObjects;
+}
+
+template <typename Type>
+GeneralResult<std::vector<Type>> clone(const std::vector<Type>& arguments) {
+    return cloneVec(arguments);
+}
+
+}  // namespace
+
+GeneralResult<Memory> clone(const Memory& memory) {
+    common::NativeHandle nativeHandle;
+    nativeHandle.ints = memory.handle.ints;
+    nativeHandle.fds.reserve(memory.handle.fds.size());
+    for (const auto& fd : memory.handle.fds) {
+        const int newFd = dup(fd.get());
+        if (newFd < 0) {
+            return NN_ERROR() << "Couldn't dup a file descriptor";
+        }
+        nativeHandle.fds.emplace_back(newFd);
+    }
+    return Memory{
+            .handle = std::move(nativeHandle),
+            .size = memory.size,
+            .name = memory.name,
+    };
+}
+
+GeneralResult<RequestMemoryPool> clone(const RequestMemoryPool& requestPool) {
+    using Tag = RequestMemoryPool::Tag;
+    switch (requestPool.getTag()) {
+        case Tag::pool:
+            return RequestMemoryPool::make<Tag::pool>(NN_TRY(clone(requestPool.get<Tag::pool>())));
+        case Tag::token:
+            return RequestMemoryPool::make<Tag::token>(requestPool.get<Tag::token>());
+    }
+    // Using explicit type conversion because std::variant inside the RequestMemoryPool confuses the
+    // compiler.
+    return (NN_ERROR() << "Unrecognized request pool tag: " << requestPool.getTag())
+            .
+            operator GeneralResult<RequestMemoryPool>();
+}
+
+GeneralResult<Request> clone(const Request& request) {
+    return Request{
+            .inputs = request.inputs,
+            .outputs = request.outputs,
+            .pools = NN_TRY(clone(request.pools)),
+    };
+}
+
+GeneralResult<Model> clone(const Model& model) {
+    return Model{
+            .main = model.main,
+            .referenced = model.referenced,
+            .operandValues = model.operandValues,
+            .pools = NN_TRY(clone(model.pools)),
+            .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16,
+            .extensionNameToPrefix = model.extensionNameToPrefix,
+    };
+}
+
+}  // namespace aidl::android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/aidl/vts/OWNERS b/neuralnetworks/aidl/vts/OWNERS
new file mode 100644
index 0000000..6719a5b
--- /dev/null
+++ b/neuralnetworks/aidl/vts/OWNERS
@@ -0,0 +1,12 @@
+# Neuralnetworks team
+butlermichael@google.com
+dgross@google.com
+jeanluc@google.com
+levp@google.com
+miaowang@google.com
+mikie@google.com
+mks@google.com
+pszczepaniak@google.com
+slavash@google.com
+vddang@google.com
+xusongw@google.com
diff --git a/neuralnetworks/aidl/vts/functional/Android.bp b/neuralnetworks/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..aa7afbf
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Android.bp
@@ -0,0 +1,68 @@
+//
+// 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.
+//
+
+cc_test {
+    name: "VtsHalNeuralnetworksTargetTest",
+    defaults: [
+        "neuralnetworks_vts_functional_defaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "BasicTests.cpp",
+        "Callbacks.cpp",
+        "CompilationCachingTests.cpp",
+        "GeneratedTestHarness.cpp",
+        "MemoryDomainTests.cpp",
+        "QualityOfServiceTests.cpp",
+        "TestAssertions.cpp",
+        "TestMain.cpp",
+        "Utils.cpp",
+        "ValidateModel.cpp",
+        "ValidateRequest.cpp",
+        "VtsHalNeuralnetworks.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libnativewindow",
+        "libvndksupport",
+    ],
+    static_libs: [
+        "android.hardware.common-V2-ndk_platform",
+        "android.hardware.neuralnetworks-V1-ndk_platform",
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libgmock",
+        "libhidlmemory",
+        "libneuralnetworks_generated_test_harness",
+        "libneuralnetworks_utils",
+        "libsync",
+        "neuralnetworks_utils_hal_aidl",
+    ],
+    whole_static_libs: [
+        "neuralnetworks_generated_V1_0_example",
+        "neuralnetworks_generated_V1_1_example",
+        "neuralnetworks_generated_V1_2_example",
+        "neuralnetworks_generated_V1_3_example",
+    ],
+    header_libs: [
+        "libbase_headers",
+        "libneuralnetworks_headers",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/neuralnetworks/aidl/vts/functional/AndroidTest.xml b/neuralnetworks/aidl/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..384d420
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<configuration description="Runs VtsHalNeuralnetworksTargetTest.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="VtsHalNeuralnetworksTargetTest->/data/local/tmp/VtsHalNeuralnetworksTargetTest" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="VtsHalNeuralnetworksTargetTest" />
+        <option name="native-test-timeout" value="20m" />
+    </test>
+</configuration>
diff --git a/neuralnetworks/aidl/vts/functional/BasicTests.cpp b/neuralnetworks/aidl/vts/functional/BasicTests.cpp
new file mode 100644
index 0000000..b2f4507
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/BasicTests.cpp
@@ -0,0 +1,193 @@
+/*
+ * 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 "neuralnetworks_aidl_hal_test"
+
+#include <aidl/android/hardware/neuralnetworks/Capabilities.h>
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+#include <aidl/android/hardware/neuralnetworks/Operand.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/Priority.h>
+#include <android/binder_interface_utils.h>
+
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using implementation::PreparedModelCallback;
+
+// create device test
+TEST_P(NeuralNetworksAidlTest, CreateDevice) {}
+
+// initialization
+TEST_P(NeuralNetworksAidlTest, GetCapabilitiesTest) {
+    Capabilities capabilities;
+    const auto retStatus = kDevice->getCapabilities(&capabilities);
+    ASSERT_TRUE(retStatus.isOk());
+
+    auto isPositive = [](const PerformanceInfo& perf) {
+        return perf.execTime > 0.0f && perf.powerUsage > 0.0f;
+    };
+
+    EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceScalar));
+    EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceTensor));
+    const auto& opPerf = capabilities.operandPerformance;
+    EXPECT_TRUE(
+            std::all_of(opPerf.begin(), opPerf.end(),
+                        [isPositive](const OperandPerformance& a) { return isPositive(a.info); }));
+    EXPECT_TRUE(std::is_sorted(opPerf.begin(), opPerf.end(),
+                               [](const OperandPerformance& a, const OperandPerformance& b) {
+                                   return a.type < b.type;
+                               }));
+    EXPECT_TRUE(std::all_of(opPerf.begin(), opPerf.end(), [](const OperandPerformance& a) {
+        return a.type != OperandType::SUBGRAPH;
+    }));
+    EXPECT_TRUE(isPositive(capabilities.ifPerformance));
+    EXPECT_TRUE(isPositive(capabilities.whilePerformance));
+}
+
+// detect cycle
+TEST_P(NeuralNetworksAidlTest, CycleTest) {
+    // opnd0 = TENSOR_FLOAT32            // model input
+    // opnd1 = TENSOR_FLOAT32            // model input
+    // opnd2 = INT32                     // model input
+    // opnd3 = ADD(opnd0, opnd4, opnd2)
+    // opnd4 = ADD(opnd1, opnd3, opnd2)
+    // opnd5 = ADD(opnd4, opnd0, opnd2)  // model output
+    //
+    //            +-----+
+    //            |     |
+    //            v     |
+    // 3 = ADD(0, 4, 2) |
+    // |                |
+    // +----------+     |
+    //            |     |
+    //            v     |
+    // 4 = ADD(1, 3, 2) |
+    // |                |
+    // +----------------+
+    // |
+    // |
+    // +-------+
+    //         |
+    //         v
+    // 5 = ADD(4, 0, 2)
+
+    const std::vector<Operand> operands = {
+            {
+                    // operands[0]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[1]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[2]
+                    .type = OperandType::INT32,
+                    .dimensions = {},
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[3]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[4]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[5]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::SUBGRAPH_OUTPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+    };
+
+    const std::vector<Operation> operations = {
+            {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+            {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+            {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+    };
+
+    Subgraph subgraph = {
+            .operands = operands,
+            .operations = operations,
+            .inputIndexes = {0, 1, 2},
+            .outputIndexes = {5},
+    };
+    const Model model = {
+            .main = std::move(subgraph),
+            .referenced = {},
+            .operandValues = {},
+            .pools = {},
+    };
+
+    // ensure that getSupportedOperations() checks model validity
+    std::vector<bool> supportedOps;
+    const auto supportedOpsStatus = kDevice->getSupportedOperations(model, &supportedOps);
+    ASSERT_FALSE(supportedOpsStatus.isOk());
+    ASSERT_EQ(supportedOpsStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+    ASSERT_EQ(static_cast<ErrorStatus>(supportedOpsStatus.getServiceSpecificError()),
+              ErrorStatus::INVALID_ARGUMENT);
+
+    // ensure that prepareModel() checks model validity
+    auto preparedModelCallback = ndk::SharedRefBase::make<PreparedModelCallback>();
+    auto prepareLaunchStatus =
+            kDevice->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority,
+                                  kNoDeadline, {}, {}, kEmptyCacheToken, preparedModelCallback);
+    //     Note that preparation can fail for reasons other than an
+    //     invalid model (invalid model should result in
+    //     INVALID_ARGUMENT) -- for example, perhaps not all
+    //     operations are supported, or perhaps the device hit some
+    //     kind of capacity limit.
+    ASSERT_FALSE(prepareLaunchStatus.isOk());
+    EXPECT_EQ(prepareLaunchStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+    EXPECT_NE(static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError()),
+              ErrorStatus::NONE);
+
+    EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+    EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/Callbacks.cpp b/neuralnetworks/aidl/vts/functional/Callbacks.cpp
new file mode 100644
index 0000000..ca2bb48
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Callbacks.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "Callbacks"
+
+#include "Callbacks.h"
+
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <limits>
+
+namespace aidl::android::hardware::neuralnetworks::implementation {
+
+ndk::ScopedAStatus PreparedModelCallback::notify(
+        ErrorStatus errorStatus, const std::shared_ptr<IPreparedModel>& preparedModel) {
+    {
+        std::lock_guard<std::mutex> hold(mMutex);
+        // quick-return if object has already been notified
+        if (mNotified) {
+            return ndk::ScopedAStatus::ok();
+        }
+        // store results and mark as notified
+        mErrorStatus = errorStatus;
+        mPreparedModel = preparedModel;
+        mNotified = true;
+    }
+    mCondition.notify_all();
+    return ndk::ScopedAStatus::ok();
+}
+
+void PreparedModelCallback::wait() const {
+    std::unique_lock<std::mutex> lock(mMutex);
+    mCondition.wait(lock, [this] { return mNotified; });
+}
+
+ErrorStatus PreparedModelCallback::getStatus() const {
+    wait();
+    return mErrorStatus;
+}
+
+std::shared_ptr<IPreparedModel> PreparedModelCallback::getPreparedModel() const {
+    wait();
+    return mPreparedModel;
+}
+
+}  // namespace aidl::android::hardware::neuralnetworks::implementation
diff --git a/neuralnetworks/aidl/vts/functional/Callbacks.h b/neuralnetworks/aidl/vts/functional/Callbacks.h
new file mode 100644
index 0000000..0eb4d5f
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Callbacks.h
@@ -0,0 +1,131 @@
+/*
+ * 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_NEURALNETWORKS_AIDL_CALLBACKS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_CALLBACKS_H
+
+#include <android-base/thread_annotations.h>
+#include <condition_variable>
+#include <mutex>
+
+#include <aidl/android/hardware/neuralnetworks/BnPreparedModelCallback.h>
+#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
+#include <aidl/android/hardware/neuralnetworks/IPreparedModel.h>
+
+/*
+ * The Callback classes are used internally by the NeuralNetworks runtime to
+ * synchronize between different threads. An asynchronous task is launched
+ * paired with a callback object. When a client thread requires the output being
+ * generated by the asynchronous task, the client thread can wait for the result
+ * and be blocked until it has completed. Any wait may safely be called
+ * concurrently, even on the same callback object. When the asynchronous task
+ * has finished its workload, it must immediately call "notify". If the
+ * asynchronous task has failed to launch, the function that tried to launch the
+ * asynchronous task must immediately call "notify". This "notify" call
+ * awakens any client threads waiting on the callback object.
+ *
+ * These classes exist to enable synchronization across AIDL. When
+ * synchronization is only required in the same process, consider using
+ * std::future, std::mutex, std::condition_variable, or std::experimental::latch
+ * instead.
+ */
+
+namespace aidl::android::hardware::neuralnetworks::implementation {
+
+/**
+ * The PreparedModelCallback class is used to receive the error status of
+ * preparing a model as well as the prepared model from a task executing
+ * asynchronously with respect to the runtime. If a calling thread calls wait
+ * or get* on a PreparedModelCallback object and the corresponding asynchronous
+ * task has not finished preparing the model, the calling thread will block
+ * until the asynchronous task has called notify.
+ *
+ * If the callback object is notified more than once, only the results of the
+ * first call to notify are used, and the results from subsequent calls are
+ * discarded.
+ *
+ * This callback object is passed as an argument to IDevice::prepareModel*.
+ */
+class PreparedModelCallback : public BnPreparedModelCallback {
+  public:
+    /**
+     * IPreparedModelCallback::notify marks the callback object with the return
+     * status of the asynchronous model preparation along with the prepared
+     * model, and allows all prior and future wait calls on the
+     * PreparedModelCallback object to proceed.
+     *
+     * IPreparedModelCallback::notify must be called on a given PreparedModelCallback object.
+     *
+     * If the callback object is notified more than once, only the results of
+     * the first call to notify are used, and the results from subsequent calls
+     * are discarded.
+     *
+     * @param status Error status returned from asynchronously preparing the
+     *     model; will be:
+     *     - NONE if the asynchronous preparation was successful
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     *     - INVALID_ARGUMENT if the input model is invalid
+     * @param preparedModel Returned model that has been prepared for execution,
+     *     nullptr if the model was unable to be prepared.
+     */
+    ndk::ScopedAStatus notify(ErrorStatus status,
+                              const std::shared_ptr<IPreparedModel>& preparedModel) override;
+
+    /**
+     * PreparedModelCallback::wait blocks until notify has been called on the
+     * callback object.
+     */
+    void wait() const;
+
+    /**
+     * Retrieves the error status returned from the asynchronous task launched
+     * by IDevice::prepareModel*. If IDevice::prepareModel* has not finished
+     * asynchronously preparing the model, this call will block until the
+     * asynchronous task notifies the object.
+     *
+     * @return status Error status returned from asynchronously preparing the
+     *     model; will be:
+     *     - NONE if the asynchronous preparation was successful
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     *     - INVALID_ARGUMENT if the input model is invalid
+     */
+    ErrorStatus getStatus() const;
+
+    /**
+     * Retrieves the model that has been prepared for execution from the
+     * asynchronous task launched by IDevice::prepareModel*. If
+     * IDevice::prepareModel* has not finished asynchronously preparing the
+     * model, this call will block until the asynchronous task notifies the
+     * object.
+     *
+     * @return preparedModel Returned model that has been prepared for
+     *     execution, nullptr if the model was unable to be prepared.
+     */
+    std::shared_ptr<IPreparedModel> getPreparedModel() const;
+
+  private:
+    mutable std::mutex mMutex;
+    mutable std::condition_variable mCondition;
+    bool mNotified GUARDED_BY(mMutex) = false;
+    ErrorStatus mErrorStatus = ErrorStatus::GENERAL_FAILURE;
+    std::shared_ptr<IPreparedModel> mPreparedModel;
+};
+
+}  // namespace aidl::android::hardware::neuralnetworks::implementation
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_CALLBACKS_H
diff --git a/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp b/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp
new file mode 100644
index 0000000..e0b529f
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp
@@ -0,0 +1,1177 @@
+/*
+ * 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 "neuralnetworks_aidl_hal_test"
+
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_status.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <gtest/gtest.h>
+#include <hidlmemory/mapping.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <iterator>
+#include <random>
+#include <thread>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "MemoryUtils.h"
+#include "TestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+// Forward declaration of the mobilenet generated test models in
+// frameworks/ml/nn/runtime/test/generated/.
+namespace generated_tests::mobilenet_224_gender_basic_fixed {
+const test_helper::TestModel& get_test_model();
+}  // namespace generated_tests::mobilenet_224_gender_basic_fixed
+
+namespace generated_tests::mobilenet_quantized {
+const test_helper::TestModel& get_test_model();
+}  // namespace generated_tests::mobilenet_quantized
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using namespace test_helper;
+using implementation::PreparedModelCallback;
+
+namespace float32_model {
+
+constexpr auto get_test_model = generated_tests::mobilenet_224_gender_basic_fixed::get_test_model;
+
+}  // namespace float32_model
+
+namespace quant8_model {
+
+constexpr auto get_test_model = generated_tests::mobilenet_quantized::get_test_model;
+
+}  // namespace quant8_model
+
+namespace {
+
+enum class AccessMode { READ_WRITE, READ_ONLY, WRITE_ONLY };
+
+// Creates cache handles based on provided file groups.
+// The outer vector corresponds to handles and the inner vector is for fds held by each handle.
+void createCacheFds(const std::vector<std::string>& files, const std::vector<AccessMode>& mode,
+                    std::vector<ndk::ScopedFileDescriptor>* fds) {
+    fds->clear();
+    fds->reserve(files.size());
+    for (uint32_t i = 0; i < files.size(); i++) {
+        const auto& file = files[i];
+        int fd;
+        if (mode[i] == AccessMode::READ_ONLY) {
+            fd = open(file.c_str(), O_RDONLY);
+        } else if (mode[i] == AccessMode::WRITE_ONLY) {
+            fd = open(file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+        } else if (mode[i] == AccessMode::READ_WRITE) {
+            fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+        } else {
+            FAIL();
+        }
+        ASSERT_GE(fd, 0);
+        fds->emplace_back(fd);
+    }
+}
+
+void createCacheFds(const std::vector<std::string>& files, AccessMode mode,
+                    std::vector<ndk::ScopedFileDescriptor>* fds) {
+    createCacheFds(files, std::vector<AccessMode>(files.size(), mode), fds);
+}
+
+// Create a chain of broadcast operations. The second operand is always constant tensor [1].
+// For simplicity, activation scalar is shared. The second operand is not shared
+// in the model to let driver maintain a non-trivial size of constant data and the corresponding
+// data locations in cache.
+//
+//                --------- activation --------
+//                ↓      ↓      ↓             ↓
+// E.g. input -> ADD -> ADD -> ADD -> ... -> ADD -> output
+//                ↑      ↑      ↑             ↑
+//               [1]    [1]    [1]           [1]
+//
+// This function assumes the operation is either ADD or MUL.
+template <typename CppType, TestOperandType operandType>
+TestModel createLargeTestModelImpl(TestOperationType op, uint32_t len) {
+    EXPECT_TRUE(op == TestOperationType::ADD || op == TestOperationType::MUL);
+
+    // Model operations and operands.
+    std::vector<TestOperation> operations(len);
+    std::vector<TestOperand> operands(len * 2 + 2);
+
+    // The activation scalar, value = 0.
+    operands[0] = {
+            .type = TestOperandType::INT32,
+            .dimensions = {},
+            .numberOfConsumers = len,
+            .scale = 0.0f,
+            .zeroPoint = 0,
+            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+            .data = TestBuffer::createFromVector<int32_t>({0}),
+    };
+
+    // The buffer value of the constant second operand. The logical value is always 1.0f.
+    CppType bufferValue;
+    // The scale of the first and second operand.
+    float scale1, scale2;
+    if (operandType == TestOperandType::TENSOR_FLOAT32) {
+        bufferValue = 1.0f;
+        scale1 = 0.0f;
+        scale2 = 0.0f;
+    } else if (op == TestOperationType::ADD) {
+        bufferValue = 1;
+        scale1 = 1.0f;
+        scale2 = 1.0f;
+    } else {
+        // To satisfy the constraint on quant8 MUL: input0.scale * input1.scale < output.scale,
+        // set input1 to have scale = 0.5f and bufferValue = 2, i.e. 1.0f in floating point.
+        bufferValue = 2;
+        scale1 = 1.0f;
+        scale2 = 0.5f;
+    }
+
+    for (uint32_t i = 0; i < len; i++) {
+        const uint32_t firstInputIndex = i * 2 + 1;
+        const uint32_t secondInputIndex = firstInputIndex + 1;
+        const uint32_t outputIndex = secondInputIndex + 1;
+
+        // The first operation input.
+        operands[firstInputIndex] = {
+                .type = operandType,
+                .dimensions = {1},
+                .numberOfConsumers = 1,
+                .scale = scale1,
+                .zeroPoint = 0,
+                .lifetime = (i == 0 ? TestOperandLifeTime::MODEL_INPUT
+                                    : TestOperandLifeTime::TEMPORARY_VARIABLE),
+                .data = (i == 0 ? TestBuffer::createFromVector<CppType>({1}) : TestBuffer()),
+        };
+
+        // The second operation input, value = 1.
+        operands[secondInputIndex] = {
+                .type = operandType,
+                .dimensions = {1},
+                .numberOfConsumers = 1,
+                .scale = scale2,
+                .zeroPoint = 0,
+                .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                .data = TestBuffer::createFromVector<CppType>({bufferValue}),
+        };
+
+        // The operation. All operations share the same activation scalar.
+        // The output operand is created as an input in the next iteration of the loop, in the case
+        // of all but the last member of the chain; and after the loop as a model output, in the
+        // case of the last member of the chain.
+        operations[i] = {
+                .type = op,
+                .inputs = {firstInputIndex, secondInputIndex, /*activation scalar*/ 0},
+                .outputs = {outputIndex},
+        };
+    }
+
+    // For TestOperationType::ADD, output = 1 + 1 * len = len + 1
+    // For TestOperationType::MUL, output = 1 * 1 ^ len = 1
+    CppType outputResult = static_cast<CppType>(op == TestOperationType::ADD ? len + 1u : 1u);
+
+    // The model output.
+    operands.back() = {
+            .type = operandType,
+            .dimensions = {1},
+            .numberOfConsumers = 0,
+            .scale = scale1,
+            .zeroPoint = 0,
+            .lifetime = TestOperandLifeTime::MODEL_OUTPUT,
+            .data = TestBuffer::createFromVector<CppType>({outputResult}),
+    };
+
+    return {
+            .main = {.operands = std::move(operands),
+                     .operations = std::move(operations),
+                     .inputIndexes = {1},
+                     .outputIndexes = {len * 2 + 1}},
+            .isRelaxed = false,
+    };
+}
+
+}  // namespace
+
+// Tag for the compilation caching tests.
+class CompilationCachingTestBase : public testing::Test {
+  protected:
+    CompilationCachingTestBase(std::shared_ptr<IDevice> device, OperandType type)
+        : kDevice(std::move(device)), kOperandType(type) {}
+
+    void SetUp() override {
+        testing::Test::SetUp();
+        ASSERT_NE(kDevice.get(), nullptr);
+
+        // Create cache directory. The cache directory and a temporary cache file is always created
+        // to test the behavior of prepareModelFromCache, even when caching is not supported.
+        char cacheDirTemp[] = "/data/local/tmp/TestCompilationCachingXXXXXX";
+        char* cacheDir = mkdtemp(cacheDirTemp);
+        ASSERT_NE(cacheDir, nullptr);
+        mCacheDir = cacheDir;
+        mCacheDir.push_back('/');
+
+        NumberOfCacheFiles numCacheFiles;
+        const auto ret = kDevice->getNumberOfCacheFilesNeeded(&numCacheFiles);
+        ASSERT_TRUE(ret.isOk());
+
+        mNumModelCache = numCacheFiles.numModelCache;
+        mNumDataCache = numCacheFiles.numDataCache;
+        ASSERT_GE(mNumModelCache, 0) << "Invalid numModelCache: " << mNumModelCache;
+        ASSERT_GE(mNumDataCache, 0) << "Invalid numDataCache: " << mNumDataCache;
+        mIsCachingSupported = mNumModelCache > 0 || mNumDataCache > 0;
+
+        // Create empty cache files.
+        mTmpCache = mCacheDir + "tmp";
+        for (uint32_t i = 0; i < mNumModelCache; i++) {
+            mModelCache.push_back({mCacheDir + "model" + std::to_string(i)});
+        }
+        for (uint32_t i = 0; i < mNumDataCache; i++) {
+            mDataCache.push_back({mCacheDir + "data" + std::to_string(i)});
+        }
+        // Placeholder handles, use AccessMode::WRITE_ONLY for createCacheFds to create files.
+        std::vector<ndk::ScopedFileDescriptor> modelHandle, dataHandle, tmpHandle;
+        createCacheFds(mModelCache, AccessMode::WRITE_ONLY, &modelHandle);
+        createCacheFds(mDataCache, AccessMode::WRITE_ONLY, &dataHandle);
+        createCacheFds({mTmpCache}, AccessMode::WRITE_ONLY, &tmpHandle);
+
+        if (!mIsCachingSupported) {
+            LOG(INFO) << "NN VTS: Early termination of test because vendor service does not "
+                         "support compilation caching.";
+            std::cout << "[          ]   Early termination of test because vendor service does not "
+                         "support compilation caching."
+                      << std::endl;
+        }
+    }
+
+    void TearDown() override {
+        // If the test passes, remove the tmp directory.  Otherwise, keep it for debugging purposes.
+        if (!testing::Test::HasFailure()) {
+            // Recursively remove the cache directory specified by mCacheDir.
+            auto callback = [](const char* entry, const struct stat*, int, struct FTW*) {
+                return remove(entry);
+            };
+            nftw(mCacheDir.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
+        }
+        testing::Test::TearDown();
+    }
+
+    // Model and examples creators. According to kOperandType, the following methods will return
+    // either float32 model/examples or the quant8 variant.
+    TestModel createTestModel() {
+        if (kOperandType == OperandType::TENSOR_FLOAT32) {
+            return float32_model::get_test_model();
+        } else {
+            return quant8_model::get_test_model();
+        }
+    }
+
+    TestModel createLargeTestModel(OperationType op, uint32_t len) {
+        if (kOperandType == OperandType::TENSOR_FLOAT32) {
+            return createLargeTestModelImpl<float, TestOperandType::TENSOR_FLOAT32>(
+                    static_cast<TestOperationType>(op), len);
+        } else {
+            return createLargeTestModelImpl<uint8_t, TestOperandType::TENSOR_QUANT8_ASYMM>(
+                    static_cast<TestOperationType>(op), len);
+        }
+    }
+
+    // See if the service can handle the model.
+    bool isModelFullySupported(const Model& model) {
+        std::vector<bool> supportedOps;
+        const auto supportedCall = kDevice->getSupportedOperations(model, &supportedOps);
+        EXPECT_TRUE(supportedCall.isOk());
+        EXPECT_EQ(supportedOps.size(), model.main.operations.size());
+        if (!supportedCall.isOk() || supportedOps.size() != model.main.operations.size()) {
+            return false;
+        }
+        return std::all_of(supportedOps.begin(), supportedOps.end(),
+                           [](bool valid) { return valid; });
+    }
+
+    void saveModelToCache(const Model& model,
+                          const std::vector<ndk::ScopedFileDescriptor>& modelCache,
+                          const std::vector<ndk::ScopedFileDescriptor>& dataCache,
+                          std::shared_ptr<IPreparedModel>* preparedModel = nullptr) {
+        if (preparedModel != nullptr) *preparedModel = nullptr;
+
+        // Launch prepare model.
+        std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+                ndk::SharedRefBase::make<PreparedModelCallback>();
+        std::vector<uint8_t> cacheToken(std::begin(mToken), std::end(mToken));
+        const auto prepareLaunchStatus = kDevice->prepareModel(
+                model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority, kNoDeadline,
+                modelCache, dataCache, cacheToken, preparedModelCallback);
+        ASSERT_TRUE(prepareLaunchStatus.isOk());
+
+        // Retrieve prepared model.
+        preparedModelCallback->wait();
+        ASSERT_EQ(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+        if (preparedModel != nullptr) {
+            *preparedModel = preparedModelCallback->getPreparedModel();
+        }
+    }
+
+    bool checkEarlyTermination(ErrorStatus status) {
+        if (status == ErrorStatus::GENERAL_FAILURE) {
+            LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+                         "save the prepared model that it does not support.";
+            std::cout << "[          ]   Early termination of test because vendor service cannot "
+                         "save the prepared model that it does not support."
+                      << std::endl;
+            return true;
+        }
+        return false;
+    }
+
+    bool checkEarlyTermination(const Model& model) {
+        if (!isModelFullySupported(model)) {
+            LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+                         "prepare model that it does not support.";
+            std::cout << "[          ]   Early termination of test because vendor service cannot "
+                         "prepare model that it does not support."
+                      << std::endl;
+            return true;
+        }
+        return false;
+    }
+
+    void prepareModelFromCache(const std::vector<ndk::ScopedFileDescriptor>& modelCache,
+                               const std::vector<ndk::ScopedFileDescriptor>& dataCache,
+                               std::shared_ptr<IPreparedModel>* preparedModel,
+                               ErrorStatus* status) {
+        // Launch prepare model from cache.
+        std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+                ndk::SharedRefBase::make<PreparedModelCallback>();
+        std::vector<uint8_t> cacheToken(std::begin(mToken), std::end(mToken));
+        const auto prepareLaunchStatus = kDevice->prepareModelFromCache(
+                kNoDeadline, modelCache, dataCache, cacheToken, preparedModelCallback);
+        ASSERT_TRUE(prepareLaunchStatus.isOk() ||
+                    prepareLaunchStatus.getExceptionCode() == EX_SERVICE_SPECIFIC)
+                << "prepareLaunchStatus: " << prepareLaunchStatus.getDescription();
+        if (!prepareLaunchStatus.isOk()) {
+            *preparedModel = nullptr;
+            *status = static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError());
+            return;
+        }
+
+        // Retrieve prepared model.
+        preparedModelCallback->wait();
+        *status = preparedModelCallback->getStatus();
+        *preparedModel = preparedModelCallback->getPreparedModel();
+    }
+
+    // Absolute path to the temporary cache directory.
+    std::string mCacheDir;
+
+    // Groups of file paths for model and data cache in the tmp cache directory, initialized with
+    // size = mNum{Model|Data}Cache. The outer vector corresponds to handles and the inner vector is
+    // for fds held by each handle.
+    std::vector<std::string> mModelCache;
+    std::vector<std::string> mDataCache;
+
+    // A separate temporary file path in the tmp cache directory.
+    std::string mTmpCache;
+
+    uint8_t mToken[static_cast<uint32_t>(IDevice::BYTE_SIZE_OF_CACHE_TOKEN)] = {};
+    uint32_t mNumModelCache;
+    uint32_t mNumDataCache;
+    uint32_t mIsCachingSupported;
+
+    const std::shared_ptr<IDevice> kDevice;
+    // The primary data type of the testModel.
+    const OperandType kOperandType;
+};
+
+using CompilationCachingTestParam = std::tuple<NamedDevice, OperandType>;
+
+// A parameterized fixture of CompilationCachingTestBase. Every test will run twice, with the first
+// pass running with float32 models and the second pass running with quant8 models.
+class CompilationCachingTest : public CompilationCachingTestBase,
+                               public testing::WithParamInterface<CompilationCachingTestParam> {
+  protected:
+    CompilationCachingTest()
+        : CompilationCachingTestBase(getData(std::get<NamedDevice>(GetParam())),
+                                     std::get<OperandType>(GetParam())) {}
+};
+
+TEST_P(CompilationCachingTest, CacheSavingAndRetrieval) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+    std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+
+    // Save the compilation to cache.
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(model, modelCache, dataCache);
+    }
+
+    // Retrieve preparedModel from cache.
+    {
+        preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (!mIsCachingSupported) {
+            ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+            ASSERT_EQ(preparedModel, nullptr);
+            return;
+        } else if (checkEarlyTermination(status)) {
+            ASSERT_EQ(preparedModel, nullptr);
+            return;
+        } else {
+            ASSERT_EQ(status, ErrorStatus::NONE);
+            ASSERT_NE(preparedModel, nullptr);
+        }
+    }
+
+    // Execute and verify results.
+    EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+}
+
+TEST_P(CompilationCachingTest, CacheSavingAndRetrievalNonZeroOffset) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+    std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+
+    // Save the compilation to cache.
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        uint8_t placeholderBytes[] = {0, 0};
+        // Write a placeholder integer to the cache.
+        // The driver should be able to handle non-empty cache and non-zero fd offset.
+        for (uint32_t i = 0; i < modelCache.size(); i++) {
+            ASSERT_EQ(write(modelCache[i].get(), &placeholderBytes, sizeof(placeholderBytes)),
+                      sizeof(placeholderBytes));
+        }
+        for (uint32_t i = 0; i < dataCache.size(); i++) {
+            ASSERT_EQ(write(dataCache[i].get(), &placeholderBytes, sizeof(placeholderBytes)),
+                      sizeof(placeholderBytes));
+        }
+        saveModelToCache(model, modelCache, dataCache);
+    }
+
+    // Retrieve preparedModel from cache.
+    {
+        preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        uint8_t placeholderByte = 0;
+        // Advance the offset of each handle by one byte.
+        // The driver should be able to handle non-zero fd offset.
+        for (uint32_t i = 0; i < modelCache.size(); i++) {
+            ASSERT_GE(read(modelCache[i].get(), &placeholderByte, 1), 0);
+        }
+        for (uint32_t i = 0; i < dataCache.size(); i++) {
+            ASSERT_GE(read(dataCache[i].get(), &placeholderByte, 1), 0);
+        }
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (!mIsCachingSupported) {
+            ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+            ASSERT_EQ(preparedModel, nullptr);
+            return;
+        } else if (checkEarlyTermination(status)) {
+            ASSERT_EQ(preparedModel, nullptr);
+            return;
+        } else {
+            ASSERT_EQ(status, ErrorStatus::NONE);
+            ASSERT_NE(preparedModel, nullptr);
+        }
+    }
+
+    // Execute and verify results.
+    EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+}
+
+TEST_P(CompilationCachingTest, SaveToCacheInvalidNumCache) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+
+    // Test with number of model cache files greater than mNumModelCache.
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        // Pass an additional cache file for model cache.
+        mModelCache.push_back({mTmpCache});
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache.pop_back();
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+        // Check if prepareModelFromCache fails.
+        preparedModel = nullptr;
+        ErrorStatus status;
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::INVALID_ARGUMENT) {
+            ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Test with number of model cache files smaller than mNumModelCache.
+    if (mModelCache.size() > 0) {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        // Pop out the last cache file.
+        auto tmp = mModelCache.back();
+        mModelCache.pop_back();
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache.push_back(tmp);
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+        // Check if prepareModelFromCache fails.
+        preparedModel = nullptr;
+        ErrorStatus status;
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::INVALID_ARGUMENT) {
+            ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Test with number of data cache files greater than mNumDataCache.
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        // Pass an additional cache file for data cache.
+        mDataCache.push_back({mTmpCache});
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache.pop_back();
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+        // Check if prepareModelFromCache fails.
+        preparedModel = nullptr;
+        ErrorStatus status;
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::INVALID_ARGUMENT) {
+            ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Test with number of data cache files smaller than mNumDataCache.
+    if (mDataCache.size() > 0) {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        // Pop out the last cache file.
+        auto tmp = mDataCache.back();
+        mDataCache.pop_back();
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache.push_back(tmp);
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+        // Check if prepareModelFromCache fails.
+        preparedModel = nullptr;
+        ErrorStatus status;
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::INVALID_ARGUMENT) {
+            ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+}
+
+TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidNumCache) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+
+    // Save the compilation to cache.
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(model, modelCache, dataCache);
+    }
+
+    // Test with number of model cache files greater than mNumModelCache.
+    {
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        mModelCache.push_back({mTmpCache});
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache.pop_back();
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::GENERAL_FAILURE) {
+            ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Test with number of model cache files smaller than mNumModelCache.
+    if (mModelCache.size() > 0) {
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        auto tmp = mModelCache.back();
+        mModelCache.pop_back();
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache.push_back(tmp);
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::GENERAL_FAILURE) {
+            ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Test with number of data cache files greater than mNumDataCache.
+    {
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        mDataCache.push_back({mTmpCache});
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache.pop_back();
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::GENERAL_FAILURE) {
+            ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Test with number of data cache files smaller than mNumDataCache.
+    if (mDataCache.size() > 0) {
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        auto tmp = mDataCache.back();
+        mDataCache.pop_back();
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache.push_back(tmp);
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::GENERAL_FAILURE) {
+            ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+}
+
+TEST_P(CompilationCachingTest, SaveToCacheInvalidAccessMode) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+    std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE);
+    std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE);
+
+    // Go through each handle in model cache, test with invalid access mode.
+    for (uint32_t i = 0; i < mNumModelCache; i++) {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        modelCacheMode[i] = AccessMode::READ_ONLY;
+        createCacheFds(mModelCache, modelCacheMode, &modelCache);
+        createCacheFds(mDataCache, dataCacheMode, &dataCache);
+        modelCacheMode[i] = AccessMode::READ_WRITE;
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+        // Check if prepareModelFromCache fails.
+        preparedModel = nullptr;
+        ErrorStatus status;
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::INVALID_ARGUMENT) {
+            ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Go through each handle in data cache, test with invalid access mode.
+    for (uint32_t i = 0; i < mNumDataCache; i++) {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        dataCacheMode[i] = AccessMode::READ_ONLY;
+        createCacheFds(mModelCache, modelCacheMode, &modelCache);
+        createCacheFds(mDataCache, dataCacheMode, &dataCache);
+        dataCacheMode[i] = AccessMode::READ_WRITE;
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+        // Check if prepareModelFromCache fails.
+        preparedModel = nullptr;
+        ErrorStatus status;
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::INVALID_ARGUMENT) {
+            ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+}
+
+TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidAccessMode) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+    std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE);
+    std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE);
+
+    // Save the compilation to cache.
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(model, modelCache, dataCache);
+    }
+
+    // Go through each handle in model cache, test with invalid access mode.
+    for (uint32_t i = 0; i < mNumModelCache; i++) {
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        modelCacheMode[i] = AccessMode::WRITE_ONLY;
+        createCacheFds(mModelCache, modelCacheMode, &modelCache);
+        createCacheFds(mDataCache, dataCacheMode, &dataCache);
+        modelCacheMode[i] = AccessMode::READ_WRITE;
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Go through each handle in data cache, test with invalid access mode.
+    for (uint32_t i = 0; i < mNumDataCache; i++) {
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        dataCacheMode[i] = AccessMode::WRITE_ONLY;
+        createCacheFds(mModelCache, modelCacheMode, &modelCache);
+        createCacheFds(mDataCache, dataCacheMode, &dataCache);
+        dataCacheMode[i] = AccessMode::READ_WRITE;
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+}
+
+// Copy file contents between files.
+// The vector sizes must match.
+static void copyCacheFiles(const std::vector<std::string>& from,
+                           const std::vector<std::string>& to) {
+    constexpr size_t kBufferSize = 1000000;
+    uint8_t buffer[kBufferSize];
+
+    ASSERT_EQ(from.size(), to.size());
+    for (uint32_t i = 0; i < from.size(); i++) {
+        int fromFd = open(from[i].c_str(), O_RDONLY);
+        int toFd = open(to[i].c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+        ASSERT_GE(fromFd, 0);
+        ASSERT_GE(toFd, 0);
+
+        ssize_t readBytes;
+        while ((readBytes = read(fromFd, &buffer, kBufferSize)) > 0) {
+            ASSERT_EQ(write(toFd, &buffer, readBytes), readBytes);
+        }
+        ASSERT_GE(readBytes, 0);
+
+        close(fromFd);
+        close(toFd);
+    }
+}
+
+// Number of operations in the large test model.
+constexpr uint32_t kLargeModelSize = 100;
+constexpr uint32_t kNumIterationsTOCTOU = 100;
+
+TEST_P(CompilationCachingTest, SaveToCache_TOCTOU) {
+    if (!mIsCachingSupported) return;
+
+    // Create test models and check if fully supported by the service.
+    const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+    const Model modelMul = createModel(testModelMul);
+    if (checkEarlyTermination(modelMul)) return;
+    const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+    const Model modelAdd = createModel(testModelAdd);
+    if (checkEarlyTermination(modelAdd)) return;
+
+    // Save the modelMul compilation to cache.
+    auto modelCacheMul = mModelCache;
+    for (auto& cache : modelCacheMul) {
+        cache.append("_mul");
+    }
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(modelMul, modelCache, dataCache);
+    }
+
+    // Use a different token for modelAdd.
+    mToken[0]++;
+
+    // This test is probabilistic, so we run it multiple times.
+    for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) {
+        // Save the modelAdd compilation to cache.
+        {
+            std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+            createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+
+            // Spawn a thread to copy the cache content concurrently while saving to cache.
+            std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache));
+            saveModelToCache(modelAdd, modelCache, dataCache);
+            thread.join();
+        }
+
+        // Retrieve preparedModel from cache.
+        {
+            std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+            ErrorStatus status;
+            std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+            createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+            prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+
+            // The preparation may fail or succeed, but must not crash. If the preparation succeeds,
+            // the prepared model must be executed with the correct result and not crash.
+            if (status != ErrorStatus::NONE) {
+                ASSERT_EQ(preparedModel, nullptr);
+            } else {
+                ASSERT_NE(preparedModel, nullptr);
+                EvaluatePreparedModel(kDevice, preparedModel, testModelAdd,
+                                      /*testKind=*/TestKind::GENERAL);
+            }
+        }
+    }
+}
+
+TEST_P(CompilationCachingTest, PrepareFromCache_TOCTOU) {
+    if (!mIsCachingSupported) return;
+
+    // Create test models and check if fully supported by the service.
+    const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+    const Model modelMul = createModel(testModelMul);
+    if (checkEarlyTermination(modelMul)) return;
+    const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+    const Model modelAdd = createModel(testModelAdd);
+    if (checkEarlyTermination(modelAdd)) return;
+
+    // Save the modelMul compilation to cache.
+    auto modelCacheMul = mModelCache;
+    for (auto& cache : modelCacheMul) {
+        cache.append("_mul");
+    }
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(modelMul, modelCache, dataCache);
+    }
+
+    // Use a different token for modelAdd.
+    mToken[0]++;
+
+    // This test is probabilistic, so we run it multiple times.
+    for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) {
+        // Save the modelAdd compilation to cache.
+        {
+            std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+            createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+            saveModelToCache(modelAdd, modelCache, dataCache);
+        }
+
+        // Retrieve preparedModel from cache.
+        {
+            std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+            ErrorStatus status;
+            std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+            createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+
+            // Spawn a thread to copy the cache content concurrently while preparing from cache.
+            std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache));
+            prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+            thread.join();
+
+            // The preparation may fail or succeed, but must not crash. If the preparation succeeds,
+            // the prepared model must be executed with the correct result and not crash.
+            if (status != ErrorStatus::NONE) {
+                ASSERT_EQ(preparedModel, nullptr);
+            } else {
+                ASSERT_NE(preparedModel, nullptr);
+                EvaluatePreparedModel(kDevice, preparedModel, testModelAdd,
+                                      /*testKind=*/TestKind::GENERAL);
+            }
+        }
+    }
+}
+
+TEST_P(CompilationCachingTest, ReplaceSecuritySensitiveCache) {
+    if (!mIsCachingSupported) return;
+
+    // Create test models and check if fully supported by the service.
+    const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+    const Model modelMul = createModel(testModelMul);
+    if (checkEarlyTermination(modelMul)) return;
+    const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+    const Model modelAdd = createModel(testModelAdd);
+    if (checkEarlyTermination(modelAdd)) return;
+
+    // Save the modelMul compilation to cache.
+    auto modelCacheMul = mModelCache;
+    for (auto& cache : modelCacheMul) {
+        cache.append("_mul");
+    }
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(modelMul, modelCache, dataCache);
+    }
+
+    // Use a different token for modelAdd.
+    mToken[0]++;
+
+    // Save the modelAdd compilation to cache.
+    {
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(modelAdd, modelCache, dataCache);
+    }
+
+    // Replace the model cache of modelAdd with modelMul.
+    copyCacheFiles(modelCacheMul, mModelCache);
+
+    // Retrieve the preparedModel from cache, expect failure.
+    {
+        std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+        createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+}
+
+// TODO(b/179270601): restore kNamedDeviceChoices.
+static const auto kOperandTypeChoices =
+        testing::Values(OperandType::TENSOR_FLOAT32, OperandType::TENSOR_QUANT8_ASYMM);
+
+std::string printCompilationCachingTest(
+        const testing::TestParamInfo<CompilationCachingTestParam>& info) {
+    const auto& [namedDevice, operandType] = info.param;
+    const std::string type = (operandType == OperandType::TENSOR_FLOAT32 ? "float32" : "quant8");
+    return gtestCompliantName(getName(namedDevice) + "_" + type);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CompilationCachingTest);
+INSTANTIATE_TEST_SUITE_P(TestCompilationCaching, CompilationCachingTest,
+                         testing::Combine(testing::ValuesIn(getNamedDevices()),
+                                          kOperandTypeChoices),
+                         printCompilationCachingTest);
+
+using CompilationCachingSecurityTestParam = std::tuple<NamedDevice, OperandType, uint32_t>;
+
+class CompilationCachingSecurityTest
+    : public CompilationCachingTestBase,
+      public testing::WithParamInterface<CompilationCachingSecurityTestParam> {
+  protected:
+    CompilationCachingSecurityTest()
+        : CompilationCachingTestBase(getData(std::get<NamedDevice>(GetParam())),
+                                     std::get<OperandType>(GetParam())) {}
+
+    void SetUp() {
+        CompilationCachingTestBase::SetUp();
+        generator.seed(kSeed);
+    }
+
+    // Get a random integer within a closed range [lower, upper].
+    template <typename T>
+    T getRandomInt(T lower, T upper) {
+        std::uniform_int_distribution<T> dis(lower, upper);
+        return dis(generator);
+    }
+
+    // Randomly flip one single bit of the cache entry.
+    void flipOneBitOfCache(const std::string& filename, bool* skip) {
+        FILE* pFile = fopen(filename.c_str(), "r+");
+        ASSERT_EQ(fseek(pFile, 0, SEEK_END), 0);
+        long int fileSize = ftell(pFile);
+        if (fileSize == 0) {
+            fclose(pFile);
+            *skip = true;
+            return;
+        }
+        ASSERT_EQ(fseek(pFile, getRandomInt(0l, fileSize - 1), SEEK_SET), 0);
+        int readByte = fgetc(pFile);
+        ASSERT_NE(readByte, EOF);
+        ASSERT_EQ(fseek(pFile, -1, SEEK_CUR), 0);
+        ASSERT_NE(fputc(static_cast<uint8_t>(readByte) ^ (1U << getRandomInt(0, 7)), pFile), EOF);
+        fclose(pFile);
+        *skip = false;
+    }
+
+    // Randomly append bytes to the cache entry.
+    void appendBytesToCache(const std::string& filename, bool* skip) {
+        FILE* pFile = fopen(filename.c_str(), "a");
+        uint32_t appendLength = getRandomInt(1, 256);
+        for (uint32_t i = 0; i < appendLength; i++) {
+            ASSERT_NE(fputc(getRandomInt<uint8_t>(0, 255), pFile), EOF);
+        }
+        fclose(pFile);
+        *skip = false;
+    }
+
+    enum class ExpectedResult { GENERAL_FAILURE, NOT_CRASH };
+
+    // Test if the driver behaves as expected when given corrupted cache or token.
+    // The modifier will be invoked after save to cache but before prepare from cache.
+    // The modifier accepts one pointer argument "skip" as the returning value, indicating
+    // whether the test should be skipped or not.
+    void testCorruptedCache(ExpectedResult expected, std::function<void(bool*)> modifier) {
+        const TestModel& testModel = createTestModel();
+        const Model model = createModel(testModel);
+        if (checkEarlyTermination(model)) return;
+
+        // Save the compilation to cache.
+        {
+            std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+            createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+            saveModelToCache(model, modelCache, dataCache);
+        }
+
+        bool skip = false;
+        modifier(&skip);
+        if (skip) return;
+
+        // Retrieve preparedModel from cache.
+        {
+            std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+            ErrorStatus status;
+            std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+            createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+            prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+
+            switch (expected) {
+                case ExpectedResult::GENERAL_FAILURE:
+                    ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+                    ASSERT_EQ(preparedModel, nullptr);
+                    break;
+                case ExpectedResult::NOT_CRASH:
+                    ASSERT_EQ(preparedModel == nullptr, status != ErrorStatus::NONE);
+                    break;
+                default:
+                    FAIL();
+            }
+        }
+    }
+
+    const uint32_t kSeed = std::get<uint32_t>(GetParam());
+    std::mt19937 generator;
+};
+
+TEST_P(CompilationCachingSecurityTest, CorruptedModelCache) {
+    if (!mIsCachingSupported) return;
+    for (uint32_t i = 0; i < mNumModelCache; i++) {
+        testCorruptedCache(ExpectedResult::GENERAL_FAILURE,
+                           [this, i](bool* skip) { flipOneBitOfCache(mModelCache[i], skip); });
+    }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongLengthModelCache) {
+    if (!mIsCachingSupported) return;
+    for (uint32_t i = 0; i < mNumModelCache; i++) {
+        testCorruptedCache(ExpectedResult::GENERAL_FAILURE,
+                           [this, i](bool* skip) { appendBytesToCache(mModelCache[i], skip); });
+    }
+}
+
+TEST_P(CompilationCachingSecurityTest, CorruptedDataCache) {
+    if (!mIsCachingSupported) return;
+    for (uint32_t i = 0; i < mNumDataCache; i++) {
+        testCorruptedCache(ExpectedResult::NOT_CRASH,
+                           [this, i](bool* skip) { flipOneBitOfCache(mDataCache[i], skip); });
+    }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongLengthDataCache) {
+    if (!mIsCachingSupported) return;
+    for (uint32_t i = 0; i < mNumDataCache; i++) {
+        testCorruptedCache(ExpectedResult::NOT_CRASH,
+                           [this, i](bool* skip) { appendBytesToCache(mDataCache[i], skip); });
+    }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongToken) {
+    if (!mIsCachingSupported) return;
+    testCorruptedCache(ExpectedResult::GENERAL_FAILURE, [this](bool* skip) {
+        // Randomly flip one single bit in mToken.
+        uint32_t ind =
+                getRandomInt(0u, static_cast<uint32_t>(IDevice::BYTE_SIZE_OF_CACHE_TOKEN) - 1);
+        mToken[ind] ^= (1U << getRandomInt(0, 7));
+        *skip = false;
+    });
+}
+
+std::string printCompilationCachingSecurityTest(
+        const testing::TestParamInfo<CompilationCachingSecurityTestParam>& info) {
+    const auto& [namedDevice, operandType, seed] = info.param;
+    const std::string type = (operandType == OperandType::TENSOR_FLOAT32 ? "float32" : "quant8");
+    return gtestCompliantName(getName(namedDevice) + "_" + type + "_" + std::to_string(seed));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CompilationCachingSecurityTest);
+INSTANTIATE_TEST_SUITE_P(TestCompilationCaching, CompilationCachingSecurityTest,
+                         testing::Combine(testing::ValuesIn(getNamedDevices()), kOperandTypeChoices,
+                                          testing::Range(0U, 10U)),
+                         printCompilationCachingSecurityTest);
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
new file mode 100644
index 0000000..4beb828
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
@@ -0,0 +1,925 @@
+/*
+ * 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 "GeneratedTestHarness.h"
+
+#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/sync.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <chrono>
+#include <iostream>
+#include <iterator>
+#include <numeric>
+#include <vector>
+
+#include <MemoryUtils.h>
+#include <android/binder_status.h>
+#include <nnapi/Result.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+#include "Callbacks.h"
+#include "TestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+namespace nn = ::android::nn;
+using namespace test_helper;
+using implementation::PreparedModelCallback;
+
+namespace {
+
+enum class OutputType { FULLY_SPECIFIED, UNSPECIFIED, INSUFFICIENT, MISSED_DEADLINE };
+
+struct TestConfig {
+    Executor executor;
+    bool measureTiming;
+    OutputType outputType;
+    MemoryType memoryType;
+    // `reportSkipping` indicates if a test should print an info message in case
+    // it is skipped. The field is set to true by default and is set to false in
+    // quantization coupling tests to suppress skipping a test
+    bool reportSkipping;
+    TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType)
+        : executor(executor),
+          measureTiming(measureTiming),
+          outputType(outputType),
+          memoryType(memoryType),
+          reportSkipping(true) {}
+    TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType,
+               bool reportSkipping)
+        : executor(executor),
+          measureTiming(measureTiming),
+          outputType(outputType),
+          memoryType(memoryType),
+          reportSkipping(reportSkipping) {}
+};
+
+enum class IOType { INPUT, OUTPUT };
+
+class DeviceMemoryAllocator {
+  public:
+    DeviceMemoryAllocator(const std::shared_ptr<IDevice>& device,
+                          const std::shared_ptr<IPreparedModel>& preparedModel,
+                          const TestModel& testModel)
+        : kDevice(device), kPreparedModel(preparedModel), kTestModel(testModel) {}
+
+    // Allocate device memory for a target input/output operand.
+    // Return {IBuffer object, token} if successful.
+    // Return {nullptr, 0} if device memory is not supported.
+    template <IOType ioType>
+    std::pair<std::shared_ptr<IBuffer>, int32_t> allocate(uint32_t index) {
+        std::pair<std::shared_ptr<IBuffer>, int32_t> buffer;
+        allocateInternal<ioType>(index, &buffer);
+        return buffer;
+    }
+
+  private:
+    template <IOType ioType>
+    void allocateInternal(int32_t index, std::pair<std::shared_ptr<IBuffer>, int32_t>* result) {
+        ASSERT_NE(result, nullptr);
+
+        // Prepare arguments.
+        BufferRole role = {.modelIndex = 0, .ioIndex = index, .frequency = 1.0f};
+        std::vector<BufferRole> inputRoles, outputRoles;
+        if constexpr (ioType == IOType::INPUT) {
+            inputRoles = {role};
+        } else {
+            outputRoles = {role};
+        }
+
+        // Allocate device memory.
+        DeviceBuffer buffer;
+        IPreparedModelParcel parcel;
+        parcel.preparedModel = kPreparedModel;
+        const auto ret = kDevice->allocate({}, {parcel}, inputRoles, outputRoles, &buffer);
+
+        // Check allocation results.
+        if (ret.isOk()) {
+            ASSERT_NE(buffer.buffer, nullptr);
+            ASSERT_GT(buffer.token, 0);
+        } else {
+            ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+            ASSERT_EQ(static_cast<ErrorStatus>(ret.getServiceSpecificError()),
+                      ErrorStatus::GENERAL_FAILURE);
+            buffer.buffer = nullptr;
+            buffer.token = 0;
+        }
+
+        // Initialize input data from TestBuffer.
+        if constexpr (ioType == IOType::INPUT) {
+            if (buffer.buffer != nullptr) {
+                // TestBuffer -> Shared memory.
+                const auto& testBuffer =
+                        kTestModel.main.operands[kTestModel.main.inputIndexes[index]].data;
+                ASSERT_GT(testBuffer.size(), 0);
+                const auto sharedMemory = nn::createSharedMemory(testBuffer.size()).value();
+                const auto memory = utils::convert(sharedMemory).value();
+                const auto mapping = nn::map(sharedMemory).value();
+                uint8_t* inputPtr = static_cast<uint8_t*>(std::get<void*>(mapping.pointer));
+                ASSERT_NE(inputPtr, nullptr);
+                const uint8_t* begin = testBuffer.get<uint8_t>();
+                const uint8_t* end = begin + testBuffer.size();
+                std::copy(begin, end, inputPtr);
+
+                // Shared memory -> IBuffer.
+                auto ret = buffer.buffer->copyFrom(memory, {});
+                ASSERT_TRUE(ret.isOk());
+            }
+        }
+        *result = {std::move(buffer.buffer), buffer.token};
+    }
+
+    const std::shared_ptr<IDevice> kDevice;
+    const std::shared_ptr<IPreparedModel> kPreparedModel;
+    const TestModel& kTestModel;
+};
+
+Subgraph createSubgraph(const TestSubgraph& testSubgraph, uint32_t* constCopySize,
+                        std::vector<const TestBuffer*>* constCopies, uint32_t* constRefSize,
+                        std::vector<const TestBuffer*>* constReferences) {
+    CHECK(constCopySize != nullptr);
+    CHECK(constCopies != nullptr);
+    CHECK(constRefSize != nullptr);
+    CHECK(constReferences != nullptr);
+
+    // Operands.
+    std::vector<Operand> operands(testSubgraph.operands.size());
+    for (uint32_t i = 0; i < testSubgraph.operands.size(); i++) {
+        const auto& op = testSubgraph.operands[i];
+
+        DataLocation loc = {};
+        if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) {
+            loc = {
+                    .poolIndex = 0,
+                    .offset = *constCopySize,
+                    .length = static_cast<int64_t>(op.data.size()),
+            };
+            constCopies->push_back(&op.data);
+            *constCopySize += op.data.alignedSize();
+        } else if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) {
+            loc = {
+                    .poolIndex = 0,
+                    .offset = *constRefSize,
+                    .length = static_cast<int64_t>(op.data.size()),
+            };
+            constReferences->push_back(&op.data);
+            *constRefSize += op.data.alignedSize();
+        } else if (op.lifetime == TestOperandLifeTime::SUBGRAPH) {
+            loc = {
+                    .poolIndex = 0,
+                    .offset = *op.data.get<uint32_t>(),
+                    .length = 0,
+            };
+        }
+
+        std::optional<OperandExtraParams> extraParams;
+        if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
+            using Tag = OperandExtraParams::Tag;
+            extraParams = OperandExtraParams::make<Tag::channelQuant>(SymmPerChannelQuantParams{
+                    .scales = op.channelQuant.scales,
+                    .channelDim = static_cast<int32_t>(op.channelQuant.channelDim)});
+        }
+
+        operands[i] = {.type = static_cast<OperandType>(op.type),
+                       .dimensions = utils::toSigned(op.dimensions).value(),
+                       .scale = op.scale,
+                       .zeroPoint = op.zeroPoint,
+                       .lifetime = static_cast<OperandLifeTime>(op.lifetime),
+                       .location = loc,
+                       .extraParams = std::move(extraParams)};
+    }
+
+    // Operations.
+    std::vector<Operation> operations(testSubgraph.operations.size());
+    std::transform(testSubgraph.operations.begin(), testSubgraph.operations.end(),
+                   operations.begin(), [](const TestOperation& op) -> Operation {
+                       return {.type = static_cast<OperationType>(op.type),
+                               .inputs = utils::toSigned(op.inputs).value(),
+                               .outputs = utils::toSigned(op.outputs).value()};
+                   });
+
+    return {.operands = std::move(operands),
+            .operations = std::move(operations),
+            .inputIndexes = utils::toSigned(testSubgraph.inputIndexes).value(),
+            .outputIndexes = utils::toSigned(testSubgraph.outputIndexes).value()};
+}
+
+void copyTestBuffers(const std::vector<const TestBuffer*>& buffers, uint8_t* output) {
+    uint32_t offset = 0;
+    for (const TestBuffer* buffer : buffers) {
+        const uint8_t* begin = buffer->get<uint8_t>();
+        const uint8_t* end = begin + buffer->size();
+        std::copy(begin, end, output + offset);
+        offset += buffer->alignedSize();
+    }
+}
+
+}  // namespace
+
+void waitForSyncFence(int syncFd) {
+    constexpr int kInfiniteTimeout = -1;
+    ASSERT_GT(syncFd, 0);
+    int r = sync_wait(syncFd, kInfiniteTimeout);
+    ASSERT_GE(r, 0);
+}
+
+Model createModel(const TestModel& testModel) {
+    uint32_t constCopySize = 0;
+    uint32_t constRefSize = 0;
+    std::vector<const TestBuffer*> constCopies;
+    std::vector<const TestBuffer*> constReferences;
+
+    Subgraph mainSubgraph = createSubgraph(testModel.main, &constCopySize, &constCopies,
+                                           &constRefSize, &constReferences);
+    std::vector<Subgraph> refSubgraphs(testModel.referenced.size());
+    std::transform(testModel.referenced.begin(), testModel.referenced.end(), refSubgraphs.begin(),
+                   [&constCopySize, &constCopies, &constRefSize,
+                    &constReferences](const TestSubgraph& testSubgraph) {
+                       return createSubgraph(testSubgraph, &constCopySize, &constCopies,
+                                             &constRefSize, &constReferences);
+                   });
+
+    // Constant copies.
+    std::vector<uint8_t> operandValues(constCopySize);
+    copyTestBuffers(constCopies, operandValues.data());
+
+    // Shared memory.
+    std::vector<nn::SharedMemory> pools = {};
+    if (constRefSize > 0) {
+        const auto pool = nn::createSharedMemory(constRefSize).value();
+        pools.push_back(pool);
+
+        // load data
+        const auto mappedMemory = nn::map(pool).value();
+        uint8_t* mappedPtr = static_cast<uint8_t*>(std::get<void*>(mappedMemory.pointer));
+        CHECK(mappedPtr != nullptr);
+
+        copyTestBuffers(constReferences, mappedPtr);
+    }
+
+    std::vector<Memory> aidlPools;
+    aidlPools.reserve(pools.size());
+    for (auto& pool : pools) {
+        auto aidlPool = utils::convert(pool).value();
+        aidlPools.push_back(std::move(aidlPool));
+    }
+
+    return {.main = std::move(mainSubgraph),
+            .referenced = std::move(refSubgraphs),
+            .operandValues = std::move(operandValues),
+            .pools = std::move(aidlPools),
+            .relaxComputationFloat32toFloat16 = testModel.isRelaxed};
+}
+
+static bool isOutputSizeGreaterThanOne(const TestModel& testModel, uint32_t index) {
+    const auto byteSize = testModel.main.operands[testModel.main.outputIndexes[index]].data.size();
+    return byteSize > 1u;
+}
+
+static void makeOutputInsufficientSize(uint32_t outputIndex, Request* request) {
+    auto& length = request->outputs[outputIndex].location.length;
+    ASSERT_GT(length, 1u);
+    length -= 1u;
+}
+
+static void makeOutputDimensionsUnspecified(Model* model) {
+    for (auto i : model->main.outputIndexes) {
+        auto& dims = model->main.operands[i].dimensions;
+        std::fill(dims.begin(), dims.end(), 0);
+    }
+}
+
+// Manages the lifetime of memory resources used in an execution.
+class ExecutionContext {
+  public:
+    ExecutionContext(std::shared_ptr<IDevice> device, std::shared_ptr<IPreparedModel> preparedModel)
+        : kDevice(std::move(device)), kPreparedModel(std::move(preparedModel)) {}
+
+    std::optional<Request> createRequest(const TestModel& testModel, MemoryType memoryType);
+    std::vector<TestBuffer> getOutputBuffers(const TestModel& testModel,
+                                             const Request& request) const;
+
+  private:
+    // Get a TestBuffer with data copied from an IBuffer object.
+    void getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
+                   TestBuffer* testBuffer) const;
+
+    static constexpr uint32_t kInputPoolIndex = 0;
+    static constexpr uint32_t kOutputPoolIndex = 1;
+    static constexpr uint32_t kDeviceMemoryBeginIndex = 2;
+
+    const std::shared_ptr<IDevice> kDevice;
+    const std::shared_ptr<IPreparedModel> kPreparedModel;
+    std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory;
+    std::vector<std::shared_ptr<IBuffer>> mBuffers;
+};
+
+std::optional<Request> ExecutionContext::createRequest(const TestModel& testModel,
+                                                       MemoryType memoryType) {
+    // Memory pools are organized as:
+    // - 0: Input shared memory pool
+    // - 1: Output shared memory pool
+    // - [2, 2+i): Input device memories
+    // - [2+i, 2+i+o): Output device memories
+    DeviceMemoryAllocator allocator(kDevice, kPreparedModel, testModel);
+    std::vector<int32_t> tokens;
+    mBuffers.clear();
+
+    // Model inputs.
+    std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
+    size_t inputSize = 0;
+    for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+        const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
+        if (op.data.size() == 0) {
+            // Omitted input.
+            inputs[i] = {.hasNoValue = true};
+            continue;
+        } else if (memoryType == MemoryType::DEVICE) {
+            SCOPED_TRACE("Input index = " + std::to_string(i));
+            auto [buffer, token] = allocator.allocate<IOType::INPUT>(i);
+            if (buffer != nullptr) {
+                DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
+                                                                      kDeviceMemoryBeginIndex)};
+                mBuffers.push_back(std::move(buffer));
+                tokens.push_back(token);
+                inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+                continue;
+            }
+        }
+
+        // Reserve shared memory for input.
+        DataLocation loc = {.poolIndex = kInputPoolIndex,
+                            .offset = static_cast<int64_t>(inputSize),
+                            .length = static_cast<int64_t>(op.data.size())};
+        inputSize += op.data.alignedSize();
+        inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+    }
+
+    // Model outputs.
+    std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
+    size_t outputSize = 0;
+    for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
+        const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
+        if (memoryType == MemoryType::DEVICE) {
+            SCOPED_TRACE("Output index = " + std::to_string(i));
+            auto [buffer, token] = allocator.allocate<IOType::OUTPUT>(i);
+            if (buffer != nullptr) {
+                DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
+                                                                      kDeviceMemoryBeginIndex)};
+                mBuffers.push_back(std::move(buffer));
+                tokens.push_back(token);
+                outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+                continue;
+            }
+        }
+
+        // In the case of zero-sized output, we should at least provide a one-byte buffer.
+        // This is because zero-sized tensors are only supported internally to the driver, or
+        // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
+        // tensor as model output. Otherwise, we will have two semantic conflicts:
+        // - "Zero dimension" conflicts with "unspecified dimension".
+        // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
+        size_t bufferSize = std::max<size_t>(op.data.size(), 1);
+
+        // Reserve shared memory for output.
+        DataLocation loc = {.poolIndex = kOutputPoolIndex,
+                            .offset = static_cast<int64_t>(outputSize),
+                            .length = static_cast<int64_t>(bufferSize)};
+        outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
+        outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+    }
+
+    if (memoryType == MemoryType::DEVICE && mBuffers.empty()) {
+        return std::nullopt;
+    }
+
+    // Memory pools.
+    if (memoryType == MemoryType::BLOB_AHWB) {
+        mInputMemory = TestBlobAHWB::create(std::max<size_t>(inputSize, 1));
+        mOutputMemory = TestBlobAHWB::create(std::max<size_t>(outputSize, 1));
+    } else {
+        mInputMemory = TestAshmem::create(std::max<size_t>(inputSize, 1));
+        mOutputMemory = TestAshmem::create(std::max<size_t>(outputSize, 1));
+    }
+    CHECK_NE(mInputMemory, nullptr);
+    CHECK_NE(mOutputMemory, nullptr);
+    std::vector<RequestMemoryPool> pools;
+    pools.reserve(kDeviceMemoryBeginIndex + mBuffers.size());
+
+    auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
+    CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
+    auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
+    CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
+
+    pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
+            std::move(copiedInputMemory).value()));
+    pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
+            std::move(copiedOutputMemory).value()));
+    for (const auto& token : tokens) {
+        pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::token>(token));
+    }
+
+    // Copy input data to the input shared memory pool.
+    uint8_t* inputPtr = mInputMemory->getPointer();
+    for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+        if (!inputs[i].hasNoValue && inputs[i].location.poolIndex == kInputPoolIndex) {
+            const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
+            const uint8_t* begin = op.data.get<uint8_t>();
+            const uint8_t* end = begin + op.data.size();
+            std::copy(begin, end, inputPtr + inputs[i].location.offset);
+        }
+    }
+    return Request{
+            .inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
+}
+
+std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const TestModel& testModel,
+                                                           const Request& request) const {
+    // Copy out output results.
+    uint8_t* outputPtr = mOutputMemory->getPointer();
+    std::vector<TestBuffer> outputBuffers;
+    for (uint32_t i = 0; i < request.outputs.size(); i++) {
+        const auto& outputLoc = request.outputs[i].location;
+        if (outputLoc.poolIndex == kOutputPoolIndex) {
+            outputBuffers.emplace_back(outputLoc.length, outputPtr + outputLoc.offset);
+        } else {
+            const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
+            if (op.data.size() == 0) {
+                outputBuffers.emplace_back(0, nullptr);
+            } else {
+                SCOPED_TRACE("Output index = " + std::to_string(i));
+                const uint32_t bufferIndex = outputLoc.poolIndex - kDeviceMemoryBeginIndex;
+                TestBuffer buffer;
+                getBuffer(mBuffers[bufferIndex], op.data.size(), &buffer);
+                outputBuffers.push_back(std::move(buffer));
+            }
+        }
+    }
+    return outputBuffers;
+}
+
+// Get a TestBuffer with data copied from an IBuffer object.
+void ExecutionContext::getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
+                                 TestBuffer* testBuffer) const {
+    // IBuffer -> Shared memory.
+    auto sharedMemory = nn::createSharedMemory(size).value();
+    auto aidlMemory = utils::convert(sharedMemory).value();
+    const auto ret = buffer->copyTo(aidlMemory);
+    ASSERT_TRUE(ret.isOk());
+
+    // Shared memory -> TestBuffer.
+    const auto outputMemory = nn::map(sharedMemory).value();
+    const uint8_t* outputPtr = std::visit(
+            [](auto* ptr) { return static_cast<const uint8_t*>(ptr); }, outputMemory.pointer);
+    ASSERT_NE(outputPtr, nullptr);
+    ASSERT_NE(testBuffer, nullptr);
+    *testBuffer = TestBuffer(size, outputPtr);
+}
+
+static bool hasZeroSizedOutput(const TestModel& testModel) {
+    return std::any_of(testModel.main.outputIndexes.begin(), testModel.main.outputIndexes.end(),
+                       [&testModel](uint32_t index) {
+                           return testModel.main.operands[index].data.size() == 0;
+                       });
+}
+
+void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
+                           const std::shared_ptr<IPreparedModel>& preparedModel,
+                           const TestModel& testModel, const TestConfig& testConfig,
+                           bool* skipped = nullptr) {
+    if (skipped != nullptr) {
+        *skipped = false;
+    }
+    // If output0 does not have size larger than one byte, we can not test with insufficient buffer.
+    if (testConfig.outputType == OutputType::INSUFFICIENT &&
+        !isOutputSizeGreaterThanOne(testModel, 0)) {
+        return;
+    }
+
+    ExecutionContext context(device, preparedModel);
+    auto maybeRequest = context.createRequest(testModel, testConfig.memoryType);
+    // Skip if testing memory domain but no device memory has been allocated.
+    if (!maybeRequest.has_value()) {
+        return;
+    }
+
+    Request request = std::move(maybeRequest).value();
+
+    constexpr uint32_t kInsufficientOutputIndex = 0;
+    if (testConfig.outputType == OutputType::INSUFFICIENT) {
+        makeOutputInsufficientSize(kInsufficientOutputIndex, &request);
+    }
+
+    int64_t loopTimeoutDuration = kOmittedTimeoutDuration;
+    // OutputType::MISSED_DEADLINE is only used by
+    // TestKind::INTINITE_LOOP_TIMEOUT tests to verify that an infinite loop is
+    // aborted after a timeout.
+    if (testConfig.outputType == OutputType::MISSED_DEADLINE) {
+        // Override the default loop timeout duration with a small value to
+        // speed up test execution.
+        constexpr int64_t kMillisecond = 1'000'000;
+        loopTimeoutDuration = 1 * kMillisecond;
+    }
+
+    ErrorStatus executionStatus;
+    std::vector<OutputShape> outputShapes;
+    Timing timing = kNoTiming;
+    switch (testConfig.executor) {
+        case Executor::SYNC: {
+            SCOPED_TRACE("synchronous");
+
+            ExecutionResult executionResult;
+            // execute
+            const auto ret = preparedModel->executeSynchronously(request, testConfig.measureTiming,
+                                                                 kNoDeadline, loopTimeoutDuration,
+                                                                 &executionResult);
+            ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
+                    << ret.getDescription();
+            if (ret.isOk()) {
+                executionStatus = executionResult.outputSufficientSize
+                                          ? ErrorStatus::NONE
+                                          : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
+                outputShapes = std::move(executionResult.outputShapes);
+                timing = executionResult.timing;
+            } else {
+                executionStatus = static_cast<ErrorStatus>(ret.getServiceSpecificError());
+            }
+            break;
+        }
+        case Executor::FENCED: {
+            SCOPED_TRACE("fenced");
+            ErrorStatus result = ErrorStatus::NONE;
+            ndk::ScopedFileDescriptor syncFenceFd;
+            std::shared_ptr<IFencedExecutionCallback> fencedCallback;
+            auto ret = preparedModel->executeFenced(request, {}, testConfig.measureTiming,
+                                                    kNoDeadline, loopTimeoutDuration, kNoDuration,
+                                                    &syncFenceFd, &fencedCallback);
+            ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
+                    << ret.getDescription();
+            if (!ret.isOk()) {
+                result = static_cast<ErrorStatus>(ret.getServiceSpecificError());
+                executionStatus = result;
+            } else if (syncFenceFd.get() != -1) {
+                std::vector<ndk::ScopedFileDescriptor> waitFor;
+                auto dupFd = dup(syncFenceFd.get());
+                ASSERT_NE(dupFd, -1);
+                waitFor.emplace_back(dupFd);
+                // If a sync fence is returned, try start another run waiting for the sync fence.
+                ret = preparedModel->executeFenced(request, waitFor, testConfig.measureTiming,
+                                                   kNoDeadline, loopTimeoutDuration, kNoDuration,
+                                                   &syncFenceFd, &fencedCallback);
+                ASSERT_TRUE(ret.isOk());
+                waitForSyncFence(syncFenceFd.get());
+            }
+            if (result == ErrorStatus::NONE) {
+                ASSERT_NE(fencedCallback, nullptr);
+                Timing timingFenced;
+                auto ret =
+                        fencedCallback->getExecutionInfo(&timing, &timingFenced, &executionStatus);
+                ASSERT_TRUE(ret.isOk());
+            }
+            break;
+        }
+        default: {
+            FAIL() << "Unsupported execution mode for AIDL interface.";
+        }
+    }
+
+    if (testConfig.outputType != OutputType::FULLY_SPECIFIED &&
+        executionStatus == ErrorStatus::GENERAL_FAILURE) {
+        if (skipped != nullptr) {
+            *skipped = true;
+        }
+        if (!testConfig.reportSkipping) {
+            return;
+        }
+        LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+                     "execute model that it does not support.";
+        std::cout << "[          ]   Early termination of test because vendor service cannot "
+                     "execute model that it does not support."
+                  << std::endl;
+        GTEST_SKIP();
+    }
+    if (!testConfig.measureTiming) {
+        EXPECT_EQ(timing, kNoTiming);
+    } else {
+        if (timing.timeOnDevice != -1 && timing.timeInDriver != -1) {
+            EXPECT_LE(timing.timeOnDevice, timing.timeInDriver);
+        }
+    }
+
+    switch (testConfig.outputType) {
+        case OutputType::FULLY_SPECIFIED:
+            if (testConfig.executor == Executor::FENCED && hasZeroSizedOutput(testModel)) {
+                // Executor::FENCED does not support zero-sized output.
+                ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
+                return;
+            }
+            // If the model output operands are fully specified, outputShapes must be either
+            // either empty, or have the same number of elements as the number of outputs.
+            ASSERT_EQ(ErrorStatus::NONE, executionStatus);
+            ASSERT_TRUE(outputShapes.size() == 0 ||
+                        outputShapes.size() == testModel.main.outputIndexes.size());
+            break;
+        case OutputType::UNSPECIFIED:
+            if (testConfig.executor == Executor::FENCED) {
+                // For Executor::FENCED, the output shape must be fully specified.
+                ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
+                return;
+            }
+            // If the model output operands are not fully specified, outputShapes must have
+            // the same number of elements as the number of outputs.
+            ASSERT_EQ(ErrorStatus::NONE, executionStatus);
+            ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
+            break;
+        case OutputType::INSUFFICIENT:
+            if (testConfig.executor == Executor::FENCED) {
+                // For Executor::FENCED, the output shape must be fully specified.
+                ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
+                return;
+            }
+            ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus);
+            ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
+            // Check that all returned output dimensions are at least as fully specified as the
+            // union of the information about the corresponding operand in the model and in the
+            // request. In this test, all model outputs have known rank with all dimensions
+            // unspecified, and no dimensional information is provided in the request.
+            for (uint32_t i = 0; i < outputShapes.size(); i++) {
+                ASSERT_EQ(outputShapes[i].isSufficient, i != kInsufficientOutputIndex);
+                const auto& actual = outputShapes[i].dimensions;
+                const auto& golden =
+                        testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
+                ASSERT_EQ(actual.size(), golden.size());
+                for (uint32_t j = 0; j < actual.size(); j++) {
+                    if (actual[j] == 0) continue;
+                    EXPECT_EQ(actual[j], golden[j]) << "index: " << j;
+                }
+            }
+            return;
+        case OutputType::MISSED_DEADLINE:
+            ASSERT_TRUE(executionStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
+                        executionStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT)
+                    << "executionStatus = " << executionStatus;
+            return;
+    }
+
+    // Go through all outputs, check returned output shapes.
+    for (uint32_t i = 0; i < outputShapes.size(); i++) {
+        EXPECT_TRUE(outputShapes[i].isSufficient);
+        const auto& expect = testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
+        const auto unsignedActual = nn::toUnsigned(outputShapes[i].dimensions);
+        ASSERT_TRUE(unsignedActual.has_value());
+        const std::vector<uint32_t>& actual = unsignedActual.value();
+        EXPECT_EQ(expect, actual);
+    }
+
+    // Retrieve execution results.
+    const std::vector<TestBuffer> outputs = context.getOutputBuffers(testModel, request);
+
+    // We want "close-enough" results.
+    checkResults(testModel, outputs);
+}
+
+void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
+                           const std::shared_ptr<IPreparedModel>& preparedModel,
+                           const TestModel& testModel, TestKind testKind) {
+    std::vector<OutputType> outputTypesList;
+    std::vector<bool> measureTimingList;
+    std::vector<Executor> executorList;
+    std::vector<MemoryType> memoryTypeList;
+
+    switch (testKind) {
+        case TestKind::GENERAL: {
+            outputTypesList = {OutputType::FULLY_SPECIFIED};
+            measureTimingList = {false, true};
+            executorList = {Executor::SYNC};
+            memoryTypeList = {MemoryType::ASHMEM};
+        } break;
+        case TestKind::DYNAMIC_SHAPE: {
+            outputTypesList = {OutputType::UNSPECIFIED, OutputType::INSUFFICIENT};
+            measureTimingList = {false, true};
+            executorList = {Executor::SYNC, Executor::FENCED};
+            memoryTypeList = {MemoryType::ASHMEM};
+        } break;
+        case TestKind::MEMORY_DOMAIN: {
+            outputTypesList = {OutputType::FULLY_SPECIFIED};
+            measureTimingList = {false};
+            executorList = {Executor::SYNC, Executor::FENCED};
+            memoryTypeList = {MemoryType::BLOB_AHWB, MemoryType::DEVICE};
+        } break;
+        case TestKind::FENCED_COMPUTE: {
+            outputTypesList = {OutputType::FULLY_SPECIFIED};
+            measureTimingList = {false, true};
+            executorList = {Executor::FENCED};
+            memoryTypeList = {MemoryType::ASHMEM};
+        } break;
+        case TestKind::QUANTIZATION_COUPLING: {
+            LOG(FATAL) << "Wrong TestKind for EvaluatePreparedModel";
+            return;
+        } break;
+        case TestKind::INTINITE_LOOP_TIMEOUT: {
+            outputTypesList = {OutputType::MISSED_DEADLINE};
+            measureTimingList = {false, true};
+            executorList = {Executor::SYNC, Executor::FENCED};
+            memoryTypeList = {MemoryType::ASHMEM};
+        } break;
+    }
+
+    for (const OutputType outputType : outputTypesList) {
+        for (const bool measureTiming : measureTimingList) {
+            for (const Executor executor : executorList) {
+                for (const MemoryType memoryType : memoryTypeList) {
+                    const TestConfig testConfig(executor, measureTiming, outputType, memoryType);
+                    EvaluatePreparedModel(device, preparedModel, testModel, testConfig);
+                }
+            }
+        }
+    }
+}
+
+void EvaluatePreparedCoupledModels(const std::shared_ptr<IDevice>& device,
+                                   const std::shared_ptr<IPreparedModel>& preparedModel,
+                                   const TestModel& testModel,
+                                   const std::shared_ptr<IPreparedModel>& preparedCoupledModel,
+                                   const TestModel& coupledModel) {
+    const std::vector<OutputType> outputTypesList = {OutputType::FULLY_SPECIFIED};
+    const std::vector<bool> measureTimingList = {false, true};
+    const std::vector<Executor> executorList = {Executor::SYNC, Executor::FENCED};
+
+    for (const OutputType outputType : outputTypesList) {
+        for (const bool measureTiming : measureTimingList) {
+            for (const Executor executor : executorList) {
+                const TestConfig testConfig(executor, measureTiming, outputType, MemoryType::ASHMEM,
+                                            /*reportSkipping=*/false);
+                bool baseSkipped = false;
+                EvaluatePreparedModel(device, preparedModel, testModel, testConfig, &baseSkipped);
+                bool coupledSkipped = false;
+                EvaluatePreparedModel(device, preparedCoupledModel, coupledModel, testConfig,
+                                      &coupledSkipped);
+                ASSERT_EQ(baseSkipped, coupledSkipped);
+                if (baseSkipped) {
+                    LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+                                 "execute model that it does not support.";
+                    std::cout << "[          ]   Early termination of test because vendor service "
+                                 "cannot "
+                                 "execute model that it does not support."
+                              << std::endl;
+                    GTEST_SKIP();
+                }
+            }
+        }
+    }
+}
+
+void Execute(const std::shared_ptr<IDevice>& device, const TestModel& testModel,
+             TestKind testKind) {
+    Model model = createModel(testModel);
+    if (testKind == TestKind::DYNAMIC_SHAPE) {
+        makeOutputDimensionsUnspecified(&model);
+    }
+
+    std::shared_ptr<IPreparedModel> preparedModel;
+    switch (testKind) {
+        case TestKind::GENERAL:
+        case TestKind::DYNAMIC_SHAPE:
+        case TestKind::MEMORY_DOMAIN:
+        case TestKind::FENCED_COMPUTE:
+        case TestKind::INTINITE_LOOP_TIMEOUT: {
+            createPreparedModel(device, model, &preparedModel);
+            if (preparedModel == nullptr) return;
+            EvaluatePreparedModel(device, preparedModel, testModel, testKind);
+        } break;
+        case TestKind::QUANTIZATION_COUPLING: {
+            ASSERT_TRUE(testModel.hasQuant8CoupledOperands());
+            createPreparedModel(device, model, &preparedModel,
+                                /*reportSkipping*/ false);
+            TestModel signedQuantizedModel = convertQuant8AsymmOperandsToSigned(testModel);
+            std::shared_ptr<IPreparedModel> preparedCoupledModel;
+            createPreparedModel(device, createModel(signedQuantizedModel), &preparedCoupledModel,
+                                /*reportSkipping*/ false);
+            // If we couldn't prepare a model with unsigned quantization, we must
+            // fail to prepare a model with signed quantization as well.
+            if (preparedModel == nullptr) {
+                ASSERT_EQ(preparedCoupledModel, nullptr);
+                // If we failed to prepare both of the models, we can safely skip
+                // the test.
+                LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+                             "prepare model that it does not support.";
+                std::cout
+                        << "[          ]   Early termination of test because vendor service cannot "
+                           "prepare model that it does not support."
+                        << std::endl;
+                GTEST_SKIP();
+            }
+            ASSERT_NE(preparedCoupledModel, nullptr);
+            EvaluatePreparedCoupledModels(device, preparedModel, testModel, preparedCoupledModel,
+                                          signedQuantizedModel);
+        } break;
+    }
+}
+
+void GeneratedTestBase::SetUp() {
+    testing::TestWithParam<GeneratedTestParam>::SetUp();
+    ASSERT_NE(kDevice, nullptr);
+}
+
+std::vector<NamedModel> getNamedModels(const FilterFn& filter) {
+    return TestModelManager::get().getTestModels(filter);
+}
+
+std::vector<NamedModel> getNamedModels(const FilterNameFn& filter) {
+    return TestModelManager::get().getTestModels(filter);
+}
+
+std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info) {
+    const auto& [namedDevice, namedModel] = info.param;
+    return gtestCompliantName(getName(namedDevice) + "_" + getName(namedModel));
+}
+
+// Tag for the generated tests
+class GeneratedTest : public GeneratedTestBase {};
+
+// Tag for the dynamic output shape tests
+class DynamicOutputShapeTest : public GeneratedTest {};
+
+// Tag for the memory domain tests
+class MemoryDomainTest : public GeneratedTest {};
+
+// Tag for the fenced compute tests
+class FencedComputeTest : public GeneratedTest {};
+
+// Tag for the dynamic output shape tests
+class QuantizationCouplingTest : public GeneratedTest {};
+
+// Tag for the loop timeout tests
+class InfiniteLoopTimeoutTest : public GeneratedTest {};
+
+TEST_P(GeneratedTest, Test) {
+    Execute(kDevice, kTestModel, TestKind::GENERAL);
+}
+
+TEST_P(DynamicOutputShapeTest, Test) {
+    Execute(kDevice, kTestModel, TestKind::DYNAMIC_SHAPE);
+}
+
+TEST_P(MemoryDomainTest, Test) {
+    Execute(kDevice, kTestModel, TestKind::MEMORY_DOMAIN);
+}
+
+TEST_P(FencedComputeTest, Test) {
+    Execute(kDevice, kTestModel, TestKind::FENCED_COMPUTE);
+}
+
+TEST_P(QuantizationCouplingTest, Test) {
+    Execute(kDevice, kTestModel, TestKind::QUANTIZATION_COUPLING);
+}
+
+TEST_P(InfiniteLoopTimeoutTest, Test) {
+    Execute(kDevice, kTestModel, TestKind::INTINITE_LOOP_TIMEOUT);
+}
+
+INSTANTIATE_GENERATED_TEST(GeneratedTest,
+                           [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest, [](const TestModel& testModel) {
+    return !testModel.expectFailure && !testModel.hasScalarOutputs();
+});
+
+INSTANTIATE_GENERATED_TEST(MemoryDomainTest,
+                           [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+INSTANTIATE_GENERATED_TEST(FencedComputeTest,
+                           [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) {
+    return !testModel.expectFailure && testModel.hasQuant8CoupledOperands() &&
+           testModel.main.operations.size() == 1;
+});
+
+INSTANTIATE_GENERATED_TEST(InfiniteLoopTimeoutTest, [](const TestModel& testModel) {
+    return testModel.isInfiniteLoopTimeoutTest();
+});
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h
new file mode 100644
index 0000000..ad40f06
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.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_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H
+
+#include <functional>
+#include <vector>
+
+#include <TestHarness.h>
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using NamedModel = Named<const test_helper::TestModel*>;
+using GeneratedTestParam = std::tuple<NamedDevice, NamedModel>;
+
+class GeneratedTestBase : public testing::TestWithParam<GeneratedTestParam> {
+  protected:
+    void SetUp() override;
+    const std::shared_ptr<IDevice> kDevice = getData(std::get<NamedDevice>(GetParam()));
+    const test_helper::TestModel& kTestModel = *getData(std::get<NamedModel>(GetParam()));
+};
+
+using FilterFn = std::function<bool(const test_helper::TestModel&)>;
+std::vector<NamedModel> getNamedModels(const FilterFn& filter);
+
+using FilterNameFn = std::function<bool(const std::string&)>;
+std::vector<NamedModel> getNamedModels(const FilterNameFn& filter);
+
+std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info);
+
+#define INSTANTIATE_GENERATED_TEST(TestSuite, filter)                                     \
+    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestSuite);                             \
+    INSTANTIATE_TEST_SUITE_P(TestGenerated, TestSuite,                                    \
+                             testing::Combine(testing::ValuesIn(getNamedDevices()),       \
+                                              testing::ValuesIn(getNamedModels(filter))), \
+                             printGeneratedTest)
+
+// Tag for the validation tests, instantiated in VtsHalNeuralnetworks.cpp.
+// TODO: Clean up the hierarchy for ValidationTest.
+class ValidationTest : public GeneratedTestBase {};
+
+Model createModel(const test_helper::TestModel& testModel);
+
+void PrepareModel(const std::shared_ptr<IDevice>& device, const Model& model,
+                  std::shared_ptr<IPreparedModel>* preparedModel);
+
+enum class TestKind {
+    // Runs a test model and compares the results to a golden data
+    GENERAL,
+    // Same as GENERAL but sets dimensions for the output tensors to zeros
+    DYNAMIC_SHAPE,
+    // Same as GENERAL but use device memories for inputs and outputs
+    MEMORY_DOMAIN,
+    // Same as GENERAL but use executeFenced for exeuction
+    FENCED_COMPUTE,
+    // Tests if quantized model with TENSOR_QUANT8_ASYMM produces the same result
+    // (OK/SKIPPED/FAILED) as the model with all such tensors converted to
+    // TENSOR_QUANT8_ASYMM_SIGNED.
+    QUANTIZATION_COUPLING,
+    // Runs a test model and verifies that MISSED_DEADLINE_* is returned.
+    INTINITE_LOOP_TIMEOUT
+};
+
+void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
+                           const std::shared_ptr<IPreparedModel>& preparedModel,
+                           const test_helper::TestModel& testModel, TestKind testKind);
+
+void waitForSyncFence(int syncFd);
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H
diff --git a/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h b/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h
new file mode 100644
index 0000000..c9fd432
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h
@@ -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.
+ */
+
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+namespace aidl::android::hardware::neuralnetworks {
+
+class LogTestCaseToLogcat : public ::testing::EmptyTestEventListener {
+  public:
+    void OnTestStart(const ::testing::TestInfo& test_info) override {
+        LOG(INFO) << "[Test Case] " << test_info.test_suite_name() << "." << test_info.name()
+                  << " BEGIN";
+    }
+
+    void OnTestEnd(const ::testing::TestInfo& test_info) override {
+        LOG(INFO) << "[Test Case] " << test_info.test_suite_name() << "." << test_info.name()
+                  << " END";
+    }
+};
+
+}  // namespace aidl::android::hardware::neuralnetworks
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H
diff --git a/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
new file mode 100644
index 0000000..a37a0ca
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
@@ -0,0 +1,1176 @@
+/*
+ * 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 "neuralnetworks_aidl_hal_test"
+
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_status.h>
+#include <gtest/gtest.h>
+
+#include <LegacyUtils.h>
+#include <TestHarness.h>
+#include <Utils.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+#include "AidlHalInterfaces.h"
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "MemoryUtils.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using namespace test_helper;
+using implementation::PreparedModelCallback;
+
+namespace {
+
+// An AIDL driver is likely to support at least one of the following operand types.
+const std::vector<TestOperandType> kTestOperandTypeChoicesVector = {
+        TestOperandType::TENSOR_FLOAT32,
+        TestOperandType::TENSOR_FLOAT16,
+        TestOperandType::TENSOR_QUANT8_ASYMM,
+        TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
+};
+const auto kTestOperandTypeChoices = testing::ValuesIn(kTestOperandTypeChoicesVector);
+// TODO(b/179270601): restore kNamedDeviceChoices
+
+bool isInChoices(TestOperandType type) {
+    return std::count(kTestOperandTypeChoicesVector.begin(), kTestOperandTypeChoicesVector.end(),
+                      type) > 0;
+}
+
+bool isFloat(TestOperandType type) {
+    CHECK(isInChoices(type));
+    return type == TestOperandType::TENSOR_FLOAT32 || type == TestOperandType::TENSOR_FLOAT16;
+}
+
+// Create placeholder buffers for model constants as well as inputs and outputs.
+// We only care about the size here because we will not check accuracy in validation tests.
+void createDummyData(TestModel* testModel) {
+    for (auto& operand : testModel->main.operands) {
+        if (operand.data != nullptr) continue;
+        switch (operand.lifetime) {
+            case TestOperandLifeTime::SUBGRAPH_INPUT:
+            case TestOperandLifeTime::SUBGRAPH_OUTPUT:
+            case TestOperandLifeTime::CONSTANT_COPY:
+            case TestOperandLifeTime::CONSTANT_REFERENCE: {
+                const uint32_t size = nn::nonExtensionOperandSizeOfData(
+                        static_cast<nn::OperandType>(operand.type), operand.dimensions);
+                operand.data = TestBuffer(size);
+            } break;
+            default:
+                break;
+        }
+    }
+}
+
+TestOperand createInt32Scalar(int32_t value) {
+    return {
+            .type = TestOperandType::INT32,
+            .dimensions = {},
+            .numberOfConsumers = 1,
+            .scale = 0.0f,
+            .zeroPoint = 0,
+            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+            .data = TestBuffer::createFromVector<int32_t>({value}),
+    };
+}
+
+// Construct a test model with multiple CONV_2D operations with the given operand as inputs.
+// The dimensions of the filters are chosen to ensure outputs has the same dimensions as inputs.
+// We choose CONV_2D operation because it is commonly supported by most drivers.
+TestModel createConvModel(const TestOperand& operand, uint32_t numOperations) {
+    CHECK(isInChoices(operand.type));
+
+    TestOperand weight = {.type = operand.type,
+                          .dimensions = {operand.dimensions[3], 3, 3, operand.dimensions[3]},
+                          .numberOfConsumers = 1,
+                          .scale = isFloat(operand.type) ? 0.0f : 1.0f,
+                          .zeroPoint = 0,
+                          .lifetime = TestOperandLifeTime::CONSTANT_COPY};
+
+    TestOperand bias = {
+            .type = isFloat(operand.type) ? operand.type : TestOperandType::TENSOR_INT32,
+            .dimensions = {operand.dimensions[3]},
+            .numberOfConsumers = 1,
+            .scale = operand.scale * weight.scale,
+            .zeroPoint = 0,
+            .lifetime = TestOperandLifeTime::CONSTANT_COPY};
+
+    TestOperand output = operand;
+    output.numberOfConsumers = 0;
+    output.lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT;
+
+    const std::vector<TestOperand> operands = {
+            operand,
+            std::move(weight),
+            std::move(bias),
+            createInt32Scalar(1),  // same padding
+            createInt32Scalar(1),  // width stride
+            createInt32Scalar(1),  // height stride
+            createInt32Scalar(0),  // activation = NONE
+            std::move(output),
+    };
+
+    TestModel model;
+    for (uint32_t i = 0; i < numOperations; i++) {
+        model.main.operands.insert(model.main.operands.end(), operands.begin(), operands.end());
+        const uint32_t inputIndex = operands.size() * i;
+        const uint32_t outputIndex = inputIndex + operands.size() - 1;
+        std::vector<uint32_t> inputs(operands.size() - 1);
+        std::iota(inputs.begin(), inputs.end(), inputIndex);
+        model.main.operations.push_back({.type = TestOperationType::CONV_2D,
+                                         .inputs = std::move(inputs),
+                                         .outputs = {outputIndex}});
+        model.main.inputIndexes.push_back(inputIndex);
+        model.main.outputIndexes.push_back(outputIndex);
+    }
+    createDummyData(&model);
+    return model;
+}
+
+// Construct a test model with a single ADD operation with the given operand as input0 and input1.
+// This is to cover additional cases that the CONV_2D model does not support, e.g. arbitrary input
+// operand rank, scalar input operand. We choose ADD operation because it is commonly supported by
+// most drivers.
+TestModel createSingleAddModel(const TestOperand& operand) {
+    CHECK(isInChoices(operand.type));
+
+    TestOperand act = {
+            .type = TestOperandType::INT32,
+            .dimensions = {},
+            .numberOfConsumers = 1,
+            .scale = 0.0f,
+            .zeroPoint = 0,
+            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+    };
+
+    TestOperand output = operand;
+    output.numberOfConsumers = 0;
+    output.lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT;
+
+    TestModel model = {
+            .main =
+                    {
+                            .operands =
+                                    {
+                                            operand,
+                                            operand,
+                                            std::move(act),
+                                            output,
+                                    },
+                            .operations = {{.type = TestOperationType::ADD,
+                                            .inputs = {0, 1, 2},
+                                            .outputs = {3}}},
+                            .inputIndexes = {0, 1, 2},
+                            .outputIndexes = {3},
+                    },
+    };
+    createDummyData(&model);
+    return model;
+}
+
+// A placeholder invalid IPreparedModel class for MemoryDomainAllocateTest.InvalidPreparedModel
+class InvalidPreparedModel : public BnPreparedModel {
+  public:
+    ndk::ScopedAStatus executeSynchronously(const Request&, bool, int64_t, int64_t,
+                                            ExecutionResult*) override {
+        return ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE));
+    }
+    ndk::ScopedAStatus executeFenced(const Request&, const std::vector<ndk::ScopedFileDescriptor>&,
+                                     bool, int64_t, int64_t, int64_t, ndk::ScopedFileDescriptor*,
+                                     std::shared_ptr<IFencedExecutionCallback>*) override {
+        return ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE));
+    }
+};
+
+template <typename... Args>
+std::vector<RequestMemoryPool> createRequestMemoryPools(const Args&... pools) {
+    std::vector<RequestMemoryPool> memoryPools;
+    memoryPools.reserve(sizeof...(Args));
+    // This fold operator calls push_back on each of the function arguments.
+    (memoryPools.push_back(utils::clone(pools).value()), ...);
+    return memoryPools;
+};
+
+}  // namespace
+
+class MemoryDomainTestBase : public testing::Test {
+  protected:
+    MemoryDomainTestBase(std::shared_ptr<IDevice> device, TestOperandType type)
+        : kDevice(std::move(device)),
+          kTestOperandType(type),
+          kTestOperand(kTestOperandMap.at(type)),
+          kTestOperandDataSize(nn::nonExtensionOperandSizeOfData(static_cast<nn::OperandType>(type),
+                                                                 kTestOperand.dimensions)) {}
+
+    void SetUp() override {
+        testing::Test::SetUp();
+        ASSERT_NE(kDevice, nullptr);
+    }
+
+    std::shared_ptr<IPreparedModel> createConvPreparedModel(const TestOperand& testOperand,
+                                                            uint32_t numOperations = 1) {
+        const TestModel testModel = createConvModel(testOperand, numOperations);
+        const Model model = createModel(testModel);
+        std::shared_ptr<IPreparedModel> preparedModel;
+        createPreparedModel(kDevice, model, &preparedModel, /*reportSkipping=*/false);
+        return preparedModel;
+    }
+
+    std::shared_ptr<IPreparedModel> createAddPreparedModel(const TestOperand& testOperand) {
+        const TestModel testModel = createSingleAddModel(testOperand);
+        const Model model = createModel(testModel);
+        std::shared_ptr<IPreparedModel> preparedModel;
+        createPreparedModel(kDevice, model, &preparedModel, /*reportSkipping=*/false);
+        return preparedModel;
+    }
+
+    static const std::map<TestOperandType, TestOperand> kTestOperandMap;
+
+    const std::shared_ptr<IDevice> kDevice;
+    const TestOperandType kTestOperandType;
+    const TestOperand& kTestOperand;
+    const uint32_t kTestOperandDataSize;
+};
+
+const std::map<TestOperandType, TestOperand> MemoryDomainTestBase::kTestOperandMap = {
+        {TestOperandType::TENSOR_FLOAT32,
+         {
+                 .type = TestOperandType::TENSOR_FLOAT32,
+                 .dimensions = {1, 32, 32, 8},
+                 .numberOfConsumers = 1,
+                 .scale = 0.0f,
+                 .zeroPoint = 0,
+                 .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+         }},
+        {TestOperandType::TENSOR_FLOAT16,
+         {
+                 .type = TestOperandType::TENSOR_FLOAT16,
+                 .dimensions = {1, 32, 32, 8},
+                 .numberOfConsumers = 1,
+                 .scale = 0.0f,
+                 .zeroPoint = 0,
+                 .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+         }},
+        {TestOperandType::TENSOR_QUANT8_ASYMM,
+         {
+                 .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                 .dimensions = {1, 32, 32, 8},
+                 .numberOfConsumers = 1,
+                 .scale = 0.5f,
+                 .zeroPoint = 0,
+                 .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+         }},
+        {TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
+         {
+                 .type = TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
+                 .dimensions = {1, 32, 32, 8},
+                 .numberOfConsumers = 1,
+                 .scale = 0.5f,
+                 .zeroPoint = 0,
+                 .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+         }},
+};
+
+using MemoryDomainAllocateTestParam = std::tuple<NamedDevice, TestOperandType>;
+class MemoryDomainAllocateTest : public MemoryDomainTestBase,
+                                 public testing::WithParamInterface<MemoryDomainAllocateTestParam> {
+  protected:
+    MemoryDomainAllocateTest()
+        : MemoryDomainTestBase(getData(std::get<NamedDevice>(GetParam())),
+                               std::get<TestOperandType>(GetParam())) {}
+
+    struct AllocateTestArgs {
+        std::vector<int32_t> dimensions;
+        std::vector<std::shared_ptr<IPreparedModel>> preparedModels;
+        std::vector<BufferRole> inputRoles;
+        std::vector<BufferRole> outputRoles;
+    };
+
+    // Validation test for IDevice::allocate. The driver is expected to fail with INVALID_ARGUMENT,
+    // or GENERAL_FAILURE if memory domain is not supported.
+    void validateAllocate(AllocateTestArgs args) {
+        std::vector<IPreparedModelParcel> preparedModelParcels;
+        preparedModelParcels.reserve(args.preparedModels.size());
+        for (const auto& model : args.preparedModels) {
+            preparedModelParcels.push_back({.preparedModel = model});
+        }
+        DeviceBuffer buffer;
+        const auto ret =
+                kDevice->allocate({.dimensions = std::move(args.dimensions)}, preparedModelParcels,
+                                  args.inputRoles, args.outputRoles, &buffer);
+
+        ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+        ASSERT_TRUE(static_cast<ErrorStatus>(ret.getServiceSpecificError()) ==
+                            ErrorStatus::INVALID_ARGUMENT ||
+                    static_cast<ErrorStatus>(ret.getServiceSpecificError()) ==
+                            ErrorStatus::GENERAL_FAILURE);
+    }
+
+    void testConflictOperands(const std::shared_ptr<IPreparedModel>& model1,
+                              const std::shared_ptr<IPreparedModel>& model2) {
+        validateAllocate({
+                .preparedModels = {model1, model2},
+                .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+                               {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+        });
+        validateAllocate({
+                .preparedModels = {model1, model2},
+                .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+                .outputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+        });
+        validateAllocate({
+                .preparedModels = {model1, model2},
+                .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+                                {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+        });
+    }
+};
+
+TEST_P(MemoryDomainAllocateTest, EmptyRole) {
+    // Test with empty prepared models and roles.
+    validateAllocate({});
+
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    // Test again with non-empty prepared models but empty roles.
+    validateAllocate({
+            .preparedModels = {preparedModel},
+    });
+}
+
+TEST_P(MemoryDomainAllocateTest, NullptrPreparedModel) {
+    // Test with nullptr prepared model as input role.
+    validateAllocate({
+            .preparedModels = {nullptr},
+            .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+
+    // Test with nullptr prepared model as output role.
+    validateAllocate({
+            .preparedModels = {nullptr},
+            .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+}
+
+TEST_P(MemoryDomainAllocateTest, InvalidPreparedModel) {
+    std::shared_ptr<InvalidPreparedModel> invalidPreparedModel =
+            ndk::SharedRefBase::make<InvalidPreparedModel>();
+
+    // Test with invalid prepared model as input role.
+    validateAllocate({
+            .preparedModels = {invalidPreparedModel},
+            .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+
+    // Test with invalid prepared model as output role.
+    validateAllocate({
+            .preparedModels = {invalidPreparedModel},
+            .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+}
+
+TEST_P(MemoryDomainAllocateTest, InvalidModelIndex) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    // This should fail, because the model index is out of bound.
+    validateAllocate({
+            .preparedModels = {preparedModel},
+            .inputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+    });
+
+    // This should fail, because the model index is out of bound.
+    validateAllocate({
+            .preparedModels = {preparedModel},
+            .outputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+    });
+}
+
+TEST_P(MemoryDomainAllocateTest, InvalidIOIndex) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    // This should fail, because the model only has one input.
+    validateAllocate({
+            .preparedModels = {preparedModel},
+            .inputRoles = {{.modelIndex = 0, .ioIndex = 1, .frequency = 1.0f}},
+    });
+
+    // This should fail, because the model only has one output.
+    validateAllocate({
+            .preparedModels = {preparedModel},
+            .outputRoles = {{.modelIndex = 0, .ioIndex = 1, .frequency = 1.0f}},
+    });
+}
+
+TEST_P(MemoryDomainAllocateTest, InvalidFrequency) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    for (float invalidFreq : {10.0f, 0.0f, -0.5f}) {
+        // Test with invalid frequency for input roles.
+        validateAllocate({
+                .preparedModels = {preparedModel},
+                .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = invalidFreq}},
+        });
+        // Test with invalid frequency for output roles.
+        validateAllocate({
+                .preparedModels = {preparedModel},
+                .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = invalidFreq}},
+        });
+    }
+}
+
+TEST_P(MemoryDomainAllocateTest, SameRoleSpecifiedTwice) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    // Same role with same model index.
+    validateAllocate({
+            .preparedModels = {preparedModel},
+            .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+                           {.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+    validateAllocate({
+            .preparedModels = {preparedModel},
+            .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+                            {.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+
+    // Different model indexes, but logically referring to the same role.
+    validateAllocate({
+            .preparedModels = {preparedModel, preparedModel},
+            .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+                           {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+    });
+    validateAllocate({
+            .preparedModels = {preparedModel, preparedModel},
+            .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+                            {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+    });
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictOperandType) {
+    const std::map<TestOperandType, TestOperandType> conflictTypeMap = {
+            {TestOperandType::TENSOR_FLOAT32, TestOperandType::TENSOR_FLOAT16},
+            {TestOperandType::TENSOR_FLOAT16, TestOperandType::TENSOR_FLOAT32},
+            {TestOperandType::TENSOR_QUANT8_ASYMM, TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+            {TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, TestOperandType::TENSOR_QUANT8_ASYMM},
+    };
+
+    TestOperand conflictTestOperand = kTestOperand;
+    const auto it = conflictTypeMap.find(kTestOperandType);
+    ASSERT_FALSE(it == conflictTypeMap.end());
+    conflictTestOperand.type = it->second;
+
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
+    if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+    testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictScale) {
+    if (isFloat(kTestOperandType)) return;
+
+    TestOperand conflictTestOperand = kTestOperand;
+    ASSERT_NE(conflictTestOperand.scale, 1.0f);
+    conflictTestOperand.scale = 1.0f;
+
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
+    if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+    testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictZeroPoint) {
+    if (isFloat(kTestOperandType)) return;
+
+    TestOperand conflictTestOperand = kTestOperand;
+    ASSERT_NE(conflictTestOperand.zeroPoint, 10);
+    conflictTestOperand.zeroPoint = 10;
+
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
+    if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+    testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictRankBetweenRoles) {
+    TestOperand conflictTestOperand = kTestOperand;
+    conflictTestOperand.dimensions.pop_back();
+
+    auto preparedModel = createAddPreparedModel(kTestOperand);
+    auto conflictPreparedModel = createAddPreparedModel(conflictTestOperand);
+    if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+    testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictDimensionsBetweenRoles) {
+    TestOperand conflictTestOperand = kTestOperand;
+    conflictTestOperand.dimensions[0] = 4;
+
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
+    if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+    testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictRankBetweenRoleAndDesc) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    auto badDimensions = utils::toSigned(kTestOperand.dimensions).value();
+    badDimensions.pop_back();
+
+    validateAllocate({
+            .dimensions = badDimensions,
+            .preparedModels = {preparedModel},
+            .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+    validateAllocate({
+            .dimensions = badDimensions,
+            .preparedModels = {preparedModel},
+            .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictDimensionsBetweenRoleAndDesc) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    auto badDimensions = utils::toSigned(kTestOperand.dimensions).value();
+    badDimensions[0] = 4;
+
+    validateAllocate({
+            .dimensions = badDimensions,
+            .preparedModels = {preparedModel},
+            .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+    validateAllocate({
+            .dimensions = badDimensions,
+            .preparedModels = {preparedModel},
+            .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+    });
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictRankWithScalarRole) {
+    auto preparedModel = createAddPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    // This should fail, because the target operand is a scalar but a non-empty dimension is
+    // specified.
+    validateAllocate({
+            .dimensions = {1},
+            .preparedModels = {preparedModel},
+            .inputRoles = {{.modelIndex = 0, .ioIndex = 2, .frequency = 1.0f}},
+    });
+}
+
+std::string printMemoryDomainAllocateTest(
+        const testing::TestParamInfo<MemoryDomainAllocateTestParam>& info) {
+    const auto& [namedDevice, operandType] = info.param;
+    const std::string type = toString(static_cast<OperandType>(operandType));
+    return gtestCompliantName(getName(namedDevice) + "_" + type);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainAllocateTest);
+INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainAllocateTest,
+                         testing::Combine(testing::ValuesIn(getNamedDevices()),
+                                          kTestOperandTypeChoices),
+                         printMemoryDomainAllocateTest);
+
+class MemoryDomainCopyTestBase : public MemoryDomainTestBase {
+  protected:
+    MemoryDomainCopyTestBase(std::shared_ptr<IDevice> device, TestOperandType type)
+        : MemoryDomainTestBase(std::move(device), type) {}
+
+    // Allocates device memory for roles of a single prepared model.
+    // Returns {IBuffer, token} if success; returns {nullptr, 0} if not supported.
+    DeviceBuffer allocateBuffer(const std::shared_ptr<IPreparedModel>& preparedModel,
+                                const std::vector<int32_t>& inputIndexes,
+                                const std::vector<int32_t>& outputIndexes,
+                                const std::vector<int32_t>& dimensions) {
+        if (preparedModel == nullptr) {
+            return {.buffer = nullptr, .token = 0};
+        }
+
+        std::vector<BufferRole> inputRoles(inputIndexes.size()), outputRoles(outputIndexes.size());
+        auto trans = [](int32_t ind) -> BufferRole {
+            return {.modelIndex = 0, .ioIndex = ind, .frequency = 1.0f};
+        };
+        std::transform(inputIndexes.begin(), inputIndexes.end(), inputRoles.begin(), trans);
+        std::transform(outputIndexes.begin(), outputIndexes.end(), outputRoles.begin(), trans);
+
+        IPreparedModelParcel parcel;
+        parcel.preparedModel = preparedModel;
+
+        DeviceBuffer buffer;
+
+        const auto ret = kDevice->allocate({.dimensions = dimensions}, {parcel}, inputRoles,
+                                           outputRoles, &buffer);
+
+        if (!ret.isOk()) {
+            EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+            EXPECT_EQ(static_cast<ErrorStatus>(ret.getServiceSpecificError()),
+                      ErrorStatus::GENERAL_FAILURE);
+            return DeviceBuffer{
+                    .buffer = nullptr,
+                    .token = 0,
+            };
+        }
+
+        EXPECT_NE(buffer.buffer, nullptr);
+        EXPECT_GT(buffer.token, 0);
+
+        return buffer;
+    }
+
+    DeviceBuffer allocateBuffer(const std::shared_ptr<IPreparedModel>& preparedModel,
+                                const std::vector<int32_t>& inputIndexes,
+                                const std::vector<int32_t>& outputIndexes) {
+        return allocateBuffer(preparedModel, inputIndexes, outputIndexes, {});
+    }
+
+    Memory allocateSharedMemory(uint32_t size) {
+        const auto sharedMemory = nn::createSharedMemory(size).value();
+        auto memory = utils::convert(sharedMemory).value();
+        EXPECT_EQ(memory.size, size);
+        return memory;
+    }
+
+    void testCopyFrom(const std::shared_ptr<IBuffer>& buffer, const Memory& memory,
+                      const std::vector<int32_t>& dimensions, ErrorStatus expectedStatus) {
+        const auto ret = buffer->copyFrom(memory, dimensions);
+        if (expectedStatus == ErrorStatus::NONE) {
+            ASSERT_TRUE(ret.isOk());
+        } else {
+            ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+            ASSERT_EQ(expectedStatus, static_cast<ErrorStatus>(ret.getServiceSpecificError()));
+        }
+    }
+
+    void testCopyTo(const std::shared_ptr<IBuffer>& buffer, const Memory& memory,
+                    ErrorStatus expectedStatus) {
+        const auto ret = buffer->copyTo(memory);
+        if (expectedStatus == ErrorStatus::NONE) {
+            ASSERT_TRUE(ret.isOk());
+        } else {
+            ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+            ASSERT_EQ(expectedStatus, static_cast<ErrorStatus>(ret.getServiceSpecificError()));
+        }
+    }
+
+    void initializeDeviceMemory(const std::shared_ptr<IBuffer>& buffer) {
+        Memory memory = allocateSharedMemory(kTestOperandDataSize);
+        ASSERT_EQ(memory.size, kTestOperandDataSize);
+        testCopyFrom(buffer, memory, utils::toSigned(kTestOperand.dimensions).value(),
+                     ErrorStatus::NONE);
+    }
+};
+
+using MemoryDomainCopyTestParam = std::tuple<NamedDevice, TestOperandType>;
+class MemoryDomainCopyTest : public MemoryDomainCopyTestBase,
+                             public testing::WithParamInterface<MemoryDomainCopyTestParam> {
+  protected:
+    MemoryDomainCopyTest()
+        : MemoryDomainCopyTestBase(getData(std::get<NamedDevice>(GetParam())),
+                                   std::get<TestOperandType>(GetParam())) {}
+};
+
+TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidMemorySize) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+
+    uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
+    Memory badMemory1 = allocateSharedMemory(badMemorySize1);
+    Memory badMemory2 = allocateSharedMemory(badMemorySize2);
+    testCopyFrom(buffer, badMemory1, {}, ErrorStatus::INVALID_ARGUMENT);
+    testCopyFrom(buffer, badMemory2, {}, ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidMemorySize_DynamicShape) {
+    TestOperand testOperand = kTestOperand;
+    testOperand.dimensions[0] = 0;
+    auto preparedModel = createConvPreparedModel(testOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+
+    uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
+    Memory badMemory1 = allocateSharedMemory(badMemorySize1);
+    Memory badMemory2 = allocateSharedMemory(badMemorySize2);
+    Memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
+
+    const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value();
+    auto badDimensions = goodDimensions;
+    badDimensions[0] = 2;
+
+    testCopyFrom(buffer, badMemory1, goodDimensions, ErrorStatus::INVALID_ARGUMENT);
+    testCopyFrom(buffer, badMemory2, goodDimensions, ErrorStatus::INVALID_ARGUMENT);
+    testCopyFrom(buffer, goodMemory, goodDimensions, ErrorStatus::NONE);
+    testCopyFrom(buffer, goodMemory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidDimensions) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+
+    Memory memory = allocateSharedMemory(kTestOperandDataSize);
+
+    const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value();
+    std::vector<int32_t> badDimensions = goodDimensions;
+    badDimensions.pop_back();
+    testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+    badDimensions = goodDimensions;
+    badDimensions[0] = 2;
+    testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+    badDimensions = goodDimensions;
+    badDimensions[0] = 0;
+    testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+    testCopyFrom(buffer, memory, {}, ErrorStatus::NONE);
+    testCopyFrom(buffer, memory, goodDimensions, ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidDimensions_DynamicShape) {
+    TestOperand testOperand = kTestOperand;
+    testOperand.dimensions[0] = 0;
+    auto preparedModel = createConvPreparedModel(testOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+
+    Memory memory = allocateSharedMemory(kTestOperandDataSize);
+
+    const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value();
+    std::vector<int32_t> badDimensions = goodDimensions;
+    badDimensions.pop_back();
+    testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+    badDimensions = goodDimensions;
+    badDimensions[0] = 2;
+    badDimensions[3] = 4;
+    testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+    badDimensions = goodDimensions;
+    badDimensions[0] = 1;
+    badDimensions[3] = 0;
+    testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+    testCopyFrom(buffer, memory, {}, ErrorStatus::INVALID_ARGUMENT);
+    testCopyFrom(buffer, memory, goodDimensions, ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyTo_UninitializedMemory) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+
+    Memory memory = allocateSharedMemory(kTestOperandDataSize);
+    testCopyTo(buffer, memory, ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyTo_InvalidMemorySize) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+
+    uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
+    Memory badMemory1 = allocateSharedMemory(badMemorySize1);
+    Memory badMemory2 = allocateSharedMemory(badMemorySize2);
+    Memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
+
+    initializeDeviceMemory(buffer);
+    testCopyTo(buffer, badMemory1, ErrorStatus::INVALID_ARGUMENT);
+    testCopyTo(buffer, badMemory2, ErrorStatus::INVALID_ARGUMENT);
+    testCopyTo(buffer, goodMemory, ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyTo_InvalidMemorySize_DynamicShape) {
+    TestOperand testOperand = kTestOperand;
+    testOperand.dimensions[0] = 0;
+    auto preparedModel = createConvPreparedModel(testOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+
+    uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
+    Memory badMemory1 = allocateSharedMemory(badMemorySize1);
+    Memory badMemory2 = allocateSharedMemory(badMemorySize2);
+    Memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
+
+    initializeDeviceMemory(buffer);
+    testCopyTo(buffer, badMemory1, ErrorStatus::INVALID_ARGUMENT);
+    testCopyTo(buffer, badMemory2, ErrorStatus::INVALID_ARGUMENT);
+    testCopyTo(buffer, goodMemory, ErrorStatus::NONE);
+}
+
+std::string printMemoryDomainCopyTest(
+        const testing::TestParamInfo<MemoryDomainCopyTestParam>& info) {
+    const auto& [namedDevice, operandType] = info.param;
+    const std::string type = toString(static_cast<OperandType>(operandType));
+    return gtestCompliantName(getName(namedDevice) + "_" + type);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainCopyTest);
+INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainCopyTest,
+                         testing::Combine(testing::ValuesIn(getNamedDevices()),
+                                          kTestOperandTypeChoices),
+                         printMemoryDomainCopyTest);
+
+using MemoryDomainExecutionTestParam = std::tuple<NamedDevice, TestOperandType, Executor>;
+class MemoryDomainExecutionTest
+    : public MemoryDomainCopyTestBase,
+      public testing::WithParamInterface<MemoryDomainExecutionTestParam> {
+  protected:
+    MemoryDomainExecutionTest()
+        : MemoryDomainCopyTestBase(getData(std::get<NamedDevice>(GetParam())),
+                                   std::get<TestOperandType>(GetParam())) {}
+
+    RequestMemoryPool createSharedMemoryPool(uint32_t size) {
+        return RequestMemoryPool(allocateSharedMemory(size));
+    }
+
+    RequestMemoryPool createDeviceMemoryPool(uint32_t token) {
+        return RequestMemoryPool(static_cast<int32_t>(token));
+    }
+
+    void testExecution(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request,
+                       ErrorStatus expectedStatus) {
+        switch (kExecutor) {
+            case Executor::SYNC:
+                EXPECT_EQ(executeSync(preparedModel, request), expectedStatus);
+                break;
+            case Executor::FENCED:
+                EXPECT_EQ(executeFenced(preparedModel, request), expectedStatus);
+                break;
+            default:
+                ASSERT_TRUE(false);
+        }
+    }
+
+    ErrorStatus executeSync(const std::shared_ptr<IPreparedModel>& preparedModel,
+                            const Request& request) {
+        ExecutionResult executionResult;
+        const auto ret = preparedModel->executeSynchronously(
+                request, false, kNoDeadline, kOmittedTimeoutDuration, &executionResult);
+
+        if (!ret.isOk()) {
+            EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+            return static_cast<ErrorStatus>(ret.getServiceSpecificError());
+        }
+        const ErrorStatus executionStatus = executionResult.outputSufficientSize
+                                                    ? ErrorStatus::NONE
+                                                    : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
+        EXPECT_EQ(executionResult.timing, kNoTiming);
+        return executionStatus;
+    }
+
+    ErrorStatus executeFenced(const std::shared_ptr<IPreparedModel>& preparedModel,
+                              const Request& request) {
+        ndk::ScopedFileDescriptor syncFence;
+        std::shared_ptr<IFencedExecutionCallback> fencedCallback;
+        const auto ret = preparedModel->executeFenced(request, {}, false, kNoDeadline,
+                                                      kOmittedTimeoutDuration, kNoDuration,
+                                                      &syncFence, &fencedCallback);
+        if (!ret.isOk()) {
+            EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+            return static_cast<ErrorStatus>(ret.getServiceSpecificError());
+        }
+        if (syncFence.get() != -1) {
+            waitForSyncFence(syncFence.get());
+        }
+        EXPECT_NE(fencedCallback, nullptr);
+
+        ErrorStatus executionStatus = ErrorStatus::GENERAL_FAILURE;
+        Timing time = kNoTiming;
+        Timing timeFenced = kNoTiming;
+        const auto retExecutionInfo =
+                fencedCallback->getExecutionInfo(&time, &timeFenced, &executionStatus);
+        EXPECT_TRUE(retExecutionInfo.isOk());
+        EXPECT_EQ(time, kNoTiming);
+        return executionStatus;
+    }
+
+    const Executor kExecutor = std::get<Executor>(GetParam());
+};
+
+TEST_P(MemoryDomainExecutionTest, InvalidToken) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    if (preparedModel == nullptr) return;
+
+    RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool badDeviceMemory1 = createDeviceMemoryPool(0);    // Invalid token.
+    RequestMemoryPool badDeviceMemory2 = createDeviceMemoryPool(100);  // Unknown token.
+    RequestArgument sharedMemoryArg = {
+            .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+
+    testExecution(preparedModel,
+                  {.inputs = {deviceMemoryArg},
+                   .outputs = {sharedMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory1)},
+                  ErrorStatus::INVALID_ARGUMENT);
+    testExecution(preparedModel,
+                  {.inputs = {deviceMemoryArg},
+                   .outputs = {sharedMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory2)},
+                  ErrorStatus::INVALID_ARGUMENT);
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg},
+                   .outputs = {deviceMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory1)},
+                  ErrorStatus::INVALID_ARGUMENT);
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg},
+                   .outputs = {deviceMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory2)},
+                  ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainExecutionTest, InvalidPreparedModel) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+    auto badPreparedModel = createConvPreparedModel(kTestOperand);
+    if (badPreparedModel == nullptr) return;
+
+    RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool deviceMemory = createDeviceMemoryPool(token);
+    RequestArgument sharedMemoryArg = {
+            .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+
+    // This should fail, because the buffer is not allocated for badPreparedModel.
+    initializeDeviceMemory(buffer);
+    testExecution(badPreparedModel,
+                  {.inputs = {deviceMemoryArg},
+                   .outputs = {sharedMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+    testExecution(badPreparedModel,
+                  {.inputs = {sharedMemoryArg},
+                   .outputs = {deviceMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainExecutionTest, InvalidIOIndex) {
+    auto preparedModel = createConvPreparedModel(kTestOperand, 2);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {});
+    if (buffer == nullptr) return;
+
+    RequestMemoryPool sharedMemory1 = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool sharedMemory2 = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool sharedMemory3 = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool deviceMemory = createDeviceMemoryPool(token);
+    RequestArgument sharedMemoryArg1 = {
+            .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument sharedMemoryArg2 = {
+            .location = {.poolIndex = 1, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument sharedMemoryArg3 = {
+            .location = {.poolIndex = 2, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument deviceMemoryArg = {.location = {.poolIndex = 3}};
+
+    // This should fail, because the device memory is not allocated for input 1.
+    initializeDeviceMemory(buffer);
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg1, deviceMemoryArg},
+                   .outputs = {sharedMemoryArg2, sharedMemoryArg3},
+                   .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, sharedMemory3,
+                                                     deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+
+    // This should fail, because the device memory is not allocated for output 1.
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg1, sharedMemoryArg2},
+                   .outputs = {sharedMemoryArg3, deviceMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, sharedMemory3,
+                                                     deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainExecutionTest, InvalidIOType) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto [inputBuffer, inputToken] = allocateBuffer(preparedModel, {0}, {});
+    auto [outputBuffer, outputToken] = allocateBuffer(preparedModel, {}, {0});
+    if (inputBuffer == nullptr || outputBuffer == nullptr) return;
+
+    RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool deviceMemory = createDeviceMemoryPool(inputToken);
+    RequestArgument sharedMemoryArg = {
+            .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+
+    // This should fail, because the device memory is allocated for input but used as output.
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg},
+                   .outputs = {deviceMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+
+    // This should fail, because the device memory is allocated for output but used as input.
+    deviceMemory.set<RequestMemoryPool::Tag::token>(outputToken);
+    initializeDeviceMemory(outputBuffer);
+    testExecution(preparedModel,
+                  {.inputs = {deviceMemoryArg},
+                   .outputs = {sharedMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainExecutionTest, UninitializedMemory) {
+    auto preparedModel = createConvPreparedModel(kTestOperand);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+    if (buffer == nullptr) return;
+
+    RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool deviceMemory = createDeviceMemoryPool(token);
+    RequestArgument sharedMemoryArg = {
+            .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+
+    // This should fail, because the device memory is not initialized.
+    testExecution(preparedModel,
+                  {.inputs = {deviceMemoryArg},
+                   .outputs = {sharedMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::GENERAL_FAILURE);
+
+    // This should initialize the device memory.
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg},
+                   .outputs = {deviceMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::NONE);
+
+    // Test again with initialized device memory.
+    testExecution(preparedModel,
+                  {.inputs = {deviceMemoryArg},
+                   .outputs = {sharedMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainExecutionTest, SameRequestMultipleRoles) {
+    auto preparedModel = createConvPreparedModel(kTestOperand, 2);
+    auto [buffer, token] = allocateBuffer(preparedModel, {0, 1}, {0, 1});
+    if (buffer == nullptr) return;
+
+    RequestMemoryPool sharedMemory1 = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool sharedMemory2 = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool deviceMemory = createDeviceMemoryPool(token);
+    RequestArgument sharedMemoryArg1 = {
+            .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument sharedMemoryArg2 = {
+            .location = {.poolIndex = 1, .offset = 0, .length = kTestOperandDataSize}};
+    RequestArgument deviceMemoryArg = {.location = {.poolIndex = 2}};
+
+    // This should fail, because the same device memory cannot be used for both input and output.
+    initializeDeviceMemory(buffer);
+    testExecution(preparedModel,
+                  {.inputs = {deviceMemoryArg, sharedMemoryArg1},
+                   .outputs = {deviceMemoryArg, sharedMemoryArg2},
+                   .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+
+    // This should fail, because the same device memory cannot be used for multiple outputs.
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg1, sharedMemoryArg2},
+                   .outputs = {deviceMemoryArg, deviceMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+
+    // The same device memory can be used for multiple inputs.
+    initializeDeviceMemory(buffer);
+    testExecution(preparedModel,
+                  {.inputs = {deviceMemoryArg, deviceMemoryArg},
+                   .outputs = {sharedMemoryArg1, sharedMemoryArg2},
+                   .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)},
+                  ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainExecutionTest, InvalidDimensions) {
+    // FENCED execution does not support dynamic shape.
+    if (kExecutor == Executor::FENCED) return;
+
+    TestOperand testOperand = kTestOperand;
+    testOperand.dimensions[0] = 0;
+    auto preparedModel = createConvPreparedModel(testOperand);
+    auto deviceBuffer = allocateBuffer(preparedModel, {0}, {0},
+                                       utils::toSigned(kTestOperand.dimensions).value());
+    if (deviceBuffer.buffer == nullptr) return;
+
+    RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+    RequestMemoryPool deviceMemory = createDeviceMemoryPool(deviceBuffer.token);
+    auto badDimensions = utils::toSigned(kTestOperand.dimensions).value();
+    badDimensions[0] = 2;
+    RequestArgument sharedMemoryArg = {
+            .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize},
+            .dimensions = badDimensions};
+    RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+    RequestArgument deviceMemoryArgWithBadDimensions = {.location = {.poolIndex = 1},
+                                                        .dimensions = badDimensions};
+
+    initializeDeviceMemory(deviceBuffer.buffer);
+    testExecution(preparedModel,
+                  {.inputs = {deviceMemoryArgWithBadDimensions},
+                   .outputs = {sharedMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg},
+                   .outputs = {deviceMemoryArgWithBadDimensions},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::INVALID_ARGUMENT);
+
+    testExecution(preparedModel,
+                  {.inputs = {sharedMemoryArg},
+                   .outputs = {deviceMemoryArg},
+                   .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+                  ErrorStatus::GENERAL_FAILURE);
+}
+
+const auto kExecutorChoices = testing::Values(Executor::SYNC, Executor::FENCED);
+
+std::string printMemoryDomainExecutionTest(
+        const testing::TestParamInfo<MemoryDomainExecutionTestParam>& info) {
+    const auto& [namedDevice, operandType, executor] = info.param;
+    const std::string type = toString(static_cast<OperandType>(operandType));
+    const std::string executorStr = toString(executor);
+    return gtestCompliantName(getName(namedDevice) + "_" + type + "_" + executorStr);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainExecutionTest);
+INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainExecutionTest,
+                         testing::Combine(testing::ValuesIn(getNamedDevices()),
+                                          kTestOperandTypeChoices, kExecutorChoices),
+                         printMemoryDomainExecutionTest);
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp
new file mode 100644
index 0000000..58db98f
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_enums.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_status.h>
+
+#include <nnapi/hal/aidl/Conversions.h>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "Utils.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using implementation::PreparedModelCallback;
+using test_helper::TestBuffer;
+using test_helper::TestModel;
+
+enum class DeadlineBoundType { NOW, UNLIMITED, SHORT };
+constexpr std::array<DeadlineBoundType, 3> deadlineBounds = {
+        DeadlineBoundType::NOW, DeadlineBoundType::UNLIMITED, DeadlineBoundType::SHORT};
+std::string toString(DeadlineBoundType type) {
+    switch (type) {
+        case DeadlineBoundType::NOW:
+            return "NOW";
+        case DeadlineBoundType::UNLIMITED:
+            return "UNLIMITED";
+        case DeadlineBoundType::SHORT:
+            return "SHORT";
+    }
+    LOG(FATAL) << "Unrecognized DeadlineBoundType: " << static_cast<int>(type);
+    return {};
+}
+
+constexpr auto kShortDuration = std::chrono::milliseconds{5};
+
+using Results = std::tuple<ErrorStatus, std::vector<OutputShape>, Timing>;
+using MaybeResults = std::optional<Results>;
+
+static int64_t makeDeadline(DeadlineBoundType deadlineBoundType) {
+    const auto getNanosecondsSinceEpoch = [](const auto& time) -> int64_t {
+        const auto timeSinceEpoch = time.time_since_epoch();
+        return std::chrono::duration_cast<std::chrono::nanoseconds>(timeSinceEpoch).count();
+    };
+
+    std::chrono::steady_clock::time_point timePoint;
+    switch (deadlineBoundType) {
+        case DeadlineBoundType::NOW:
+            timePoint = std::chrono::steady_clock::now();
+            break;
+        case DeadlineBoundType::UNLIMITED:
+            timePoint = std::chrono::steady_clock::time_point::max();
+            break;
+        case DeadlineBoundType::SHORT:
+            timePoint = std::chrono::steady_clock::now() + kShortDuration;
+            break;
+    }
+
+    return getNanosecondsSinceEpoch(timePoint);
+}
+
+void runPrepareModelTest(const std::shared_ptr<IDevice>& device, const Model& model,
+                         Priority priority, std::optional<DeadlineBoundType> deadlineBound) {
+    int64_t deadline = kNoDeadline;
+    if (deadlineBound.has_value()) {
+        deadline = makeDeadline(deadlineBound.value());
+    }
+
+    // see if service can handle model
+    std::vector<bool> supportedOps;
+    const auto supportedCallStatus = device->getSupportedOperations(model, &supportedOps);
+    ASSERT_TRUE(supportedCallStatus.isOk());
+    ASSERT_NE(0ul, supportedOps.size());
+    const bool fullySupportsModel =
+            std::all_of(supportedOps.begin(), supportedOps.end(), [](bool valid) { return valid; });
+
+    // launch prepare model
+    const std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+            ndk::SharedRefBase::make<PreparedModelCallback>();
+    const auto prepareLaunchStatus =
+            device->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, priority, deadline,
+                                 {}, {}, kEmptyCacheToken, preparedModelCallback);
+    ASSERT_TRUE(prepareLaunchStatus.isOk())
+            << "prepareLaunchStatus: " << prepareLaunchStatus.getDescription();
+
+    // retrieve prepared model
+    preparedModelCallback->wait();
+    const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+    const std::shared_ptr<IPreparedModel> preparedModel = preparedModelCallback->getPreparedModel();
+
+    // The getSupportedOperations call returns a list of operations that are guaranteed not to fail
+    // if prepareModel is called, and 'fullySupportsModel' is true i.f.f. the entire model is
+    // guaranteed. If a driver has any doubt that it can prepare an operation, it must return false.
+    // So here, if a driver isn't sure if it can support an operation, but reports that it
+    // successfully prepared the model, the test can continue.
+    if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
+        ASSERT_EQ(nullptr, preparedModel.get());
+        return;
+    }
+
+    // verify return status
+    if (!deadlineBound.has_value()) {
+        EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+    } else {
+        switch (deadlineBound.value()) {
+            case DeadlineBoundType::NOW:
+            case DeadlineBoundType::SHORT:
+                // Either the driver successfully completed the task or it
+                // aborted and returned MISSED_DEADLINE_*.
+                EXPECT_TRUE(prepareReturnStatus == ErrorStatus::NONE ||
+                            prepareReturnStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
+                            prepareReturnStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT);
+                break;
+            case DeadlineBoundType::UNLIMITED:
+                // If an unlimited deadline is supplied, we expect the execution to
+                // proceed normally. In this case, check it normally by breaking out
+                // of the switch statement.
+                EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+                break;
+        }
+    }
+    ASSERT_EQ(prepareReturnStatus == ErrorStatus::NONE, preparedModel.get() != nullptr);
+}
+
+void runPrepareModelTests(const std::shared_ptr<IDevice>& device, const Model& model) {
+    // test priority
+    for (auto priority : ndk::enum_range<Priority>{}) {
+        SCOPED_TRACE("priority: " + toString(priority));
+        if (priority == kDefaultPriority) continue;
+        runPrepareModelTest(device, model, priority, {});
+    }
+
+    // test deadline
+    for (auto deadlineBound : deadlineBounds) {
+        SCOPED_TRACE("deadlineBound: " + toString(deadlineBound));
+        runPrepareModelTest(device, model, kDefaultPriority, deadlineBound);
+    }
+}
+
+static MaybeResults executeSynchronously(const std::shared_ptr<IPreparedModel>& preparedModel,
+                                         const Request& request, int64_t deadline) {
+    SCOPED_TRACE("synchronous");
+    const bool measure = false;
+
+    // run execution
+    ExecutionResult executionResult;
+    const auto ret = preparedModel->executeSynchronously(request, measure, deadline,
+                                                         kOmittedTimeoutDuration, &executionResult);
+    EXPECT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
+            << ret.getDescription();
+    if (!ret.isOk()) {
+        if (ret.getExceptionCode() != EX_SERVICE_SPECIFIC) {
+            return std::nullopt;
+        }
+        return MaybeResults(
+                {static_cast<ErrorStatus>(ret.getServiceSpecificError()), {}, kNoTiming});
+    }
+
+    // return results
+    return MaybeResults({executionResult.outputSufficientSize
+                                 ? ErrorStatus::NONE
+                                 : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE,
+                         std::move(executionResult.outputShapes), executionResult.timing});
+}
+
+void runExecutionTest(const std::shared_ptr<IPreparedModel>& preparedModel,
+                      const TestModel& testModel, const Request& request,
+                      const ExecutionContext& context, DeadlineBoundType deadlineBound) {
+    const auto deadline = makeDeadline(deadlineBound);
+
+    // Perform execution and unpack results.
+    const auto results = executeSynchronously(preparedModel, request, deadline);
+    if (!results.has_value()) return;
+    const auto& [status, outputShapes, timing] = results.value();
+
+    // Verify no timing information was returned
+    EXPECT_EQ(timing, kNoTiming);
+
+    // Validate deadline information if applicable.
+    switch (deadlineBound) {
+        case DeadlineBoundType::NOW:
+        case DeadlineBoundType::SHORT:
+            // Either the driver successfully completed the task or it
+            // aborted and returned MISSED_DEADLINE_*.
+            ASSERT_TRUE(status == ErrorStatus::NONE ||
+                        status == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
+                        status == ErrorStatus::MISSED_DEADLINE_PERSISTENT);
+            break;
+        case DeadlineBoundType::UNLIMITED:
+            // If an unlimited deadline is supplied, we expect the execution to
+            // proceed normally. In this case, check it normally by breaking out
+            // of the switch statement.
+            ASSERT_EQ(ErrorStatus::NONE, status);
+            break;
+    }
+
+    // If the model output operands are fully specified, outputShapes must be either
+    // either empty, or have the same number of elements as the number of outputs.
+    ASSERT_TRUE(outputShapes.size() == 0 ||
+                outputShapes.size() == testModel.main.outputIndexes.size());
+
+    // Go through all outputs, check returned output shapes.
+    for (uint32_t i = 0; i < outputShapes.size(); i++) {
+        EXPECT_TRUE(outputShapes[i].isSufficient);
+        const auto expect =
+                utils::toSigned(testModel.main.operands[testModel.main.outputIndexes[i]].dimensions)
+                        .value();
+        const std::vector<int32_t>& actual = outputShapes[i].dimensions;
+        EXPECT_EQ(expect, actual);
+    }
+
+    // Retrieve execution results.
+    const std::vector<TestBuffer> outputs = context.getOutputBuffers(request);
+
+    // We want "close-enough" results.
+    if (status == ErrorStatus::NONE) {
+        checkResults(testModel, outputs);
+    }
+}
+
+void runExecutionTests(const std::shared_ptr<IPreparedModel>& preparedModel,
+                       const TestModel& testModel, const Request& request,
+                       const ExecutionContext& context) {
+    for (auto deadlineBound : deadlineBounds) {
+        runExecutionTest(preparedModel, testModel, request, context, deadlineBound);
+    }
+}
+
+void runTests(const std::shared_ptr<IDevice>& device, const TestModel& testModel) {
+    // setup
+    const Model model = createModel(testModel);
+
+    // run prepare model tests
+    runPrepareModelTests(device, model);
+
+    // prepare model
+    std::shared_ptr<IPreparedModel> preparedModel;
+    createPreparedModel(device, model, &preparedModel);
+    if (preparedModel == nullptr) return;
+
+    // run execution tests
+    ExecutionContext context;
+    const Request request = context.createRequest(testModel);
+    runExecutionTests(preparedModel, testModel, request, context);
+}
+
+class DeadlineTest : public GeneratedTestBase {};
+
+TEST_P(DeadlineTest, Test) {
+    runTests(kDevice, kTestModel);
+}
+
+INSTANTIATE_GENERATED_TEST(DeadlineTest,
+                           [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/TestAssertions.cpp b/neuralnetworks/aidl/vts/functional/TestAssertions.cpp
new file mode 100644
index 0000000..a9e9456
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/TestAssertions.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 <aidl/android/hardware/neuralnetworks/IPreparedModel.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/OperationType.h>
+
+#include <ControlFlow.h>
+#include <TestHarness.h>
+
+namespace aidl::android::hardware::neuralnetworks {
+
+namespace nn = ::android::nn;
+
+static_assert(static_cast<uint64_t>(IPreparedModel::DEFAULT_LOOP_TIMEOUT_DURATION_NS) ==
+              nn::operation_while::kTimeoutNsDefault);
+static_assert(static_cast<uint64_t>(IPreparedModel::MAXIMUM_LOOP_TIMEOUT_DURATION_NS) ==
+              nn::operation_while::kTimeoutNsMaximum);
+
+// Make sure that the HIDL enums are compatible with the values defined in
+// frameworks/ml/nn/tools/test_generator/test_harness/include/TestHarness.h.
+using namespace test_helper;
+#define CHECK_TEST_ENUM(EnumType, enumValue) \
+    static_assert(static_cast<EnumType>(Test##EnumType::enumValue) == EnumType::enumValue)
+
+CHECK_TEST_ENUM(OperandType, FLOAT32);
+CHECK_TEST_ENUM(OperandType, INT32);
+CHECK_TEST_ENUM(OperandType, UINT32);
+CHECK_TEST_ENUM(OperandType, TENSOR_FLOAT32);
+CHECK_TEST_ENUM(OperandType, TENSOR_INT32);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_ASYMM);
+CHECK_TEST_ENUM(OperandType, BOOL);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT16_SYMM);
+CHECK_TEST_ENUM(OperandType, TENSOR_FLOAT16);
+CHECK_TEST_ENUM(OperandType, TENSOR_BOOL8);
+CHECK_TEST_ENUM(OperandType, FLOAT16);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_SYMM_PER_CHANNEL);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT16_ASYMM);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_SYMM);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_ASYMM_SIGNED);
+
+CHECK_TEST_ENUM(OperationType, ADD);
+CHECK_TEST_ENUM(OperationType, AVERAGE_POOL_2D);
+CHECK_TEST_ENUM(OperationType, CONCATENATION);
+CHECK_TEST_ENUM(OperationType, CONV_2D);
+CHECK_TEST_ENUM(OperationType, DEPTHWISE_CONV_2D);
+CHECK_TEST_ENUM(OperationType, DEPTH_TO_SPACE);
+CHECK_TEST_ENUM(OperationType, DEQUANTIZE);
+CHECK_TEST_ENUM(OperationType, EMBEDDING_LOOKUP);
+CHECK_TEST_ENUM(OperationType, FLOOR);
+CHECK_TEST_ENUM(OperationType, FULLY_CONNECTED);
+CHECK_TEST_ENUM(OperationType, HASHTABLE_LOOKUP);
+CHECK_TEST_ENUM(OperationType, L2_NORMALIZATION);
+CHECK_TEST_ENUM(OperationType, L2_POOL_2D);
+CHECK_TEST_ENUM(OperationType, LOCAL_RESPONSE_NORMALIZATION);
+CHECK_TEST_ENUM(OperationType, LOGISTIC);
+CHECK_TEST_ENUM(OperationType, LSH_PROJECTION);
+CHECK_TEST_ENUM(OperationType, LSTM);
+CHECK_TEST_ENUM(OperationType, MAX_POOL_2D);
+CHECK_TEST_ENUM(OperationType, MUL);
+CHECK_TEST_ENUM(OperationType, RELU);
+CHECK_TEST_ENUM(OperationType, RELU1);
+CHECK_TEST_ENUM(OperationType, RELU6);
+CHECK_TEST_ENUM(OperationType, RESHAPE);
+CHECK_TEST_ENUM(OperationType, RESIZE_BILINEAR);
+CHECK_TEST_ENUM(OperationType, RNN);
+CHECK_TEST_ENUM(OperationType, SOFTMAX);
+CHECK_TEST_ENUM(OperationType, SPACE_TO_DEPTH);
+CHECK_TEST_ENUM(OperationType, SVDF);
+CHECK_TEST_ENUM(OperationType, TANH);
+CHECK_TEST_ENUM(OperationType, BATCH_TO_SPACE_ND);
+CHECK_TEST_ENUM(OperationType, DIV);
+CHECK_TEST_ENUM(OperationType, MEAN);
+CHECK_TEST_ENUM(OperationType, PAD);
+CHECK_TEST_ENUM(OperationType, SPACE_TO_BATCH_ND);
+CHECK_TEST_ENUM(OperationType, SQUEEZE);
+CHECK_TEST_ENUM(OperationType, STRIDED_SLICE);
+CHECK_TEST_ENUM(OperationType, SUB);
+CHECK_TEST_ENUM(OperationType, TRANSPOSE);
+CHECK_TEST_ENUM(OperationType, ABS);
+CHECK_TEST_ENUM(OperationType, ARGMAX);
+CHECK_TEST_ENUM(OperationType, ARGMIN);
+CHECK_TEST_ENUM(OperationType, AXIS_ALIGNED_BBOX_TRANSFORM);
+CHECK_TEST_ENUM(OperationType, BIDIRECTIONAL_SEQUENCE_LSTM);
+CHECK_TEST_ENUM(OperationType, BIDIRECTIONAL_SEQUENCE_RNN);
+CHECK_TEST_ENUM(OperationType, BOX_WITH_NMS_LIMIT);
+CHECK_TEST_ENUM(OperationType, CAST);
+CHECK_TEST_ENUM(OperationType, CHANNEL_SHUFFLE);
+CHECK_TEST_ENUM(OperationType, DETECTION_POSTPROCESSING);
+CHECK_TEST_ENUM(OperationType, EQUAL);
+CHECK_TEST_ENUM(OperationType, EXP);
+CHECK_TEST_ENUM(OperationType, EXPAND_DIMS);
+CHECK_TEST_ENUM(OperationType, GATHER);
+CHECK_TEST_ENUM(OperationType, GENERATE_PROPOSALS);
+CHECK_TEST_ENUM(OperationType, GREATER);
+CHECK_TEST_ENUM(OperationType, GREATER_EQUAL);
+CHECK_TEST_ENUM(OperationType, GROUPED_CONV_2D);
+CHECK_TEST_ENUM(OperationType, HEATMAP_MAX_KEYPOINT);
+CHECK_TEST_ENUM(OperationType, INSTANCE_NORMALIZATION);
+CHECK_TEST_ENUM(OperationType, LESS);
+CHECK_TEST_ENUM(OperationType, LESS_EQUAL);
+CHECK_TEST_ENUM(OperationType, LOG);
+CHECK_TEST_ENUM(OperationType, LOGICAL_AND);
+CHECK_TEST_ENUM(OperationType, LOGICAL_NOT);
+CHECK_TEST_ENUM(OperationType, LOGICAL_OR);
+CHECK_TEST_ENUM(OperationType, LOG_SOFTMAX);
+CHECK_TEST_ENUM(OperationType, MAXIMUM);
+CHECK_TEST_ENUM(OperationType, MINIMUM);
+CHECK_TEST_ENUM(OperationType, NEG);
+CHECK_TEST_ENUM(OperationType, NOT_EQUAL);
+CHECK_TEST_ENUM(OperationType, PAD_V2);
+CHECK_TEST_ENUM(OperationType, POW);
+CHECK_TEST_ENUM(OperationType, PRELU);
+CHECK_TEST_ENUM(OperationType, QUANTIZE);
+CHECK_TEST_ENUM(OperationType, QUANTIZED_16BIT_LSTM);
+CHECK_TEST_ENUM(OperationType, RANDOM_MULTINOMIAL);
+CHECK_TEST_ENUM(OperationType, REDUCE_ALL);
+CHECK_TEST_ENUM(OperationType, REDUCE_ANY);
+CHECK_TEST_ENUM(OperationType, REDUCE_MAX);
+CHECK_TEST_ENUM(OperationType, REDUCE_MIN);
+CHECK_TEST_ENUM(OperationType, REDUCE_PROD);
+CHECK_TEST_ENUM(OperationType, REDUCE_SUM);
+CHECK_TEST_ENUM(OperationType, ROI_ALIGN);
+CHECK_TEST_ENUM(OperationType, ROI_POOLING);
+CHECK_TEST_ENUM(OperationType, RSQRT);
+CHECK_TEST_ENUM(OperationType, SELECT);
+CHECK_TEST_ENUM(OperationType, SIN);
+CHECK_TEST_ENUM(OperationType, SLICE);
+CHECK_TEST_ENUM(OperationType, SPLIT);
+CHECK_TEST_ENUM(OperationType, SQRT);
+CHECK_TEST_ENUM(OperationType, TILE);
+CHECK_TEST_ENUM(OperationType, TOPK_V2);
+CHECK_TEST_ENUM(OperationType, TRANSPOSE_CONV_2D);
+CHECK_TEST_ENUM(OperationType, UNIDIRECTIONAL_SEQUENCE_LSTM);
+CHECK_TEST_ENUM(OperationType, UNIDIRECTIONAL_SEQUENCE_RNN);
+CHECK_TEST_ENUM(OperationType, RESIZE_NEAREST_NEIGHBOR);
+
+#undef CHECK_TEST_ENUM
+
+}  // namespace aidl::android::hardware::neuralnetworks
diff --git a/neuralnetworks/aidl/vts/functional/TestMain.cpp b/neuralnetworks/aidl/vts/functional/TestMain.cpp
new file mode 100644
index 0000000..1d58608
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/TestMain.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include "LogTestCaseToLogcat.h"
+
+int main(int argc, char** argv) {
+    testing::InitGoogleTest(&argc, argv);
+    testing::UnitTest::GetInstance()->listeners().Append(
+            new aidl::android::hardware::neuralnetworks::LogTestCaseToLogcat());
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/neuralnetworks/aidl/vts/functional/Utils.cpp b/neuralnetworks/aidl/vts/functional/Utils.cpp
new file mode 100644
index 0000000..3c7f5f7
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Utils.cpp
@@ -0,0 +1,253 @@
+/*
+ * 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 "Utils.h"
+
+#include <aidl/android/hardware/neuralnetworks/IPreparedModelParcel.h>
+#include <aidl/android/hardware/neuralnetworks/Operand.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <android-base/logging.h>
+#include <android/binder_status.h>
+#include <android/hardware_buffer.h>
+
+#include <iostream>
+#include <limits>
+#include <numeric>
+
+#include <MemoryUtils.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+namespace aidl::android::hardware::neuralnetworks {
+
+using test_helper::TestBuffer;
+using test_helper::TestModel;
+
+uint32_t sizeOfData(OperandType type) {
+    switch (type) {
+        case OperandType::FLOAT32:
+        case OperandType::INT32:
+        case OperandType::UINT32:
+        case OperandType::TENSOR_FLOAT32:
+        case OperandType::TENSOR_INT32:
+            return 4;
+        case OperandType::TENSOR_QUANT16_SYMM:
+        case OperandType::TENSOR_FLOAT16:
+        case OperandType::FLOAT16:
+        case OperandType::TENSOR_QUANT16_ASYMM:
+            return 2;
+        case OperandType::TENSOR_QUANT8_ASYMM:
+        case OperandType::BOOL:
+        case OperandType::TENSOR_BOOL8:
+        case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case OperandType::TENSOR_QUANT8_SYMM:
+        case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+            return 1;
+        case OperandType::SUBGRAPH:
+            return 0;
+        default:
+            CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+            return 0;
+    }
+}
+
+static bool isTensor(OperandType type) {
+    switch (type) {
+        case OperandType::FLOAT32:
+        case OperandType::INT32:
+        case OperandType::UINT32:
+        case OperandType::FLOAT16:
+        case OperandType::BOOL:
+        case OperandType::SUBGRAPH:
+            return false;
+        case OperandType::TENSOR_FLOAT32:
+        case OperandType::TENSOR_INT32:
+        case OperandType::TENSOR_QUANT16_SYMM:
+        case OperandType::TENSOR_FLOAT16:
+        case OperandType::TENSOR_QUANT16_ASYMM:
+        case OperandType::TENSOR_QUANT8_ASYMM:
+        case OperandType::TENSOR_BOOL8:
+        case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case OperandType::TENSOR_QUANT8_SYMM:
+        case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+            return true;
+        default:
+            CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+            return false;
+    }
+}
+
+uint32_t sizeOfData(const Operand& operand) {
+    const uint32_t dataSize = sizeOfData(operand.type);
+    if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+    return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+                           std::multiplies<>{});
+}
+
+std::unique_ptr<TestAshmem> TestAshmem::create(uint32_t size) {
+    auto ashmem = std::make_unique<TestAshmem>(size);
+    return ashmem->mIsValid ? std::move(ashmem) : nullptr;
+}
+
+void TestAshmem::initialize(uint32_t size) {
+    mIsValid = false;
+    ASSERT_GT(size, 0);
+    const auto sharedMemory = nn::createSharedMemory(size).value();
+    mMappedMemory = nn::map(sharedMemory).value();
+    mPtr = static_cast<uint8_t*>(std::get<void*>(mMappedMemory.pointer));
+    CHECK_NE(mPtr, nullptr);
+    mAidlMemory = utils::convert(sharedMemory).value();
+    mIsValid = true;
+}
+
+std::unique_ptr<TestBlobAHWB> TestBlobAHWB::create(uint32_t size) {
+    auto ahwb = std::make_unique<TestBlobAHWB>(size);
+    return ahwb->mIsValid ? std::move(ahwb) : nullptr;
+}
+
+void TestBlobAHWB::initialize(uint32_t size) {
+    mIsValid = false;
+    ASSERT_GT(size, 0);
+    const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
+    const AHardwareBuffer_Desc desc = {
+            .width = size,
+            .height = 1,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_BLOB,
+            .usage = usage,
+            .stride = size,
+    };
+
+    ASSERT_EQ(AHardwareBuffer_allocate(&desc, &mAhwb), 0);
+    ASSERT_NE(mAhwb, nullptr);
+
+    const auto sharedMemory =
+            nn::createSharedMemoryFromAHWB(mAhwb, /*takeOwnership=*/false).value();
+    mMapping = nn::map(sharedMemory).value();
+    mPtr = static_cast<uint8_t*>(std::get<void*>(mMapping.pointer));
+    CHECK_NE(mPtr, nullptr);
+    mAidlMemory = utils::convert(sharedMemory).value();
+
+    mIsValid = true;
+}
+
+TestBlobAHWB::~TestBlobAHWB() {
+    if (mAhwb) {
+        AHardwareBuffer_unlock(mAhwb, nullptr);
+        AHardwareBuffer_release(mAhwb);
+    }
+}
+
+std::string gtestCompliantName(std::string name) {
+    // gtest test names must only contain alphanumeric characters
+    std::replace_if(
+            name.begin(), name.end(), [](char c) { return !std::isalnum(c); }, '_');
+    return name;
+}
+
+::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) {
+    return os << toString(errorStatus);
+}
+
+Request ExecutionContext::createRequest(const TestModel& testModel, MemoryType memoryType) {
+    CHECK(memoryType == MemoryType::ASHMEM || memoryType == MemoryType::BLOB_AHWB);
+
+    // Model inputs.
+    std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
+    size_t inputSize = 0;
+    for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+        const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
+        if (op.data.size() == 0) {
+            // Omitted input.
+            inputs[i] = {.hasNoValue = true};
+        } else {
+            DataLocation loc = {.poolIndex = kInputPoolIndex,
+                                .offset = static_cast<int64_t>(inputSize),
+                                .length = static_cast<int64_t>(op.data.size())};
+            inputSize += op.data.alignedSize();
+            inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+        }
+    }
+
+    // Model outputs.
+    std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
+    size_t outputSize = 0;
+    for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
+        const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
+
+        // In the case of zero-sized output, we should at least provide a one-byte buffer.
+        // This is because zero-sized tensors are only supported internally to the driver, or
+        // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
+        // tensor as model output. Otherwise, we will have two semantic conflicts:
+        // - "Zero dimension" conflicts with "unspecified dimension".
+        // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
+        size_t bufferSize = std::max<size_t>(op.data.size(), 1);
+
+        DataLocation loc = {.poolIndex = kOutputPoolIndex,
+                            .offset = static_cast<int64_t>(outputSize),
+                            .length = static_cast<int64_t>(bufferSize)};
+        outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
+        outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+    }
+
+    // Allocate memory pools.
+    if (memoryType == MemoryType::ASHMEM) {
+        mInputMemory = TestAshmem::create(inputSize);
+        mOutputMemory = TestAshmem::create(outputSize);
+    } else {
+        mInputMemory = TestBlobAHWB::create(inputSize);
+        mOutputMemory = TestBlobAHWB::create(outputSize);
+    }
+    CHECK_NE(mInputMemory, nullptr);
+    CHECK_NE(mOutputMemory, nullptr);
+
+    auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
+    CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
+    auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
+    CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
+
+    std::vector<RequestMemoryPool> pools;
+    pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
+            std::move(copiedInputMemory).value()));
+    pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
+            std::move(copiedOutputMemory).value()));
+
+    // Copy input data to the memory pool.
+    uint8_t* inputPtr = mInputMemory->getPointer();
+    for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+        const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
+        if (op.data.size() > 0) {
+            const uint8_t* begin = op.data.get<uint8_t>();
+            const uint8_t* end = begin + op.data.size();
+            std::copy(begin, end, inputPtr + inputs[i].location.offset);
+        }
+    }
+
+    return {.inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
+}
+
+std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const Request& request) const {
+    // Copy out output results.
+    uint8_t* outputPtr = mOutputMemory->getPointer();
+    std::vector<TestBuffer> outputBuffers;
+    for (const auto& output : request.outputs) {
+        outputBuffers.emplace_back(output.location.length, outputPtr + output.location.offset);
+    }
+    return outputBuffers;
+}
+
+}  // namespace aidl::android::hardware::neuralnetworks
diff --git a/neuralnetworks/aidl/vts/functional/Utils.h b/neuralnetworks/aidl/vts/functional/Utils.h
new file mode 100644
index 0000000..266301c
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Utils.h
@@ -0,0 +1,153 @@
+/*
+ * 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_NEURALNETWORKS_AIDL_UTILS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_UTILS_H
+
+#include <android-base/logging.h>
+#include <android/hardware_buffer.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iosfwd>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+#include <aidl/android/hardware/neuralnetworks/Memory.h>
+#include <aidl/android/hardware/neuralnetworks/Operand.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/Priority.h>
+#include <aidl/android/hardware/neuralnetworks/Request.h>
+
+#include <TestHarness.h>
+#include <nnapi/SharedMemory.h>
+
+namespace aidl::android::hardware::neuralnetworks {
+
+namespace nn = ::android::nn;
+
+inline constexpr Priority kDefaultPriority = Priority::MEDIUM;
+
+inline constexpr Timing kNoTiming = {.timeOnDevice = -1, .timeInDriver = -1};
+inline constexpr int64_t kNoDeadline = -1;
+inline constexpr int64_t kOmittedTimeoutDuration = -1;
+inline constexpr int64_t kNoDuration = -1;
+inline const std::vector<uint8_t> kEmptyCacheToken(IDevice::BYTE_SIZE_OF_CACHE_TOKEN);
+
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const Operand& operand);
+
+// Convenience class to manage the lifetime of memory resources.
+class TestMemoryBase {
+    DISALLOW_COPY_AND_ASSIGN(TestMemoryBase);
+
+  public:
+    TestMemoryBase() = default;
+    virtual ~TestMemoryBase() = default;
+    uint8_t* getPointer() const { return mPtr; }
+    const Memory* getAidlMemory() const { return &mAidlMemory; }
+
+  protected:
+    uint8_t* mPtr = nullptr;
+    Memory mAidlMemory;
+    bool mIsValid = false;
+};
+
+class TestAshmem : public TestMemoryBase {
+  public:
+    static std::unique_ptr<TestAshmem> create(uint32_t size);
+
+    // Prefer TestAshmem::create.
+    // The constructor calls initialize, which constructs the memory resources. This is a workaround
+    // that gtest macros cannot be used directly in a constructor.
+    TestAshmem(uint32_t size) { initialize(size); }
+
+  private:
+    void initialize(uint32_t size);
+    nn::Mapping mMappedMemory;
+};
+
+class TestBlobAHWB : public TestMemoryBase {
+  public:
+    static std::unique_ptr<TestBlobAHWB> create(uint32_t size);
+
+    // Prefer TestBlobAHWB::create.
+    // The constructor calls initialize, which constructs the memory resources. This is a
+    // workaround that gtest macros cannot be used directly in a constructor.
+    TestBlobAHWB(uint32_t size) { initialize(size); }
+    ~TestBlobAHWB();
+
+  private:
+    void initialize(uint32_t size);
+    AHardwareBuffer* mAhwb = nullptr;
+    nn::Mapping mMapping;
+};
+
+enum class MemoryType { ASHMEM, BLOB_AHWB, DEVICE };
+
+// Manages the lifetime of memory resources used in an execution.
+class ExecutionContext {
+    DISALLOW_COPY_AND_ASSIGN(ExecutionContext);
+
+  public:
+    static constexpr uint32_t kInputPoolIndex = 0;
+    static constexpr uint32_t kOutputPoolIndex = 1;
+
+    ExecutionContext() = default;
+
+    // Create HIDL Request from the TestModel struct.
+    Request createRequest(const test_helper::TestModel& testModel,
+                          MemoryType memoryType = MemoryType::ASHMEM);
+
+    // After execution, copy out output results from the output memory pool.
+    std::vector<test_helper::TestBuffer> getOutputBuffers(const Request& request) const;
+
+  private:
+    std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory;
+};
+
+template <typename Type>
+using Named = std::pair<std::string, Type>;
+
+template <typename Type>
+const std::string& getName(const Named<Type>& namedData) {
+    return namedData.first;
+}
+
+template <typename Type>
+const Type& getData(const Named<Type>& namedData) {
+    return namedData.second;
+}
+
+std::string gtestCompliantName(std::string name);
+
+// pretty-print values for error messages
+::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus);
+
+}  // namespace aidl::android::hardware::neuralnetworks
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_UTILS_H
diff --git a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
new file mode 100644
index 0000000..b84d981
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
@@ -0,0 +1,1338 @@
+/*
+ * 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 "neuralnetworks_aidl_hal_test"
+
+#include <aidl/android/hardware/common/NativeHandle.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_enums.h>
+#include <android/binder_interface_utils.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+#include <optional>
+#include <type_traits>
+#include <utility>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using common::NativeHandle;
+using implementation::PreparedModelCallback;
+
+using PrepareModelMutation = std::function<void(Model*, ExecutionPreference*, Priority*)>;
+
+///////////////////////// UTILITY FUNCTIONS /////////////////////////
+
+static void validateGetSupportedOperations(const std::shared_ptr<IDevice>& device,
+                                           const std::string& message, const Model& model) {
+    SCOPED_TRACE(message + " [getSupportedOperations]");
+
+    std::vector<bool> supported;
+    const auto retStatus = device->getSupportedOperations(model, &supported);
+
+    ASSERT_FALSE(retStatus.isOk());
+    ASSERT_EQ(retStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+    ASSERT_EQ(static_cast<ErrorStatus>(retStatus.getServiceSpecificError()),
+              ErrorStatus::INVALID_ARGUMENT);
+}
+
+static void validatePrepareModel(const std::shared_ptr<IDevice>& device, const std::string& message,
+                                 const Model& model, ExecutionPreference preference,
+                                 Priority priority) {
+    SCOPED_TRACE(message + " [prepareModel]");
+
+    std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+            ndk::SharedRefBase::make<PreparedModelCallback>();
+    const auto prepareLaunchStatus =
+            device->prepareModel(model, preference, priority, kNoDeadline, {}, {}, kEmptyCacheToken,
+                                 preparedModelCallback);
+    ASSERT_FALSE(prepareLaunchStatus.isOk());
+    ASSERT_EQ(prepareLaunchStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+    ASSERT_EQ(static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError()),
+              ErrorStatus::INVALID_ARGUMENT);
+
+    preparedModelCallback->wait();
+    ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+    ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, prepareReturnStatus);
+    std::shared_ptr<IPreparedModel> preparedModel = preparedModelCallback->getPreparedModel();
+    ASSERT_EQ(nullptr, preparedModel.get());
+}
+
+static bool validExecutionPreference(ExecutionPreference preference) {
+    return preference == ExecutionPreference::LOW_POWER ||
+           preference == ExecutionPreference::FAST_SINGLE_ANSWER ||
+           preference == ExecutionPreference::SUSTAINED_SPEED;
+}
+
+static bool validExecutionPriority(Priority priority) {
+    return priority == Priority::LOW || priority == Priority::MEDIUM || priority == Priority::HIGH;
+}
+
+// Primary validation function. This function will take a valid model, apply a
+// mutation to invalidate the model, the execution preference, or the priority,
+// then pass these to supportedOperations and/or prepareModel if that method is
+// called with an invalid argument.
+static void validate(const std::shared_ptr<IDevice>& device, const std::string& message,
+                     const Model& originalModel, const PrepareModelMutation& mutate) {
+    Model model = utils::clone(originalModel).value();
+    ExecutionPreference preference = ExecutionPreference::FAST_SINGLE_ANSWER;
+    Priority priority = kDefaultPriority;
+    mutate(&model, &preference, &priority);
+
+    if (validExecutionPreference(preference) && validExecutionPriority(priority)) {
+        validateGetSupportedOperations(device, message, model);
+    }
+
+    validatePrepareModel(device, message, model, preference, priority);
+}
+
+static uint32_t addOperand(Model* model) {
+    model->main.operands.push_back({
+            .type = OperandType::INT32,
+            .dimensions = {},
+            .scale = 0.0f,
+            .zeroPoint = 0,
+            .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+            .location = {.poolIndex = 0, .offset = 0, .length = 0},
+    });
+    return model->main.operands.size() - 1;
+}
+
+static uint32_t addOperand(Model* model, OperandLifeTime lifetime) {
+    uint32_t index = addOperand(model);
+    model->main.operands[index].lifetime = lifetime;
+    return index;
+}
+
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model?  This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+    const size_t operandValuesSize = model.operandValues.size();
+    return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+//   value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+    // sizeOfData will abort if the specified type is an extension type or OEM type.
+    const size_t sizeOfOperand = sizeOfData(*operand);
+    EXPECT_NE(sizeOfOperand, size_t(0));
+    operand->location.poolIndex = 0;
+    operand->location.offset = 0;
+    operand->location.length = sizeOfOperand;
+    if (model->operandValues.size() < sizeOfOperand) {
+        model->operandValues.resize(sizeOfOperand);
+    }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder.  It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents.  However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+                  "expected a trivially copyable type");
+    return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const std::vector<Type>& vec) {
+    return std::accumulate(vec.begin(), vec.end(), 0,
+                           [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+    size_t size = 0;
+
+    size += sizeForBinder(symmPerChannelQuantParams.scales);
+    size += sizeForBinder(symmPerChannelQuantParams.channelDim);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const std::optional<OperandExtraParams>& optionalExtraParams) {
+    if (!optionalExtraParams.has_value()) {
+        return 0;
+    }
+    const auto& extraParams = optionalExtraParams.value();
+    using Tag = OperandExtraParams::Tag;
+    switch (extraParams.getTag()) {
+        case Tag::channelQuant:
+            return sizeForBinder(extraParams.get<Tag::channelQuant>());
+        case Tag::extension:
+            return sizeForBinder(extraParams.get<Tag::extension>());
+    }
+    LOG(FATAL) << "Unrecognized extraParams tag: " << static_cast<int>(extraParams.getTag());
+    return 0;
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+    size_t size = 0;
+
+    size += sizeForBinder(operand.type);
+    size += sizeForBinder(operand.dimensions);
+    size += sizeForBinder(operand.scale);
+    size += sizeForBinder(operand.zeroPoint);
+    size += sizeForBinder(operand.lifetime);
+    size += sizeForBinder(operand.location);
+    size += sizeForBinder(operand.extraParams);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+    size_t size = 0;
+
+    size += sizeForBinder(operation.type);
+    size += sizeForBinder(operation.inputs);
+    size += sizeForBinder(operation.outputs);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const std::string& name) {
+    return name.size();
+}
+
+template <>
+size_t sizeForBinder(const Memory& memory) {
+    // This is just a guess.
+
+    size_t size = 0;
+    const NativeHandle& handle = memory.handle;
+    size += sizeof(decltype(handle.fds)::value_type) * handle.fds.size();
+    size += sizeof(decltype(handle.ints)::value_type) * handle.ints.size();
+    size += sizeForBinder(memory.name);
+    size += sizeof(memory);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Subgraph& subgraph) {
+    size_t size = 0;
+
+    size += sizeForBinder(subgraph.operands);
+    size += sizeForBinder(subgraph.operations);
+    size += sizeForBinder(subgraph.inputIndexes);
+    size += sizeForBinder(subgraph.outputIndexes);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const ExtensionNameAndPrefix& extensionNameToPrefix) {
+    size_t size = 0;
+
+    size += sizeForBinder(extensionNameToPrefix.name);
+    size += sizeForBinder(extensionNameToPrefix.prefix);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+    size_t size = 0;
+
+    size += sizeForBinder(model.main);
+    size += sizeForBinder(model.referenced);
+    size += sizeForBinder(model.operandValues);
+    size += sizeForBinder(model.pools);
+    size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+    size += sizeForBinder(model.extensionNameToPrefix);
+
+    return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+//     "The Binder transaction buffer has a limited fixed size,
+//     currently 1Mb, which is shared by all transactions in progress
+//     for the process."
+//
+// Will our representation fit under this limit?  There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+    // Instead of using this fixed buffer size, we might instead be able to use
+    // ProcessState::self()->getMmapSize(). However, this has a potential
+    // problem: The binder/mmap size of the current process does not necessarily
+    // indicate the binder/mmap size of the service (i.e., the other process).
+    // The only way it would be a good indication is if both the current process
+    // and the service use the default size.
+    static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+    return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const std::shared_ptr<IDevice>& device, const Model& model,
+                                     const std::vector<uint32_t>& numberOfConsumers) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        const Operation& operationObj = model.main.operations[operation];
+        for (uint32_t input : operationObj.inputs) {
+            if (model.main.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+                model.main.operands[input].lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+                // This operation reads an operand written by some
+                // other operation.  Move this operation to the
+                // beginning of the sequence, ensuring that it reads
+                // the operand before that operand is written, thereby
+                // violating execution order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a reader";
+                validate(device, message, model,
+                         [operation](Model* model, ExecutionPreference*, Priority*) {
+                             auto& operations = model->main.operations;
+                             std::rotate(operations.begin(), operations.begin() + operation,
+                                         operations.begin() + operation + 1);
+                         });
+                break;  // only need to do this once per operation
+            }
+        }
+        for (uint32_t output : operationObj.outputs) {
+            if (numberOfConsumers[output] > 0) {
+                // This operation writes an operand read by some other
+                // operation.  Move this operation to the end of the
+                // sequence, ensuring that it writes the operand after
+                // that operand is read, thereby violating execution
+                // order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a writer";
+                validate(device, message, model,
+                         [operation](Model* model, ExecutionPreference*, Priority*) {
+                             auto& operations = model->main.operations;
+                             std::rotate(operations.begin() + operation,
+                                         operations.begin() + operation + 1, operations.end());
+                         });
+                break;  // only need to do this once per operation
+            }
+        }
+    }
+}
+
+///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
+
+static const int32_t invalidOperandTypes[] = {
+        -1,
+        static_cast<int32_t>(*(ndk::enum_range<OperandType>().end() - 1)) + 1,
+};
+
+static void mutateOperandTypeTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        for (int32_t invalidOperandType : invalidOperandTypes) {
+            const std::string message = "mutateOperandTypeTest: operand " +
+                                        std::to_string(operand) + " set to value " +
+                                        std::to_string(invalidOperandType);
+            validate(device, message, model,
+                     [operand, invalidOperandType](Model* model, ExecutionPreference*, Priority*) {
+                         model->main.operands[operand].type =
+                                 static_cast<OperandType>(invalidOperandType);
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND RANK /////////////////////////
+
+static uint32_t getInvalidRank(OperandType type) {
+    switch (type) {
+        case OperandType::FLOAT16:
+        case OperandType::FLOAT32:
+        case OperandType::INT32:
+        case OperandType::UINT32:
+        case OperandType::BOOL:
+            return 1;
+        case OperandType::TENSOR_BOOL8:
+        case OperandType::TENSOR_FLOAT16:
+        case OperandType::TENSOR_FLOAT32:
+        case OperandType::TENSOR_INT32:
+        case OperandType::TENSOR_QUANT8_ASYMM:
+        case OperandType::TENSOR_QUANT8_SYMM:
+        case OperandType::TENSOR_QUANT16_ASYMM:
+        case OperandType::TENSOR_QUANT16_SYMM:
+        case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+            return 0;
+        default:
+            return 0;
+    }
+}
+
+static void mutateOperandRankTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        const uint32_t invalidRank = getInvalidRank(model.main.operands[operand].type);
+        if (invalidRank == 0) {
+            continue;
+        }
+        const std::string message = "mutateOperandRankTest: operand " + std::to_string(operand) +
+                                    " has rank of " + std::to_string(invalidRank);
+        validate(device, message, model,
+                 [operand, invalidRank](Model* model, ExecutionPreference*, Priority*) {
+                     model->main.operands[operand].dimensions =
+                             std::vector<int32_t>(invalidRank, 0);
+                 });
+    }
+}
+
+///////////////////////// VALIDATE OPERAND SCALE /////////////////////////
+
+static float getInvalidScale(OperandType type) {
+    switch (type) {
+        case OperandType::FLOAT16:
+        case OperandType::FLOAT32:
+        case OperandType::INT32:
+        case OperandType::UINT32:
+        case OperandType::BOOL:
+        case OperandType::TENSOR_BOOL8:
+        case OperandType::TENSOR_FLOAT16:
+        case OperandType::TENSOR_FLOAT32:
+        case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case OperandType::SUBGRAPH:
+            return 1.0f;
+        case OperandType::TENSOR_INT32:
+            return -1.0f;
+        case OperandType::TENSOR_QUANT8_SYMM:
+        case OperandType::TENSOR_QUANT8_ASYMM:
+        case OperandType::TENSOR_QUANT16_ASYMM:
+        case OperandType::TENSOR_QUANT16_SYMM:
+            return 0.0f;
+        default:
+            return 0.0f;
+    }
+}
+
+static void mutateOperandScaleTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        const float invalidScale = getInvalidScale(model.main.operands[operand].type);
+        const std::string message = "mutateOperandScaleTest: operand " + std::to_string(operand) +
+                                    " has scale of " + std::to_string(invalidScale);
+        validate(device, message, model,
+                 [operand, invalidScale](Model* model, ExecutionPreference*, Priority*) {
+                     model->main.operands[operand].scale = invalidScale;
+                 });
+    }
+}
+
+///////////////////////// VALIDATE OPERAND ZERO POINT /////////////////////////
+
+static std::vector<int32_t> getInvalidZeroPoints(OperandType type) {
+    switch (type) {
+        case OperandType::FLOAT16:
+        case OperandType::FLOAT32:
+        case OperandType::INT32:
+        case OperandType::UINT32:
+        case OperandType::BOOL:
+        case OperandType::TENSOR_BOOL8:
+        case OperandType::TENSOR_FLOAT16:
+        case OperandType::TENSOR_FLOAT32:
+        case OperandType::TENSOR_INT32:
+        case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case OperandType::SUBGRAPH:
+            return {1};
+        case OperandType::TENSOR_QUANT8_ASYMM:
+            return {-1, 256};
+        case OperandType::TENSOR_QUANT8_SYMM:
+            return {-129, -1, 1, 128};
+        case OperandType::TENSOR_QUANT16_ASYMM:
+            return {-1, 65536};
+        case OperandType::TENSOR_QUANT16_SYMM:
+            return {-32769, -1, 1, 32768};
+        default:
+            return {};
+    }
+}
+
+static void mutateOperandZeroPointTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        const std::vector<int32_t> invalidZeroPoints =
+                getInvalidZeroPoints(model.main.operands[operand].type);
+        for (int32_t invalidZeroPoint : invalidZeroPoints) {
+            const std::string message = "mutateOperandZeroPointTest: operand " +
+                                        std::to_string(operand) + " has zero point of " +
+                                        std::to_string(invalidZeroPoint);
+            validate(device, message, model,
+                     [operand, invalidZeroPoint](Model* model, ExecutionPreference*, Priority*) {
+                         model->main.operands[operand].zeroPoint = invalidZeroPoint;
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+                                                        const Operand& operand) {
+    // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+    // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+    // Ways to get an invalid lifetime:
+    // - change whether a lifetime means an operand should have a writer
+    std::vector<OperandLifeTime> ret;
+    switch (operand.lifetime) {
+        case OperandLifeTime::SUBGRAPH_OUTPUT:
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            ret = {
+                    OperandLifeTime::SUBGRAPH_INPUT,
+                    OperandLifeTime::CONSTANT_COPY,
+            };
+            break;
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_POOL:
+        case OperandLifeTime::SUBGRAPH_INPUT:
+            ret = {
+                    OperandLifeTime::TEMPORARY_VARIABLE,
+                    OperandLifeTime::SUBGRAPH_OUTPUT,
+            };
+            break;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+            // is this operand written (then CONSTANT_COPY would be
+            // invalid) or not (then TEMPORARY_VARIABLE would be
+            // invalid)?
+            break;
+        case OperandLifeTime::SUBGRAPH:
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+    if (!operandSize ||
+        exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+        // Unknown size or too-large size
+        ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+    }
+
+    return ret;
+}
+
+static void mutateOperandLifeTimeTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        const std::vector<OperandLifeTime> invalidLifeTimes =
+                getInvalidLifeTimes(model, modelSize, model.main.operands[operand]);
+        for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+            const std::string message = "mutateOperandLifetimeTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(invalidLifeTime) + " instead of lifetime " +
+                                        toString(model.main.operands[operand].lifetime);
+            validate(device, message, model,
+                     [operand, invalidLifeTime](Model* model, ExecutionPreference*, Priority*) {
+                         static const DataLocation kZeroDataLocation = {};
+                         Operand& operandObj = model->main.operands[operand];
+                         switch (operandObj.lifetime) {
+                             case OperandLifeTime::SUBGRAPH_INPUT: {
+                                 auto& inputs = model->main.inputIndexes;
+                                 inputs.erase(std::remove(inputs.begin(), inputs.end(), operand),
+                                              inputs.end());
+                                 break;
+                             }
+                             case OperandLifeTime::SUBGRAPH_OUTPUT: {
+                                 auto& outputs = model->main.outputIndexes;
+                                 outputs.erase(std::remove(outputs.begin(), outputs.end(), operand),
+                                               outputs.end());
+                                 break;
+                             }
+                             default:
+                                 break;
+                         }
+                         operandObj.lifetime = invalidLifeTime;
+                         operandObj.location = kZeroDataLocation;
+                         switch (invalidLifeTime) {
+                             case OperandLifeTime::CONSTANT_COPY: {
+                                 becomeConstantCopy(model, &operandObj);
+                                 break;
+                             }
+                             case OperandLifeTime::SUBGRAPH_INPUT:
+                                 model->main.inputIndexes.push_back(operand);
+                                 break;
+                             case OperandLifeTime::SUBGRAPH_OUTPUT:
+                                 model->main.outputIndexes.push_back(operand);
+                                 break;
+                             default:
+                                 break;
+                         }
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+                                                             const Operand& operand) {
+    // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+    // - change whether a lifetime means an operand is a model input, a model output, or neither
+    // - preserve whether or not a lifetime means an operand should have a writer
+    switch (operand.lifetime) {
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_POOL:
+            return OperandLifeTime::SUBGRAPH_INPUT;
+        case OperandLifeTime::SUBGRAPH_INPUT: {
+            const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+            if (!operandSize ||
+                exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+                // Unknown size or too-large size
+                break;
+            }
+            return OperandLifeTime::CONSTANT_COPY;
+        }
+        case OperandLifeTime::SUBGRAPH_OUTPUT:
+            return OperandLifeTime::TEMPORARY_VARIABLE;
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            return OperandLifeTime::SUBGRAPH_OUTPUT;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+            // appropriate choice -- is this operand written (then
+            // TEMPORARY_VARIABLE would be appropriate) or not (then
+            // CONSTANT_COPY would be appropriate)?
+            break;
+        case OperandLifeTime::SUBGRAPH:
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const std::shared_ptr<IDevice>& device,
+                                         const Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        const std::optional<OperandLifeTime> changedLifeTime =
+                getInputOutputLifeTime(model, modelSize, model.main.operands[operand]);
+        if (changedLifeTime) {
+            const std::string message = "mutateOperandInputOutputTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(*changedLifeTime) + " instead of lifetime " +
+                                        toString(model.main.operands[operand].lifetime);
+            validate(device, message, model,
+                     [operand, changedLifeTime](Model* model, ExecutionPreference*, Priority*) {
+                         static const DataLocation kZeroDataLocation = {};
+                         Operand& operandObj = model->main.operands[operand];
+                         operandObj.lifetime = *changedLifeTime;
+                         operandObj.location = kZeroDataLocation;
+                         if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+                             becomeConstantCopy(model, &operandObj);
+                         }
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        for (size_t badOutputNum = 0;
+             badOutputNum < model.main.operations[operation].outputs.size(); ++badOutputNum) {
+            const uint32_t outputOperandIndex =
+                    model.main.operations[operation].outputs[badOutputNum];
+            const std::string message = "mutateOperandAddWriterTest: operation " +
+                                        std::to_string(operation) + " writes to " +
+                                        std::to_string(outputOperandIndex);
+            // We'll insert a copy of the operation, all of whose
+            // OTHER output operands are newly-created -- i.e.,
+            // there'll only be a duplicate write of ONE of that
+            // operation's output operands.
+            validate(device, message, model,
+                     [operation, badOutputNum](Model* model, ExecutionPreference*, Priority*) {
+                         Operation newOperation = model->main.operations[operation];
+                         for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+                              ++outputNum) {
+                             if (outputNum == badOutputNum) continue;
+
+                             Operand operandValue =
+                                     model->main.operands[newOperation.outputs[outputNum]];
+                             if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+                                 operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                             } else {
+                                 ASSERT_EQ(operandValue.lifetime,
+                                           OperandLifeTime::TEMPORARY_VARIABLE);
+                             }
+                             newOperation.outputs[outputNum] = model->main.operands.size();
+                             model->main.operands.push_back(operandValue);
+                         }
+                         // Where do we insert the extra writer (a new
+                         // operation)?  It has to be later than all the
+                         // writers of its inputs.  The easiest thing to do
+                         // is to insert it at the end of the operation
+                         // sequence.
+                         model->main.operations.push_back(newOperation);
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE EXTRA ??? /////////////////////////
+
+// TODO: Operand::location
+
+///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
+
+static void mutateOperand(Operand* operand, OperandType type) {
+    Operand newOperand = *operand;
+    newOperand.type = type;
+    switch (type) {
+        case OperandType::FLOAT16:
+        case OperandType::FLOAT32:
+        case OperandType::INT32:
+        case OperandType::UINT32:
+        case OperandType::BOOL:
+            newOperand.dimensions = {};
+            newOperand.scale = 0.0f;
+            newOperand.zeroPoint = 0;
+            break;
+        case OperandType::TENSOR_BOOL8:
+        case OperandType::TENSOR_FLOAT16:
+        case OperandType::TENSOR_FLOAT32:
+            newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions
+                                                                   : std::vector<int32_t>({1});
+            newOperand.scale = 0.0f;
+            newOperand.zeroPoint = 0;
+            break;
+        case OperandType::TENSOR_INT32:
+            newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions
+                                                                   : std::vector<int32_t>({1});
+            newOperand.zeroPoint = 0;
+            break;
+        case OperandType::TENSOR_QUANT8_ASYMM:
+        case OperandType::TENSOR_QUANT8_SYMM:
+        case OperandType::TENSOR_QUANT16_ASYMM:
+        case OperandType::TENSOR_QUANT16_SYMM:
+            newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions
+                                                                   : std::vector<int32_t>({1});
+            newOperand.scale = operand->scale != 0.0f ? operand->scale : 1.0f;
+            break;
+        case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: {
+            newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions
+                                                                   : std::vector<int32_t>({1});
+            newOperand.scale = 0.0f;
+            newOperand.zeroPoint = 0;
+
+            SymmPerChannelQuantParams channelQuant;
+            channelQuant.channelDim = 0;
+            channelQuant.scales = std::vector<float>(
+                    operand->dimensions.size() > 0 ? static_cast<size_t>(operand->dimensions[0])
+                                                   : 0);
+            for (size_t i = 0; i < channelQuant.scales.size(); ++i) {
+                channelQuant.scales[i] = 1.0f;
+            }
+            newOperand.extraParams->set<OperandExtraParams::Tag::channelQuant>(
+                    std::move(channelQuant));
+        } break;
+        default:
+            break;
+    }
+    *operand = newOperand;
+}
+
+static bool mutateOperationOperandTypeSkip(size_t operand, OperandType type, const Model& model) {
+    if (type == model.main.operands[operand].type) {
+        return true;
+    }
+    for (const Operation& operation : model.main.operations) {
+        // Skip mutateOperationOperandTypeTest for the following operations.
+        // - LSH_PROJECTION's second argument is allowed to have any type.
+        // - ARGMIN and ARGMAX's first argument can be any of
+        // TENSOR_(FLOAT16|FLOAT32|INT32|QUANT8_ASYMM).
+        // - CAST's argument can be any of TENSOR_(FLOAT16|FLOAT32|INT32|QUANT8_ASYMM).
+        // - RANDOM_MULTINOMIAL's argument can be either TENSOR_FLOAT16 or TENSOR_FLOAT32.
+        // - DEQUANTIZE input can be any of
+        // TENSOR_(QUANT8_ASYMM|QUANT8_ASYMM_SIGNED|QUANT8_SYMM|QUANT8_SYMM_PER_CHANNEL),
+        // output can be of either TENSOR_FLOAT16 or TENSOR_FLOAT32.
+        // - QUANTIZE input can be either TENSOR_FLOAT16 or TENSOR_FLOAT32
+        // - CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL
+        // - DEPTHWISE_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL
+        // - GROUPED_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL
+        // - TRANSPOSE_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL
+        // - AXIS_ALIGNED_BBOX_TRANSFORM bounding boxes (arg 1) can be of
+        //     TENSOR_QUANT8_ASYMM or TENSOR_QUANT8_ASYMM_SIGNED.
+        // - RANK's input can have any TENSOR_* type.
+        switch (operation.type) {
+            case OperationType::LSH_PROJECTION: {
+                if (operand == operation.inputs[1]) {
+                    return true;
+                }
+            } break;
+            case OperationType::CAST:
+            case OperationType::ARGMAX:
+            case OperationType::ARGMIN: {
+                if (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32 ||
+                    type == OperandType::TENSOR_INT32 || type == OperandType::TENSOR_QUANT8_ASYMM ||
+                    type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED) {
+                    return true;
+                }
+            } break;
+            case OperationType::QUANTIZE: {
+                if (operand == operation.inputs[0] &&
+                    (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) {
+                    return true;
+                }
+                if (operand == operation.outputs[0] &&
+                    (type == OperandType::TENSOR_QUANT8_ASYMM ||
+                     type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) {
+                    return true;
+                }
+            } break;
+            case OperationType::RANDOM_MULTINOMIAL: {
+                if (operand == operation.inputs[0] &&
+                    (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) {
+                    return true;
+                }
+            } break;
+            case OperationType::DEQUANTIZE: {
+                if (operand == operation.inputs[0] &&
+                    (type == OperandType::TENSOR_QUANT8_ASYMM ||
+                     type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED ||
+                     type == OperandType::TENSOR_QUANT8_SYMM ||
+                     type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)) {
+                    return true;
+                }
+                if (operand == operation.outputs[0] &&
+                    (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) {
+                    return true;
+                }
+            } break;
+            case OperationType::TRANSPOSE_CONV_2D:
+            case OperationType::GROUPED_CONV_2D:
+            case OperationType::DEPTHWISE_CONV_2D:
+            case OperationType::CONV_2D: {
+                if (operand == operation.inputs[1] &&
+                    (type == OperandType::TENSOR_QUANT8_ASYMM ||
+                     type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)) {
+                    return true;
+                }
+            } break;
+            case OperationType::AXIS_ALIGNED_BBOX_TRANSFORM: {
+                if (operand == operation.inputs[1] &&
+                    (type == OperandType::TENSOR_QUANT8_ASYMM ||
+                     type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) {
+                    return true;
+                }
+            } break;
+            case OperationType::RANK: {
+                if (operand == operation.inputs[0] &&
+                    (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32 ||
+                     type == OperandType::TENSOR_INT32 ||
+                     type == OperandType::TENSOR_QUANT8_ASYMM ||
+                     type == OperandType::TENSOR_QUANT16_SYMM ||
+                     type == OperandType::TENSOR_BOOL8 ||
+                     type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL ||
+                     type == OperandType::TENSOR_QUANT16_ASYMM ||
+                     type == OperandType::TENSOR_QUANT8_SYMM ||
+                     type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) {
+                    return true;
+                }
+            } break;
+            default:
+                break;
+        }
+    }
+    return false;
+}
+
+static void mutateOperationOperandTypeTest(const std::shared_ptr<IDevice>& device,
+                                           const Model& model) {
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        for (OperandType invalidOperandType : ndk::enum_range<OperandType>()) {
+            if (mutateOperationOperandTypeSkip(operand, invalidOperandType, model)) {
+                continue;
+            }
+            const std::string message = "mutateOperationOperandTypeTest: operand " +
+                                        std::to_string(operand) + " set to type " +
+                                        toString(invalidOperandType);
+            validate(device, message, model,
+                     [operand, invalidOperandType](Model* model, ExecutionPreference*, Priority*) {
+                         mutateOperand(&model->main.operands[operand], invalidOperandType);
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION TYPE /////////////////////////
+
+static const int32_t invalidOperationTypes[] = {
+        -1,
+        static_cast<int32_t>(*(ndk::enum_range<OperationType>().end() - 1)) + 1,
+};
+
+static void mutateOperationTypeTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        for (int32_t invalidOperationType : invalidOperationTypes) {
+            const std::string message = "mutateOperationTypeTest: operation " +
+                                        std::to_string(operation) + " set to value " +
+                                        std::to_string(invalidOperationType);
+            validate(device, message, model,
+                     [operation, invalidOperationType](Model* model, ExecutionPreference*,
+                                                       Priority*) {
+                         model->main.operations[operation].type =
+                                 static_cast<OperationType>(invalidOperationType);
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION INPUT OPERAND INDEX /////////////////////////
+
+static void mutateOperationInputOperandIndexTest(const std::shared_ptr<IDevice>& device,
+                                                 const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        const uint32_t invalidOperand = model.main.operands.size();
+        for (size_t input = 0; input < model.main.operations[operation].inputs.size(); ++input) {
+            const std::string message = "mutateOperationInputOperandIndexTest: operation " +
+                                        std::to_string(operation) + " input " +
+                                        std::to_string(input);
+            validate(device, message, model,
+                     [operation, input, invalidOperand](Model* model, ExecutionPreference*,
+                                                        Priority*) {
+                         model->main.operations[operation].inputs[input] = invalidOperand;
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION OUTPUT OPERAND INDEX /////////////////////////
+
+static void mutateOperationOutputOperandIndexTest(const std::shared_ptr<IDevice>& device,
+                                                  const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        const uint32_t invalidOperand = model.main.operands.size();
+        for (size_t output = 0; output < model.main.operations[operation].outputs.size();
+             ++output) {
+            const std::string message = "mutateOperationOutputOperandIndexTest: operation " +
+                                        std::to_string(operation) + " output " +
+                                        std::to_string(output);
+            validate(device, message, model,
+                     [operation, output, invalidOperand](Model* model, ExecutionPreference*,
+                                                         Priority*) {
+                         model->main.operations[operation].outputs[output] = invalidOperand;
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const std::shared_ptr<IDevice>& device,
+                                           const Model& model,
+                                           const std::vector<uint32_t>& numberOfConsumers) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        for (size_t outputNum = 0; outputNum < model.main.operations[operation].outputs.size();
+             ++outputNum) {
+            const uint32_t outputOperandIndex = model.main.operations[operation].outputs[outputNum];
+            if (numberOfConsumers[outputOperandIndex] > 0) {
+                const std::string message = "mutateOperationRemoveWriteTest: operation " +
+                                            std::to_string(operation) + " writes to " +
+                                            std::to_string(outputOperandIndex);
+                validate(device, message, model,
+                         [operation, outputNum](Model* model, ExecutionPreference*, Priority*) {
+                             int32_t& outputOperandIndex =
+                                     model->main.operations[operation].outputs[outputNum];
+                             Operand operandValue = model->main.operands[outputOperandIndex];
+                             if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+                                 operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                             } else {
+                                 ASSERT_EQ(operandValue.lifetime,
+                                           OperandLifeTime::TEMPORARY_VARIABLE);
+                             }
+                             outputOperandIndex = model->main.operands.size();
+                             model->main.operands.push_back(operandValue);
+                         });
+            }
+        }
+    }
+}
+
+///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
+
+static void removeValueAndDecrementGreaterValues(std::vector<int32_t>* vec, uint32_t value) {
+    if (vec) {
+        // remove elements matching "value"
+        vec->erase(std::remove(vec->begin(), vec->end(), value), vec->end());
+
+        // decrement elements exceeding "value"
+        std::transform(vec->begin(), vec->end(), vec->begin(),
+                       [value](uint32_t v) { return v > value ? v-- : v; });
+    }
+}
+
+static void removeOperand(Model* model, uint32_t index) {
+    model->main.operands.erase(model->main.operands.begin() + index);
+    for (Operation& operation : model->main.operations) {
+        removeValueAndDecrementGreaterValues(&operation.inputs, index);
+        removeValueAndDecrementGreaterValues(&operation.outputs, index);
+    }
+    removeValueAndDecrementGreaterValues(&model->main.inputIndexes, index);
+    removeValueAndDecrementGreaterValues(&model->main.outputIndexes, index);
+}
+
+static bool removeOperandSkip(size_t operandIndex, const Model& model,
+                              const std::vector<uint32_t>& numberOfConsumers) {
+    if (numberOfConsumers[operandIndex] == 0) {
+        // Removing an unused operand has no effect.
+        return true;
+    }
+    for (const Operation& operation : model.main.operations) {
+        // Skip removeOperandTest for the following operations.
+        // - SPLIT's outputs are not checked during prepareModel.
+        if (operation.type == OperationType::SPLIT) {
+            for (const size_t index : operation.outputs) {
+                if (index == operandIndex) {
+                    return true;
+                }
+            }
+        }
+        // BIDIRECTIONAL_SEQUENCE_LSTM and BIDIRECTIONAL_SEQUENCE_RNN can have
+        // either one, two, three or four outputs depending on their
+        // mergeOutputs parameter and if state outputs are provided.
+        // UNIDIRECTIONAL_SEQUENCE_LSTM and UNIDIRECTIONAL_SEQUENCE_RNN can have
+        // either one or three outputs depending on whether state outputs are
+        // provided.
+        if (operation.type == OperationType::UNIDIRECTIONAL_SEQUENCE_LSTM ||
+            operation.type == OperationType::UNIDIRECTIONAL_SEQUENCE_RNN ||
+            operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_LSTM ||
+            operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_RNN) {
+            for (const size_t index : operation.outputs) {
+                if (index == operandIndex) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+static void removeOperandTest(const std::shared_ptr<IDevice>& device, const Model& model,
+                              const std::vector<uint32_t>& numberOfConsumers) {
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        if (removeOperandSkip(operand, model, numberOfConsumers)) {
+            continue;
+        }
+        const std::string message = "removeOperandTest: operand " + std::to_string(operand);
+        validate(device, message, model, [operand](Model* model, ExecutionPreference*, Priority*) {
+            removeOperand(model, operand);
+        });
+    }
+}
+
+///////////////////////// REMOVE OPERATION /////////////////////////
+
+static void removeOperation(Model* model, uint32_t index) {
+    auto& operations = model->main.operations;
+    operations.erase(operations.begin() + index);
+}
+
+static void removeOperationTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        const std::string message = "removeOperationTest: operation " + std::to_string(operation);
+        validate(device, message, model,
+                 [operation](Model* model, ExecutionPreference*, Priority*) {
+                     removeOperation(model, operation);
+                 });
+    }
+}
+
+///////////////////////// REMOVE OPERATION INPUT /////////////////////////
+
+static bool removeOperationInputSkip(const Operation& op, size_t input) {
+    // Skip removeOperationInputTest for the following operations.
+    // - CONCATENATION has at least 2 inputs, with the last element being INT32.
+    // - CONV_2D, DEPTHWISE_CONV_2D, MAX_POOL_2D, AVERAGE_POOL_2D, L2_POOL_2D, RESIZE_BILINEAR,
+    //   SPACE_TO_DEPTH, SPACE_TO_DEPTH, SPACE_TO_BATCH_ND, BATCH_TO_SPACE_ND can have an optional
+    //   layout parameter.
+    //   RESIZE_BILINEAR and RESIZE_NEAREST_NEIGHBOR can have optional
+    //   align_corners and half_pixel_centers parameters.
+    // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional axis
+    //   parameter.
+    switch (op.type) {
+        case OperationType::CONCATENATION: {
+            if (op.inputs.size() > 2 && input != op.inputs.size() - 1) {
+                return true;
+            }
+        } break;
+        case OperationType::DEPTHWISE_CONV_2D: {
+            if ((op.inputs.size() == 12 && input == 11) || (op.inputs.size() == 9 && input == 8)) {
+                return true;
+            }
+        } break;
+        case OperationType::CONV_2D:
+        case OperationType::AVERAGE_POOL_2D:
+        case OperationType::MAX_POOL_2D:
+        case OperationType::L2_POOL_2D: {
+            if ((op.inputs.size() == 11 && input == 10) || (op.inputs.size() == 8 && input == 7)) {
+                return true;
+            }
+        } break;
+        case OperationType::RESIZE_BILINEAR: {
+            if (op.inputs.size() >= 4 && input >= 3) {
+                return true;
+            }
+        } break;
+        case OperationType::RESIZE_NEAREST_NEIGHBOR: {
+            if (op.inputs.size() >= 5 && input >= 3) {
+                return true;
+            }
+        } break;
+        case OperationType::SPACE_TO_DEPTH:
+        case OperationType::DEPTH_TO_SPACE:
+        case OperationType::BATCH_TO_SPACE_ND: {
+            if (op.inputs.size() == 3 && input == 2) {
+                return true;
+            }
+        } break;
+        case OperationType::SPACE_TO_BATCH_ND: {
+            if (op.inputs.size() == 4 && input == 3) {
+                return true;
+            }
+        } break;
+        case OperationType::L2_NORMALIZATION: {
+            if (op.inputs.size() == 2 && input == 1) {
+                return true;
+            }
+        } break;
+        case OperationType::LOCAL_RESPONSE_NORMALIZATION: {
+            if (op.inputs.size() == 6 && input == 5) {
+                return true;
+            }
+        } break;
+        case OperationType::SOFTMAX: {
+            if (op.inputs.size() == 3 && input == 2) {
+                return true;
+            }
+        } break;
+        default:
+            break;
+    }
+    return false;
+}
+
+static void removeOperationInputTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        for (size_t input = 0; input < model.main.operations[operation].inputs.size(); ++input) {
+            const Operation& op = model.main.operations[operation];
+            if (removeOperationInputSkip(op, input)) {
+                continue;
+            }
+            const std::string message = "removeOperationInputTest: operation " +
+                                        std::to_string(operation) + ", input " +
+                                        std::to_string(input);
+            validate(device, message, model,
+                     [operation, input](Model* model, ExecutionPreference*, Priority*) {
+                         auto& inputs = model->main.operations[operation].inputs;
+                         inputs.erase(inputs.begin() + input);
+                     });
+        }
+    }
+}
+
+///////////////////////// REMOVE OPERATION OUTPUT /////////////////////////
+
+static void removeOperationOutputTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        for (size_t output = 0; output < model.main.operations[operation].outputs.size();
+             ++output) {
+            const std::string message = "removeOperationOutputTest: operation " +
+                                        std::to_string(operation) + ", output " +
+                                        std::to_string(output);
+            validate(device, message, model,
+                     [operation, output](Model* model, ExecutionPreference*, Priority*) {
+                         auto& outputs = model->main.operations[operation].outputs;
+                         outputs.erase(outputs.begin() + output);
+                     });
+        }
+    }
+}
+
+///////////////////////// MODEL VALIDATION /////////////////////////
+
+// TODO: remove model input
+// TODO: remove model output
+// TODO: add unused operation
+
+///////////////////////// ADD OPERATION INPUT /////////////////////////
+
+static bool addOperationInputSkip(const Operation& op) {
+    // Skip addOperationInputTest for the following operations.
+    // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional INT32 axis
+    //   parameter.
+    if ((op.type == OperationType::L2_NORMALIZATION && op.inputs.size() == 1) ||
+        (op.type == OperationType::LOCAL_RESPONSE_NORMALIZATION && op.inputs.size() == 5) ||
+        (op.type == OperationType::SOFTMAX && op.inputs.size() == 2) ||
+        (op.type == OperationType::RESIZE_BILINEAR && op.inputs.size() < 6) ||
+        (op.type == OperationType::RESIZE_NEAREST_NEIGHBOR && op.inputs.size() < 6)) {
+        return true;
+    }
+    return false;
+}
+
+static void addOperationInputTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        if (addOperationInputSkip(model.main.operations[operation])) {
+            continue;
+        }
+        const std::string message = "addOperationInputTest: operation " + std::to_string(operation);
+        validate(device, message, model,
+                 [operation](Model* model, ExecutionPreference*, Priority*) {
+                     uint32_t index = addOperand(model, OperandLifeTime::SUBGRAPH_INPUT);
+                     model->main.operations[operation].inputs.push_back(index);
+                     model->main.inputIndexes.push_back(index);
+                 });
+    }
+}
+
+///////////////////////// ADD OPERATION OUTPUT /////////////////////////
+
+static void addOperationOutputTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        const std::string message =
+                "addOperationOutputTest: operation " + std::to_string(operation);
+        validate(device, message, model,
+                 [operation](Model* model, ExecutionPreference*, Priority*) {
+                     uint32_t index = addOperand(model, OperandLifeTime::SUBGRAPH_OUTPUT);
+                     model->main.operations[operation].outputs.push_back(index);
+                     model->main.outputIndexes.push_back(index);
+                 });
+    }
+}
+
+///////////////////////// VALIDATE EXECUTION PREFERENCE /////////////////////////
+
+static const int32_t invalidExecutionPreferences[] = {
+        static_cast<int32_t>(ExecutionPreference::LOW_POWER) - 1,        // lower bound
+        static_cast<int32_t>(ExecutionPreference::SUSTAINED_SPEED) + 1,  // upper bound
+};
+
+static void mutateExecutionPreferenceTest(const std::shared_ptr<IDevice>& device,
+                                          const Model& model) {
+    for (int32_t invalidPreference : invalidExecutionPreferences) {
+        const std::string message =
+                "mutateExecutionPreferenceTest: preference " + std::to_string(invalidPreference);
+        validate(device, message, model,
+                 [invalidPreference](Model*, ExecutionPreference* preference, Priority*) {
+                     *preference = static_cast<ExecutionPreference>(invalidPreference);
+                 });
+    }
+}
+
+///////////////////////// VALIDATE PRIORITY /////////////////////////
+
+static const int32_t invalidPriorities[] = {
+        static_cast<int32_t>(Priority::LOW) - 1,   // lower bound
+        static_cast<int32_t>(Priority::HIGH) + 1,  // upper bound
+};
+
+static void mutateExecutionPriorityTest(const std::shared_ptr<IDevice>& device,
+                                        const Model& model) {
+    for (int32_t invalidPriority : invalidPriorities) {
+        const std::string message =
+                "mutatePriorityTest: priority " + std::to_string(invalidPriority);
+        validate(device, message, model,
+                 [invalidPriority](Model*, ExecutionPreference*, Priority* priority) {
+                     *priority = static_cast<Priority>(invalidPriority);
+                 });
+    }
+}
+
+////////////////////////// ENTRY POINT //////////////////////////////
+
+void validateModel(const std::shared_ptr<IDevice>& device, const Model& model) {
+    const auto numberOfConsumers = nn::countNumberOfConsumers(
+            model.main.operands.size(), nn::convert(model.main.operations).value());
+    mutateExecutionOrderTest(device, model, numberOfConsumers);
+    mutateOperandTypeTest(device, model);
+    mutateOperandRankTest(device, model);
+    mutateOperandScaleTest(device, model);
+    mutateOperandZeroPointTest(device, model);
+    mutateOperandLifeTimeTest(device, model);
+    mutateOperandInputOutputTest(device, model);
+    mutateOperandAddWriterTest(device, model);
+    mutateOperationOperandTypeTest(device, model);
+    mutateOperationTypeTest(device, model);
+    mutateOperationInputOperandIndexTest(device, model);
+    mutateOperationOutputOperandIndexTest(device, model);
+    mutateOperationRemoveWriteTest(device, model, numberOfConsumers);
+    removeOperandTest(device, model, numberOfConsumers);
+    removeOperationTest(device, model);
+    removeOperationInputTest(device, model);
+    removeOperationOutputTest(device, model);
+    addOperationInputTest(device, model);
+    addOperationOutputTest(device, model);
+    mutateExecutionPreferenceTest(device, model);
+    mutateExecutionPriorityTest(device, model);
+}
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/ValidateRequest.cpp b/neuralnetworks/aidl/vts/functional/ValidateRequest.cpp
new file mode 100644
index 0000000..db8f429
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/ValidateRequest.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 "neuralnetworks_aidl_hal_test"
+
+#include <android/binder_auto_utils.h>
+
+#include <chrono>
+
+#include <TestHarness.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using ExecutionMutation = std::function<void(Request*)>;
+
+///////////////////////// UTILITY FUNCTIONS /////////////////////////
+
+// Primary validation function. This function will take a valid request, apply a
+// mutation to it to invalidate the request, then pass it to interface calls
+// that use the request.
+static void validate(const std::shared_ptr<IPreparedModel>& preparedModel,
+                     const std::string& message, const Request& originalRequest,
+                     const ExecutionMutation& mutate) {
+    Request request = utils::clone(originalRequest).value();
+    mutate(&request);
+
+    // We'd like to test both with timing requested and without timing
+    // requested. Rather than running each test both ways, we'll decide whether
+    // to request timing by hashing the message. We do not use std::hash because
+    // it is not guaranteed stable across executions.
+    char hash = 0;
+    for (auto c : message) {
+        hash ^= c;
+    };
+    bool measure = (hash & 1);
+
+    // synchronous
+    {
+        SCOPED_TRACE(message + " [executeSynchronously]");
+        ExecutionResult executionResult;
+        const auto executeStatus = preparedModel->executeSynchronously(
+                request, measure, kNoDeadline, kOmittedTimeoutDuration, &executionResult);
+        ASSERT_FALSE(executeStatus.isOk());
+        ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+        ASSERT_EQ(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()),
+                  ErrorStatus::INVALID_ARGUMENT);
+    }
+
+    // fenced
+    {
+        SCOPED_TRACE(message + " [executeFenced]");
+        ndk::ScopedFileDescriptor syncFence;
+        std::shared_ptr<IFencedExecutionCallback> callback;
+        const auto executeStatus = preparedModel->executeFenced(request, {}, false, kNoDeadline,
+                                                                kOmittedTimeoutDuration,
+                                                                kNoDuration, &syncFence, &callback);
+        ASSERT_FALSE(executeStatus.isOk());
+        ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+        ASSERT_EQ(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()),
+                  ErrorStatus::INVALID_ARGUMENT);
+    }
+}
+
+///////////////////////// REMOVE INPUT ////////////////////////////////////
+
+static void removeInputTest(const std::shared_ptr<IPreparedModel>& preparedModel,
+                            const Request& request) {
+    for (size_t input = 0; input < request.inputs.size(); ++input) {
+        const std::string message = "removeInput: removed input " + std::to_string(input);
+        validate(preparedModel, message, request, [input](Request* request) {
+            request->inputs.erase(request->inputs.begin() + input);
+        });
+    }
+}
+
+///////////////////////// REMOVE OUTPUT ////////////////////////////////////
+
+static void removeOutputTest(const std::shared_ptr<IPreparedModel>& preparedModel,
+                             const Request& request) {
+    for (size_t output = 0; output < request.outputs.size(); ++output) {
+        const std::string message = "removeOutput: removed Output " + std::to_string(output);
+        validate(preparedModel, message, request, [output](Request* request) {
+            request->outputs.erase(request->outputs.begin() + output);
+        });
+    }
+}
+
+///////////////////////////// ENTRY POINT //////////////////////////////////
+
+void validateRequest(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request) {
+    removeInputTest(preparedModel, request);
+    removeOutputTest(preparedModel, request);
+}
+
+void validateRequestFailure(const std::shared_ptr<IPreparedModel>& preparedModel,
+                            const Request& request) {
+    SCOPED_TRACE("Expecting request to fail [executeSynchronously]");
+    ExecutionResult executionResult;
+    const auto executeStatus = preparedModel->executeSynchronously(
+            request, false, kNoDeadline, kOmittedTimeoutDuration, &executionResult);
+
+    ASSERT_FALSE(executeStatus.isOk());
+    ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+    ASSERT_NE(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()), ErrorStatus::NONE);
+}
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp
new file mode 100644
index 0000000..2d91b8e
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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 "neuralnetworks_aidl_hal_test"
+#include "VtsHalNeuralnetworks.h"
+
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <android/binder_status.h>
+#include <gtest/gtest.h>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <TestHarness.h>
+#include <aidl/Vintf.h>
+#include <nnapi/hal/aidl/Conversions.h>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "Utils.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using implementation::PreparedModelCallback;
+
+// internal helper function
+void createPreparedModel(const std::shared_ptr<IDevice>& device, const Model& model,
+                         std::shared_ptr<IPreparedModel>* preparedModel, bool reportSkipping) {
+    ASSERT_NE(nullptr, preparedModel);
+    *preparedModel = nullptr;
+
+    // see if service can handle model
+    std::vector<bool> supportedOperations;
+    const auto supportedCallStatus = device->getSupportedOperations(model, &supportedOperations);
+    ASSERT_TRUE(supportedCallStatus.isOk());
+    ASSERT_NE(0ul, supportedOperations.size());
+    const bool fullySupportsModel = std::all_of(
+            supportedOperations.begin(), supportedOperations.end(), [](bool v) { return v; });
+
+    // launch prepare model
+    const std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+            ndk::SharedRefBase::make<PreparedModelCallback>();
+    const auto prepareLaunchStatus =
+            device->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority,
+                                 kNoDeadline, {}, {}, kEmptyCacheToken, preparedModelCallback);
+    ASSERT_TRUE(prepareLaunchStatus.isOk()) << prepareLaunchStatus.getDescription();
+
+    // retrieve prepared model
+    preparedModelCallback->wait();
+    const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+    *preparedModel = preparedModelCallback->getPreparedModel();
+
+    // The getSupportedOperations call returns a list of operations that are guaranteed not to fail
+    // if prepareModel is called, and 'fullySupportsModel' is true i.f.f. the entire model is
+    // guaranteed. If a driver has any doubt that it can prepare an operation, it must return false.
+    // So here, if a driver isn't sure if it can support an operation, but reports that it
+    // successfully prepared the model, the test can continue.
+    if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
+        ASSERT_EQ(nullptr, preparedModel->get());
+        if (!reportSkipping) {
+            return;
+        }
+        LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot prepare "
+                     "model that it does not support.";
+        std::cout << "[          ]   Early termination of test because vendor service cannot "
+                     "prepare model that it does not support."
+                  << std::endl;
+        GTEST_SKIP();
+    }
+
+    ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+    ASSERT_NE(nullptr, preparedModel->get());
+}
+
+void NeuralNetworksAidlTest::SetUp() {
+    testing::TestWithParam<NeuralNetworksAidlTestParam>::SetUp();
+    ASSERT_NE(kDevice, nullptr);
+}
+
+static NamedDevice makeNamedDevice(const std::string& name) {
+    ndk::SpAIBinder binder(AServiceManager_getService(name.c_str()));
+    return {name, IDevice::fromBinder(binder)};
+}
+
+static std::vector<NamedDevice> getNamedDevicesImpl() {
+    // Retrieves the name of all service instances that implement IDevice,
+    // including any Lazy HAL instances.
+    const std::vector<std::string> names = ::android::getAidlHalInstanceNames(IDevice::descriptor);
+
+    // Get a handle to each device and pair it with its name.
+    std::vector<NamedDevice> namedDevices;
+    namedDevices.reserve(names.size());
+    std::transform(names.begin(), names.end(), std::back_inserter(namedDevices), makeNamedDevice);
+    return namedDevices;
+}
+
+const std::vector<NamedDevice>& getNamedDevices() {
+    const static std::vector<NamedDevice> devices = getNamedDevicesImpl();
+    return devices;
+}
+
+std::string printNeuralNetworksAidlTest(
+        const testing::TestParamInfo<NeuralNetworksAidlTestParam>& info) {
+    return gtestCompliantName(getName(info.param));
+}
+
+INSTANTIATE_DEVICE_TEST(NeuralNetworksAidlTest);
+
+// Forward declaration from ValidateModel.cpp
+void validateModel(const std::shared_ptr<IDevice>& device, const Model& model);
+// Forward declaration from ValidateRequest.cpp
+void validateRequest(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request);
+// Forward declaration from ValidateRequest.cpp
+void validateRequestFailure(const std::shared_ptr<IPreparedModel>& preparedModel,
+                            const Request& request);
+
+void validateEverything(const std::shared_ptr<IDevice>& device, const Model& model,
+                        const Request& request) {
+    validateModel(device, model);
+
+    // Create IPreparedModel.
+    std::shared_ptr<IPreparedModel> preparedModel;
+    createPreparedModel(device, model, &preparedModel);
+    if (preparedModel == nullptr) return;
+
+    validateRequest(preparedModel, request);
+    // HIDL also had test that expected executeFenced to fail on received null fd (-1). This is not
+    // allowed in AIDL and will result in EX_TRANSACTION_FAILED.
+}
+
+void validateFailure(const std::shared_ptr<IDevice>& device, const Model& model,
+                     const Request& request) {
+    // TODO: Should this always succeed?
+    //       What if the invalid input is part of the model (i.e., a parameter).
+    validateModel(device, model);
+
+    // Create IPreparedModel.
+    std::shared_ptr<IPreparedModel> preparedModel;
+    createPreparedModel(device, model, &preparedModel);
+    if (preparedModel == nullptr) return;
+
+    validateRequestFailure(preparedModel, request);
+}
+
+TEST_P(ValidationTest, Test) {
+    const Model model = createModel(kTestModel);
+    ExecutionContext context;
+    const Request request = context.createRequest(kTestModel);
+    if (kTestModel.expectFailure) {
+        validateFailure(kDevice, model, request);
+    } else {
+        validateEverything(kDevice, model, request);
+    }
+}
+
+INSTANTIATE_GENERATED_TEST(ValidationTest, [](const std::string& testName) {
+    // Skip validation for the "inputs_as_internal" and "all_tensors_as_inputs"
+    // generated tests.
+    return testName.find("inputs_as_internal") == std::string::npos &&
+           testName.find("all_tensors_as_inputs") == std::string::npos;
+});
+
+std::string toString(Executor executor) {
+    switch (executor) {
+        case Executor::ASYNC:
+            return "ASYNC";
+        case Executor::SYNC:
+            return "SYNC";
+        case Executor::BURST:
+            return "BURST";
+        case Executor::FENCED:
+            return "FENCED";
+        default:
+            CHECK(false);
+    }
+}
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h
new file mode 100644
index 0000000..9b81ee1
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h
@@ -0,0 +1,61 @@
+/*
+ * 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_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H
+
+#include <gtest/gtest.h>
+#include <vector>
+
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+
+#include "Callbacks.h"
+#include "Utils.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using NamedDevice = Named<std::shared_ptr<IDevice>>;
+using NeuralNetworksAidlTestParam = NamedDevice;
+
+class NeuralNetworksAidlTest : public testing::TestWithParam<NeuralNetworksAidlTestParam> {
+  protected:
+    void SetUp() override;
+    const std::shared_ptr<IDevice> kDevice = getData(GetParam());
+};
+
+const std::vector<NamedDevice>& getNamedDevices();
+
+std::string printNeuralNetworksAidlTest(
+        const testing::TestParamInfo<NeuralNetworksAidlTestParam>& info);
+
+#define INSTANTIATE_DEVICE_TEST(TestSuite)                                                 \
+    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestSuite);                              \
+    INSTANTIATE_TEST_SUITE_P(PerInstance, TestSuite, testing::ValuesIn(getNamedDevices()), \
+                             printNeuralNetworksAidlTest)
+
+// Create an IPreparedModel object. If the model cannot be prepared,
+// "preparedModel" will be nullptr instead.
+void createPreparedModel(const std::shared_ptr<IDevice>& device, const Model& model,
+                         std::shared_ptr<IPreparedModel>* preparedModel,
+                         bool reportSkipping = true);
+
+enum class Executor { ASYNC, SYNC, BURST, FENCED };
+
+std::string toString(Executor executor);
+
+}  // namespace aidl::android::hardware::neuralnetworks::vts::functional
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H
diff --git a/neuralnetworks/utils/common/Android.bp b/neuralnetworks/utils/common/Android.bp
index 21562cf..50295f1 100644
--- a/neuralnetworks/utils/common/Android.bp
+++ b/neuralnetworks/utils/common/Android.bp
@@ -22,9 +22,36 @@
     export_include_dirs: ["include"],
     cflags: ["-Wthread-safety"],
     static_libs: [
+        "libarect",
         "neuralnetworks_types",
     ],
     shared_libs: [
         "libhidlbase",
+        "libnativewindow",
     ],
 }
+
+cc_test {
+    name: "neuralnetworks_utils_hal_common_test",
+    srcs: ["test/*.cpp"],
+    static_libs: [
+        "android.hardware.neuralnetworks@1.0",
+        "libgmock",
+        "libneuralnetworks_common",
+        "neuralnetworks_types",
+        "neuralnetworks_utils_hal_common",
+    ],
+    shared_libs: [
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libbase",
+        "libcutils",
+        "libfmq",
+        "libhidlbase",
+        "libhidlmemory",
+        "liblog",
+        "libnativewindow",
+        "libutils",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
index b3989e5..547f203 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
@@ -24,15 +24,21 @@
 #include <functional>
 #include <vector>
 
-// Shorthand
+// Shorthands
 namespace android::hardware::neuralnetworks {
 namespace hal = ::android::hardware::neuralnetworks;
 }  // namespace android::hardware::neuralnetworks
 
-// Shorthand
+// Shorthands
+namespace aidl::android::hardware::neuralnetworks {
+namespace aidl_hal = ::aidl::android::hardware::neuralnetworks;
+}  // namespace aidl::android::hardware::neuralnetworks
+
+// Shorthands
 namespace android::nn {
 namespace hal = ::android::hardware::neuralnetworks;
-}
+namespace aidl_hal = ::aidl::android::hardware::neuralnetworks;
+}  // namespace android::nn
 
 namespace android::hardware::neuralnetworks::utils {
 
@@ -68,10 +74,12 @@
 std::vector<uint32_t> countNumberOfConsumers(size_t numberOfOperands,
                                              const std::vector<nn::Operation>& operations);
 
-nn::GeneralResult<nn::Memory> createSharedMemoryFromHidlMemory(const hidl_memory& memory);
+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::SharedHandle& handle);
-nn::GeneralResult<nn::SharedHandle> sharedHandleFromNativeHandle(const native_handle_t* handle);
+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);
 
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h b/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
index 95a20a8..209b663 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
+
 #include <android/hidl/base/1.0/IBase.h>
 #include <hidl/HidlSupport.h>
 #include <nnapi/Result.h>
@@ -50,7 +53,8 @@
     })
 
 template <typename Type>
-nn::GeneralResult<Type> makeGeneralFailure(nn::Result<Type> result, nn::ErrorStatus status) {
+nn::GeneralResult<Type> makeGeneralFailure(
+        nn::Result<Type> result, nn::ErrorStatus status = nn::ErrorStatus::GENERAL_FAILURE) {
     if (!result.has_value()) {
         return nn::error(status) << std::move(result).error();
     }
@@ -75,7 +79,8 @@
 }
 
 template <typename Type>
-nn::ExecutionResult<Type> makeExecutionFailure(nn::Result<Type> result, nn::ErrorStatus status) {
+nn::ExecutionResult<Type> makeExecutionFailure(
+        nn::Result<Type> result, nn::ErrorStatus status = nn::ErrorStatus::GENERAL_FAILURE) {
     return makeExecutionFailure(makeGeneralFailure(result, status));
 }
 
@@ -86,4 +91,6 @@
     } else                                                              \
         return NN_ERROR(canonical)
 
-}  // namespace android::hardware::neuralnetworks::utils
\ No newline at end of file
+}  // namespace android::hardware::neuralnetworks::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h
index 8c04b88..0e98c2e 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h
@@ -31,9 +31,9 @@
   public:
     nn::Request::MemoryDomainToken getToken() const override;
 
-    nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override;
+    nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override;
 
-    nn::GeneralResult<void> copyFrom(const nn::Memory& src,
+    nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src,
                                      const nn::Dimensions& dimensions) const override;
 };
 
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h
new file mode 100644
index 0000000..996858c
--- /dev/null
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H
+
+#include <nnapi/IBurst.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::utils {
+
+class InvalidBurst final : public nn::IBurst {
+  public:
+    OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override;
+
+    nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
+            const nn::Request& request, nn::MeasureTiming measure) const override;
+};
+
+}  // namespace android::hardware::neuralnetworks::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h
index 5e62b9a..d843526 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h
@@ -32,7 +32,7 @@
 class InvalidDevice final : public nn::IDevice {
   public:
     InvalidDevice(std::string name, std::string versionString, nn::Version featureLevel,
-                  nn::DeviceType type, std::vector<nn::Extension> extensions,
+                  nn::DeviceType type, bool isUpdatable, std::vector<nn::Extension> extensions,
                   nn::Capabilities capabilities,
                   std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded);
 
@@ -40,6 +40,7 @@
     const std::string& getVersionString() const override;
     nn::Version getFeatureLevel() const override;
     nn::DeviceType getType() const override;
+    bool isUpdatable() const override;
     const std::vector<nn::Extension>& getSupportedExtensions() const override;
     const nn::Capabilities& getCapabilities() const override;
     std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
@@ -70,6 +71,7 @@
     const std::string kVersionString;
     const nn::Version kFeatureLevel;
     const nn::DeviceType kType;
+    const bool kIsUpdatable;
     const std::vector<nn::Extension> kExtensions;
     const nn::Capabilities kCapabilities;
     const std::pair<uint32_t, uint32_t> kNumberOfCacheFilesNeeded;
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h
index 985cddb..3e1dca7 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h
@@ -40,6 +40,8 @@
             const nn::OptionalDuration& loopTimeoutDuration,
             const nn::OptionalDuration& timeoutDurationAfterFence) const override;
 
+    nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
     std::any getUnderlyingResource() const override;
 };
 
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
index 9d5e3e6..c8ca6f2 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
@@ -42,13 +42,13 @@
                              nn::SharedBuffer buffer);
 
     nn::SharedBuffer getBuffer() const;
-    nn::SharedBuffer recover(const nn::IBuffer* failingBuffer, bool blocking) const;
+    nn::GeneralResult<nn::SharedBuffer> recover(const nn::IBuffer* failingBuffer) const;
 
     nn::Request::MemoryDomainToken getToken() const override;
 
-    nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override;
+    nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override;
 
-    nn::GeneralResult<void> copyFrom(const nn::Memory& src,
+    nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src,
                                      const nn::Dimensions& dimensions) const override;
 
   private:
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h
new file mode 100644
index 0000000..3b87330
--- /dev/null
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H
+
+#include <android-base/thread_annotations.h>
+#include <nnapi/IBurst.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::utils {
+
+class ResilientBurst final : public nn::IBurst,
+                             public std::enable_shared_from_this<ResilientBurst> {
+    struct PrivateConstructorTag {};
+
+  public:
+    using Factory = std::function<nn::GeneralResult<nn::SharedBurst>()>;
+
+    static nn::GeneralResult<std::shared_ptr<const ResilientBurst>> create(Factory makeBurst);
+
+    ResilientBurst(PrivateConstructorTag tag, Factory makeBurst, nn::SharedBurst burst);
+
+    nn::SharedBurst getBurst() const;
+    nn::GeneralResult<nn::SharedBurst> recover(const nn::IBurst* failingBurst) const;
+
+    OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override;
+
+    nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
+            const nn::Request& request, nn::MeasureTiming measure) const override;
+
+  private:
+    const Factory kMakeBurst;
+    mutable std::mutex mMutex;
+    mutable nn::SharedBurst mBurst GUARDED_BY(mMutex);
+};
+
+}  // namespace android::hardware::neuralnetworks::utils
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h
index 84ae799..8199c52 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h
@@ -53,6 +53,7 @@
     const std::string& getVersionString() const override;
     nn::Version getFeatureLevel() const override;
     nn::DeviceType getType() const override;
+    bool isUpdatable() const override;
     const std::vector<nn::Extension>& getSupportedExtensions() const override;
     const nn::Capabilities& getCapabilities() const override;
     std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
index faae673..a6c1b19 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
@@ -30,7 +30,8 @@
 
 namespace android::hardware::neuralnetworks::utils {
 
-class ResilientPreparedModel final : public nn::IPreparedModel {
+class ResilientPreparedModel final : public nn::IPreparedModel,
+                                     public std::enable_shared_from_this<ResilientPreparedModel> {
     struct PrivateConstructorTag {};
 
   public:
@@ -43,8 +44,8 @@
                                     nn::SharedPreparedModel preparedModel);
 
     nn::SharedPreparedModel getPreparedModel() const;
-    nn::SharedPreparedModel recover(const nn::IPreparedModel* failingPreparedModel,
-                                    bool blocking) const;
+    nn::GeneralResult<nn::SharedPreparedModel> recover(
+            const nn::IPreparedModel* failingPreparedModel) const;
 
     nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
             const nn::Request& request, nn::MeasureTiming measure,
@@ -57,9 +58,14 @@
             const nn::OptionalDuration& loopTimeoutDuration,
             const nn::OptionalDuration& timeoutDurationAfterFence) const override;
 
+    nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
     std::any getUnderlyingResource() const override;
 
   private:
+    bool isValidInternal() const EXCLUDES(mMutex);
+    nn::GeneralResult<nn::SharedBurst> configureExecutionBurstInternal() const;
+
     const Factory kMakePreparedModel;
     mutable std::mutex mMutex;
     mutable nn::SharedPreparedModel mPreparedModel GUARDED_BY(mMutex);
diff --git a/neuralnetworks/utils/common/src/CommonUtils.cpp b/neuralnetworks/utils/common/src/CommonUtils.cpp
index c04c8df..7a5035f 100644
--- a/neuralnetworks/utils/common/src/CommonUtils.cpp
+++ b/neuralnetworks/utils/common/src/CommonUtils.cpp
@@ -20,11 +20,14 @@
 
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
+#include <android/hardware_buffer.h>
+#include <hidl/HidlSupport.h>
 #include <nnapi/Result.h>
 #include <nnapi/SharedMemory.h>
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
 #include <nnapi/Validation.h>
+#include <vndk/hardware_buffer.h>
 
 #include <algorithm>
 #include <any>
@@ -203,13 +206,13 @@
 nn::GeneralResult<void> unflushDataFromSharedToPointer(
         const nn::Request& request, const std::optional<nn::Request>& maybeRequestInShared) {
     if (!maybeRequestInShared.has_value() || maybeRequestInShared->pools.empty() ||
-        !std::holds_alternative<nn::Memory>(maybeRequestInShared->pools.back())) {
+        !std::holds_alternative<nn::SharedMemory>(maybeRequestInShared->pools.back())) {
         return {};
     }
     const auto& requestInShared = *maybeRequestInShared;
 
     // Map the memory.
-    const auto& outputMemory = std::get<nn::Memory>(requestInShared.pools.back());
+    const auto& outputMemory = std::get<nn::SharedMemory>(requestInShared.pools.back());
     const auto [pointer, size, context] = NN_TRY(map(outputMemory));
     const uint8_t* constantPointer =
             std::visit([](const auto& o) { return static_cast<const uint8_t*>(o); }, pointer);
@@ -248,44 +251,128 @@
     return nn::countNumberOfConsumers(numberOfOperands, operations);
 }
 
-nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::SharedHandle& handle) {
-    if (handle == nullptr) {
-        return {};
+nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::SharedMemory& memory) {
+    if (memory == nullptr) {
+        return NN_ERROR() << "Memory must be non-empty";
+    }
+    if (const auto* handle = std::get_if<nn::Handle>(&memory->handle)) {
+        return hidl_memory(memory->name, NN_TRY(hidlHandleFromSharedHandle(*handle)), memory->size);
     }
 
+    const auto* ahwb = std::get<nn::HardwareBufferHandle>(memory->handle).get();
+    AHardwareBuffer_Desc bufferDesc;
+    AHardwareBuffer_describe(ahwb, &bufferDesc);
+
+    if (bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB) {
+        CHECK_EQ(memory->size, bufferDesc.width);
+        CHECK_EQ(memory->name, "hardware_buffer_blob");
+    } else {
+        CHECK_EQ(memory->size, 0u);
+        CHECK_EQ(memory->name, "hardware_buffer");
+    }
+
+    const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
+    const hidl_handle hidlHandle(nativeHandle);
+    hidl_handle handle(hidlHandle);
+
+    return hidl_memory(memory->name, std::move(handle), memory->size);
+}
+
+static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
+    return (value + multiple - 1) / multiple * multiple;
+}
+
+nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory) {
+    CHECK_LE(memory.size(), std::numeric_limits<uint32_t>::max());
+
+    if (memory.name() != "hardware_buffer_blob") {
+        return std::make_shared<const nn::Memory>(nn::Memory{
+                .handle = NN_TRY(sharedHandleFromNativeHandle(memory.handle())),
+                .size = static_cast<uint32_t>(memory.size()),
+                .name = memory.name(),
+        });
+    }
+
+    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 std::make_shared<const nn::Memory>(nn::Memory{
+            .handle = nn::HardwareBufferHandle(hardwareBuffer, /*takeOwnership=*/true),
+            .size = static_cast<uint32_t>(memory.size()),
+            .name = memory.name(),
+    });
+}
+
+nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle) {
     std::vector<base::unique_fd> fds;
-    fds.reserve(handle->fds.size());
-    for (const auto& fd : handle->fds) {
-        int dupFd = dup(fd);
+    fds.reserve(handle.fds.size());
+    for (const auto& fd : handle.fds) {
+        const int dupFd = dup(fd);
         if (dupFd == -1) {
             return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
         }
         fds.emplace_back(dupFd);
     }
 
-    native_handle_t* nativeHandle = native_handle_create(handle->fds.size(), handle->ints.size());
+    constexpr size_t kIntMax = std::numeric_limits<int>::max();
+    CHECK_LE(handle.fds.size(), kIntMax);
+    CHECK_LE(handle.ints.size(), kIntMax);
+    native_handle_t* nativeHandle = native_handle_create(static_cast<int>(handle.fds.size()),
+                                                         static_cast<int>(handle.ints.size()));
     if (nativeHandle == nullptr) {
         return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle";
     }
     for (size_t i = 0; i < fds.size(); ++i) {
         nativeHandle->data[i] = fds[i].release();
     }
-    std::copy(handle->ints.begin(), handle->ints.end(), &nativeHandle->data[nativeHandle->numFds]);
+    std::copy(handle.ints.begin(), handle.ints.end(), &nativeHandle->data[nativeHandle->numFds]);
 
     hidl_handle hidlHandle;
     hidlHandle.setTo(nativeHandle, /*shouldOwn=*/true);
     return hidlHandle;
 }
 
-nn::GeneralResult<nn::SharedHandle> sharedHandleFromNativeHandle(const native_handle_t* handle) {
+nn::GeneralResult<nn::Handle> sharedHandleFromNativeHandle(const native_handle_t* handle) {
     if (handle == nullptr) {
-        return nullptr;
+        return NN_ERROR() << "sharedHandleFromNativeHandle failed because handle is nullptr";
     }
 
     std::vector<base::unique_fd> fds;
     fds.reserve(handle->numFds);
     for (int i = 0; i < handle->numFds; ++i) {
-        int dupFd = dup(handle->data[i]);
+        const int dupFd = dup(handle->data[i]);
         if (dupFd == -1) {
             return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
         }
@@ -295,18 +382,18 @@
     std::vector<int> ints(&handle->data[handle->numFds],
                           &handle->data[handle->numFds + handle->numInts]);
 
-    return std::make_shared<const nn::Handle>(nn::Handle{
-            .fds = std::move(fds),
-            .ints = std::move(ints),
-    });
+    return nn::Handle{.fds = std::move(fds), .ints = std::move(ints)};
 }
 
 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) {
-        handles[i] =
-                NN_TRY(hal::utils::hidlHandleFromSharedHandle(syncFences[i].getSharedHandle()));
+        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;
 }
diff --git a/neuralnetworks/utils/common/src/InvalidBuffer.cpp b/neuralnetworks/utils/common/src/InvalidBuffer.cpp
index c6f75d7..e73001d 100644
--- a/neuralnetworks/utils/common/src/InvalidBuffer.cpp
+++ b/neuralnetworks/utils/common/src/InvalidBuffer.cpp
@@ -30,11 +30,11 @@
     return nn::Request::MemoryDomainToken{};
 }
 
-nn::GeneralResult<void> InvalidBuffer::copyTo(const nn::Memory& /*dst*/) const {
+nn::GeneralResult<void> InvalidBuffer::copyTo(const nn::SharedMemory& /*dst*/) const {
     return NN_ERROR() << "InvalidBuffer";
 }
 
-nn::GeneralResult<void> InvalidBuffer::copyFrom(const nn::Memory& /*src*/,
+nn::GeneralResult<void> InvalidBuffer::copyFrom(const nn::SharedMemory& /*src*/,
                                                 const nn::Dimensions& /*dimensions*/) const {
     return NN_ERROR() << "InvalidBuffer";
 }
diff --git a/neuralnetworks/utils/common/src/InvalidBurst.cpp b/neuralnetworks/utils/common/src/InvalidBurst.cpp
new file mode 100644
index 0000000..81ca18d
--- /dev/null
+++ b/neuralnetworks/utils/common/src/InvalidBurst.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 "InvalidBurst.h"
+
+#include <nnapi/IBurst.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::utils {
+
+InvalidBurst::OptionalCacheHold InvalidBurst::cacheMemory(
+        const nn::SharedMemory& /*memory*/) const {
+    return nullptr;
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> InvalidBurst::execute(
+        const nn::Request& /*request*/, nn::MeasureTiming /*measure*/) const {
+    return NN_ERROR() << "InvalidBurst";
+}
+
+}  // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/src/InvalidDevice.cpp b/neuralnetworks/utils/common/src/InvalidDevice.cpp
index 535ccb4..81bca7f 100644
--- a/neuralnetworks/utils/common/src/InvalidDevice.cpp
+++ b/neuralnetworks/utils/common/src/InvalidDevice.cpp
@@ -32,13 +32,14 @@
 namespace android::hardware::neuralnetworks::utils {
 
 InvalidDevice::InvalidDevice(std::string name, std::string versionString, nn::Version featureLevel,
-                             nn::DeviceType type, std::vector<nn::Extension> extensions,
-                             nn::Capabilities capabilities,
+                             nn::DeviceType type, bool isUpdatable,
+                             std::vector<nn::Extension> extensions, nn::Capabilities capabilities,
                              std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded)
     : kName(std::move(name)),
       kVersionString(std::move(versionString)),
       kFeatureLevel(featureLevel),
       kType(type),
+      kIsUpdatable(isUpdatable),
       kExtensions(std::move(extensions)),
       kCapabilities(std::move(capabilities)),
       kNumberOfCacheFilesNeeded(numberOfCacheFilesNeeded) {}
@@ -59,6 +60,10 @@
     return kType;
 }
 
+bool InvalidDevice::isUpdatable() const {
+    return kIsUpdatable;
+}
+
 const std::vector<nn::Extension>& InvalidDevice::getSupportedExtensions() const {
     return kExtensions;
 }
diff --git a/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp b/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp
index a46f4ac..9081e1f 100644
--- a/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp
+++ b/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp
@@ -42,6 +42,10 @@
     return NN_ERROR() << "InvalidPreparedModel";
 }
 
+nn::GeneralResult<nn::SharedBurst> InvalidPreparedModel::configureExecutionBurst() const {
+    return NN_ERROR() << "InvalidPreparedModel";
+}
+
 std::any InvalidPreparedModel::getUnderlyingResource() const {
     return {};
 }
diff --git a/neuralnetworks/utils/common/src/ResilientBuffer.cpp b/neuralnetworks/utils/common/src/ResilientBuffer.cpp
index cf5496a..1904375 100644
--- a/neuralnetworks/utils/common/src/ResilientBuffer.cpp
+++ b/neuralnetworks/utils/common/src/ResilientBuffer.cpp
@@ -20,6 +20,7 @@
 #include <android-base/thread_annotations.h>
 #include <nnapi/IBuffer.h>
 #include <nnapi/Result.h>
+#include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
 
 #include <functional>
@@ -29,6 +30,34 @@
 #include <vector>
 
 namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+template <typename FnType>
+auto protect(const ResilientBuffer& resilientBuffer, const FnType& fn)
+        -> decltype(fn(*resilientBuffer.getBuffer())) {
+    auto buffer = resilientBuffer.getBuffer();
+    auto result = fn(*buffer);
+
+    // Immediately return if device is not dead.
+    if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) {
+        return result;
+    }
+
+    // Attempt recovery and return if it fails.
+    auto maybeBuffer = resilientBuffer.recover(buffer.get());
+    if (!maybeBuffer.has_value()) {
+        const auto& [resultErrorMessage, resultErrorCode] = result.error();
+        const auto& [recoveryErrorMessage, recoveryErrorCode] = maybeBuffer.error();
+        return nn::error(resultErrorCode)
+               << resultErrorMessage << ", and failed to recover dead buffer with error "
+               << recoveryErrorCode << ": " << recoveryErrorMessage;
+    }
+    buffer = std::move(maybeBuffer).value();
+
+    return fn(*buffer);
+}
+
+}  // namespace
 
 nn::GeneralResult<std::shared_ptr<const ResilientBuffer>> ResilientBuffer::create(
         Factory makeBuffer) {
@@ -53,9 +82,16 @@
     std::lock_guard guard(mMutex);
     return mBuffer;
 }
-nn::SharedBuffer ResilientBuffer::recover(const nn::IBuffer* /*failingBuffer*/,
-                                          bool /*blocking*/) const {
+nn::GeneralResult<nn::SharedBuffer> ResilientBuffer::recover(
+        const nn::IBuffer* failingBuffer) const {
     std::lock_guard guard(mMutex);
+
+    // Another caller updated the failing prepared model.
+    if (mBuffer.get() != failingBuffer) {
+        return mBuffer;
+    }
+
+    mBuffer = NN_TRY(kMakeBuffer());
     return mBuffer;
 }
 
@@ -63,13 +99,17 @@
     return getBuffer()->getToken();
 }
 
-nn::GeneralResult<void> ResilientBuffer::copyTo(const nn::Memory& dst) const {
-    return getBuffer()->copyTo(dst);
+nn::GeneralResult<void> ResilientBuffer::copyTo(const nn::SharedMemory& dst) const {
+    const auto fn = [&dst](const nn::IBuffer& buffer) { return buffer.copyTo(dst); };
+    return protect(*this, fn);
 }
 
-nn::GeneralResult<void> ResilientBuffer::copyFrom(const nn::Memory& src,
+nn::GeneralResult<void> ResilientBuffer::copyFrom(const nn::SharedMemory& src,
                                                   const nn::Dimensions& dimensions) const {
-    return getBuffer()->copyFrom(src, dimensions);
+    const auto fn = [&src, &dimensions](const nn::IBuffer& buffer) {
+        return buffer.copyFrom(src, dimensions);
+    };
+    return protect(*this, fn);
 }
 
 }  // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/src/ResilientBurst.cpp b/neuralnetworks/utils/common/src/ResilientBurst.cpp
new file mode 100644
index 0000000..5ca868b
--- /dev/null
+++ b/neuralnetworks/utils/common/src/ResilientBurst.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "ResilientBurst.h"
+
+#include <android-base/logging.h>
+#include <android-base/thread_annotations.h>
+#include <nnapi/IBurst.h>
+#include <nnapi/Result.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+template <typename FnType>
+auto protect(const ResilientBurst& resilientBurst, const FnType& fn)
+        -> decltype(fn(*resilientBurst.getBurst())) {
+    auto burst = resilientBurst.getBurst();
+    auto result = fn(*burst);
+
+    // Immediately return if burst is not dead.
+    if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) {
+        return result;
+    }
+
+    // Attempt recovery and return if it fails.
+    auto maybeBurst = resilientBurst.recover(burst.get());
+    if (!maybeBurst.has_value()) {
+        auto [resultErrorMessage, resultErrorCode, resultOutputShapes] = std::move(result).error();
+        const auto& [recoveryErrorMessage, recoveryErrorCode] = maybeBurst.error();
+        return nn::error(resultErrorCode, std::move(resultOutputShapes))
+               << resultErrorMessage << ", and failed to recover dead burst object with error "
+               << recoveryErrorCode << ": " << recoveryErrorMessage;
+    }
+    burst = std::move(maybeBurst).value();
+
+    return fn(*burst);
+}
+
+}  // namespace
+
+nn::GeneralResult<std::shared_ptr<const ResilientBurst>> ResilientBurst::create(Factory makeBurst) {
+    if (makeBurst == nullptr) {
+        return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
+               << "utils::ResilientBurst::create must have non-empty makeBurst";
+    }
+    auto burst = NN_TRY(makeBurst());
+    CHECK(burst != nullptr);
+    return std::make_shared<ResilientBurst>(PrivateConstructorTag{}, std::move(makeBurst),
+                                            std::move(burst));
+}
+
+ResilientBurst::ResilientBurst(PrivateConstructorTag /*tag*/, Factory makeBurst,
+                               nn::SharedBurst burst)
+    : kMakeBurst(std::move(makeBurst)), mBurst(std::move(burst)) {
+    CHECK(kMakeBurst != nullptr);
+    CHECK(mBurst != nullptr);
+}
+
+nn::SharedBurst ResilientBurst::getBurst() const {
+    std::lock_guard guard(mMutex);
+    return mBurst;
+}
+
+nn::GeneralResult<nn::SharedBurst> ResilientBurst::recover(const nn::IBurst* failingBurst) const {
+    std::lock_guard guard(mMutex);
+
+    // Another caller updated the failing burst.
+    if (mBurst.get() != failingBurst) {
+        return mBurst;
+    }
+
+    mBurst = NN_TRY(kMakeBurst());
+    return mBurst;
+}
+
+ResilientBurst::OptionalCacheHold ResilientBurst::cacheMemory(
+        const nn::SharedMemory& memory) const {
+    return getBurst()->cacheMemory(memory);
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> ResilientBurst::execute(
+        const nn::Request& request, nn::MeasureTiming measure) const {
+    const auto fn = [&request, measure](const nn::IBurst& burst) {
+        return burst.execute(request, measure);
+    };
+    return protect(*this, fn);
+}
+
+}  // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/src/ResilientDevice.cpp b/neuralnetworks/utils/common/src/ResilientDevice.cpp
index 6ad3fad..13965af 100644
--- a/neuralnetworks/utils/common/src/ResilientDevice.cpp
+++ b/neuralnetworks/utils/common/src/ResilientDevice.cpp
@@ -122,12 +122,14 @@
     };
     if (compare(&IDevice::getName) || compare(&IDevice::getVersionString) ||
         compare(&IDevice::getFeatureLevel) || compare(&IDevice::getType) ||
-        compare(&IDevice::getSupportedExtensions) || compare(&IDevice::getCapabilities)) {
+        compare(&IDevice::isUpdatable) || compare(&IDevice::getSupportedExtensions) ||
+        compare(&IDevice::getCapabilities)) {
         LOG(ERROR) << "Recovered device has different metadata than what is cached. Marking "
                       "IDevice object as invalid.";
         device = std::make_shared<const InvalidDevice>(
-                kName, kVersionString, mDevice->getFeatureLevel(), mDevice->getType(), kExtensions,
-                kCapabilities, mDevice->getNumberOfCacheFilesNeeded());
+                kName, kVersionString, mDevice->getFeatureLevel(), mDevice->getType(),
+                mDevice->isUpdatable(), kExtensions, kCapabilities,
+                mDevice->getNumberOfCacheFilesNeeded());
         mIsValid = false;
     }
 
@@ -151,6 +153,10 @@
     return getDevice()->getType();
 }
 
+bool ResilientDevice::isUpdatable() const {
+    return getDevice()->isUpdatable();
+}
+
 const std::vector<nn::Extension>& ResilientDevice::getSupportedExtensions() const {
     return kExtensions;
 }
@@ -180,6 +186,7 @@
         const nn::Model& model, nn::ExecutionPreference preference, nn::Priority priority,
         nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
         const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
+#if 0
     auto self = shared_from_this();
     ResilientPreparedModel::Factory makePreparedModel = [device = std::move(self), model,
                                                          preference, priority, deadline, modelCache,
@@ -188,29 +195,41 @@
                                             dataCache, token);
     };
     return ResilientPreparedModel::create(std::move(makePreparedModel));
+#else
+    return prepareModelInternal(model, preference, priority, deadline, modelCache, dataCache,
+                                token);
+#endif
 }
 
 nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelFromCache(
         nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
         const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
+#if 0
     auto self = shared_from_this();
     ResilientPreparedModel::Factory makePreparedModel = [device = std::move(self), deadline,
                                                          modelCache, dataCache, token] {
         return device->prepareModelFromCacheInternal(deadline, modelCache, dataCache, token);
     };
     return ResilientPreparedModel::create(std::move(makePreparedModel));
+#else
+    return prepareModelFromCacheInternal(deadline, modelCache, dataCache, token);
+#endif
 }
 
 nn::GeneralResult<nn::SharedBuffer> ResilientDevice::allocate(
         const nn::BufferDesc& desc, const std::vector<nn::SharedPreparedModel>& preparedModels,
         const std::vector<nn::BufferRole>& inputRoles,
         const std::vector<nn::BufferRole>& outputRoles) const {
+#if 0
     auto self = shared_from_this();
     ResilientBuffer::Factory makeBuffer = [device = std::move(self), desc, preparedModels,
                                            inputRoles, outputRoles] {
         return device->allocateInternal(desc, preparedModels, inputRoles, outputRoles);
     };
     return ResilientBuffer::create(std::move(makeBuffer));
+#else
+    return allocateInternal(desc, preparedModels, inputRoles, outputRoles);
+#endif
 }
 
 bool ResilientDevice::isValidInternal() const {
@@ -225,8 +244,8 @@
     if (!isValidInternal()) {
         return std::make_shared<const InvalidPreparedModel>();
     }
-    const auto fn = [&model, preference, priority, deadline, &modelCache, &dataCache,
-                     token](const nn::IDevice& device) {
+    const auto fn = [&model, preference, priority, &deadline, &modelCache, &dataCache,
+                     &token](const nn::IDevice& device) {
         return device.prepareModel(model, preference, priority, deadline, modelCache, dataCache,
                                    token);
     };
@@ -239,7 +258,7 @@
     if (!isValidInternal()) {
         return std::make_shared<const InvalidPreparedModel>();
     }
-    const auto fn = [deadline, &modelCache, &dataCache, token](const nn::IDevice& device) {
+    const auto fn = [&deadline, &modelCache, &dataCache, &token](const nn::IDevice& device) {
         return device.prepareModelFromCache(deadline, modelCache, dataCache, token);
     };
     return protect(*this, fn, /*blocking=*/false);
diff --git a/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp b/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp
index b8acee1..5dd5f99 100644
--- a/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp
+++ b/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp
@@ -16,19 +16,52 @@
 
 #include "ResilientPreparedModel.h"
 
+#include "InvalidBurst.h"
+#include "ResilientBurst.h"
+
 #include <android-base/logging.h>
 #include <android-base/thread_annotations.h>
 #include <nnapi/IPreparedModel.h>
 #include <nnapi/Result.h>
+#include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
 
 #include <functional>
 #include <memory>
 #include <mutex>
+#include <sstream>
 #include <utility>
 #include <vector>
 
 namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+template <typename FnType>
+auto protect(const ResilientPreparedModel& resilientPreparedModel, const FnType& fn)
+        -> decltype(fn(*resilientPreparedModel.getPreparedModel())) {
+    auto preparedModel = resilientPreparedModel.getPreparedModel();
+    auto result = fn(*preparedModel);
+
+    // Immediately return if prepared model is not dead.
+    if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) {
+        return result;
+    }
+
+    // Attempt recovery and return if it fails.
+    auto maybePreparedModel = resilientPreparedModel.recover(preparedModel.get());
+    if (!maybePreparedModel.has_value()) {
+        const auto& [message, code] = maybePreparedModel.error();
+        std::ostringstream oss;
+        oss << ", and failed to recover dead prepared model with error " << code << ": " << message;
+        result.error().message += oss.str();
+        return result;
+    }
+    preparedModel = std::move(maybePreparedModel).value();
+
+    return fn(*preparedModel);
+}
+
+}  // namespace
 
 nn::GeneralResult<std::shared_ptr<const ResilientPreparedModel>> ResilientPreparedModel::create(
         Factory makePreparedModel) {
@@ -55,9 +88,16 @@
     return mPreparedModel;
 }
 
-nn::SharedPreparedModel ResilientPreparedModel::recover(
-        const nn::IPreparedModel* /*failingPreparedModel*/, bool /*blocking*/) const {
+nn::GeneralResult<nn::SharedPreparedModel> ResilientPreparedModel::recover(
+        const nn::IPreparedModel* failingPreparedModel) const {
     std::lock_guard guard(mMutex);
+
+    // Another caller updated the failing prepared model.
+    if (mPreparedModel.get() != failingPreparedModel) {
+        return mPreparedModel;
+    }
+
+    mPreparedModel = NN_TRY(kMakePreparedModel());
     return mPreparedModel;
 }
 
@@ -65,7 +105,11 @@
 ResilientPreparedModel::execute(const nn::Request& request, nn::MeasureTiming measure,
                                 const nn::OptionalTimePoint& deadline,
                                 const nn::OptionalDuration& loopTimeoutDuration) const {
-    return getPreparedModel()->execute(request, measure, deadline, loopTimeoutDuration);
+    const auto fn = [&request, measure, &deadline,
+                     &loopTimeoutDuration](const nn::IPreparedModel& preparedModel) {
+        return preparedModel.execute(request, measure, deadline, loopTimeoutDuration);
+    };
+    return protect(*this, fn);
 }
 
 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
@@ -75,12 +119,43 @@
                                       const nn::OptionalTimePoint& deadline,
                                       const nn::OptionalDuration& loopTimeoutDuration,
                                       const nn::OptionalDuration& timeoutDurationAfterFence) const {
-    return getPreparedModel()->executeFenced(request, waitFor, measure, deadline,
-                                             loopTimeoutDuration, timeoutDurationAfterFence);
+    const auto fn = [&request, &waitFor, measure, &deadline, &loopTimeoutDuration,
+                     &timeoutDurationAfterFence](const nn::IPreparedModel& preparedModel) {
+        return preparedModel.executeFenced(request, waitFor, measure, deadline, loopTimeoutDuration,
+                                           timeoutDurationAfterFence);
+    };
+    return protect(*this, fn);
+}
+
+nn::GeneralResult<nn::SharedBurst> ResilientPreparedModel::configureExecutionBurst() const {
+#if 0
+    auto self = shared_from_this();
+    ResilientBurst::Factory makeBurst =
+            [preparedModel = std::move(self)]() -> nn::GeneralResult<nn::SharedBurst> {
+        return preparedModel->configureExecutionBurst();
+    };
+    return ResilientBurst::create(std::move(makeBurst));
+#else
+    return configureExecutionBurstInternal();
+#endif
 }
 
 std::any ResilientPreparedModel::getUnderlyingResource() const {
     return getPreparedModel()->getUnderlyingResource();
 }
 
+bool ResilientPreparedModel::isValidInternal() const {
+    return true;
+}
+
+nn::GeneralResult<nn::SharedBurst> ResilientPreparedModel::configureExecutionBurstInternal() const {
+    if (!isValidInternal()) {
+        return std::make_shared<const InvalidBurst>();
+    }
+    const auto fn = [](const nn::IPreparedModel& preparedModel) {
+        return preparedModel.configureExecutionBurst();
+    };
+    return protect(*this, fn);
+}
+
 }  // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/test/MockBuffer.h b/neuralnetworks/utils/common/test/MockBuffer.h
new file mode 100644
index 0000000..59d5700
--- /dev/null
+++ b/neuralnetworks/utils/common/test/MockBuffer.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IBuffer.h>
+#include <nnapi/Types.h>
+
+namespace android::nn {
+
+class MockBuffer final : public IBuffer {
+  public:
+    MOCK_METHOD(Request::MemoryDomainToken, getToken, (), (const, override));
+    MOCK_METHOD(GeneralResult<void>, copyTo, (const SharedMemory& dst), (const, override));
+    MOCK_METHOD(GeneralResult<void>, copyFrom,
+                (const SharedMemory& src, const Dimensions& dimensions), (const, override));
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER
diff --git a/neuralnetworks/utils/common/test/MockDevice.h b/neuralnetworks/utils/common/test/MockDevice.h
new file mode 100644
index 0000000..5566968
--- /dev/null
+++ b/neuralnetworks/utils/common/test/MockDevice.h
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+
+namespace android::nn {
+
+class MockDevice final : public IDevice {
+  public:
+    MOCK_METHOD(const std::string&, getName, (), (const, override));
+    MOCK_METHOD(const std::string&, getVersionString, (), (const, override));
+    MOCK_METHOD(Version, getFeatureLevel, (), (const, override));
+    MOCK_METHOD(DeviceType, getType, (), (const, override));
+    MOCK_METHOD(bool, isUpdatable, (), (const, override));
+    MOCK_METHOD(const std::vector<Extension>&, getSupportedExtensions, (), (const, override));
+    MOCK_METHOD(const Capabilities&, getCapabilities, (), (const, override));
+    MOCK_METHOD((std::pair<uint32_t, uint32_t>), getNumberOfCacheFilesNeeded, (),
+                (const, override));
+    MOCK_METHOD(GeneralResult<void>, wait, (), (const, override));
+    MOCK_METHOD(GeneralResult<std::vector<bool>>, getSupportedOperations, (const Model& model),
+                (const, override));
+    MOCK_METHOD(GeneralResult<SharedPreparedModel>, prepareModel,
+                (const Model& model, ExecutionPreference preference, Priority priority,
+                 OptionalTimePoint deadline, const std::vector<SharedHandle>& modelCache,
+                 const std::vector<SharedHandle>& dataCache, const CacheToken& token),
+                (const, override));
+    MOCK_METHOD(GeneralResult<SharedPreparedModel>, prepareModelFromCache,
+                (OptionalTimePoint deadline, const std::vector<SharedHandle>& modelCache,
+                 const std::vector<SharedHandle>& dataCache, const CacheToken& token),
+                (const, override));
+    MOCK_METHOD(GeneralResult<SharedBuffer>, allocate,
+                (const BufferDesc& desc, const std::vector<SharedPreparedModel>& preparedModels,
+                 const std::vector<BufferRole>& inputRoles,
+                 const std::vector<BufferRole>& outputRoles),
+                (const, override));
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/utils/common/test/MockPreparedModel.h b/neuralnetworks/utils/common/test/MockPreparedModel.h
new file mode 100644
index 0000000..418af61
--- /dev/null
+++ b/neuralnetworks/utils/common/test/MockPreparedModel.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+
+namespace android::nn {
+
+class MockPreparedModel final : public IPreparedModel {
+  public:
+    MOCK_METHOD((ExecutionResult<std::pair<std::vector<OutputShape>, Timing>>), execute,
+                (const Request& request, MeasureTiming measure, const OptionalTimePoint& deadline,
+                 const OptionalDuration& loopTimeoutDuration),
+                (const, override));
+    MOCK_METHOD((GeneralResult<std::pair<SyncFence, ExecuteFencedInfoCallback>>), executeFenced,
+                (const Request& request, const std::vector<SyncFence>& waitFor,
+                 MeasureTiming measure, const OptionalTimePoint& deadline,
+                 const OptionalDuration& loopTimeoutDuration,
+                 const OptionalDuration& timeoutDurationAfterFence),
+                (const, override));
+    MOCK_METHOD(GeneralResult<SharedBurst>, configureExecutionBurst, (), (const, override));
+    MOCK_METHOD(std::any, getUnderlyingResource, (), (const, override));
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/utils/common/test/ResilientBufferTest.cpp b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp
new file mode 100644
index 0000000..7afd020
--- /dev/null
+++ b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/ResilientBuffer.h>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include "MockBuffer.h"
+
+namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+constexpr auto kToken = nn::Request::MemoryDomainToken{1};
+
+using SharedMockBuffer = std::shared_ptr<const nn::MockBuffer>;
+using MockBufferFactory = ::testing::MockFunction<nn::GeneralResult<nn::SharedBuffer>()>;
+
+SharedMockBuffer createConfiguredMockBuffer() {
+    return std::make_shared<const nn::MockBuffer>();
+}
+
+std::tuple<std::shared_ptr<const nn::MockBuffer>, std::unique_ptr<MockBufferFactory>,
+           std::shared_ptr<const ResilientBuffer>>
+setup() {
+    auto mockBuffer = std::make_shared<const nn::MockBuffer>();
+
+    auto mockBufferFactory = std::make_unique<MockBufferFactory>();
+    EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(mockBuffer));
+
+    auto buffer = ResilientBuffer::create(mockBufferFactory->AsStdFunction()).value();
+    return std::make_tuple(std::move(mockBuffer), std::move(mockBufferFactory), std::move(buffer));
+}
+
+constexpr auto makeError = [](nn::ErrorStatus status) {
+    return [status](const auto&... /*args*/) { return nn::error(status); };
+};
+const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE);
+const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT);
+
+const auto kNoError = nn::GeneralResult<void>{};
+
+}  // namespace
+
+TEST(ResilientBufferTest, invalidBufferFactory) {
+    // setup call
+    const auto invalidBufferFactory = ResilientBuffer::Factory{};
+
+    // run test
+    const auto result = ResilientBuffer::create(invalidBufferFactory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(ResilientBufferTest, bufferFactoryFailure) {
+    // setup call
+    const auto invalidBufferFactory = kReturnGeneralFailure;
+
+    // run test
+    const auto result = ResilientBuffer::create(invalidBufferFactory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientBufferTest, getBuffer) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+
+    // run test
+    const auto result = buffer->getBuffer();
+
+    // verify result
+    EXPECT_TRUE(result == mockBuffer);
+}
+
+TEST(ResilientBufferTest, getToken) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, getToken()).Times(1).WillOnce(Return(kToken));
+
+    // run test
+    const auto token = buffer->getToken();
+
+    // verify result
+    EXPECT_EQ(token, kToken);
+}
+
+TEST(ResilientBufferTest, copyTo) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(Return(kNoError));
+
+    // run test
+    const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+    const auto result = buffer->copyTo(memory);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientBufferTest, copyToError) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+    const auto result = buffer->copyTo(memory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientBufferTest, copyToDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnDeadObject);
+    EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+    const auto result = buffer->copyTo(memory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientBufferTest, copyToDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnDeadObject);
+    const auto recoveredMockBuffer = createConfiguredMockBuffer();
+    EXPECT_CALL(*recoveredMockBuffer, copyTo(_)).Times(1).WillOnce(Return(kNoError));
+    EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer));
+
+    // run test
+    const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+    const auto result = buffer->copyTo(memory);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientBufferTest, copyFrom) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(Return(kNoError));
+
+    // run test
+    const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+    const auto result = buffer->copyFrom(memory, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientBufferTest, copyFromError) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+    const auto result = buffer->copyFrom(memory, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientBufferTest, copyFromDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnDeadObject);
+    EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+    const auto result = buffer->copyFrom(memory, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientBufferTest, copyFromDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnDeadObject);
+    const auto recoveredMockBuffer = createConfiguredMockBuffer();
+    EXPECT_CALL(*recoveredMockBuffer, copyFrom(_, _)).Times(1).WillOnce(Return(kNoError));
+    EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer));
+
+    // run test
+    const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+    const auto result = buffer->copyFrom(memory, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientBufferTest, recover) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    const auto recoveredMockBuffer = createConfiguredMockBuffer();
+    EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer));
+
+    // run test
+    const auto result = buffer->recover(mockBuffer.get());
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() == recoveredMockBuffer);
+}
+
+TEST(ResilientBufferTest, recoverFailure) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    const auto recoveredMockBuffer = createConfiguredMockBuffer();
+    EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = buffer->recover(mockBuffer.get());
+
+    // verify result
+    EXPECT_FALSE(result.has_value());
+}
+
+TEST(ResilientBufferTest, someoneElseRecovered) {
+    // setup call
+    const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+    const auto recoveredMockBuffer = createConfiguredMockBuffer();
+    EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer));
+    buffer->recover(mockBuffer.get());
+
+    // run test
+    const auto result = buffer->recover(mockBuffer.get());
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() == recoveredMockBuffer);
+}
+
+}  // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp b/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp
new file mode 100644
index 0000000..3abd724
--- /dev/null
+++ b/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp
@@ -0,0 +1,725 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/ResilientDevice.h>
+#include <tuple>
+#include <utility>
+#include "MockBuffer.h"
+#include "MockDevice.h"
+#include "MockPreparedModel.h"
+
+namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+using SharedMockDevice = std::shared_ptr<const nn::MockDevice>;
+using MockDeviceFactory = ::testing::MockFunction<nn::GeneralResult<nn::SharedDevice>(bool)>;
+
+const std::string kName = "Google-MockV1";
+const std::string kVersionString = "version1";
+const auto kExtensions = std::vector<nn::Extension>{};
+constexpr auto kNoInfo = std::numeric_limits<float>::max();
+constexpr auto kNoPerformanceInfo =
+        nn::Capabilities::PerformanceInfo{.execTime = kNoInfo, .powerUsage = kNoInfo};
+const auto kCapabilities = nn::Capabilities{
+        .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+        .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+        .operandPerformance = nn::Capabilities::OperandPerformanceTable::create({}).value(),
+        .ifPerformance = kNoPerformanceInfo,
+        .whilePerformance = kNoPerformanceInfo};
+constexpr auto kNumberOfCacheFilesNeeded = std::pair<uint32_t, uint32_t>(5, 3);
+
+SharedMockDevice createConfiguredMockDevice() {
+    auto mockDevice = std::make_shared<const nn::MockDevice>();
+
+    // Setup default actions for each relevant call.
+    constexpr auto getName_ret = []() -> const std::string& { return kName; };
+    constexpr auto getVersionString_ret = []() -> const std::string& { return kVersionString; };
+    constexpr auto kFeatureLevel = nn::Version::ANDROID_OC_MR1;
+    constexpr auto kDeviceType = nn::DeviceType::ACCELERATOR;
+    constexpr auto getSupportedExtensions_ret = []() -> const std::vector<nn::Extension>& {
+        return kExtensions;
+    };
+    constexpr auto getCapabilities_ret = []() -> const nn::Capabilities& { return kCapabilities; };
+
+    // Setup default actions for each relevant call.
+    ON_CALL(*mockDevice, getName()).WillByDefault(getName_ret);
+    ON_CALL(*mockDevice, getVersionString()).WillByDefault(getVersionString_ret);
+    ON_CALL(*mockDevice, getFeatureLevel()).WillByDefault(Return(kFeatureLevel));
+    ON_CALL(*mockDevice, getType()).WillByDefault(Return(kDeviceType));
+    ON_CALL(*mockDevice, getSupportedExtensions()).WillByDefault(getSupportedExtensions_ret);
+    ON_CALL(*mockDevice, getCapabilities()).WillByDefault(getCapabilities_ret);
+    ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded())
+            .WillByDefault(Return(kNumberOfCacheFilesNeeded));
+
+    // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+    // uninteresting methods calls.
+    EXPECT_CALL(*mockDevice, getName()).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getVersionString()).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getFeatureLevel()).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getType()).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getSupportedExtensions()).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getCapabilities()).Times(testing::AnyNumber());
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded()).Times(testing::AnyNumber());
+
+    return mockDevice;
+}
+
+std::tuple<SharedMockDevice, std::unique_ptr<MockDeviceFactory>,
+           std::shared_ptr<const ResilientDevice>>
+setup() {
+    auto mockDevice = createConfiguredMockDevice();
+
+    auto mockDeviceFactory = std::make_unique<MockDeviceFactory>();
+    EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(Return(mockDevice));
+
+    auto device = ResilientDevice::create(mockDeviceFactory->AsStdFunction()).value();
+    return std::make_tuple(std::move(mockDevice), std::move(mockDeviceFactory), std::move(device));
+}
+
+constexpr auto makeError = [](nn::ErrorStatus status) {
+    return [status](const auto&... /*args*/) { return nn::error(status); };
+};
+const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE);
+const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT);
+
+}  // namespace
+
+TEST(ResilientDeviceTest, invalidDeviceFactory) {
+    // setup call
+    const auto invalidDeviceFactory = ResilientDevice::Factory{};
+
+    // run test
+    const auto result = ResilientDevice::create(invalidDeviceFactory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(ResilientDeviceTest, preparedModelFactoryFailure) {
+    // setup call
+    const auto invalidDeviceFactory = kReturnGeneralFailure;
+
+    // run test
+    const auto result = ResilientDevice::create(invalidDeviceFactory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, cachedData) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+
+    // run test and verify results
+    EXPECT_EQ(device->getName(), kName);
+    EXPECT_EQ(device->getVersionString(), kVersionString);
+    EXPECT_EQ(device->getSupportedExtensions(), kExtensions);
+    EXPECT_EQ(device->getCapabilities(), kCapabilities);
+}
+
+TEST(ResilientDeviceTest, getFeatureLevel) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    constexpr auto kFeatureLevel = nn::Version::ANDROID_OC_MR1;
+    EXPECT_CALL(*mockDevice, getFeatureLevel()).Times(1).WillOnce(Return(kFeatureLevel));
+
+    // run test
+    const auto featureLevel = device->getFeatureLevel();
+
+    // verify results
+    EXPECT_EQ(featureLevel, kFeatureLevel);
+}
+
+TEST(ResilientDeviceTest, getType) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    constexpr auto kDeviceType = nn::DeviceType::ACCELERATOR;
+    EXPECT_CALL(*mockDevice, getType()).Times(1).WillOnce(Return(kDeviceType));
+
+    // run test
+    const auto type = device->getType();
+
+    // verify results
+    EXPECT_EQ(type, kDeviceType);
+}
+
+TEST(ResilientDeviceTest, getNumberOfCacheFilesNeeded) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded())
+            .Times(1)
+            .WillOnce(Return(kNumberOfCacheFilesNeeded));
+
+    // run test
+    const auto numberOfCacheFilesNeeded = device->getNumberOfCacheFilesNeeded();
+
+    // verify results
+    EXPECT_EQ(numberOfCacheFilesNeeded, kNumberOfCacheFilesNeeded);
+}
+
+TEST(ResilientDeviceTest, getDevice) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+
+    // run test
+    const auto result = device->getDevice();
+
+    // verify result
+    EXPECT_TRUE(result == mockDevice);
+}
+
+TEST(ResilientDeviceTest, wait) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(Return(nn::GeneralResult<void>{}));
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, waitError) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, waitDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnDeadObject);
+    EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, waitDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnDeadObject);
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*recoveredMockDevice, wait()).Times(1).WillOnce(Return(nn::GeneralResult<void>{}));
+    EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->wait();
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, getSupportedOperations) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_))
+            .Times(1)
+            .WillOnce(Return(nn::GeneralResult<std::vector<bool>>{}));
+
+    // run test
+    const auto result = device->getSupportedOperations({});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, getSupportedOperationsError) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->getSupportedOperations({});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, getSupportedOperationsDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnDeadObject);
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->getSupportedOperations({});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, getSupportedOperationsDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnDeadObject);
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*recoveredMockDevice, getSupportedOperations(_))
+            .Times(1)
+            .WillOnce(Return(nn::GeneralResult<std::vector<bool>>{}));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->getSupportedOperations({});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, prepareModel) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Return(mockPreparedModel));
+
+    // run test
+    const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, prepareModelError) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, prepareModelDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnDeadObject);
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, prepareModelDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnDeadObject);
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+    EXPECT_CALL(*recoveredMockDevice, prepareModel(_, _, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Return(mockPreparedModel));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, prepareModelFromCache) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(Return(mockPreparedModel));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, prepareModelFromCacheError) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, prepareModelFromCacheDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnDeadObject);
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, prepareModelFromCacheDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnDeadObject);
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+    EXPECT_CALL(*recoveredMockDevice, prepareModelFromCache(_, _, _, _))
+            .Times(1)
+            .WillOnce(Return(mockPreparedModel));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, allocate) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto mockBuffer = std::make_shared<const nn::MockBuffer>();
+    EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(Return(mockBuffer));
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, allocateError) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, allocateDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject);
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, allocateDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject);
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    const auto mockBuffer = std::make_shared<const nn::MockBuffer>();
+    EXPECT_CALL(*recoveredMockDevice, allocate(_, _, _, _)).Times(1).WillOnce(Return(mockBuffer));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, recover) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() == recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverFailure) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*mockDeviceFactory, Call(_)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    EXPECT_FALSE(result.has_value());
+}
+
+TEST(ResilientDeviceTest, someoneElseRecovered) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+    device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() == recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetName) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    const std::string kDifferentName = "Google-DifferentName";
+    const auto ret = [&kDifferentName]() -> const std::string& { return kDifferentName; };
+    EXPECT_CALL(*recoveredMockDevice, getName()).Times(1).WillOnce(ret);
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+    EXPECT_TRUE(result.value() != mockDevice);
+    EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetVersionString) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    const std::string kDifferentVersionString = "differentversion";
+    const auto ret = [&kDifferentVersionString]() -> const std::string& {
+        return kDifferentVersionString;
+    };
+    EXPECT_CALL(*recoveredMockDevice, getVersionString()).Times(1).WillOnce(ret);
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+    EXPECT_TRUE(result.value() != mockDevice);
+    EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetFeatureLevel) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*recoveredMockDevice, getFeatureLevel())
+            .Times(1)
+            .WillOnce(Return(nn::Version::ANDROID_P));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+    EXPECT_TRUE(result.value() != mockDevice);
+    EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetType) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+    EXPECT_TRUE(result.value() != mockDevice);
+    EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetSupportedExtensions) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    const auto kDifferentExtensions =
+            std::vector<nn::Extension>{nn::Extension{.name = "", .operandTypes = {}}};
+    const auto ret = [&kDifferentExtensions]() -> const std::vector<nn::Extension>& {
+        return kDifferentExtensions;
+    };
+    EXPECT_CALL(*recoveredMockDevice, getSupportedExtensions()).Times(1).WillOnce(ret);
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+    EXPECT_TRUE(result.value() != mockDevice);
+    EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetCapabilities) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    const auto kDifferentCapabilities = nn::Capabilities{
+            .relaxedFloat32toFloat16PerformanceTensor = {.execTime = 0.5f, .powerUsage = 0.5f},
+            .operandPerformance = nn::Capabilities::OperandPerformanceTable::create({}).value()};
+    const auto ret = [&kDifferentCapabilities]() -> const nn::Capabilities& {
+        return kDifferentCapabilities;
+    };
+    EXPECT_CALL(*recoveredMockDevice, getCapabilities()).Times(1).WillOnce(ret);
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+    // run test
+    const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+    EXPECT_TRUE(result.value() != mockDevice);
+    EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchInvalidPrepareModel) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+    device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // run test
+    auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchInvalidPrepareModelFromCache) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+    device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // run test
+    auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchInvalidAllocate) {
+    // setup call
+    const auto [mockDevice, mockDeviceFactory, device] = setup();
+    const auto recoveredMockDevice = createConfiguredMockDevice();
+    EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU));
+    EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+    device->recover(mockDevice.get(), /*blocking=*/false);
+
+    // run test
+    auto result = device->allocate({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() != nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp b/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp
new file mode 100644
index 0000000..6d86e10
--- /dev/null
+++ b/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp
@@ -0,0 +1,297 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/ResilientPreparedModel.h>
+#include <utility>
+#include "MockPreparedModel.h"
+
+namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+using SharedMockPreparedModel = std::shared_ptr<const nn::MockPreparedModel>;
+using MockPreparedModelFactory =
+        ::testing::MockFunction<nn::GeneralResult<nn::SharedPreparedModel>()>;
+
+SharedMockPreparedModel createConfiguredMockPreparedModel() {
+    return std::make_shared<const nn::MockPreparedModel>();
+}
+
+std::tuple<std::shared_ptr<const nn::MockPreparedModel>, std::unique_ptr<MockPreparedModelFactory>,
+           std::shared_ptr<const ResilientPreparedModel>>
+setup() {
+    auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+
+    auto mockPreparedModelFactory = std::make_unique<MockPreparedModelFactory>();
+    EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(Return(mockPreparedModel));
+
+    auto buffer = ResilientPreparedModel::create(mockPreparedModelFactory->AsStdFunction()).value();
+    return std::make_tuple(std::move(mockPreparedModel), std::move(mockPreparedModelFactory),
+                           std::move(buffer));
+}
+
+constexpr auto makeError = [](nn::ErrorStatus status) {
+    return [status](const auto&... /*args*/) { return nn::error(status); };
+};
+const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE);
+const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT);
+
+const auto kNoExecutionError =
+        nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>{};
+const auto kNoFencedExecutionError =
+        nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>(
+                std::make_pair(nn::SyncFence::createAsSignaled(), nullptr));
+
+struct FakeResource {};
+
+}  // namespace
+
+TEST(ResilientPreparedModelTest, invalidPreparedModelFactory) {
+    // setup call
+    const auto invalidPreparedModelFactory = ResilientPreparedModel::Factory{};
+
+    // run test
+    const auto result = ResilientPreparedModel::create(invalidPreparedModelFactory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(ResilientPreparedModelTest, preparedModelFactoryFailure) {
+    // setup call
+    const auto invalidPreparedModelFactory = kReturnGeneralFailure;
+
+    // run test
+    const auto result = ResilientPreparedModel::create(invalidPreparedModelFactory);
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientPreparedModelTest, getPreparedModel) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+
+    // run test
+    const auto result = preparedModel->getPreparedModel();
+
+    // verify result
+    EXPECT_TRUE(result == mockPreparedModel);
+}
+
+TEST(ResilientPreparedModelTest, execute) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _))
+            .Times(1)
+            .WillOnce(Return(kNoExecutionError));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientPreparedModelTest, executeError) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientPreparedModelTest, executeDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject);
+    constexpr auto ret = [] { return nn::error(nn::ErrorStatus::GENERAL_FAILURE); };
+    EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(ret);
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientPreparedModelTest, executeDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject);
+    const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+    EXPECT_CALL(*recoveredMockPreparedModel, execute(_, _, _, _))
+            .Times(1)
+            .WillOnce(Return(kNoExecutionError));
+    EXPECT_CALL(*mockPreparedModelFactory, Call())
+            .Times(1)
+            .WillOnce(Return(recoveredMockPreparedModel));
+
+    // run test
+    const auto result = preparedModel->execute({}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientPreparedModelTest, executeFenced) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Return(kNoFencedExecutionError));
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientPreparedModelTest, executeFencedError) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientPreparedModelTest, executeFencedDeadObjectFailedRecovery) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnDeadObject);
+    EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_FALSE(result.has_value());
+    EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientPreparedModelTest, executeFencedDeadObjectSuccessfulRecovery) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(kReturnDeadObject);
+    const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+    EXPECT_CALL(*recoveredMockPreparedModel, executeFenced(_, _, _, _, _, _))
+            .Times(1)
+            .WillOnce(Return(kNoFencedExecutionError));
+    EXPECT_CALL(*mockPreparedModelFactory, Call())
+            .Times(1)
+            .WillOnce(Return(recoveredMockPreparedModel));
+
+    // run test
+    const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientPreparedModelTest, getUnderlyingResource) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    EXPECT_CALL(*mockPreparedModel, getUnderlyingResource())
+            .Times(1)
+            .WillOnce(Return(FakeResource{}));
+
+    // run test
+    const auto resource = preparedModel->getUnderlyingResource();
+
+    // verify resource
+    const FakeResource* maybeFakeResource = std::any_cast<FakeResource>(&resource);
+    EXPECT_NE(maybeFakeResource, nullptr);
+}
+
+TEST(ResilientPreparedModelTest, recover) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModelFactory, Call())
+            .Times(1)
+            .WillOnce(Return(recoveredMockPreparedModel));
+
+    // run test
+    const auto result = preparedModel->recover(mockPreparedModel.get());
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() == recoveredMockPreparedModel);
+}
+
+TEST(ResilientPreparedModelTest, recoverFailure) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+    // run test
+    const auto result = preparedModel->recover(mockPreparedModel.get());
+
+    // verify result
+    EXPECT_FALSE(result.has_value());
+}
+
+TEST(ResilientPreparedModelTest, someoneElseRecovered) {
+    // setup call
+    const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+    const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+    EXPECT_CALL(*mockPreparedModelFactory, Call())
+            .Times(1)
+            .WillOnce(Return(recoveredMockPreparedModel));
+    preparedModel->recover(mockPreparedModel.get());
+
+    // run test
+    const auto result = preparedModel->recover(mockPreparedModel.get());
+
+    // verify result
+    ASSERT_TRUE(result.has_value())
+            << "Failed with " << result.error().code << ": " << result.error().message;
+    EXPECT_TRUE(result.value() == recoveredMockPreparedModel);
+}
+
+}  // namespace android::hardware::neuralnetworks::utils
diff --git a/oemlock/aidl/Android.bp b/oemlock/aidl/Android.bp
new file mode 100644
index 0000000..bfc99e7
--- /dev/null
+++ b/oemlock/aidl/Android.bp
@@ -0,0 +1,16 @@
+aidl_interface {
+    name: "android.hardware.oemlock",
+    vendor_available: true,
+    srcs: ["android/hardware/oemlock/*.aidl"],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+}
diff --git a/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl
new file mode 100644
index 0000000..e3c974d
--- /dev/null
+++ b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.oemlock;
+@VintfStability
+interface IOemLock {
+  String getName();
+  boolean isOemUnlockAllowedByCarrier();
+  boolean isOemUnlockAllowedByDevice();
+  android.hardware.oemlock.OemLockSecureStatus setOemUnlockAllowedByCarrier(in boolean allowed, in byte[] signature);
+  void setOemUnlockAllowedByDevice(in boolean allowed);
+}
diff --git a/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl
new file mode 100644
index 0000000..9d1327d
--- /dev/null
+++ b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.oemlock;
+@Backing(type="int") @VintfStability
+enum OemLockSecureStatus {
+  OK = 0,
+  FAILED = 1,
+  INVALID_SIGNATURE = 2,
+}
diff --git a/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl b/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl
new file mode 100644
index 0000000..674ff85
--- /dev/null
+++ b/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl
@@ -0,0 +1,81 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.oemlock;
+
+import android.hardware.oemlock.OemLockSecureStatus;
+
+/*
+ * The OEM lock prevents the bootloader from allowing the device to be flashed.
+ *
+ * Both the carrier and the device itself have a say as to whether OEM unlock is
+ * allowed and both must agree that is allowed in order for unlock to be
+ * possible.
+ */
+@VintfStability
+interface IOemLock {
+    /**
+     * Returns a vendor specific identifier of the HAL.
+     *
+     * The name returned must not be interpreted by the framework but must be
+     * passed to vendor code which may use it to identify the security protocol
+     * used by setOemUnlockAllowedByCarrier. This allows the vendor to identify
+     * the protocol without having to maintain a device-to-protocol mapping.
+     *
+     * @return name of the implementation and STATUS_OK if get name successfully
+     */
+    String getName();
+
+    /**
+     * Returns whether OEM unlock is allowed by the carrier.
+     *
+     * @return the current state(allowed/not allowed) of the flag
+     * and STATUS_OK if the flag was successfully read.
+     */
+    boolean isOemUnlockAllowedByCarrier();
+
+    /**
+     * Returns whether OEM unlock ia allowed by the device.
+     *
+     * @return the current state(allowed/not allowed) of the flag
+     * and STATUS_OK if the flag was successfully read.
+     */
+    boolean isOemUnlockAllowedByDevice();
+
+    /**
+     * Updates whether OEM unlock is allowed by the carrier.
+     *
+     * The implementation may require a vendor defined signature to prove the
+     * validity of this request in order to harden its security.
+     *
+     * @param allowed is the new value of the flag.
+     * @param signature to prove validity of this request or empty if not
+     *        required.
+     * @return OK if the flag was successfully updated,
+     *         INVALID_SIGNATURE if a signature is required but the wrong one
+     *         was provided
+     *         FAILED if the update was otherwise unsuccessful.
+     */
+    OemLockSecureStatus setOemUnlockAllowedByCarrier(in boolean allowed, in byte[] signature);
+
+    /**
+     * Updates whether OEM unlock is allowed by the device.
+     *
+     * @param allowed the new value of the flag.
+     * @return STATUS_OK if the flag was successfully updated.
+     */
+    void setOemUnlockAllowedByDevice(in boolean allowed);
+}
diff --git a/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl b/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl
new file mode 100644
index 0000000..3c11377
--- /dev/null
+++ b/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.oemlock;
+
+@VintfStability
+@Backing(type="int")
+enum OemLockSecureStatus {
+    /**
+     * The operation completed successfully.
+     */
+    OK,
+    /**
+     * The operation encountered a problem.
+     */
+    FAILED,
+    /**
+     * An invalid signature was provided so the operation was not performed.
+     */
+    INVALID_SIGNATURE,
+}
diff --git a/oemlock/aidl/default/Android.bp b/oemlock/aidl/default/Android.bp
new file mode 100644
index 0000000..464b0a3
--- /dev/null
+++ b/oemlock/aidl/default/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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.
+//
+
+cc_binary {
+    name: "android.hardware.oemlock-service.example",
+    relative_install_path: "hw",
+    init_rc: ["android.hardware.oemlock-service.example.rc"],
+    vintf_fragments: ["android.hardware.oemlock-service.example.xml"],
+    vendor: true,
+    srcs: [
+        "service.cpp",
+        "OemLock.cpp",
+    ],
+    shared_libs: [
+        "android.hardware.oemlock-V1-ndk_platform",
+        "libbase",
+        "libbinder_ndk",
+    ],
+}
diff --git a/oemlock/aidl/default/OemLock.cpp b/oemlock/aidl/default/OemLock.cpp
new file mode 100644
index 0000000..646b532
--- /dev/null
+++ b/oemlock/aidl/default/OemLock.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 "OemLock.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace oemlock {
+
+// Methods from ::android::hardware::oemlock::IOemLock follow.
+
+::ndk::ScopedAStatus OemLock::getName(std::string *out_name) {
+    (void)out_name;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus OemLock::setOemUnlockAllowedByCarrier(bool in_allowed, const std::vector<uint8_t> &in_signature, OemLockSecureStatus *_aidl_return) {
+    (void)in_allowed;
+    (void)in_signature;
+    (void)_aidl_return;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus OemLock::isOemUnlockAllowedByCarrier(bool *out_allowed) {
+    (void)out_allowed;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus OemLock::setOemUnlockAllowedByDevice(bool in_allowed) {
+    (void)in_allowed;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus OemLock::isOemUnlockAllowedByDevice(bool *out_allowed) {
+    (void)out_allowed;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace oemlock
+} // namespace hardware
+} // namespace android
+} // aidl
diff --git a/oemlock/aidl/default/OemLock.h b/oemlock/aidl/default/OemLock.h
new file mode 100644
index 0000000..b0df414
--- /dev/null
+++ b/oemlock/aidl/default/OemLock.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/oemlock/BnOemLock.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace oemlock {
+
+using ::aidl::android::hardware::oemlock::IOemLock;
+using ::aidl::android::hardware::oemlock::OemLockSecureStatus;
+
+struct OemLock : public BnOemLock {
+public:
+    OemLock() = default;
+
+    // Methods from ::android::hardware::oemlock::IOemLock follow.
+    ::ndk::ScopedAStatus getName(std::string* out_name) override;
+    ::ndk::ScopedAStatus isOemUnlockAllowedByCarrier(bool* out_allowed) override;
+    ::ndk::ScopedAStatus isOemUnlockAllowedByDevice(bool* out_allowed) override;
+    ::ndk::ScopedAStatus setOemUnlockAllowedByCarrier(bool in_allowed, const std::vector<uint8_t>& in_signature, OemLockSecureStatus* _aidl_return) override;
+    ::ndk::ScopedAStatus setOemUnlockAllowedByDevice(bool in_allowed) override;
+};
+
+} // namespace oemlock
+} // namespace hardware
+} // namespace android
+} // aidl
diff --git a/oemlock/aidl/default/android.hardware.oemlock-service.example.rc b/oemlock/aidl/default/android.hardware.oemlock-service.example.rc
new file mode 100644
index 0000000..57b79d3
--- /dev/null
+++ b/oemlock/aidl/default/android.hardware.oemlock-service.example.rc
@@ -0,0 +1,4 @@
+service vendor.oemlock_default /vendor/bin/hw/android.hardware.oemlock-service.example
+    class hal
+    user hsm
+    group hsm
diff --git a/oemlock/aidl/default/android.hardware.oemlock-service.example.xml b/oemlock/aidl/default/android.hardware.oemlock-service.example.xml
new file mode 100644
index 0000000..b9f137f
--- /dev/null
+++ b/oemlock/aidl/default/android.hardware.oemlock-service.example.xml
@@ -0,0 +1,9 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.oemlock</name>
+        <interface>
+            <name>IOemLock</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/oemlock/aidl/default/service.cpp b/oemlock/aidl/default/service.cpp
new file mode 100644
index 0000000..af828a0
--- /dev/null
+++ b/oemlock/aidl/default/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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 <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "OemLock.h"
+
+using ::aidl::android::hardware::oemlock::OemLock;
+
+int main() {
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+    std::shared_ptr<OemLock> oemlock = ndk::SharedRefBase::make<OemLock>();
+
+    const std::string instance = std::string() + OemLock::descriptor + "/default";
+    binder_status_t status = AServiceManager_addService(oemlock->asBinder().get(), instance.c_str());
+    CHECK(status == STATUS_OK);
+
+    ABinderProcess_joinThreadPool();
+    return -1; // Should never be reached
+}
diff --git a/oemlock/aidl/vts/Android.bp b/oemlock/aidl/vts/Android.bp
new file mode 100644
index 0000000..18b53c2
--- /dev/null
+++ b/oemlock/aidl/vts/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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.
+//
+
+cc_test {
+    name: "VtsHalOemLockTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: ["VtsHalOemLockTargetTest.cpp"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbase",
+    ],
+    static_libs: ["android.hardware.oemlock-V1-ndk_platform"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/oemlock/aidl/vts/OWNERS b/oemlock/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/oemlock/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp b/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp
new file mode 100644
index 0000000..6bf6298
--- /dev/null
+++ b/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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 <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/oemlock/IOemLock.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::hardware::oemlock::IOemLock;
+using ::aidl::android::hardware::oemlock::OemLockSecureStatus;
+
+using ndk::SpAIBinder;
+
+struct OemLockAidlTest : public ::testing::TestWithParam<std::string> {
+    virtual void SetUp() override {
+        oemlock = IOemLock::fromBinder(
+            SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+        ASSERT_NE(oemlock, nullptr);
+    }
+
+    virtual void TearDown() override {}
+
+    std::shared_ptr<IOemLock> oemlock;
+};
+
+/*
+ * Check the name can be retrieved
+ */
+TEST_P(OemLockAidlTest, GetName) {
+    std::string name;
+
+    const auto ret = oemlock->getName(&name);
+
+    ASSERT_TRUE(ret.isOk());
+    // Any value acceptable
+};
+
+/*
+ * Check the unlock allowed by device state can be queried
+ */
+TEST_P(OemLockAidlTest, QueryUnlockAllowedByDevice) {
+    bool allowed;
+
+    const auto ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+
+    ASSERT_TRUE(ret.isOk());
+    // Any value acceptable
+}
+
+/*
+ * Check unlock allowed by device state can be toggled
+ */
+TEST_P(OemLockAidlTest, AllowedByDeviceCanBeToggled) {
+    bool allowed;
+
+    // Get the original state so it can be restored
+    const auto get_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+    ASSERT_TRUE(get_ret.isOk());
+    const bool originallyAllowed = allowed;
+
+    // Toggle the state
+    const auto set_ret = oemlock->setOemUnlockAllowedByDevice(!originallyAllowed);
+    ASSERT_TRUE(set_ret.isOk());
+
+    const auto check_set_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+    ASSERT_TRUE(check_set_ret.isOk());
+    ASSERT_EQ(allowed, !originallyAllowed);
+
+    // Restore the state
+    const auto restore_ret = oemlock->setOemUnlockAllowedByDevice(originallyAllowed);
+    ASSERT_TRUE(restore_ret.isOk());
+
+    const auto check_restore_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+    ASSERT_TRUE(check_restore_ret.isOk());
+    ASSERT_EQ(allowed, originallyAllowed);
+}
+
+/*
+ * Check the unlock allowed by device state can be queried
+ */
+TEST_P(OemLockAidlTest, QueryUnlockAllowedByCarrier) {
+    bool allowed;
+
+    const auto ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+
+    ASSERT_TRUE(ret.isOk());
+    // Any value acceptable
+}
+
+/*
+ * Attempt to check unlock allowed by carrier can be toggled
+ *
+ * The implementation may involve a signature which cannot be tested here. That
+ * is a valid implementation so the test will pass. If there is no signature
+ * required, the test will toggle the value.
+ */
+TEST_P(OemLockAidlTest, CarrierUnlock) {
+    const std::vector<uint8_t> noSignature = {};
+    bool allowed;
+    OemLockSecureStatus secure_status;
+
+    // Get the original state so it can be restored
+    const auto get_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+    ASSERT_TRUE(get_ret.isOk());
+    const bool originallyAllowed = allowed;
+
+    if (originallyAllowed) {
+       // Only applied to locked devices
+       return;
+    }
+
+    // Toggle the state
+    const auto set_ret = oemlock->setOemUnlockAllowedByCarrier(!originallyAllowed, noSignature, &secure_status);
+    ASSERT_TRUE(set_ret.isOk());
+    ASSERT_NE(secure_status, OemLockSecureStatus::FAILED);
+    const auto set_status = secure_status;
+
+    const auto check_set_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+    ASSERT_TRUE(check_set_ret.isOk());
+
+    if (set_status == OemLockSecureStatus::INVALID_SIGNATURE) {
+       // Signature is required so we cannot toggle the value in the test, but this is allowed
+       ASSERT_EQ(allowed, originallyAllowed);
+       return;
+    }
+
+    ASSERT_EQ(set_status, OemLockSecureStatus::OK);
+    ASSERT_EQ(allowed, !originallyAllowed);
+
+    // Restore the state
+    const auto restore_ret = oemlock->setOemUnlockAllowedByCarrier(originallyAllowed, noSignature, &secure_status);
+    ASSERT_TRUE(restore_ret.isOk());
+    ASSERT_EQ(secure_status, OemLockSecureStatus::OK);
+
+    const auto check_restore_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+    ASSERT_TRUE(check_restore_ret.isOk());
+    ASSERT_EQ(allowed, originallyAllowed);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OemLockAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+        PerInstance, OemLockAidlTest,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IOemLock::descriptor)),
+        android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/power/aidl/default/Android.bp b/power/aidl/default/Android.bp
index 07cd368..de04bcd 100644
--- a/power/aidl/default/Android.bp
+++ b/power/aidl/default/Android.bp
@@ -21,7 +21,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.power-ndk_platform",
+        "android.hardware.power-V1-ndk_platform",
     ],
     srcs: [
         "main.cpp",
diff --git a/power/aidl/vts/Android.bp b/power/aidl/vts/Android.bp
index 28b08c7..008073b 100644
--- a/power/aidl/vts/Android.bp
+++ b/power/aidl/vts/Android.bp
@@ -23,7 +23,7 @@
         "libbinder",
     ],
     static_libs: [
-        "android.hardware.power-cpp",
+        "android.hardware.power-V1-cpp",
     ],
     test_suites: [
         "vts",
diff --git a/power/stats/aidl/Android.bp b/power/stats/aidl/Android.bp
new file mode 100644
index 0000000..1688b12
--- /dev/null
+++ b/power/stats/aidl/Android.bp
@@ -0,0 +1,35 @@
+// 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.
+
+aidl_interface {
+    name: "android.hardware.power.stats",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/power/stats/*.aidl",
+    ],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+        cpp: {
+            enabled: false,
+        },
+    },
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/Channel.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/Channel.aidl
new file mode 100644
index 0000000..e0c372b
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/Channel.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable Channel {
+  int id;
+  @utf8InCpp String name;
+  @utf8InCpp String subsystem;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl
new file mode 100644
index 0000000..cd9239e
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable EnergyConsumer {
+  int id;
+  int ordinal;
+  android.hardware.power.stats.EnergyConsumerType type = android.hardware.power.stats.EnergyConsumerType.OTHER;
+  @utf8InCpp String name;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl
new file mode 100644
index 0000000..2fdc1a5
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable EnergyConsumerAttribution {
+  int uid;
+  long energyUWs;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl
new file mode 100644
index 0000000..4937aef
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable EnergyConsumerResult {
+  int id;
+  long timestampMs;
+  long energyUWs;
+  android.hardware.power.stats.EnergyConsumerAttribution[] attribution;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl
new file mode 100644
index 0000000..ce3e1f5
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+enum EnergyConsumerType {
+  OTHER = 0,
+  BLUETOOTH = 1,
+  CPU_CLUSTER = 2,
+  DISPLAY = 3,
+  GNSS = 4,
+  MOBILE_RADIO = 5,
+  WIFI = 6,
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyMeasurement.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyMeasurement.aidl
new file mode 100644
index 0000000..2d6ae16
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyMeasurement.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable EnergyMeasurement {
+  int id;
+  long timestampMs;
+  long durationMs;
+  long energyUWs;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/IPowerStats.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/IPowerStats.aidl
new file mode 100644
index 0000000..5b11695
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/IPowerStats.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+interface IPowerStats {
+  android.hardware.power.stats.PowerEntity[] getPowerEntityInfo();
+  android.hardware.power.stats.StateResidencyResult[] getStateResidency(in int[] powerEntityIds);
+  android.hardware.power.stats.EnergyConsumer[] getEnergyConsumerInfo();
+  android.hardware.power.stats.EnergyConsumerResult[] getEnergyConsumed(in int[] energyConsumerIds);
+  android.hardware.power.stats.Channel[] getEnergyMeterInfo();
+  android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(in int[] channelIds);
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/PowerEntity.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/PowerEntity.aidl
new file mode 100644
index 0000000..38a62ac
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/PowerEntity.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable PowerEntity {
+  int id;
+  @utf8InCpp String name;
+  android.hardware.power.stats.State[] states;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/State.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/State.aidl
new file mode 100644
index 0000000..1c5d7c1
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/State.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable State {
+  int id;
+  @utf8InCpp String name;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidency.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidency.aidl
new file mode 100644
index 0000000..7982f02
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidency.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable StateResidency {
+  int id;
+  long totalTimeInStateMs;
+  long totalStateEntryCount;
+  long lastEntryTimestampMs;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidencyResult.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidencyResult.aidl
new file mode 100644
index 0000000..5bc548e
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidencyResult.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.power.stats;
+@VintfStability
+parcelable StateResidencyResult {
+  int id;
+  android.hardware.power.stats.StateResidency[] stateResidencyData;
+}
diff --git a/power/stats/aidl/android/hardware/power/stats/Channel.aidl b/power/stats/aidl/android/hardware/power/stats/Channel.aidl
new file mode 100644
index 0000000..5e8962e
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/Channel.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package android.hardware.power.stats;
+
+@VintfStability
+parcelable Channel {
+    /**
+     * Unique ID of this Channel
+     */
+    int id;
+    /**
+     * Unique name of this Channel. Vendor/device specific. Opaque to framework
+     */
+    @utf8InCpp String name;
+
+    /**
+     * Name of the subsystem associated with this Channel. Opaque to framework
+     */
+    @utf8InCpp String subsystem;
+}
+
diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl
new file mode 100644
index 0000000..ec616f2
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerType;
+
+@VintfStability
+parcelable EnergyConsumer {
+    /**
+     * Unique ID of this EnergyConsumer
+     */
+    int id;
+
+    /**
+     * For a group of EnergyConsumers of the same logical type, sorting by ordinal
+     * gives their physical order. Ordinals must be consecutive integers starting from 0.
+     */
+    int ordinal;
+
+    /* Type of this EnergyConsumer */
+    EnergyConsumerType type = EnergyConsumerType.OTHER;
+
+    /**
+     * Unique name of this EnergyConsumer. Vendor/device specific. Opaque to framework
+     */
+    @utf8InCpp String name;
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl
similarity index 60%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl
index 127f98e..5767de1 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.power.stats;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
+@VintfStability
+parcelable EnergyConsumerAttribution {
+    /**
+     * Android ID / Linux UID, the accumulated energy is attributed to
+     */
+    int uid;
+    /**
+     * Accumulated energy since boot in microwatt-seconds (uWs) for this AID
+     */
+    long energyUWs;
 }
diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl
new file mode 100644
index 0000000..12d2042
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerAttribution;
+
+@VintfStability
+parcelable EnergyConsumerResult {
+    /**
+     * ID of the EnergyConsumer associated with this result
+     */
+    int id;
+    /**
+     * Time since boot in milliseconds
+     */
+    long timestampMs;
+    /**
+     * Total accumulated energy since boot in microwatt-seconds (uWs)
+     */
+    long energyUWs;
+    /**
+     * Optional attributed energy per Android ID / Linux UID for this EnergyConsumer.
+     * Sum total of attributed energy must be less than or equal to total accumulated energy.
+     */
+    EnergyConsumerAttribution[] attribution;
+}
+
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl
similarity index 64%
rename from health/1.0/default/libhealthd/healthd_board_default.cpp
rename to power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl
index 127f98e..d871ced 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.power.stats;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
+/* Indicates the type of an energy consumer reported by the Power Stats HAL */
+@VintfStability
+enum EnergyConsumerType {
+    OTHER,
+    BLUETOOTH,
+    CPU_CLUSTER,
+    DISPLAY,
+    GNSS,
+    MOBILE_RADIO,
+    WIFI,
 }
diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyMeasurement.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyMeasurement.aidl
new file mode 100644
index 0000000..d3e8f46
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyMeasurement.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+package android.hardware.power.stats;
+
+@VintfStability
+parcelable EnergyMeasurement {
+    /**
+     * ID of the Channel associated with this measurement
+     */
+    int id;
+    /**
+     * Approximate time of data capture in millseconds since boot
+     */
+    long timestampMs;
+    /**
+     * Duration in milliseconds that energy has been accumulated
+     */
+    long durationMs;
+    /**
+     * Accumulated energy in microwatt-seconds (uWs)
+     */
+    long energyUWs;
+}
+
diff --git a/power/stats/aidl/android/hardware/power/stats/IPowerStats.aidl b/power/stats/aidl/android/hardware/power/stats/IPowerStats.aidl
new file mode 100644
index 0000000..7a95f74
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/IPowerStats.aidl
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.Channel;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyMeasurement;
+import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.StateResidencyResult;
+
+@VintfStability
+interface IPowerStats {
+    /**
+     * Return information related to all supported PowerEntity(s) for which state residency data
+     * is available.
+     *
+     * A PowerEntity is defined as a platform subsystem, peripheral, or power domain that impacts
+     * the total device power consumption.
+     *
+     * @return List of information on each PowerEntity
+     */
+    PowerEntity[] getPowerEntityInfo();
+
+    /**
+     * Reports the accumulated state residency for each requested PowerEntity.
+     *
+     * Each PowerEntity may reside in one of multiple states. It may also
+     * transition from one state to another. StateResidency is defined as
+     * an accumulation of time that a PowerEntity resided in each
+     * of its possible states, the number of times that each state was
+     * entered, and a timestamp corresponding to the last time that state
+     * was entered.
+     *
+     * Data is accumulated starting at device boot.
+     *
+     * @param powerEntityIds List of IDs of PowerEntities for which data is requested.
+     *     Passing an empty list will return state residency for all available PowerEntitys.
+     *     ID of each PowerEntity is contained in PowerEntityInfo.
+     *
+     * @return StateResidency since boot for each requested PowerEntity
+     *
+     * Returns the following service-specific exceptions in order of highest priority:
+     *  - STATUS_BAD_VALUE if an invalid powerEntityId is provided
+     *  - STATUS_FAILED_TRANSACTION if any StateResidencyResult fails to be returned
+     */
+    StateResidencyResult[] getStateResidency(in int[] powerEntityIds);
+
+    /**
+     * Return the list EnergyConsumers for which energy consumption data is available.
+     *
+     * An EnergyConsumer is a device subsystem or peripheral that consumes energy. Energy
+     * consumption data may be used by framework for the purpose of power attribution.
+     *
+     * @return List of EnergyConsumers that are available.
+     */
+    EnergyConsumer[] getEnergyConsumerInfo();
+
+    /**
+     * Reports the energy consumed since boot by each requested EnergyConsumer.
+     *
+     * @param energyConsumerIds List of IDs of EnergyConsumers for which data is requested.
+     *     Passing an empty list will return state residency for all available EnergyConsumers.
+     *
+     * @return Energy consumed since boot for each requested EnergyConsumer
+     *
+     * Returns the following service-specific exceptions in order of highest priority:
+     *  - STATUS_BAD_VALUE if an invalid energyConsumerId is provided
+     *  - STATUS_FAILED_TRANSACTION if any EnergyConsumerResult fails to be returned
+     */
+    EnergyConsumerResult[] getEnergyConsumed(in int[] energyConsumerIds);
+
+    /**
+     * Return information related to all channels monitored by Energy Meters.
+     *
+     * An Energy Meter is a device that monitors energy and may support monitoring multiple
+     * channels simultaneously. A channel may correspond a bus, sense resistor, or power rail.
+     *
+     * @return Channels monitored by Energy Meters.
+     */
+    Channel[] getEnergyMeterInfo();
+
+    /**
+     * Reports accumulated energy for each specified channel.
+     *
+     * @param channelIds IDs of channels for which data is requested.
+     *     Passing an empty list will return energy measurements for all available channels.
+     *     ID of each channel is contained in ChannelInfo.
+     *
+     * @return Energy measured since boot for each requested channel
+     *
+     * Returns the following service-specific exceptions in order of highest priority:
+     *  - STATUS_BAD_VALUE if an invalid channelId is provided
+     *  - STATUS_FAILED_TRANSACTION if any EnergyMeasurement fails to be returned
+     */
+    EnergyMeasurement[] readEnergyMeter(in int[] channelIds);
+}
diff --git a/power/stats/aidl/android/hardware/power/stats/PowerEntity.aidl b/power/stats/aidl/android/hardware/power/stats/PowerEntity.aidl
new file mode 100644
index 0000000..6844a4c
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/PowerEntity.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.State;
+
+@VintfStability
+parcelable PowerEntity {
+    /**
+     * Unique ID of this PowerEntity
+     */
+    int id;
+    /**
+     * Unique name of this PowerEntity. Vendor/device specific. Opaque to framework
+     */
+    @utf8InCpp String name;
+    /**
+     * List of states that this PowerEntity may reside in
+     */
+    State[] states;
+}
\ No newline at end of file
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/power/stats/aidl/android/hardware/power/stats/State.aidl
similarity index 60%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to power/stats/aidl/android/hardware/power/stats/State.aidl
index 127f98e..33a9f70 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/power/stats/aidl/android/hardware/power/stats/State.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.power.stats;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
+@VintfStability
+parcelable State {
+    /**
+     * Unique (for a given PowerEntity) ID of this State
+     */
+    int id;
+    /**
+     * Unique (for a given PowerEntity) name of the state. Vendor/device specific.
+     * Opaque to framework
+     */
+    @utf8InCpp String name;
 }
 
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
-}
diff --git a/power/stats/aidl/android/hardware/power/stats/StateResidency.aidl b/power/stats/aidl/android/hardware/power/stats/StateResidency.aidl
new file mode 100644
index 0000000..4162517
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/StateResidency.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package android.hardware.power.stats;
+
+/**
+ * Contains residency data for a single state
+ */
+@VintfStability
+parcelable StateResidency {
+    /**
+     * ID of the state associated with this residency
+     */
+    int id;
+    /**
+     * Total time in milliseconds that the corresponding PowerEntity resided
+     * in this state since boot
+     */
+    long totalTimeInStateMs;
+    /**
+     * Total number of times that the state was entered since boot
+     */
+    long totalStateEntryCount;
+    /**
+     * Last time this state was entered. Time in milliseconds since boot
+     */
+    long lastEntryTimestampMs;
+}
\ No newline at end of file
diff --git a/power/stats/aidl/android/hardware/power/stats/StateResidencyResult.aidl b/power/stats/aidl/android/hardware/power/stats/StateResidencyResult.aidl
new file mode 100644
index 0000000..949879c
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/StateResidencyResult.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.StateResidency;
+
+@VintfStability
+parcelable StateResidencyResult {
+    /**
+     * ID of the PowerEntity associated with this result
+     */
+    int id;
+    /**
+     * Residency for each state in the PowerEntity's state space
+     */
+    StateResidency[] stateResidencyData;
+}
+
diff --git a/power/stats/aidl/default/Android.bp b/power/stats/aidl/default/Android.bp
new file mode 100644
index 0000000..595ecd6
--- /dev/null
+++ b/power/stats/aidl/default/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+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"],
+    vendor: true,
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "android.hardware.power.stats-V1-ndk_platform",
+    ],
+    srcs: [
+        "main.cpp",
+        "PowerStats.cpp",
+    ],
+}
diff --git a/power/stats/aidl/default/PowerStats.cpp b/power/stats/aidl/default/PowerStats.cpp
new file mode 100644
index 0000000..0ffbd08
--- /dev/null
+++ b/power/stats/aidl/default/PowerStats.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 "PowerStats.h"
+
+#include <android-base/logging.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace power {
+namespace stats {
+
+ndk::ScopedAStatus PowerStats::getPowerEntityInfo(std::vector<PowerEntity>* _aidl_return) {
+    (void)_aidl_return;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::getStateResidency(const std::vector<int32_t>& in_powerEntityIds,
+                                                 std::vector<StateResidencyResult>* _aidl_return) {
+    (void)in_powerEntityIds;
+    (void)_aidl_return;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::getEnergyConsumerInfo(std::vector<EnergyConsumer>* _aidl_return) {
+    (void)_aidl_return;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::getEnergyConsumed(const std::vector<int32_t>& in_energyConsumerIds,
+                                                 std::vector<EnergyConsumerResult>* _aidl_return) {
+    (void)in_energyConsumerIds;
+    (void)_aidl_return;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::getEnergyMeterInfo(std::vector<Channel>* _aidl_return) {
+    (void)_aidl_return;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::readEnergyMeter(const std::vector<int32_t>& in_channelIds,
+                                               std::vector<EnergyMeasurement>* _aidl_return) {
+    (void)in_channelIds;
+    (void)_aidl_return;
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace stats
+}  // namespace power
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/power/stats/aidl/default/PowerStats.h b/power/stats/aidl/default/PowerStats.h
new file mode 100644
index 0000000..cb98e55
--- /dev/null
+++ b/power/stats/aidl/default/PowerStats.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/power/stats/BnPowerStats.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace power {
+namespace stats {
+
+class PowerStats : public BnPowerStats {
+  public:
+    PowerStats() = default;
+    // Methods from aidl::android::hardware::power::stats::IPowerStats
+    ndk::ScopedAStatus getPowerEntityInfo(std::vector<PowerEntity>* _aidl_return) override;
+    ndk::ScopedAStatus getStateResidency(const std::vector<int32_t>& in_powerEntityIds,
+                                         std::vector<StateResidencyResult>* _aidl_return) override;
+    ndk::ScopedAStatus getEnergyConsumerInfo(std::vector<EnergyConsumer>* _aidl_return) override;
+    ndk::ScopedAStatus getEnergyConsumed(const std::vector<int32_t>& in_energyConsumerIds,
+                                         std::vector<EnergyConsumerResult>* _aidl_return) override;
+    ndk::ScopedAStatus getEnergyMeterInfo(std::vector<Channel>* _aidl_return) override;
+    ndk::ScopedAStatus readEnergyMeter(const std::vector<int32_t>& in_channelIds,
+                                       std::vector<EnergyMeasurement>* _aidl_return) override;
+};
+
+}  // namespace stats
+}  // namespace power
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/power/stats/aidl/default/main.cpp b/power/stats/aidl/default/main.cpp
new file mode 100644
index 0000000..0469b4c
--- /dev/null
+++ b/power/stats/aidl/default/main.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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 "PowerStats.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using aidl::android::hardware::power::stats::PowerStats;
+
+int main() {
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+    std::shared_ptr<PowerStats> p = ndk::SharedRefBase::make<PowerStats>();
+
+    const std::string instance = std::string() + PowerStats::descriptor + "/default";
+    binder_status_t status = AServiceManager_addService(p->asBinder().get(), instance.c_str());
+    CHECK(status == STATUS_OK);
+
+    ABinderProcess_joinThreadPool();
+    return EXIT_FAILURE;  // should not reach
+}
diff --git a/power/stats/aidl/default/power.stats-default.rc b/power/stats/aidl/default/power.stats-default.rc
new file mode 100644
index 0000000..6ff6754
--- /dev/null
+++ b/power/stats/aidl/default/power.stats-default.rc
@@ -0,0 +1,4 @@
+service vendor.power.stats-default /vendor/bin/hw/android.hardware.power.stats-service.example
+    class hal
+    user system
+    group system
diff --git a/power/stats/aidl/default/power.stats-default.xml b/power/stats/aidl/default/power.stats-default.xml
new file mode 100644
index 0000000..3b1a216
--- /dev/null
+++ b/power/stats/aidl/default/power.stats-default.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.power.stats</name>
+        <fqname>IPowerStats/default</fqname>
+    </hal>
+</manifest>
diff --git a/power/stats/aidl/vts/Android.bp b/power/stats/aidl/vts/Android.bp
new file mode 100644
index 0000000..31fb990
--- /dev/null
+++ b/power/stats/aidl/vts/Android.bp
@@ -0,0 +1,32 @@
+// 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.
+
+cc_test {
+    name: "VtsHalPowerStatsTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: ["VtsHalPowerStatsTargetTest.cpp"],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "android.hardware.power.stats-V1-ndk_platform",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/power/stats/aidl/vts/VtsHalPowerStatsTargetTest.cpp b/power/stats/aidl/vts/VtsHalPowerStatsTargetTest.cpp
new file mode 100644
index 0000000..bed3fdf
--- /dev/null
+++ b/power/stats/aidl/vts/VtsHalPowerStatsTargetTest.cpp
@@ -0,0 +1,423 @@
+/*
+ * 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 <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/power/stats/IPowerStats.h>
+#include <android-base/properties.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <algorithm>
+#include <iterator>
+#include <random>
+#include <unordered_map>
+
+using aidl::android::hardware::power::stats::Channel;
+using aidl::android::hardware::power::stats::EnergyConsumer;
+using aidl::android::hardware::power::stats::EnergyConsumerAttribution;
+using aidl::android::hardware::power::stats::EnergyConsumerResult;
+using aidl::android::hardware::power::stats::EnergyConsumerType;
+using aidl::android::hardware::power::stats::EnergyMeasurement;
+using aidl::android::hardware::power::stats::IPowerStats;
+using aidl::android::hardware::power::stats::PowerEntity;
+using aidl::android::hardware::power::stats::State;
+using aidl::android::hardware::power::stats::StateResidency;
+using aidl::android::hardware::power::stats::StateResidencyResult;
+
+using ndk::SpAIBinder;
+
+class PowerStatsAidl : public testing::TestWithParam<std::string> {
+  public:
+    virtual void SetUp() override {
+        powerstats = IPowerStats::fromBinder(
+                SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+        ASSERT_NE(nullptr, powerstats.get());
+    }
+
+    template <typename T>
+    std::vector<T> getRandomSubset(std::vector<T> const& collection);
+
+    void testNameValid(const std::string& name);
+
+    template <typename T, typename S>
+    void testUnique(std::vector<T> const& collection, S T::*field);
+
+    template <typename T, typename S, typename R>
+    void testMatching(std::vector<T> const& c1, R T::*f1, std::vector<S> const& c2, R S::*f2);
+
+    std::shared_ptr<IPowerStats> powerstats;
+};
+
+// Returns a random subset from a collection
+template <typename T>
+std::vector<T> PowerStatsAidl::getRandomSubset(std::vector<T> const& collection) {
+    if (collection.empty()) {
+        return {};
+    }
+
+    std::vector<T> selected;
+    std::sample(collection.begin(), collection.end(), std::back_inserter(selected),
+                rand() % collection.size() + 1, std::mt19937{std::random_device{}()});
+
+    return selected;
+}
+
+// Tests whether a name is valid
+void PowerStatsAidl::testNameValid(const std::string& name) {
+    EXPECT_NE(name, "");
+}
+
+// Tests whether the fields in a given collection are unique
+template <typename T, typename S>
+void PowerStatsAidl::testUnique(std::vector<T> const& collection, S T::*field) {
+    std::set<S> cSet;
+    for (auto const& elem : collection) {
+        EXPECT_TRUE(cSet.insert(elem.*field).second);
+    }
+}
+
+template <typename T, typename S, typename R>
+void PowerStatsAidl::testMatching(std::vector<T> const& c1, R T::*f1, std::vector<S> const& c2,
+                                  R S::*f2) {
+    std::set<R> c1fields, c2fields;
+    for (auto elem : c1) {
+        c1fields.insert(elem.*f1);
+    }
+
+    for (auto elem : c2) {
+        c2fields.insert(elem.*f2);
+    }
+
+    EXPECT_EQ(c1fields, c2fields);
+}
+
+// Each PowerEntity must have a valid name
+TEST_P(PowerStatsAidl, ValidatePowerEntityNames) {
+    std::vector<PowerEntity> infos;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&infos).isOk());
+
+    for (auto info : infos) {
+        testNameValid(info.name);
+    }
+}
+
+// Each power entity must have a unique name
+TEST_P(PowerStatsAidl, ValidatePowerEntityUniqueNames) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+    testUnique(entities, &PowerEntity::name);
+}
+
+// Each PowerEntity must have a unique ID
+TEST_P(PowerStatsAidl, ValidatePowerEntityIds) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+    testUnique(entities, &PowerEntity::id);
+}
+
+// Each power entity must have at least one state
+TEST_P(PowerStatsAidl, ValidateStateSize) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+    for (auto entity : entities) {
+        EXPECT_GT(entity.states.size(), 0);
+    }
+}
+
+// Each state must have a valid name
+TEST_P(PowerStatsAidl, ValidateStateNames) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+    for (auto entity : entities) {
+        for (auto state : entity.states) {
+            testNameValid(state.name);
+        }
+    }
+}
+
+// Each state must have a name that is unique to the given PowerEntity
+TEST_P(PowerStatsAidl, ValidateStateUniqueNames) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+    for (auto entity : entities) {
+        testUnique(entity.states, &State::name);
+    }
+}
+
+// Each state must have an ID that is unique to the given PowerEntity
+TEST_P(PowerStatsAidl, ValidateStateUniqueIds) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+    for (auto entity : entities) {
+        testUnique(entity.states, &State::id);
+    }
+}
+
+// State residency must return a valid status
+TEST_P(PowerStatsAidl, TestGetStateResidency) {
+    std::vector<StateResidencyResult> results;
+    ASSERT_TRUE(powerstats->getStateResidency({}, &results).isOk());
+}
+
+// State residency must return all results
+TEST_P(PowerStatsAidl, TestGetStateResidencyAllResults) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+    std::vector<StateResidencyResult> results;
+    ASSERT_TRUE(powerstats->getStateResidency({}, &results).isOk());
+
+    testMatching(entities, &PowerEntity::id, results, &StateResidencyResult::id);
+}
+
+// Each result must contain all state residencies
+TEST_P(PowerStatsAidl, TestGetStateResidencyAllStateResidencies) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+    std::vector<StateResidencyResult> results;
+    ASSERT_TRUE(powerstats->getStateResidency({}, &results).isOk());
+
+    for (auto entity : entities) {
+        auto it = std::find_if(results.begin(), results.end(),
+                               [&entity](const auto& x) { return x.id == entity.id; });
+        ASSERT_NE(it, results.end());
+
+        testMatching(entity.states, &State::id, it->stateResidencyData, &StateResidency::id);
+    }
+}
+
+// State residency must return results for each requested power entity
+TEST_P(PowerStatsAidl, TestGetStateResidencySelectedResults) {
+    std::vector<PowerEntity> entities;
+    ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+    if (entities.empty()) {
+        return;
+    }
+
+    std::vector<PowerEntity> selectedEntities = getRandomSubset(entities);
+    std::vector<int32_t> selectedIds;
+    for (auto const& entity : selectedEntities) {
+        selectedIds.push_back(entity.id);
+    }
+
+    std::vector<StateResidencyResult> selectedResults;
+    ASSERT_TRUE(powerstats->getStateResidency(selectedIds, &selectedResults).isOk());
+
+    testMatching(selectedEntities, &PowerEntity::id, selectedResults, &StateResidencyResult::id);
+}
+
+// Energy meter info must return a valid status
+TEST_P(PowerStatsAidl, TestGetEnergyMeterInfo) {
+    std::vector<Channel> info;
+    ASSERT_TRUE(powerstats->getEnergyMeterInfo(&info).isOk());
+}
+
+// Each channel must have a valid name and subsystem
+TEST_P(PowerStatsAidl, ValidateChannelNames) {
+    std::vector<Channel> channels;
+    ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+    for (auto channel : channels) {
+        testNameValid(channel.name);
+        testNameValid(channel.subsystem);
+    }
+}
+
+// Each channel must have a unique name
+TEST_P(PowerStatsAidl, ValidateChannelUniqueNames) {
+    std::vector<Channel> channels;
+    ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+
+    testUnique(channels, &Channel::name);
+}
+
+// Each channel must have a unique ID
+TEST_P(PowerStatsAidl, ValidateChannelUniqueIds) {
+    std::vector<Channel> channels;
+    ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+
+    testUnique(channels, &Channel::id);
+}
+
+// Reading energy meter must return a valid status
+TEST_P(PowerStatsAidl, TestReadEnergyMeter) {
+    std::vector<EnergyMeasurement> data;
+    ASSERT_TRUE(powerstats->readEnergyMeter({}, &data).isOk());
+}
+
+// Reading energy meter must return results for all available channels
+TEST_P(PowerStatsAidl, TestGetAllEnergyMeasurements) {
+    std::vector<Channel> channels;
+    ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+
+    std::vector<EnergyMeasurement> measurements;
+    ASSERT_TRUE(powerstats->readEnergyMeter({}, &measurements).isOk());
+
+    testMatching(channels, &Channel::id, measurements, &EnergyMeasurement::id);
+}
+
+// Reading energy must must return results for each selected channel
+TEST_P(PowerStatsAidl, TestGetSelectedEnergyMeasurements) {
+    std::vector<Channel> channels;
+    ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+    if (channels.empty()) {
+        return;
+    }
+
+    std::vector<Channel> selectedChannels = getRandomSubset(channels);
+    std::vector<int32_t> selectedIds;
+    for (auto const& channel : selectedChannels) {
+        selectedIds.push_back(channel.id);
+    }
+
+    std::vector<EnergyMeasurement> selectedMeasurements;
+    ASSERT_TRUE(powerstats->readEnergyMeter(selectedIds, &selectedMeasurements).isOk());
+
+    testMatching(selectedChannels, &Channel::id, selectedMeasurements, &EnergyMeasurement::id);
+}
+
+// Energy consumer info must return a valid status
+TEST_P(PowerStatsAidl, TestGetEnergyConsumerInfo) {
+    std::vector<EnergyConsumer> consumers;
+    ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+}
+
+// Each energy consumer must have a unique id
+TEST_P(PowerStatsAidl, TestGetEnergyConsumerUniqueId) {
+    std::vector<EnergyConsumer> consumers;
+    ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+    testUnique(consumers, &EnergyConsumer::id);
+}
+
+// Each energy consumer must have a valid name
+TEST_P(PowerStatsAidl, ValidateEnergyConsumerNames) {
+    std::vector<EnergyConsumer> consumers;
+    ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+    for (auto consumer : consumers) {
+        testNameValid(consumer.name);
+    }
+}
+
+// Each energy consumer must have a unique name
+TEST_P(PowerStatsAidl, ValidateEnergyConsumerUniqueNames) {
+    std::vector<EnergyConsumer> consumers;
+    ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+    testUnique(consumers, &EnergyConsumer::name);
+}
+
+// Energy consumers of the same type must have ordinals that are 0,1,2,..., N - 1
+TEST_P(PowerStatsAidl, ValidateEnergyConsumerOrdinals) {
+    std::vector<EnergyConsumer> consumers;
+    ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+    std::unordered_map<EnergyConsumerType, std::set<int32_t>> ordinalMap;
+
+    // Ordinals must be unique for each type
+    for (auto consumer : consumers) {
+        EXPECT_TRUE(ordinalMap[consumer.type].insert(consumer.ordinal).second);
+    }
+
+    // Min ordinal must be 0, max ordinal must be N - 1
+    for (const auto& [unused, ordinals] : ordinalMap) {
+        EXPECT_EQ(0, *std::min_element(ordinals.begin(), ordinals.end()));
+        EXPECT_EQ(ordinals.size() - 1, *std::max_element(ordinals.begin(), ordinals.end()));
+    }
+}
+
+// Energy consumed must return a valid status
+TEST_P(PowerStatsAidl, TestGetEnergyConsumed) {
+    std::vector<EnergyConsumerResult> results;
+    ASSERT_TRUE(powerstats->getEnergyConsumed({}, &results).isOk());
+}
+
+// Energy consumed must return data for all energy consumers
+TEST_P(PowerStatsAidl, TestGetAllEnergyConsumed) {
+    std::vector<EnergyConsumer> consumers;
+    ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+    std::vector<EnergyConsumerResult> results;
+    ASSERT_TRUE(powerstats->getEnergyConsumed({}, &results).isOk());
+
+    testMatching(consumers, &EnergyConsumer::id, results, &EnergyConsumerResult::id);
+}
+
+// Energy consumed must return data for each selected energy consumer
+TEST_P(PowerStatsAidl, TestGetSelectedEnergyConsumed) {
+    std::vector<EnergyConsumer> consumers;
+    ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+    if (consumers.empty()) {
+        return;
+    }
+
+    std::vector<EnergyConsumer> selectedConsumers = getRandomSubset(consumers);
+    std::vector<int32_t> selectedIds;
+    for (auto const& consumer : selectedConsumers) {
+        selectedIds.push_back(consumer.id);
+    }
+
+    std::vector<EnergyConsumerResult> selectedResults;
+    ASSERT_TRUE(powerstats->getEnergyConsumed(selectedIds, &selectedResults).isOk());
+
+    testMatching(selectedConsumers, &EnergyConsumer::id, selectedResults,
+                 &EnergyConsumerResult::id);
+}
+
+// Energy consumed attribution uids must be unique for a given energy consumer
+TEST_P(PowerStatsAidl, ValidateEnergyConsumerAttributionUniqueUids) {
+    std::vector<EnergyConsumerResult> results;
+    ASSERT_TRUE(powerstats->getEnergyConsumed({}, &results).isOk());
+
+    for (auto result : results) {
+        testUnique(result.attribution, &EnergyConsumerAttribution::uid);
+    }
+}
+
+// Energy consumed total energy >= sum total of uid-attributed energy
+TEST_P(PowerStatsAidl, TestGetEnergyConsumedAttributedEnergy) {
+    std::vector<EnergyConsumerResult> results;
+    ASSERT_TRUE(powerstats->getEnergyConsumed({}, &results).isOk());
+
+    for (auto result : results) {
+        int64_t totalAttributedEnergyUWs = 0;
+        for (auto attribution : result.attribution) {
+            totalAttributedEnergyUWs += attribution.energyUWs;
+        }
+        EXPECT_TRUE(result.energyUWs >= totalAttributedEnergyUWs);
+    }
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PowerStatsAidl);
+INSTANTIATE_TEST_SUITE_P(
+        PowerStats, PowerStatsAidl,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IPowerStats::descriptor)),
+        android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/radio/1.0/vts/functional/vts_test_util.h b/radio/1.0/vts/functional/vts_test_util.h
index 1625f11..218e823 100644
--- a/radio/1.0/vts/functional/vts_test_util.h
+++ b/radio/1.0/vts/functional/vts_test_util.h
@@ -35,6 +35,12 @@
 
 static constexpr const char* FEATURE_VOICE_CALL = "android.software.connectionservice";
 
+static constexpr const char* FEATURE_TELEPHONY = "android.hardware.telephony";
+
+static constexpr const char* FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+
+static constexpr const char* FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+
 /*
  * Generate random serial number for radio test
  */
diff --git a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
index 1b254a1..b0b984c 100644
--- a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
@@ -34,6 +34,10 @@
     if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
         ALOGI("Skipping emergencyDial because voice call is not supported in device");
         return;
+    } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+               !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+        ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+        return;
     } else {
         ALOGI("Running emergencyDial because voice call is supported in device");
     }
@@ -86,6 +90,10 @@
     if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
         ALOGI("Skipping emergencyDial because voice call is not supported in device");
         return;
+    } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+               !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+        ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+        return;
     } else {
         ALOGI("Running emergencyDial because voice call is supported in device");
     }
@@ -138,6 +146,10 @@
     if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
         ALOGI("Skipping emergencyDial because voice call is not supported in device");
         return;
+    } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+               !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+        ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+        return;
     } else {
         ALOGI("Running emergencyDial because voice call is supported in device");
     }
diff --git a/radio/1.5/vts/functional/radio_hidl_hal_api.cpp b/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
index 7166654..0b49b36 100644
--- a/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
@@ -236,7 +236,12 @@
 
     ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRP, rspInfo.error = %s\n",
           toString(radioRsp_v1_5->rspInfo.error).c_str());
-    ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+    // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+    // setSignalStrengthReportingCriteria_1_5()
+    ASSERT_TRUE(
+            CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+                             {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
 }
 
 /*
@@ -261,7 +266,12 @@
 
     ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRQ, rspInfo.error = %s\n",
           toString(radioRsp_v1_5->rspInfo.error).c_str());
-    ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+    // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+    // setSignalStrengthReportingCriteria_1_5()
+    ASSERT_TRUE(
+            CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+                             {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
 }
 
 /*
@@ -307,7 +317,12 @@
 
     ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSSINR, rspInfo.error = %s\n",
           toString(radioRsp_v1_5->rspInfo.error).c_str());
-    ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+    // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+    // setSignalStrengthReportingCriteria_1_5()
+    ASSERT_TRUE(
+            CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+                             {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
 }
 
 /*
diff --git a/radio/1.6/IRadio.hal b/radio/1.6/IRadio.hal
index 3dc80b9..c83de29 100644
--- a/radio/1.6/IRadio.hal
+++ b/radio/1.6/IRadio.hal
@@ -18,9 +18,12 @@
 
 
 import @1.0::CdmaSmsMessage;
+import @1.0::Dial;
 import @1.0::GsmSmsMessage;
 import @1.1::CardPowerState;
 import @1.2::DataRequestReason;
+import @1.4::EmergencyCallRouting;
+import @1.4::EmergencyServiceCategory;
 import @1.4::RadioAccessFamily;
 import @1.5::IRadio;
 import @1.5::AccessNetwork;
@@ -117,6 +120,9 @@
      * @param pduSessionId The pdu session id to be used for this data call.  A value of 0 means
      *     no pdu session id was attached to this call.
      *     Reference: 3GPP TS 24.007 section 11.2.3.1b
+     * @param sliceInfo SliceInfo to be used for the data connection when a handover occurs from
+     *     EPDG to 5G.  It is valid only when accessNetwork is AccessNetwork:NGRAN.  If the slice
+     *     passed from EPDG is rejected, then the data failure cause must be DataCallFailCause:SLICE_REJECTED.
      *
      * Response function is IRadioResponse.setupDataCallResponse_1_6()
      *
@@ -125,7 +131,7 @@
     oneway setupDataCall_1_6(int32_t serial, AccessNetwork accessNetwork,
             DataProfileInfo dataProfileInfo, bool roamingAllowed,
             DataRequestReason reason, vec<LinkAddress> addresses, vec<string> dnses,
-            int32_t pduSessionId);
+            int32_t pduSessionId, OptionalSliceInfo sliceInfo);
 
     /**
      * Send an SMS message
@@ -324,6 +330,9 @@
      * Instruct the radio to *only* accept the types of network provided. This
      * is stronger than setPreferredNetworkType which is a suggestion.
      *
+     * In case of an emergency call, the modem is authorized to bypass this
+     * restriction.
+     *
      * @param serial Serial number of request.
      * @param networkTypeBitmap a 32-bit bearer bitmap of RadioAccessFamily
      *
@@ -372,6 +381,64 @@
             int64_t completionDurationMillis);
 
     /**
+     * Initiate emergency voice call, with zero or more emergency service category(s), zero or
+     * more emergency Uniform Resource Names (URN), and routing information for handling the call.
+     * Android uses this request to make its emergency call instead of using @1.0::IRadio.dial
+     * if the 'address' in the 'dialInfo' field is identified as an emergency number by Android.
+     *
+     * In multi-sim scenario, if the emergency number is from a specific subscription, this radio
+     * request can still be sent out on the other subscription as long as routing is set to
+     * @1.4::EmergencyNumberRouting#EMERGENCY. This radio request will not be sent on an inactive
+     * (PIN/PUK locked) subscription unless both subscriptions are PIN/PUK locked. In this case,
+     * the request will be sent on the primary subscription.
+     *
+     * Some countries or carriers require some emergency numbers that must be handled with normal
+     * call routing if possible or emergency routing. 1) if the 'routing' field is specified as
+     * @1.4::EmergencyNumberRouting#NORMAL, the implementation must try the full radio service to
+     * use normal call routing to handle the call; if service cannot support normal routing, the
+     * implementation must use emergency routing to handle the call. 2) if 'routing' is specified
+     * as @1.4::EmergencyNumberRouting#EMERGENCY, the implementation must use emergency routing to
+     * handle the call. 3) if 'routing' is specified as @1.4::EmergencyNumberRouting#UNKNOWN,
+     * Android does not know how to handle the call.
+     *
+     * If the dialed emergency number does not have a specified emergency service category, the
+     * 'categories' field is set to @1.4::EmergencyServiceCategory#UNSPECIFIED; if the dialed
+     * emergency number does not have specified emergency Uniform Resource Names, the 'urns' field
+     * is set to an empty list. If the underlying technology used to request emergency services
+     * does not support the emergency service category or emergency uniform resource names, the
+     * field 'categories' or 'urns' may be ignored.
+     *
+     * In the scenarios that the 'address' in the 'dialInfo' field has other functions besides the
+     * emergency number function, if the 'hasKnownUserIntentEmergency' field is true, the user's
+     * intent for this dial request is emergency call, and the modem must treat this as an actual
+     * emergency dial; if the 'hasKnownUserIntentEmergency' field is false, Android does not know
+     * user's intent for this call.
+     *
+     * If 'isTesting' is true, this request is for testing purpose, and must not be sent to a real
+     * emergency service; otherwise it's for a real emergency call request.
+     *
+     * Reference: 3gpp 22.101, Section 10 - Emergency Calls;
+     *            3gpp 23.167, Section 6 - Functional description;
+     *            3gpp 24.503, Section 5.1.6.8.1 - General;
+     *            RFC 5031
+     *
+     * @param serial Serial number of request.
+     * @param dialInfo the same @1.0::Dial information used by @1.0::IRadio.dial.
+     * @param categories bitfield<@1.4::EmergencyServiceCategory> the Emergency Service Category(s)
+     *     of the call.
+     * @param urns the emergency Uniform Resource Names (URN)
+     * @param routing @1.4::EmergencyCallRouting the emergency call routing information.
+     * @param hasKnownUserIntentEmergency Flag indicating if user's intent for the emergency call
+     *     is known.
+     * @param isTesting Flag indicating if this request is for testing purpose.
+     *
+     * Response function is IRadioResponse.emergencyDialResponse()
+     */
+    oneway emergencyDial_1_6(int32_t serial, Dial dialInfo,
+            bitfield<EmergencyServiceCategory> categories, vec<string> urns,
+            EmergencyCallRouting routing, bool hasKnownUserIntentEmergency, bool isTesting);
+
+    /**
      * Get which bands the modem's background scan is acting on.
      *
      * @param serial Serial number of request.
@@ -386,7 +453,7 @@
      * cell information isn't known then the appropriate unknown value will be returned.
      * This does not cause or change the rate of unsolicited cellInfoList().
      *
-     * This is identitcal to getCellInfoList in V1.0, but it requests updated version of CellInfo.
+     * This is identical to getCellInfoList in V1.0, but it requests updated version of CellInfo.
      *
      * @param serial Serial number of request.
      *
diff --git a/radio/1.6/IRadioIndication.hal b/radio/1.6/IRadioIndication.hal
index 1b56d40..a53d7c1 100644
--- a/radio/1.6/IRadioIndication.hal
+++ b/radio/1.6/IRadioIndication.hal
@@ -23,6 +23,7 @@
 import @1.6::NetworkScanResult;
 import @1.6::SignalStrength;
 import @1.6::SetupDataCallResult;
+import @1.6::PhysicalChannelConfig;
 
 /**
  * Interface declaring unsolicited radio indications.
@@ -101,4 +102,15 @@
      * CellInfo.
      */
     oneway networkScanResult_1_6(RadioIndicationType type, NetworkScanResult result);
+
+    /**
+     * Indicates physical channel configurations.
+     *
+     * An empty configs list indicates that the radio is in idle mode.
+     *
+     * @param type Type of radio indication
+     * @param configs Vector of PhysicalChannelConfigs
+     */
+    oneway currentPhysicalChannelConfigs_1_6(RadioIndicationType type,
+            vec<PhysicalChannelConfig> configs);
 };
diff --git a/radio/1.6/IRadioResponse.hal b/radio/1.6/IRadioResponse.hal
index 6ac86c3..a1ad207 100644
--- a/radio/1.6/IRadioResponse.hal
+++ b/radio/1.6/IRadioResponse.hal
@@ -19,6 +19,7 @@
 import @1.0::SendSmsResult;
 import @1.4::RadioAccessFamily;
 import @1.5::IRadioResponse;
+import @1.5::RadioAccessSpecifier;
 import @1.6::Call;
 import @1.6::CellInfo;
 import @1.6::RegStateResult;
@@ -341,6 +342,7 @@
 
     /**
      * @param info Response info struct containing response type, serial no. and error
+     * @param specifiers List of RadioAccessSpecifiers that are scanned.
      *
      * Valid errors returned:
      *   RadioError:NONE
@@ -348,7 +350,8 @@
      *   RadioError:INTERNAL_ERR
      *   RadioError:INVALID_ARGUMENTS
      */
-    oneway getSystemSelectionChannelsResponse(RadioResponseInfo info);
+    oneway getSystemSelectionChannelsResponse(
+            RadioResponseInfo info, vec<RadioAccessSpecifier> specifiers);
 
     /**
      * This is identical to getCellInfoListResponse_1_5 but uses an updated version of CellInfo.
diff --git a/radio/1.6/types.hal b/radio/1.6/types.hal
index f4dc0bd..8560b3d 100644
--- a/radio/1.6/types.hal
+++ b/radio/1.6/types.hal
@@ -23,7 +23,10 @@
 import @1.0::RadioError;
 import @1.0::RadioResponseType;
 import @1.0::RegState;
+import @1.1::EutranBands;
+import @1.1::GeranBands;
 import @1.1::ScanStatus;
+import @1.1::UtranBands;
 import @1.2::Call;
 import @1.2::CellInfoCdma;
 import @1.2::CellConnectionStatus;
@@ -41,6 +44,7 @@
 import @1.5::CellInfoWcdma;
 import @1.5::CellInfoTdscdma;
 import @1.5::LinkAddress;
+import @1.5::NgranBands;
 import @1.5::RegStateResult.AccessTechnologySpecificInfo.Cdma2000RegistrationInfo;
 import @1.5::RegStateResult.AccessTechnologySpecificInfo.EutranRegistrationInfo;
 import @1.5::RegistrationFailCause;
@@ -281,7 +285,7 @@
      * suggestion. 0 indicates retry should be performed immediately. 0x7fffffffffffffff indicates
      * the device should not retry data setup anymore.
      */
-    uint64_t suggestedRetryTime;
+    int64_t suggestedRetryTime;
 
     /** Context ID, uniquely identifies this data connection. */
     int32_t cid;
@@ -347,11 +351,17 @@
 
     /**
      * The allocated pdu session id for this data call.
-     * A value of -1 means no pdu session id was attached to this call.
+     * A value of 0 means no pdu session id was attached to this call.
      *
      * Reference: 3GPP TS 24.007 section 11.2.3.1b
      */
     int32_t pduSessionId;
+
+    /**
+     * Slice used for this data call. It is valid only when this data call is on
+     * AccessNetwork:NGRAN.
+     */
+    OptionalSliceInfo sliceInfo;
 };
 
 /**
@@ -722,6 +732,14 @@
              */
             NrVopsInfo nrVopsInfo;
         } ngranInfo;
+
+        struct GeranRegistrationInfo {
+            /**
+             * True if the dual transfer mode is supported.
+             * Refer to 3GPP TS 44.108 section 3.4.25.3
+             */
+            bool dtmSupported;
+        } geranInfo;
     } accessTechnologySpecificInfo;
 };
 
@@ -733,3 +751,147 @@
      */
     string forwardedNumber;
 };
+
+struct PhysicalChannelConfig {
+    /** Connection status for cell. Valid values are PRIMARY_SERVING and SECONDARY_SERVING */
+    CellConnectionStatus status;
+
+    /** The radio technology for this physical channel */
+    RadioTechnology rat;
+
+    /** Downlink Absolute Radio Frequency Channel Number */
+    int32_t downlinkChannelNumber;
+
+    /** Uplink Absolute Radio Frequency Channel Number */
+    int32_t uplinkChannelNumber;
+
+    /** Downlink cell bandwidth, in kHz */
+    int32_t cellBandwidthDownlink;
+
+    /** Uplink cell bandwidth, in kHz */
+    int32_t cellBandwidthUplink;
+
+    /**
+     * A list of data calls mapped to this physical channel. The context id must match the cid of
+     * @1.5::SetupDataCallResult. An empty list means the physical channel has no data call mapped
+     * to it.
+     */
+    vec<int32_t> contextIds;
+
+    /**
+     * The physical cell identifier for this cell.
+     *
+     * In UTRAN, this value is primary scrambling code. The range is [0, 511].
+     * Reference: 3GPP TS 25.213 section 5.2.2.
+     *
+     * In EUTRAN, this value is physical layer cell identity. The range is [0, 503].
+     * Reference: 3GPP TS 36.211 section 6.11.
+     *
+     * In 5G RAN, this value is physical layer cell identity. The range is [0, 1007].
+     * Reference: 3GPP TS 38.211 section 7.4.2.1.
+     */
+    uint32_t physicalCellId;
+
+    /**
+     * The frequency band to scan.
+     */
+    safe_union Band {
+        /** Valid only if radioAccessNetwork = GERAN. */
+        GeranBands geranBand;
+        /** Valid only if radioAccessNetwork = UTRAN. */
+        UtranBands utranBand;
+        /** Valid only if radioAccessNetwork = EUTRAN. */
+        EutranBands eutranBand;
+        /** Valid only if radioAccessNetwork = NGRAN. */
+        NgranBands ngranBand;
+    } band;
+};
+
+/**
+ * Extended from @1.5 NgranBands
+ * IRadio 1.6 supports NGRAN bands up to V16.5.0
+ */
+enum NgranBands  : @1.5::NgranBands {
+    /** 3GPP TS 38.101-1, Table 5.2-1: FR1 bands */
+    BAND_26 = 26,
+    BAND_46 = 46,
+    BAND_53 = 53,
+    BAND_96 = 96,
+};
+
+/**
+ * This safe_union represents an optional slice info
+ */
+safe_union OptionalSliceInfo {
+    Monostate noinit;
+    SliceInfo value;
+};
+
+/**
+ * This struct represents a S-NSSAI as defined in 3GPP TS 24.501.
+ */
+struct SliceInfo {
+    /**
+     * The type of service provided by the slice.
+     *
+     * see: 3GPP TS 24.501 Section 9.11.2.8.
+     */
+    SliceServiceType sst;
+
+    /**
+     * Slice differentiator is the identifier of a slice that has
+     * SliceServiceType as SST. A value of -1 indicates that there is
+     * no corresponding SliceInfo of the HPLMN.
+     *
+     * see: 3GPP TS 24.501 Section 9.11.2.8.
+     */
+    int32_t sliceDifferentiator;
+
+    /**
+     * This SST corresponds to a SliceInfo (S-NSSAI) of the HPLMN; the SST is
+     * mapped to this value.
+     *
+     * see: 3GPP TS 24.501 Section 9.11.2.8.
+     */
+    SliceServiceType mappedHplmnSst;
+
+    /**
+     * Present only if both sliceDifferentiator and mappedHplmnSst are also
+     * present. This SD corresponds to a SliceInfo (S-NSSAI) of the HPLMN;
+     * sliceDifferentiator is mapped to this value. A value of -1 indicates that
+     * there is no corresponding SliceInfo of the HPLMN.
+     *
+     * see: 3GPP TS 24.501 Section 9.11.2.8.
+     */
+    int32_t mappedHplmnSD;
+};
+
+/**
+ * Slice/Service Type as defined in 3GPP TS 23.501.
+ */
+enum SliceServiceType : uint8_t {
+    /* Not specified */
+    NONE = 0,
+
+    /* Slice suitable for the handling of 5G enhanced Mobile Broadband */
+    EMBB = 1,
+
+    /**
+     * Slice suitable for the handling of ultra-reliable low latency
+     * communications
+     */
+    URLLC = 2,
+
+    /* Slice suitable for the handling of massive IoT */
+    MIOT = 3,
+};
+
+/**
+ * Expose more setup data call failures.
+ */
+enum DataCallFailCause : @1.4::DataCallFailCause {
+    /**
+     * Data call fail due to the slice not being allowed for the data call.
+     */
+    SLICE_REJECTED = 0x8CC,
+};
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 47babed..44900b8 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
@@ -56,8 +56,12 @@
     ::android::hardware::radio::V1_2::DataRequestReason reason =
             ::android::hardware::radio::V1_2::DataRequestReason::NORMAL;
 
-    Return<void> res = radio_v1_6->setupDataCall_1_6(serial, accessNetwork, dataProfileInfo,
-                                                     roamingAllowed, reason, addresses, dnses, -1);
+    ::android::hardware::radio::V1_6::OptionalSliceInfo optionalSliceInfo;
+    memset(&optionalSliceInfo, 0, sizeof(optionalSliceInfo));
+
+    Return<void> res =
+            radio_v1_6->setupDataCall_1_6(serial, accessNetwork, dataProfileInfo, roamingAllowed,
+                                          reason, addresses, dnses, -1, optionalSliceInfo);
     ASSERT_OK(res);
 
     EXPECT_EQ(std::cv_status::no_timeout, wait());
@@ -408,6 +412,167 @@
 }
 
 /*
+ * Test IRadio.emergencyDial() for the response returned.
+ */
+TEST_P(RadioHidlTest_v1_6, emergencyDial_1_6) {
+    if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
+        ALOGI("Skipping emergencyDial because voice call is not supported in device");
+        return;
+    } else {
+        ALOGI("Running emergencyDial because voice call is supported in device");
+    }
+
+    serial = GetRandomSerialNumber();
+
+    ::android::hardware::radio::V1_0::Dial dialInfo;
+    dialInfo.address = hidl_string("911");
+    int categories = static_cast<int>(
+            ::android::hardware::radio::V1_4::EmergencyServiceCategory::UNSPECIFIED);
+    std::vector<hidl_string> urns = {""};
+    ::android::hardware::radio::V1_4::EmergencyCallRouting routing =
+            ::android::hardware::radio::V1_4::EmergencyCallRouting::UNKNOWN;
+
+    Return<void> res =
+            radio_v1_6->emergencyDial_1_6(serial, dialInfo, categories, urns, routing, true, true);
+    ASSERT_OK(res);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo_v1_0.type);
+    EXPECT_EQ(serial, radioRsp_v1_6->rspInfo_v1_0.serial);
+
+    ALOGI("emergencyDial, rspInfo_v1_0.error = %s\n",
+          toString(radioRsp_v1_6->rspInfo_v1_0.error).c_str());
+
+    ::android::hardware::radio::V1_0::RadioError rspEmergencyDial =
+            radioRsp_v1_6->rspInfo_v1_0.error;
+    // In DSDS or TSTS, we only check the result if the current slot is IN_SERVICE
+    // or Emergency_Only.
+    if (isDsDsEnabled() || isTsTsEnabled()) {
+        serial = GetRandomSerialNumber();
+        radio_v1_6->getVoiceRegistrationState(serial);
+        EXPECT_EQ(std::cv_status::no_timeout, wait());
+        if (isVoiceEmergencyOnly(radioRsp_v1_6->voiceRegResp.regState) ||
+            isVoiceInService(radioRsp_v1_6->voiceRegResp.regState)) {
+            EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+        }
+    } else {
+        EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+    }
+
+    // Give some time for modem to establish the emergency call channel.
+    sleep(MODEM_EMERGENCY_CALL_ESTABLISH_TIME);
+
+    // Disconnect all the potential established calls to prevent them affecting other tests.
+    clearPotentialEstablishedCalls();
+}
+
+/*
+ * Test IRadio.emergencyDial() with specified service and its response returned.
+ */
+TEST_P(RadioHidlTest_v1_6, emergencyDial_1_6_withServices) {
+    if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
+        ALOGI("Skipping emergencyDial because voice call is not supported in device");
+        return;
+    } else {
+        ALOGI("Running emergencyDial because voice call is supported in device");
+    }
+
+    serial = GetRandomSerialNumber();
+
+    ::android::hardware::radio::V1_0::Dial dialInfo;
+    dialInfo.address = hidl_string("911");
+    int categories =
+            static_cast<int>(::android::hardware::radio::V1_4::EmergencyServiceCategory::AMBULANCE);
+    std::vector<hidl_string> urns = {"urn:service:sos.ambulance"};
+    ::android::hardware::radio::V1_4::EmergencyCallRouting routing =
+            ::android::hardware::radio::V1_4::EmergencyCallRouting::UNKNOWN;
+
+    Return<void> res =
+            radio_v1_6->emergencyDial_1_6(serial, dialInfo, categories, urns, routing, true, true);
+    ASSERT_OK(res);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo_v1_0.type);
+    EXPECT_EQ(serial, radioRsp_v1_6->rspInfo_v1_0.serial);
+
+    ALOGI("emergencyDial_withServices, rspInfo_v1_0.error = %s\n",
+          toString(radioRsp_v1_6->rspInfo_v1_0.error).c_str());
+    ::android::hardware::radio::V1_0::RadioError rspEmergencyDial =
+            radioRsp_v1_6->rspInfo_v1_0.error;
+
+    // In DSDS or TSTS, we only check the result if the current slot is IN_SERVICE
+    // or Emergency_Only.
+    if (isDsDsEnabled() || isTsTsEnabled()) {
+        serial = GetRandomSerialNumber();
+        radio_v1_6->getVoiceRegistrationState(serial);
+        EXPECT_EQ(std::cv_status::no_timeout, wait());
+        if (isVoiceEmergencyOnly(radioRsp_v1_6->voiceRegResp.regState) ||
+            isVoiceInService(radioRsp_v1_6->voiceRegResp.regState)) {
+            EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+        }
+    } else {
+        EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+    }
+    // Give some time for modem to establish the emergency call channel.
+    sleep(MODEM_EMERGENCY_CALL_ESTABLISH_TIME);
+
+    // Disconnect all the potential established calls to prevent them affecting other tests.
+    clearPotentialEstablishedCalls();
+}
+
+/*
+ * Test IRadio.emergencyDial() with known emergency call routing and its response returned.
+ */
+TEST_P(RadioHidlTest_v1_6, emergencyDial_1_6_withEmergencyRouting) {
+    if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
+        ALOGI("Skipping emergencyDial because voice call is not supported in device");
+        return;
+    } else {
+        ALOGI("Running emergencyDial because voice call is supported in device");
+    }
+
+    serial = GetRandomSerialNumber();
+
+    ::android::hardware::radio::V1_0::Dial dialInfo;
+    dialInfo.address = hidl_string("911");
+    int categories = static_cast<int>(
+            ::android::hardware::radio::V1_4::EmergencyServiceCategory::UNSPECIFIED);
+    std::vector<hidl_string> urns = {""};
+    ::android::hardware::radio::V1_4::EmergencyCallRouting routing =
+            ::android::hardware::radio::V1_4::EmergencyCallRouting::EMERGENCY;
+
+    Return<void> res =
+            radio_v1_6->emergencyDial_1_6(serial, dialInfo, categories, urns, routing, true, true);
+    ASSERT_OK(res);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo_v1_0.type);
+    EXPECT_EQ(serial, radioRsp_v1_6->rspInfo_v1_0.serial);
+
+    ALOGI("emergencyDial_withEmergencyRouting, rspInfo_v1_0.error = %s\n",
+          toString(radioRsp_v1_6->rspInfo_v1_0.error).c_str());
+    ::android::hardware::radio::V1_0::RadioError rspEmergencyDial =
+            radioRsp_v1_6->rspInfo_v1_0.error;
+
+    // In DSDS or TSTS, we only check the result if the current slot is IN_SERVICE
+    // or Emergency_Only.
+    if (isDsDsEnabled() || isTsTsEnabled()) {
+        serial = GetRandomSerialNumber();
+        radio_v1_6->getVoiceRegistrationState(serial);
+        EXPECT_EQ(std::cv_status::no_timeout, wait());
+        if (isVoiceEmergencyOnly(radioRsp_v1_6->voiceRegResp.regState) ||
+            isVoiceInService(radioRsp_v1_6->voiceRegResp.regState)) {
+            EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+        }
+    } else {
+        EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+    }
+
+    // Give some time for modem to establish the emergency call channel.
+    sleep(MODEM_EMERGENCY_CALL_ESTABLISH_TIME);
+
+    // Disconnect all the potential established calls to prevent them affecting other tests.
+    clearPotentialEstablishedCalls();
+}
+
+/*
  * Test IRadio.getCurrentCalls_1_6() for the response returned.
  */
 TEST_P(RadioHidlTest_v1_6, getCurrentCalls_1_6) {
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_test.cpp b/radio/1.6/vts/functional/radio_hidl_hal_test.cpp
index 79c3cde..59f7682 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_test.cpp
+++ b/radio/1.6/vts/functional/radio_hidl_hal_test.cpp
@@ -74,6 +74,29 @@
     return status;
 }
 
+void RadioHidlTest_v1_6::clearPotentialEstablishedCalls() {
+    // Get the current call Id to hangup the established emergency call.
+    serial = GetRandomSerialNumber();
+    radio_v1_6->getCurrentCalls_1_6(serial);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+
+    // Hang up to disconnect the established call channels.
+    for (const ::android::hardware::radio::V1_6::Call& call : radioRsp_v1_6->currentCalls) {
+        serial = GetRandomSerialNumber();
+        radio_v1_6->hangup(serial, call.base.base.index);
+        ALOGI("Hang up to disconnect the established call channel: %d", call.base.base.index);
+        EXPECT_EQ(std::cv_status::no_timeout, wait());
+        // Give some time for modem to disconnect the established call channel.
+        sleep(MODEM_EMERGENCY_CALL_DISCONNECT_TIME);
+    }
+
+    // Verify there are no more current calls.
+    serial = GetRandomSerialNumber();
+    radio_v1_6->getCurrentCalls_1_6(serial);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(0, radioRsp_v1_6->currentCalls.size());
+}
+
 void RadioHidlTest_v1_6::updateSimCardStatus() {
     serial = GetRandomSerialNumber();
     radio_v1_6->getIccCardStatus(serial);
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
index fbcd7a9..b94cd96 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
+++ b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
@@ -70,7 +70,8 @@
     ::android::hardware::radio::V1_6::RadioResponseInfo rspInfo;
 
     // Call
-    hidl_vec<::android::hardware::radio::V1_2::Call> currentCalls;
+    hidl_vec<::android::hardware::radio::V1_6::Call> currentCalls;
+    ::android::hardware::radio::V1_2::VoiceRegStateResult voiceRegResp;
 
     // Sms
     SendSmsResult sendSmsResult;
@@ -803,7 +804,8 @@
             const ::android::hardware::radio::V1_6::RadioResponseInfo& info);
 
     Return<void> getSystemSelectionChannelsResponse(
-            const ::android::hardware::radio::V1_6::RadioResponseInfo& info);
+            const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
+            const hidl_vec<::android::hardware::radio::V1_5::RadioAccessSpecifier>& specifier);
 
     Return<void> getSignalStrengthResponse_1_6(
             const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
@@ -857,6 +859,11 @@
             const ::android::hardware::hidl_vec<::android::hardware::radio::V1_6::CellInfo>&
                     records);
 
+    Return<void> currentPhysicalChannelConfigs_1_6(
+            RadioIndicationType type,
+            const ::android::hardware::hidl_vec<
+                    ::android::hardware::radio::V1_6::PhysicalChannelConfig>& configs);
+
     /* 1.5 Api */
     Return<void> uiccApplicationsEnablementChanged(RadioIndicationType type, bool enabled);
 
diff --git a/radio/1.6/vts/functional/radio_indication.cpp b/radio/1.6/vts/functional/radio_indication.cpp
index bfc54c0..e7a9680 100644
--- a/radio/1.6/vts/functional/radio_indication.cpp
+++ b/radio/1.6/vts/functional/radio_indication.cpp
@@ -30,6 +30,13 @@
     return Void();
 }
 
+Return<void> RadioIndication_v1_6::currentPhysicalChannelConfigs_1_6(
+        RadioIndicationType /*type*/,
+        const ::android::hardware::hidl_vec<
+                ::android::hardware::radio::V1_6::PhysicalChannelConfig>& /*configs*/) {
+    return Void();
+}
+
 /* 1.5 Apis */
 Return<void> RadioIndication_v1_6::uiccApplicationsEnablementChanged(RadioIndicationType /*type*/,
                                                                      bool /*enabled*/) {
diff --git a/radio/1.6/vts/functional/radio_response.cpp b/radio/1.6/vts/functional/radio_response.cpp
index 7c5cf6d..feb9e03 100644
--- a/radio/1.6/vts/functional/radio_response.cpp
+++ b/radio/1.6/vts/functional/radio_response.cpp
@@ -816,8 +816,11 @@
 }
 
 Return<void> RadioResponse_v1_6::getVoiceRegistrationStateResponse_1_2(
-        const ::android::hardware::radio::V1_0::RadioResponseInfo& /*info*/,
-        const ::android::hardware::radio::V1_2::VoiceRegStateResult& /*voiceRegResponse*/) {
+        const ::android::hardware::radio::V1_0::RadioResponseInfo& info,
+        const ::android::hardware::radio::V1_2::VoiceRegStateResult& voiceRegResponse) {
+    rspInfo_v1_0 = info;
+    voiceRegResp = voiceRegResponse;
+    parent_v1_6.notify(info.serial);
     return Void();
 }
 
@@ -1186,7 +1189,8 @@
 }
 
 Return<void> RadioResponse_v1_6::getSystemSelectionChannelsResponse(
-        const ::android::hardware::radio::V1_6::RadioResponseInfo& info) {
+        const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
+        const hidl_vec<::android::hardware::radio::V1_5::RadioAccessSpecifier>& /*specifier*/) {
     rspInfo = info;
     parent_v1_6.notify(info.serial);
     return Void();
@@ -1210,8 +1214,9 @@
 
 Return<void> RadioResponse_v1_6::getCurrentCallsResponse_1_6(
         const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
-        const ::android::hardware::hidl_vec<::android::hardware::radio::V1_6::Call>& /*calls*/) {
+        const ::android::hardware::hidl_vec<::android::hardware::radio::V1_6::Call>& calls) {
     rspInfo = info;
+    currentCalls = calls;
     parent_v1_6.notify(info.serial);
     return Void();
 }
diff --git a/radio/config/1.3/Android.bp b/radio/config/1.3/Android.bp
new file mode 100644
index 0000000..ace0de9
--- /dev/null
+++ b/radio/config/1.3/Android.bp
@@ -0,0 +1,21 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+    name: "android.hardware.radio.config@1.3",
+    root: "android.hardware",
+    srcs: [
+        "types.hal",
+        "IRadioConfig.hal",
+        "IRadioConfigResponse.hal",
+    ],
+    interfaces: [
+        "android.hardware.radio.config@1.0",
+        "android.hardware.radio.config@1.1",
+        "android.hardware.radio.config@1.2",
+        "android.hardware.radio@1.0",
+        "android.hardware.radio@1.6",
+        "android.hidl.base@1.0",
+    ],
+    gen_java: true,
+    system_ext_specific: true,
+}
diff --git a/radio/config/1.3/IRadioConfig.hal b/radio/config/1.3/IRadioConfig.hal
new file mode 100644
index 0000000..83bcf92
--- /dev/null
+++ b/radio/config/1.3/IRadioConfig.hal
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ *
+ *
+ * This interface is used by telephony and telecom to talk to cellular radio for the purpose of
+ * radio configuration, and it is not associated with any specific modem or slot.
+ * All the functions have minimum one parameter:
+ * serial: which corresponds to serial no. of request. Serial numbers must only be memorized for the
+ * duration of a method call. If clients provide colliding serials (including passing the same
+ * serial to different methods), multiple responses (one for each method call) must still be served.
+ */
+
+package android.hardware.radio.config@1.3;
+
+import @1.1::IRadioConfig;
+import IRadioConfigResponse;
+
+interface IRadioConfig extends @1.1::IRadioConfig {
+    /**
+     * Gets the available Radio Hal capabilities on the current device.
+     *
+     * This is called once per device boot up.
+     *
+     * @param serial Serial number of request
+     *
+     * Response callback is
+     * IRadioConfigResponse.getHalDeviceCapabilitiesResponse()
+     */
+    oneway getHalDeviceCapabilities(int32_t serial);
+};
diff --git a/radio/config/1.3/IRadioConfigResponse.hal b/radio/config/1.3/IRadioConfigResponse.hal
new file mode 100644
index 0000000..863754f
--- /dev/null
+++ b/radio/config/1.3/IRadioConfigResponse.hal
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package android.hardware.radio.config@1.3;
+
+import android.hardware.radio@1.6::RadioResponseInfo;
+import @1.2::IRadioConfigResponse;
+import HalDeviceCapabilities;
+
+/**
+ * Interface declaring response functions to solicited radio config requests.
+ */
+interface IRadioConfigResponse extends @1.2::IRadioConfigResponse {
+    /**
+     * @param info Response info struct containing response type, serial no. and error
+     * @param capabilities Capabilities struct containing the capabilities of the
+     * device related to the Radio HAL
+     *
+     * Valid errors returned:
+     *   RadioError:NONE
+     *   RadioError:RADIO_NOT_AVAILABLE
+     *   RadioError:INTERNAL_ERR
+     */
+    oneway getHalDeviceCapabilitiesResponse(RadioResponseInfo info,
+        HalDeviceCapabilities capabilities);
+};
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/radio/config/1.3/types.hal
similarity index 60%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to radio/config/1.3/types.hal
index 127f98e..ba964bf 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/radio/config/1.3/types.hal
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.radio.config@1.3;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
-}
+/**
+ * Contains the device capabilities with respect to the Radio HAL.
+ */
+struct HalDeviceCapabilities {
+  /**
+   * True indicates that the modem is missing features within the current
+   * version of the Radio HAL.
+   */
+  bool modemReducedFeatureSet1;
+};
diff --git a/radio/config/1.3/vts/functional/Android.bp b/radio/config/1.3/vts/functional/Android.bp
new file mode 100644
index 0000000..abd081f
--- /dev/null
+++ b/radio/config/1.3/vts/functional/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2019 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.
+//
+
+cc_test {
+    name: "VtsHalRadioConfigV1_3TargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: [
+        "radio_config_hidl_hal_api.cpp",
+        "radio_config_hidl_hal_test.cpp",
+        "radio_config_response.cpp",
+        "radio_config_indication.cpp",
+        "VtsHalRadioConfigV1_3TargetTest.cpp",
+    ],
+    static_libs: [
+        "RadioVtsTestUtilBase",
+        "android.hardware.radio.config@1.0",
+        "android.hardware.radio.config@1.1",
+        "android.hardware.radio.config@1.2",
+        "android.hardware.radio.config@1.3",
+    ],
+    header_libs: ["radio.util.header@1.0"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp b/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp
new file mode 100644
index 0000000..5772d08
--- /dev/null
+++ b/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp
@@ -0,0 +1,23 @@
+/*
+ * 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 <radio_config_hidl_hal_utils.h>
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(RadioConfigHidlTest);
+INSTANTIATE_TEST_SUITE_P(
+        PerInstance, RadioConfigHidlTest,
+        testing::ValuesIn(android::hardware::getAllHalInstanceNames(IRadioConfig::descriptor)),
+        android::hardware::PrintInstanceNameToString);
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp b/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp
new file mode 100644
index 0000000..8df02dd
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp
@@ -0,0 +1,30 @@
+/*
+ * 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 <radio_config_hidl_hal_utils.h>
+
+#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk())
+
+/*
+ * Test IRadioConfig.getHalDeviceCapabilities()
+ */
+TEST_P(RadioConfigHidlTest, getHalDeviceCapabilities) {
+    const int serial = GetRandomSerialNumber();
+    Return<void> res = radioConfig->getHalDeviceCapabilities(serial);
+    ASSERT_OK(res);
+    ALOGI("getHalDeviceCapabilities, rspInfo.error = %s\n",
+          toString(radioConfigRsp->rspInfo.error).c_str());
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp b/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp
new file mode 100644
index 0000000..de8365a
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 <radio_config_hidl_hal_utils.h>
+
+void RadioConfigHidlTest::SetUp() {
+    radioConfig = IRadioConfig::getService(GetParam());
+    if (radioConfig == NULL) {
+        sleep(60);
+        radioConfig = IRadioConfig::getService(GetParam());
+    }
+    ASSERT_NE(nullptr, radioConfig.get());
+
+    radioConfigRsp = new (std::nothrow) RadioConfigResponse(*this);
+    ASSERT_NE(nullptr, radioConfigRsp.get());
+
+    count_ = 0;
+
+    radioConfig->setResponseFunctions(radioConfigRsp, nullptr);
+}
+
+/*
+ * Notify that the response message is received.
+ */
+void RadioConfigHidlTest::notify(int receivedSerial) {
+    std::unique_lock<std::mutex> lock(mtx_);
+    if (serial == receivedSerial) {
+        count_++;
+        cv_.notify_one();
+    }
+}
+
+/*
+ * Wait till the response message is notified or till TIMEOUT_PERIOD.
+ */
+std::cv_status RadioConfigHidlTest::wait() {
+    std::unique_lock<std::mutex> lock(mtx_);
+
+    std::cv_status status = std::cv_status::no_timeout;
+    auto now = std::chrono::system_clock::now();
+    while (count_ == 0) {
+        status = cv_.wait_until(lock, now + std::chrono::seconds(TIMEOUT_PERIOD));
+        if (status == std::cv_status::timeout) {
+            return status;
+        }
+    }
+    count_--;
+    return status;
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h b/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h
new file mode 100644
index 0000000..439eb70
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h
@@ -0,0 +1,135 @@
+/*
+ * 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 <android-base/logging.h>
+
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+
+#include <android/hardware/radio/config/1.1/IRadioConfig.h>
+#include <android/hardware/radio/config/1.1/types.h>
+#include <android/hardware/radio/config/1.2/IRadioConfigIndication.h>
+#include <android/hardware/radio/config/1.2/IRadioConfigResponse.h>
+#include <android/hardware/radio/config/1.2/types.h>
+#include <android/hardware/radio/config/1.3/IRadioConfig.h>
+#include <android/hardware/radio/config/1.3/IRadioConfigResponse.h>
+#include <android/hardware/radio/config/1.3/types.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+#include <log/log.h>
+
+#include "vts_test_util.h"
+
+using namespace ::android::hardware::radio::config::V1_2;
+
+using ::android::sp;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::radio::config::V1_1::ModemsConfig;
+using ::android::hardware::radio::config::V1_1::PhoneCapability;
+using ::android::hardware::radio::config::V1_2::SimSlotStatus;
+using ::android::hardware::radio::config::V1_3::HalDeviceCapabilities;
+using ::android::hardware::radio::config::V1_3::IRadioConfig;
+using ::android::hardware::radio::V1_0::RadioResponseInfo;
+
+#define TIMEOUT_PERIOD 75
+#define RADIO_SERVICE_NAME "slot1"
+
+class RadioConfigHidlTest;
+
+/* Callback class for radio config response */
+class RadioConfigResponse : public IRadioConfigResponse {
+  protected:
+    RadioConfigHidlTest& parent;
+
+  public:
+    RadioResponseInfo rspInfo;
+    PhoneCapability phoneCap;
+
+    RadioConfigResponse(RadioConfigHidlTest& parent);
+    virtual ~RadioConfigResponse() = default;
+
+    Return<void> getSimSlotsStatusResponse(
+            const RadioResponseInfo& info,
+            const ::android::hardware::hidl_vec<
+                    ::android::hardware::radio::config::V1_0::SimSlotStatus>& slotStatus);
+
+    Return<void> getSimSlotsStatusResponse_1_2(
+            const RadioResponseInfo& info,
+            const ::android::hardware::hidl_vec<SimSlotStatus>& slotStatus);
+
+    Return<void> setSimSlotsMappingResponse(const RadioResponseInfo& info);
+
+    Return<void> getPhoneCapabilityResponse(const RadioResponseInfo& info,
+                                            const PhoneCapability& phoneCapability);
+
+    Return<void> setPreferredDataModemResponse(const RadioResponseInfo& info);
+
+    Return<void> getModemsConfigResponse(const RadioResponseInfo& info,
+                                         const ModemsConfig& mConfig);
+
+    Return<void> setModemsConfigResponse(const RadioResponseInfo& info);
+
+    Return<void> getHalDeviceCapabilitiesResponse(
+            const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
+            const HalDeviceCapabilities& halDeviceCapabilities);
+};
+
+/* Callback class for radio config indication */
+class RadioConfigIndication : public IRadioConfigIndication {
+  protected:
+    RadioConfigHidlTest& parent;
+
+  public:
+    RadioConfigIndication(RadioConfigHidlTest& parent);
+    virtual ~RadioConfigIndication() = default;
+
+    Return<void> simSlotsStatusChanged_1_2(
+            ::android::hardware::radio::V1_0::RadioIndicationType type,
+            const ::android::hardware::hidl_vec<SimSlotStatus>& slotStatus);
+};
+
+// The main test class for Radio config HIDL.
+class RadioConfigHidlTest : public ::testing::TestWithParam<std::string> {
+  protected:
+    std::mutex mtx_;
+    std::condition_variable cv_;
+    int count_;
+
+  public:
+    virtual void SetUp() override;
+
+    /* Used as a mechanism to inform the test about data/event callback */
+    void notify(int receivedSerial);
+
+    /* Test code calls this function to wait for response */
+    std::cv_status wait();
+
+    void updateSimCardStatus();
+
+    /* Serial number for radio request */
+    int serial;
+
+    /* radio config service handle */
+    sp<IRadioConfig> radioConfig;
+
+    /* radio config response handle */
+    sp<RadioConfigResponse> radioConfigRsp;
+};
diff --git a/radio/config/1.3/vts/functional/radio_config_indication.cpp b/radio/config/1.3/vts/functional/radio_config_indication.cpp
new file mode 100644
index 0000000..6fa443c
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_indication.cpp
@@ -0,0 +1,25 @@
+/*
+ * 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 <radio_config_hidl_hal_utils.h>
+
+RadioConfigIndication::RadioConfigIndication(RadioConfigHidlTest& parent) : parent(parent) {}
+
+Return<void> RadioConfigIndication::simSlotsStatusChanged_1_2(
+        ::android::hardware::radio::V1_0::RadioIndicationType /*type*/,
+        const ::android::hardware::hidl_vec<SimSlotStatus>& /*slotStatus*/) {
+    return Void();
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_response.cpp b/radio/config/1.3/vts/functional/radio_config_response.cpp
new file mode 100644
index 0000000..2a8b28b
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_response.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 <radio_config_hidl_hal_utils.h>
+
+// SimSlotStatus slotStatus;
+
+RadioConfigResponse::RadioConfigResponse(RadioConfigHidlTest& parent) : parent(parent) {}
+
+Return<void> RadioConfigResponse::getSimSlotsStatusResponse(
+        const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+        const ::android::hardware::hidl_vec<
+                ::android::hardware::radio::config::V1_0::SimSlotStatus>& /* slotStatus */) {
+    return Void();
+}
+
+Return<void> RadioConfigResponse::getSimSlotsStatusResponse_1_2(
+        const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+        const ::android::hardware::hidl_vec<SimSlotStatus>& /* slotStatus */) {
+    return Void();
+}
+
+Return<void> RadioConfigResponse::setSimSlotsMappingResponse(
+        const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+    return Void();
+}
+
+Return<void> RadioConfigResponse::getPhoneCapabilityResponse(
+        const ::android::hardware::radio::V1_0::RadioResponseInfo& info,
+        const PhoneCapability& phoneCapability) {
+    rspInfo = info;
+    phoneCap = phoneCapability;
+    parent.notify(info.serial);
+    return Void();
+}
+
+Return<void> RadioConfigResponse::setPreferredDataModemResponse(
+        const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+    return Void();
+}
+
+Return<void> RadioConfigResponse::getModemsConfigResponse(
+        const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+        const ModemsConfig& /* mConfig */) {
+    return Void();
+}
+
+Return<void> RadioConfigResponse::setModemsConfigResponse(
+        const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+    return Void();
+}
+
+Return<void> RadioConfigResponse::getHalDeviceCapabilitiesResponse(
+        const ::android::hardware::radio::V1_6::RadioResponseInfo& /* info */,
+        const ::android::hardware::radio::config::V1_3::HalDeviceCapabilities& /* capabilities */) {
+    return Void();
+}
\ No newline at end of file
diff --git a/rebootescrow/aidl/default/Android.bp b/rebootescrow/aidl/default/Android.bp
index b77272f..e6a4e7a 100644
--- a/rebootescrow/aidl/default/Android.bp
+++ b/rebootescrow/aidl/default/Android.bp
@@ -20,7 +20,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.rebootescrow-ndk_platform",
+        "android.hardware.rebootescrow-V1-ndk_platform",
     ],
     export_include_dirs: ["include"],
     srcs: [
@@ -47,7 +47,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.rebootescrow-ndk_platform",
+        "android.hardware.rebootescrow-V1-ndk_platform",
     ],
     static_libs: [
         "libhadamardutils",
diff --git a/rebootescrow/aidl/vts/functional/Android.bp b/rebootescrow/aidl/vts/functional/Android.bp
index 2cc0068..abd4937 100644
--- a/rebootescrow/aidl/vts/functional/Android.bp
+++ b/rebootescrow/aidl/vts/functional/Android.bp
@@ -25,7 +25,7 @@
         "libbinder",
     ],
     static_libs: [
-        "android.hardware.rebootescrow-cpp",
+        "android.hardware.rebootescrow-V1-cpp",
     ],
     test_suites: [
         "vts",
diff --git a/security/keymint/aidl/Android.bp b/security/keymint/aidl/Android.bp
index b5adac9..5652827 100644
--- a/security/keymint/aidl/Android.bp
+++ b/security/keymint/aidl/Android.bp
@@ -4,6 +4,9 @@
     srcs: [
         "android/hardware/security/keymint/*.aidl",
     ],
+    imports: [
+        "android.hardware.security.secureclock",
+    ],
     stability: "vintf",
     backend: {
         java: {
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl
index 46e0ae0..29ff8f8 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl
new file mode 100644
index 0000000..893b016
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl
@@ -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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.keymint;
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
+parcelable AttestationKey {
+  byte[] keyBlob;
+  android.hardware.security.keymint.KeyParameter[] attestKeyParams;
+  byte[] issuerSubjectName;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl
index ed96485..4421619 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl
index dddc9d8..e9652c3 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl
index 3d18a26..34f2749 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl
index 9e0f8dc..5d1cc68 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl
index 8fc4d42..5055d75 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl
index 7c3f2f3..1a7e9b5 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl
index cdcb08d..3faba48 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -94,6 +109,12 @@
   ATTESTATION_IDS_NOT_PROVISIONED = -75,
   INVALID_OPERATION = -76,
   STORAGE_KEY_UNSUPPORTED = -77,
+  INCOMPATIBLE_MGF_DIGEST = -78,
+  UNSUPPORTED_MGF_DIGEST = -79,
+  MISSING_NOT_BEFORE = -80,
+  MISSING_NOT_AFTER = -81,
+  MISSING_ISSUER_SUBJECT = -82,
+  INVALID_ISSUER_SUBJECT = -83,
   UNIMPLEMENTED = -100,
   VERSION_MISMATCH = -101,
   UNKNOWN_ERROR = -1000,
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl
index 9ea24f5..bd304f1 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -16,12 +31,12 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package android.hardware.security.keymint;
-@VintfStability
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
 parcelable HardwareAuthToken {
   long challenge;
   long userId;
   long authenticatorId;
   android.hardware.security.keymint.HardwareAuthenticatorType authenticatorType;
-  android.hardware.security.keymint.Timestamp timestamp;
+  android.hardware.security.secureclock.Timestamp timestamp;
   byte[] mac;
 }
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
index aef5ee0..ae64110 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
index 3d08cfe..d3c6910 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -19,15 +34,16 @@
 @VintfStability
 interface IKeyMintDevice {
   android.hardware.security.keymint.KeyMintHardwareInfo getHardwareInfo();
-  android.hardware.security.keymint.VerificationToken verifyAuthorization(in long challenge, in android.hardware.security.keymint.HardwareAuthToken token);
   void addRngEntropy(in byte[] data);
-  void generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams, out android.hardware.security.keymint.ByteArray generatedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics generatedKeyCharacteristics, out android.hardware.security.keymint.Certificate[] outCertChain);
-  void importKey(in android.hardware.security.keymint.KeyParameter[] inKeyParams, in android.hardware.security.keymint.KeyFormat inKeyFormat, in byte[] inKeyData, out android.hardware.security.keymint.ByteArray outImportedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics outImportedKeyCharacteristics, out android.hardware.security.keymint.Certificate[] outCertChain);
-  void importWrappedKey(in byte[] inWrappedKeyData, in byte[] inWrappingKeyBlob, in byte[] inMaskingKey, in android.hardware.security.keymint.KeyParameter[] inUnwrappingParams, in long inPasswordSid, in long inBiometricSid, out android.hardware.security.keymint.ByteArray outImportedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics outImportedKeyCharacteristics);
-  byte[] upgradeKey(in byte[] inKeyBlobToUpgrade, in android.hardware.security.keymint.KeyParameter[] inUpgradeParams);
-  void deleteKey(in byte[] inKeyBlob);
+  android.hardware.security.keymint.KeyCreationResult generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in @nullable android.hardware.security.keymint.AttestationKey attestationKey);
+  android.hardware.security.keymint.KeyCreationResult importKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in android.hardware.security.keymint.KeyFormat keyFormat, in byte[] keyData, in @nullable android.hardware.security.keymint.AttestationKey attestationKey);
+  android.hardware.security.keymint.KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, in byte[] wrappingKeyBlob, in byte[] maskingKey, in android.hardware.security.keymint.KeyParameter[] unwrappingParams, in long passwordSid, in long biometricSid);
+  byte[] upgradeKey(in byte[] keyBlobToUpgrade, in android.hardware.security.keymint.KeyParameter[] upgradeParams);
+  void deleteKey(in byte[] keyBlob);
   void deleteAllKeys();
   void destroyAttestationIds();
-  android.hardware.security.keymint.BeginResult begin(in android.hardware.security.keymint.KeyPurpose inPurpose, in byte[] inKeyBlob, in android.hardware.security.keymint.KeyParameter[] inParams, in android.hardware.security.keymint.HardwareAuthToken inAuthToken);
+  android.hardware.security.keymint.BeginResult begin(in android.hardware.security.keymint.KeyPurpose purpose, in byte[] keyBlob, in android.hardware.security.keymint.KeyParameter[] params, in android.hardware.security.keymint.HardwareAuthToken authToken);
+  void deviceLocked(in boolean passwordOnly, in @nullable android.hardware.security.secureclock.TimeStampToken timestampToken);
+  void earlyBootEnded();
   const int AUTH_TOKEN_MAC_LENGTH = 32;
 }
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl
index 8e3b0fc..a9b9a05 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -18,7 +33,7 @@
 package android.hardware.security.keymint;
 @VintfStability
 interface IKeyMintOperation {
-  int update(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable android.hardware.security.keymint.HardwareAuthToken inAuthToken, in @nullable android.hardware.security.keymint.VerificationToken inVerificationToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams, out @nullable android.hardware.security.keymint.ByteArray output);
-  byte[] finish(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable android.hardware.security.keymint.HardwareAuthToken authToken, in @nullable android.hardware.security.keymint.VerificationToken inVerificationToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams);
+  int update(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable android.hardware.security.keymint.HardwareAuthToken inAuthToken, in @nullable android.hardware.security.secureclock.TimeStampToken inTimeStampToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams, out @nullable android.hardware.security.keymint.ByteArray output);
+  byte[] finish(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable android.hardware.security.keymint.HardwareAuthToken authToken, in @nullable android.hardware.security.secureclock.TimeStampToken inTimeStampToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams);
   void abort();
 }
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
new file mode 100644
index 0000000..a864c3c
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.keymint;
+@VintfStability
+interface IRemotelyProvisionedComponent {
+  byte[] generateEcdsaP256KeyPair(in boolean testMode, out android.hardware.security.keymint.MacedPublicKey macedPublicKey);
+  void generateCertificateRequest(in boolean testMode, in android.hardware.security.keymint.MacedPublicKey[] keysToSign, in byte[] endpointEncryptionCertChain, in byte[] challenge, out byte[] keysToSignMac, out android.hardware.security.keymint.ProtectedData protectedData);
+  const int STATUS_FAILED = 1;
+  const int STATUS_INVALID_MAC = 2;
+  const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3;
+  const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4;
+  const int STATUS_INVALID_EEK = 5;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl
index fb4214c..b430da9 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -18,6 +33,6 @@
 package android.hardware.security.keymint;
 @VintfStability
 parcelable KeyCharacteristics {
-  android.hardware.security.keymint.KeyParameter[] softwareEnforced;
-  android.hardware.security.keymint.KeyParameter[] hardwareEnforced;
+  android.hardware.security.keymint.SecurityLevel securityLevel;
+  android.hardware.security.keymint.KeyParameter[] authorizations;
 }
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl
new file mode 100644
index 0000000..4139436
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl
@@ -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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.keymint;
+@VintfStability
+parcelable KeyCreationResult {
+  byte[] keyBlob;
+  android.hardware.security.keymint.KeyCharacteristics[] keyCharacteristics;
+  android.hardware.security.keymint.Certificate[] certificateChain;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl
index f701c80..1ad7c51 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
index 5e9f7ae..d06312a 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -16,10 +31,11 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package android.hardware.security.keymint;
-@VintfStability
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
 parcelable KeyMintHardwareInfo {
   int versionNumber;
   android.hardware.security.keymint.SecurityLevel securityLevel;
   @utf8InCpp String keyMintName;
   @utf8InCpp String keyMintAuthorName;
+  boolean timestampTokenRequired;
 }
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl
index 9728bf9..acaf60d 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl
index 4985768..f534952 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl
index 2c3b768..2706623 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl
index ecf20ad..c79614a 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl
index a6fd8c3..61bb7e4 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -23,4 +38,6 @@
   SIGN = 2,
   VERIFY = 3,
   WRAP_KEY = 5,
+  AGREE_KEY = 6,
+  ATTEST_KEY = 7,
 }
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl
new file mode 100644
index 0000000..b4caeed
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.keymint;
+@VintfStability
+parcelable MacedPublicKey {
+  byte[] macedKey;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl
index 2ecfa1e..96b63e1 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl
new file mode 100644
index 0000000..46f602f
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.keymint;
+@VintfStability
+parcelable ProtectedData {
+  byte[] protectedData;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl
index 601693f..c720d6d 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -21,4 +36,5 @@
   SOFTWARE = 0,
   TRUSTED_ENVIRONMENT = 1,
   STRONGBOX = 2,
+  KEYSTORE = 100,
 }
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl
index 38eb6e6..03982e3 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
@@ -30,6 +45,7 @@
   EC_CURVE = 268435466,
   RSA_PUBLIC_EXPONENT = 1342177480,
   INCLUDE_UNIQUE_ID = 1879048394,
+  RSA_OAEP_MGF_DIGEST = 536871115,
   BLOB_USAGE_REQUIREMENTS = 268435757,
   BOOTLOADER_ONLY = 1879048494,
   ROLLBACK_RESISTANCE = 1879048495,
@@ -40,6 +56,7 @@
   USAGE_EXPIRE_DATETIME = 1610613138,
   MIN_SECONDS_BETWEEN_OPS = 805306771,
   MAX_USES_PER_BOOT = 805306772,
+  USAGE_COUNT_LIMIT = 805306773,
   USER_ID = 805306869,
   USER_SECURE_ID = -1610612234,
   NO_AUTH_REQUIRED = 1879048695,
@@ -77,4 +94,8 @@
   MAC_LENGTH = 805307371,
   RESET_SINCE_ID_ROTATION = 1879049196,
   CONFIRMATION_TOKEN = -1879047187,
+  CERTIFICATE_SERIAL = -2147482642,
+  CERTIFICATE_SUBJECT = -1879047185,
+  CERTIFICATE_NOT_BEFORE = 1610613744,
+  CERTIFICATE_NOT_AFTER = 1610613745,
 }
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl
index bb2766c..75a19a3 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
 // THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
 ///////////////////////////////////////////////////////////////////////////////
 
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
 //
-// You must not make a backward incompatible changes to the AIDL files built
+// You must not make a backward incompatible change to any AIDL file built
 // with the aidl_interface module type with versions property set. The module
 // type is used to build AIDL files in a way that they can be used across
 // independently updatable components of the system. If a device is shipped
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
deleted file mode 100644
index 4d5b659..0000000
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.hardware.security.keymint;
-@VintfStability
-parcelable Timestamp {
-  long milliSeconds;
-}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl
deleted file mode 100644
index 5c76816..0000000
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.hardware.security.keymint;
-@VintfStability
-parcelable VerificationToken {
-  long challenge;
-  android.hardware.security.keymint.Timestamp timestamp;
-  android.hardware.security.keymint.SecurityLevel securityLevel;
-  byte[] mac;
-}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl
new file mode 100644
index 0000000..8167ceb
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl
@@ -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 android.hardware.security.keymint;
+
+import android.hardware.security.keymint.KeyParameter;
+
+/**
+ * Contains a key blob with Tag::ATTEST_KEY that can be used to sign an attestation certificate,
+ * and the DER-encoded X.501 Subject Name that will be placed in the Issuer field of the attestation
+ * certificate.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
+parcelable AttestationKey {
+    byte[] keyBlob;
+    KeyParameter[] attestKeyParams;
+    byte[] issuerSubjectName;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl b/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl
index a953859..0e5d898 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl
@@ -17,9 +17,8 @@
 package android.hardware.security.keymint;
 
 /**
- * This encodes the IKeyMintDevice attestation generated certificate.
+ * This encodes an IKeyMintDevice certificate, generated for a KeyMint asymmetric public key.
  */
-
 @VintfStability
 parcelable Certificate {
     /**
diff --git a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl
index fb24ad1..5765130 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl
@@ -42,7 +42,7 @@
     INVALID_AUTHORIZATION_TIMEOUT = -16,
     UNSUPPORTED_KEY_FORMAT = -17,
     INCOMPATIBLE_KEY_FORMAT = -18,
-    UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19,   /** For PKCS8 & PKCS12 */
+    UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19, /** For PKCS8 & PKCS12 */
     UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20, /** For PKCS8 & PKCS12 */
     INVALID_INPUT_LENGTH = -21,
     KEY_EXPORT_OPTIONS_INVALID = -22,
@@ -99,6 +99,12 @@
     ATTESTATION_IDS_NOT_PROVISIONED = -75,
     INVALID_OPERATION = -76,
     STORAGE_KEY_UNSUPPORTED = -77,
+    INCOMPATIBLE_MGF_DIGEST = -78,
+    UNSUPPORTED_MGF_DIGEST = -79,
+    MISSING_NOT_BEFORE = -80,
+    MISSING_NOT_AFTER = -81,
+    MISSING_ISSUER_SUBJECT = -82,
+    INVALID_ISSUER_SUBJECT = -83,
 
     UNIMPLEMENTED = -100,
     VERSION_MISMATCH = -101,
diff --git a/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl b/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
index 12d615f..417a0b1 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
@@ -16,7 +16,7 @@
 
 package android.hardware.security.keymint;
 
-import android.hardware.security.keymint.Timestamp;
+import android.hardware.security.secureclock.Timestamp;
 import android.hardware.security.keymint.HardwareAuthenticatorType;
 
 /**
@@ -29,6 +29,7 @@
  * appropriate for a given key operation.
  */
 @VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
 parcelable HardwareAuthToken {
     /**
      * challenge is a value that's used to enable authentication tokens to authorize specific
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
index 4944acb..13e98af 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -16,18 +16,18 @@
 
 package android.hardware.security.keymint;
 
+import android.hardware.security.keymint.AttestationKey;
 import android.hardware.security.keymint.BeginResult;
 import android.hardware.security.keymint.ByteArray;
-import android.hardware.security.keymint.Certificate;
 import android.hardware.security.keymint.HardwareAuthToken;
 import android.hardware.security.keymint.IKeyMintOperation;
-import android.hardware.security.keymint.KeyCharacteristics;
+import android.hardware.security.keymint.KeyCreationResult;
 import android.hardware.security.keymint.KeyFormat;
-import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.KeyMintHardwareInfo;
+import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.KeyPurpose;
 import android.hardware.security.keymint.SecurityLevel;
-import android.hardware.security.keymint.VerificationToken;
+import android.hardware.security.secureclock.TimeStampToken;
 
 /**
  * KeyMint device definition.
@@ -126,16 +126,22 @@
  * attacker can use them at will (though they're more secure than keys which can be
  * exfiltrated).  Therefore, IKeyMintDevice must enforce access controls.
  *
- * Access controls are defined as an "authorization list" of tag/value pairs.  Authorization tags
- * are 32-bit integers from the Tag enum, and the values are a variety of types, defined in the
- * TagType enum.  Some tags may be repeated to specify multiple values.  Whether a tag may be
- * repeated is specified in the documentation for the tag and in the TagType.  When a key is
- * created or imported, the caller specifies an authorization list.  The IKeyMintDevice must divide
- * the caller-provided authorizations into two lists, those it enforces in tee secure zone and
- * those enforced in the strongBox hardware.  These two lists are returned as the "teeEnforced"
- * and "strongboxEnforced" elements of the KeyCharacteristics struct. Note that software enforced
- * authorization list entries are not returned because they are not enforced by keymint.  The
- * IKeyMintDevice must also add the following authorizations to the appropriate list:
+ * Access controls are defined as "authorization lists" of tag/value pairs.  Authorization tags are
+ * 32-bit integers from the Tag enum, and the values are a variety of types, defined in the TagType
+ * enum.  Some tags may be repeated to specify multiple values.  Whether a tag may be repeated is
+ * specified in the documentation for the tag and in the TagType.  When a key is created or
+ * imported, the caller specifies a `key_description` authorization list.  The IKeyMintDevice must
+ * determine which tags it can and cannot enforce, and at what SecurityLevel, and return an array of
+ * `KeyCharacteristics` structures that contains everything it will enforce, associated with the
+ * appropriate security level, which is one of SOFTWARE, TRUSTED_ENVIRONMENT and STRONGBOX.
+ * Typically, implementations will only return a single KeyCharacteristics structure, because
+ * everything they enforce is enforced at the same security level.  There may be cases, however, for
+ * which multiple security levels are relevant. One example is that of a StrongBox IKeyMintDevice
+ * that relies on a TEE to enforce biometric user authentication.  In that case, the generate/import
+ * methods must return two KeyCharacteristics structs, one with SecurityLevel::TRUSTED_ENVIRONMENT
+ * and the biometric authentication-related tags, and another with SecurityLevel::STRONGBOX and
+ * everything else.  The IKeyMintDevice must also add the following authorizations to the
+ * appropriate list:
  *
  * o    Tag::OS_VERSION
  * o    Tag::OS_PATCHLEVEL
@@ -148,26 +154,27 @@
  * The caller must always provide the current date time in the keyParameter CREATION_DATETIME
  * tags.
  *
- * All authorization tags and their values, both teeEnforced and strongboxEnforced, including
- * unknown tags, must be cryptographically bound to the private/secret key material such that any
- * modification of the portion of the key blob that contains the authorization list makes it
- * impossible for the secure environment to obtain the private/secret key material.  The
- * recommended approach to meet this requirement is to use the full set of authorization tags
- * associated with a key as input to a secure key derivation function used to derive a key that
- * is used to encrypt the private/secret key material.
+ * All authorization tags and their values enforced by an IKeyMintDevice must be cryptographically
+ * bound to the private/secret key material such that any modification of the portion of the key
+ * blob that contains the authorization list makes it impossible for the secure environment to
+ * obtain the private/secret key material.  The recommended approach to meet this requirement is to
+ * use the full set of authorization tags associated with a key as input to a secure key derivation
+ * function used to derive a key (the KEK) that is used to encrypt the private/secret key material.
+ * Note that it is NOT acceptable to use a static KEK to encrypt the private/secret key material
+ * with an AEAD cipher mode, using the enforced authorization tags as AAD.  This is because
+ * Tag::APPLICATION_DATA must not be included in the authorization tags stored in the key blob, but
+ * must be provided by the caller for every use.  Assuming the Tag::APPLICATION_DATA value has
+ * sufficient entropy, this provides a cryptographic guarantee that an attacker cannot use a key
+ * without knowing the Tag::APPLICATION_DATA value, even if they compromise the IKeyMintDevice.
  *
- * IKeyMintDevice implementations ignore any tags they cannot enforce and do not return them
- * in KeyCharacteristics.  For example, Tag::ORIGINATION_EXPIRE_DATETIME provides the date and
- * time after which a key may not be used to encrypt or sign new messages.  Unless the
- * IKeyMintDevice has access to a secure source of current date/time information, it is not
- * possible for the IKeyMintDevice to enforce this tag.  An IKeyMintDevice implementation will
- * not rely on the non-secure world's notion of time, because it could be controlled by an
- * attacker. Similarly, it cannot rely on GPSr time, even if it has exclusive control of the
- * GPSr, because that might be spoofed by attacker RF signals.
- *
- * IKeyMintDevices do not use or enforce any tags they place in the softwareEnforced
- * list.  The IKeyMintDevice caller must enforce them, and it is unnecessary to enforce them
- * twice.
+ * IKeyMintDevice implementations must ignore any tags they cannot enforce and must not return them
+ * in KeyCharacteristics.  For example, Tag::ORIGINATION_EXPIRE_DATETIME provides the date and time
+ * after which a key may not be used to encrypt or sign new messages.  Unless the IKeyMintDevice has
+ * access to a secure source of current date/time information, it is not possible for the
+ * IKeyMintDevice to enforce this tag.  An IKeyMintDevice implementation will not rely on the
+ * non-secure world's notion of time, because it could be controlled by an attacker. Similarly, it
+ * cannot rely on GPSr time, even if it has exclusive control of the GPSr, because that might be
+ * spoofed by attacker RF signals.
  *
  * Some tags must be enforced by the IKeyMintDevice.  See the detailed documentation on each Tag
  * in Tag.aidl.
@@ -217,34 +224,6 @@
     KeyMintHardwareInfo getHardwareInfo();
 
     /**
-     * Verify authorizations for another IKeyMintDevice instance.
-     *
-     * On systems with both a StrongBox and a TEE IKeyMintDevice instance it is sometimes useful
-     * to ask the TEE KeyMintDevice to verify authorizations for a key hosted in StrongBox.
-     *
-     * For every StrongBox operation, Keystore is required to call this method on the TEE KeyMint,
-     * passing in the StrongBox key's hardwareEnforced authorization list and the challenge
-     * returned by StrongBox begin().  Keystore must then pass the VerificationToken to the
-     * subsequent invocations of StrongBox update() and finish().
-     *
-     * StrongBox implementations must return ErrorCode::UNIMPLEMENTED.
-     *
-     * @param the challenge returned by StrongBox's keyMint's begin().
-     *
-     * @param authToken A HardwareAuthToken if needed to authorize key usage.
-     *
-     * @return error ErrorCode::OK on success or ErrorCode::UNIMPLEMENTED if the KeyMintDevice is
-     *         a StrongBox.  If the IKeyMintDevice cannot verify one or more elements of
-     *         parametersToVerify it must not return an error code, but just omit the unverified
-     *         parameter from the VerificationToken.
-     *
-     * @return token the verification token.  See VerificationToken in VerificationToken.aidl for
-     *         details.
-     */
-    VerificationToken verifyAuthorization(in long challenge,
-                                          in HardwareAuthToken token);
-
-    /**
      * Adds entropy to the RNG used by KeyMint.  Entropy added through this method must not be the
      * only source of entropy used, and a secure mixing function must be used to mix the entropy
      * provided by this method with internally-generated entropy.  The mixing function must be
@@ -337,38 +316,18 @@
      *        provided in params.  See above for detailed specifications of which tags are required
      *        for which types of keys.
      *
-     * @return generatedKeyBlob Opaque descriptor of the generated key.  The recommended
-     *         implementation strategy is to include an encrypted copy of the key material, wrapped
-     *         in a key unavailable outside secure hardware.
+     * @param attestationKey, if provided, specifies the key that must be used to sign the
+     *        attestation certificate.  If `keyParams` does not contain a Tag::ATTESTATION_CHALLENGE
+     *        but `attestationKey` is non-null, the IKeyMintDevice must return
+     *        ErrorCode::INVALID_ARGUMENT.  If the provided AttestationKey does not contain a key
+     *        blob containing an asymmetric key with KeyPurpose::ATTEST_KEY, the IKeyMintDevice must
+     *        return ErrorCode::INVALID_PURPOSE.  If the provided AttestationKey has an empty issuer
+     *        subject name, the IKeyMintDevice must return ErrorCode::INVALID_ARGUMENT.
      *
-     * @return generatedKeyCharacteristics Description of the generated key, divided into two sets:
-     *         hardware-enforced and software-enforced.  The description here applies equally
-     *         to the key characteristics lists returned by generateKey, importKey and
-     *         importWrappedKey.  The characteristics returned by this parameter completely
-     *         describe the type and usage of the specified key.
-     *
-     *         The rule that IKeyMintDevice implementations must use for deciding whether a
-     *         given tag belongs in the hardware-enforced or software-enforced list is that if
-     *         the meaning of the tag is fully assured by secure hardware, it is hardware
-     *         enforced.  Otherwise, it's software enforced.
-     *
-     * @return outCertChain If the key is an asymmetric key, and proper keyparameters for
-     *         attestation (such as challenge) is provided, then this parameter will return the
-     *         attestation certificate.  If the signing of the attestation certificate is from a
-     *         factory key, additional certificates back to the root attestation certificate will
-     *         also be provided. Clients will need to check root certificate against a known-good
-     *         value. The certificates must be DER-encoded.  Caller needs to provide
-     *         CREATION_DATETIME as one of the attestation parameters, otherwise the attestation
-     *         certificate will not contain the creation datetime.  The first certificate in the
-     *         vector is the attestation for the generated key itself, the next certificate is
-     *         the key that signs the first certificate, and so forth.  The last certificate in
-     *         the chain is the root certificate.  If the key is a symmetric key, then no
-     *         certificate will be returned and this variable will return empty. TODO: change
-     *         certificate return to a single certificate and make it nullable b/163604282.
+     * @return The result of key creation.  See KeyCreationResult.aidl.
      */
-    void generateKey(in KeyParameter[] keyParams, out ByteArray generatedKeyBlob,
-                     out KeyCharacteristics generatedKeyCharacteristics,
-                     out Certificate[] outCertChain);
+    KeyCreationResult generateKey(
+            in KeyParameter[] keyParams, in @nullable AttestationKey attestationKey);
 
     /**
      * Imports key material into an IKeyMintDevice.  Key definition parameters and return values
@@ -396,29 +355,18 @@
      *
      * @param inKeyData The key material to import, in the format specified in keyFormat.
      *
-     * @return outImportedKeyBlob descriptor of the imported key.  The format of the keyblob will
-     *         be the google specified keyblob format.
+     * @param attestationKey, if provided, specifies the key that must be used to sign the
+     *        attestation certificate.  If `keyParams` does not contain a Tag::ATTESTATION_CHALLENGE
+     *        but `attestationKey` is non-null, the IKeyMintDevice must return
+     *        ErrorCode::INVALID_ARGUMENT.  If the provided AttestationKey does not contain a key
+     *        blob containing an asymmetric key with KeyPurpose::ATTEST_KEY, the IKeyMintDevice must
+     *        return ErrorCode::INVALID_PURPOSE.  If the provided AttestationKey has an empty issuer
+     *        subject name, the IKeyMintDevice must return ErrorCode::INVALID_ARGUMENT.
      *
-     * @return outImportedKeyCharacteristics Description of the generated key.  See the
-     *         keyCharacteristics description in generateKey.
-     *
-     * @return outCertChain If the key is an asymmetric key, and proper keyparameters for
-     *         attestation (such as challenge) is provided, then this parameter will return the
-     *         attestation certificate.  If the signing of the attestation certificate is from a
-     *         factory key, additional certificates back to the root attestation certificate will
-     *         also be provided. Clients will need to check root certificate against a known-good
-     *         value. The certificates must be DER-encoded.  Caller needs to provide
-     *         CREATION_DATETIME as one of the attestation parameters, otherwise the attestation
-     *         certificate will not contain the creation datetime.  The first certificate in the
-     *         vector is the attestation for the generated key itself, the next certificate is
-     *         the key that signs the first certificate, and so forth.  The last certificate in
-     *         the chain is the root certificate.  If the key is a symmetric key, then no
-     *         certificate will be returned and this variable will return empty.
+     * @return The result of key creation.  See KeyCreationResult.aidl.
      */
-    void importKey(in KeyParameter[] inKeyParams, in KeyFormat inKeyFormat,
-                   in byte[] inKeyData, out ByteArray outImportedKeyBlob,
-                   out KeyCharacteristics outImportedKeyCharacteristics,
-                   out Certificate[] outCertChain);
+    KeyCreationResult importKey(in KeyParameter[] keyParams, in KeyFormat keyFormat,
+            in byte[] keyData, in @nullable AttestationKey attestationKey);
 
     /**
      * Securely imports a key, or key pair, returning a key blob and a description of the imported
@@ -474,45 +422,35 @@
      *     5. Perform the equivalent of calling importKey(keyParams, keyFormat, keyData), except
      *        that the origin tag should be set to SECURELY_IMPORTED.
      *
-     * @param inWrappingKeyBlob The opaque key descriptor returned by generateKey() or importKey().
+     * @param wrappingKeyBlob The opaque key descriptor returned by generateKey() or importKey().
      *        This key must have been created with Purpose::WRAP_KEY.
      *
-     * @param inMaskingKey The 32-byte value XOR'd with the transport key in the SecureWrappedKey
+     * @param maskingKey The 32-byte value XOR'd with the transport key in the SecureWrappedKey
      *        structure.
      *
-     * @param inUnwrappingParams must contain any parameters needed to perform the unwrapping
-     *        operation.  For example, if the wrapping key is an AES key the block and padding
-     *        modes must be specified in this argument.
+     * @param unwrappingParams must contain any parameters needed to perform the unwrapping
+     *        operation.  For example, if the wrapping key is an AES key the block and padding modes
+     *        must be specified in this argument.
      *
-     * @param inPasswordSid specifies the password secure ID (SID) of the user that owns the key
-     *        being installed.  If the authorization list in wrappedKeyData contains a
-     *        Tag::USER_SECURE_IDwith a value that has the HardwareAuthenticatorType::PASSWORD
-     *        bit set, the constructed key must be bound to the SID value provided by this
-     *        argument.  If the wrappedKeyData does not contain such a tag and value, this argument
-     *        must be ignored.
+     * @param passwordSid specifies the password secure ID (SID) of the user that owns the key being
+     *        installed.  If the authorization list in wrappedKeyData contains a
+     *        Tag::USER_SECURE_IDwith a value that has the HardwareAuthenticatorType::PASSWORD bit
+     *        set, the constructed key must be bound to the SID value provided by this argument.  If
+     *        the wrappedKeyData does not contain such a tag and value, this argument must be
+     *        ignored.
      *
-     * @param inBiometricSid specifies the biometric secure ID (SID) of the user that owns the key
+     * @param biometricSid specifies the biometric secure ID (SID) of the user that owns the key
      *        being installed.  If the authorization list in wrappedKeyData contains a
      *        Tag::USER_SECURE_ID with a value that has the HardwareAuthenticatorType::FINGERPRINT
      *        bit set, the constructed key must be bound to the SID value provided by this argument.
      *        If the wrappedKeyData does not contain such a tag and value, this argument must be
      *        ignored.
      *
-     * @return outImportedKeyBlob Opaque descriptor of the imported key.  It is recommended that
-     *         the keyBlob contain a copy of the key material, wrapped in a key unavailable outside
-     *         secure hardware.
-     *
-     * @return outImportedKeyCharacteristics Description of the generated key.  See the description
-     *         of keyCharacteristics parameter in generateKey.
+     * @return The result of key creation.  See KeyCreationResult.aidl.
      */
-    void importWrappedKey(in byte[] inWrappedKeyData,
-                          in byte[] inWrappingKeyBlob,
-                          in byte[] inMaskingKey,
-                          in KeyParameter[] inUnwrappingParams,
-                          in long inPasswordSid,
-                          in long inBiometricSid,
-                          out ByteArray outImportedKeyBlob,
-                          out KeyCharacteristics outImportedKeyCharacteristics);
+    KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, in byte[] wrappingKeyBlob,
+            in byte[] maskingKey, in KeyParameter[] unwrappingParams, in long passwordSid,
+            in long biometricSid);
 
     /**
      * Upgrades an old key blob.  Keys can become "old" in two ways: IKeyMintDevice can be
@@ -547,7 +485,7 @@
      * @return A new key blob that references the same key as keyBlobToUpgrade, but is in the new
      *         format, or has the new version data.
      */
-    byte[] upgradeKey(in byte[] inKeyBlobToUpgrade, in KeyParameter[] inUpgradeParams);
+    byte[] upgradeKey(in byte[] keyBlobToUpgrade, in KeyParameter[] upgradeParams);
 
     /**
      * Deletes the key, or key pair, associated with the key blob.  Calling this function on
@@ -557,7 +495,7 @@
      *
      * @param inKeyBlob The opaque descriptor returned by generateKey() or importKey();
      */
-    void deleteKey(in byte[] inKeyBlob);
+    void deleteKey(in byte[] keyBlob);
 
     /**
      * Deletes all keys in the hardware keystore.  Used when keystore is reset completely.  After
@@ -759,9 +697,9 @@
      * values less than the key's minimum length, begin() must return ErrorCode::INVALID_MAC_LENGTH.
      *
      * @param inPurpose The purpose of the operation, one of KeyPurpose::ENCRYPT,
-     *        KeyPurpose::DECRYPT, KeyPurpose::SIGN or KeyPurpose::VERIFY.  Note that for AEAD
-     *        modes, encryption and decryption imply signing and verification, respectively, but
-     *        must be specified as KeyPurpose::ENCRYPT and KeyPurpose::DECRYPT.
+     *        KeyPurpose::DECRYPT, KeyPurpose::SIGN, KeyPurpose::VERIFY, or KeyPurpose::AGREE_KEY.
+     *        Note that for AEAD modes, encryption and decryption imply signing and verification,
+     *        respectively, but must be specified as KeyPurpose::ENCRYPT and KeyPurpose::DECRYPT.
      *
      * @param inKeyBlob The opaque key descriptor returned by generateKey() or importKey().  The key
      *        must have a purpose compatible with purpose and all of its usage requirements must be
@@ -783,8 +721,44 @@
      *         from operations that generate an IV or nonce, and IKeyMintOperation object pointer
      *         which is used to perform update(), finish() or abort() operations.
      */
-    BeginResult begin(in KeyPurpose inPurpose,
-               in byte[] inKeyBlob,
-               in KeyParameter[] inParams,
-               in HardwareAuthToken inAuthToken);
+    BeginResult begin(in KeyPurpose purpose, in byte[] keyBlob, in KeyParameter[] params,
+            in HardwareAuthToken authToken);
+
+    /**
+     * Called by client to notify the IKeyMintDevice that the device is now locked, and keys with
+     * the UNLOCKED_DEVICE_REQUIRED tag should no longer be usable.  When this function is called,
+     * the IKeyMintDevice should note the current timestamp, and attempts to use
+     * UNLOCKED_DEVICE_REQUIRED keys must be rejected with Error::DEVICE_LOCKED until an
+     * authentication token with a later timestamp is presented.  If the `passwordOnly' argument is
+     * set to true the sufficiently-recent authentication token must indicate that the user
+     * authenticated with a password, not a biometric.
+     *
+     * Note that the IKeyMintDevice UNLOCKED_DEVICE_REQUIRED semantics are slightly different from
+     * the UNLOCKED_DEVICE_REQUIRED semantics enforced by keystore.  Keystore handles device locking
+     * on a per-user basis.  Because auth tokens do not contain an Android user ID, it's not
+     * possible to replicate the keystore enformcement logic in IKeyMintDevice.  So from the
+     * IKeyMintDevice perspective, any user unlock unlocks all UNLOCKED_DEVICE_REQUIRED keys.
+     * Keystore will continue enforcing the per-user device locking.
+     *
+     * @param passwordOnly specifies whether the device must be unlocked with a password, rather
+     * than a biometric, before UNLOCKED_DEVICE_REQUIRED keys can be used.
+     *
+     * @param timestampToken is used by StrongBox implementations of IKeyMintDevice.  It
+     * provides the StrongBox IKeyMintDevice with a fresh, MACed timestamp which it can use as the
+     * device-lock time, for future comparison against auth tokens when operations using
+     * UNLOCKED_DEVICE_REQUIRED keys are attempted.  Unless the auth token timestamp is newer than
+     * the timestamp in the timestampToken, the device is still considered to be locked.
+     * Crucially, if a StrongBox IKeyMintDevice receives a deviceLocked() call with a timestampToken
+     * timestamp that is less than the timestamp in the last deviceLocked() call, it must ignore the
+     * new timestamp.  TEE IKeyMintDevice implementations will receive an empty timestampToken (zero
+     * values and empty vectors) and should use their own clock as the device-lock time.
+     */
+    void deviceLocked(in boolean passwordOnly, in @nullable TimeStampToken timestampToken);
+
+    /**
+     * Called by client to notify the IKeyMintDevice that the device has left the early boot
+     * state, and that keys with the EARLY_BOOT_ONLY tag may no longer be used.  All attempts to use
+     * an EARLY_BOOT_ONLY key after this method is called must fail with Error::INVALID_KEY_BLOB.
+     */
+    void earlyBootEnded();
 }
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
index 24960cc..8c49602 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
@@ -20,7 +20,7 @@
 import android.hardware.security.keymint.HardwareAuthToken;
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.KeyParameterArray;
-import android.hardware.security.keymint.VerificationToken;
+import android.hardware.security.secureclock.TimeStampToken;
 
 @VintfStability
 interface IKeyMintOperation {
@@ -119,10 +119,9 @@
      * @param input Data to be processed.  Note that update() may or may not consume all of the data
      *        provided.  See return value.
      *
-     * @param verificationToken Verification token, used to prove that another IKeymasterDevice HAL
-     *        has verified some parameters, and to deliver the other HAL's current timestamp, if
-     *        needed.  If not provided, all fields must be initialized to zero and vectors must be
-     *        empty.
+     * @param inTimeStampToken timestamp token, certifies the freshness of an auth token in case
+     *        the security domain of this KeyMint instance has a different clock than the
+     *        authenticator issuing the auth token.
      *
      * @return error Returns ErrorCode encountered in keymint as service specific errors. See the
      *         ErrorCode enum in ErrorCode.aidl.
@@ -141,7 +140,7 @@
     int update(in @nullable KeyParameterArray inParams,
                in @nullable byte[] input,
                in @nullable HardwareAuthToken inAuthToken,
-               in @nullable VerificationToken inVerificationToken,
+               in @nullable TimeStampToken inTimeStampToken,
                out @nullable KeyParameterArray outParams,
                out @nullable ByteArray output);
 
@@ -241,9 +240,9 @@
      *
      * @param authToken Authentication token. Can be nullable if not provided.
      *
-     * @param verificationToken Verification token, used to prove that another IKeyMintDevice HAL
-     *        has verified some parameters, and to deliver the other HAL's current timestamp, if
-     *        needed. Can be nullable if not needed.
+     * @param inTimeStampToken timestamp token, certifies the freshness of an auth token in case
+     *        the security domain of this KeyMint instance has a different clock than the
+     *        authenticator issuing the auth token.
      *
      * @return outParams Any output parameters generated by finish().
      *
@@ -252,7 +251,7 @@
     byte[] finish(in @nullable KeyParameterArray inParams, in @nullable byte[] input,
                 in @nullable byte[] inSignature,
                 in @nullable HardwareAuthToken authToken,
-                in @nullable VerificationToken inVerificationToken,
+                in @nullable TimeStampToken inTimeStampToken,
                 out @nullable KeyParameterArray outParams);
 
     /**
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
new file mode 100644
index 0000000..1b09e9d
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -0,0 +1,262 @@
+/*
+ * 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.
+ */
+
+package android.hardware.security.keymint;
+
+import android.hardware.security.keymint.MacedPublicKey;
+import android.hardware.security.keymint.ProtectedData;
+
+/**
+ * An IRemotelyProvisionedComponent is a secure-side component for which certificates can be
+ * remotely provisioned. It provides an interface for generating asymmetric key pairs and then
+ * creating a CertificateRequest that contains the generated public keys, plus other information to
+ * authenticate the request origin. The CertificateRequest can be sent to a server, which can
+ * validate the request and create certificates.
+ *
+ * This interface does not provide any way to use the generated and certified key pairs. It's
+ * intended to be implemented by a HAL service that does other things with keys (e.g. Keymint).
+ *
+ * The root of trust for secure provisioning is something called the "Boot Certificate Chain", or
+ * BCC. The BCC is a chain of public key certificates, represented as COSE_Sign1 objects containing
+ * COSE_Key representations of the public keys. The "root" of the BCC is a self-signed certificate
+ * for a device-unique public key, denoted DK_pub. All public keys in the BCC are device-unique. The
+ * public key from each certificate in the chain is used to sign the next certificate in the
+ * chain. The final, "leaf" certificate contains a public key, denoted KM_pub, whose corresponding
+ * private key, denoted KM_priv, is available for use by the IRemotelyProvisionedComponent.
+ *
+ * BCC Design
+ * ==========
+ *
+ * The BCC is designed to mirror the boot stages of a device, and to prove the content and integrity
+ * of each firmware image. In a proper BCC, each boot stage hashes its own private key with the code
+ * and any relevant configuration parameters of the next stage to produce a key pair for the next
+ * stage. Each stage also uses its own private key to sign the public key of the next stage,
+ * including in the certificate the hash of the next firmware stage, then loads the next stage,
+ * passing the private key and certificate to it in a manner that does not leak the private key to
+ * later boot stages. The BCC root key pair is generated by immutable code (e.g. ROM), from a
+ * device-unique secret. After the device-unique secret is used, it must be made unavailable to any
+ * later boot stage.
+ *
+ * In this way, booting the device incrementally builds a certificate chain that (a) identifies and
+ * validates the integrity of every stage and (b) contains a set of public keys that correspond to
+ * private keys, one known to each stage. Any stage can compute the secrets of all later stages
+ * (given the necessary input), but no stage can compute the secret of any preceding stage. Updating
+ * the firmware or configuration of any stage changes the key pair of that stage, and of all
+ * subsequent stages, and no attacker who compromised the previous version of the updated firmware
+ * can know or predict the post-update key pairs.
+ *
+ * The first BCC certificate is special because its contained public key, DK_pub, will never change,
+ * making it a permanent, device-unique identifier. Although the remaining keys in the BCC are also
+ * device-unique, they are not necessarily permanent, since they can change when the device software
+ * is updated.
+ *
+ * When the provisioning server receives a message signed by KM_priv and containing a BCC that
+ * chains from DK_pub to KM_pub, it can be certain that (barring vulnerabilities in some boot
+ * stage), the CertificateRequest came from the device associated with DK_pub, running the specific
+ * software identified by the certificates in the BCC. If the server has some mechanism for knowing
+ * which the DK_pub values of "valid" devices, it can determine whether signing certificates is
+ * appropriate.
+ *
+ * Degenerate BCCs
+ * ===============
+ *
+ * While a proper BCC, as described above, reflects the complete boot sequence from boot ROM to the
+ * secure area image of the IRemotelyProvisionedComponent, it's also possible to use a "degenerate"
+ * BCC which consists only of a single, self-signed certificate containing the public key of a
+ * hardware-bound key pair. This is an appopriate solution for devices which haven't implemented
+ * everything necessary to produce a proper BCC, but can derive a unique key pair in the secure
+ * area.  In this degenerate case, DK_pub is the same as KM_pub.
+ *
+ * BCC Privacy
+ * ===========
+ *
+ * Because the BCC constitutes an unspoofable, device-unique identifier, special care is taken to
+ * prevent its availability to entities who may wish to track devices. Two precautions are taken:
+ *
+ * 1.  The BCC is never exported from the IRemotelyProvisionedComponent except in encrypted
+ *     form. The portion of the CertificateRequest that contains the BCC is encrypted using an
+ *     Endpoint Encryption Key (EEK).  The EEK is provided in the form of a certificate chain whose
+ *     root must be pre-provisioned into the secure area (hardcoding the roots into the secure area
+ *     firmware image is a recommended approach). Multiple roots may be provisioned. If the provided
+ *     EEK does not chain back to this already-known root, the IRemotelyProvisionedComponent must
+ *     reject it.
+ *
+ * 2.  Precaution 1 above ensures that only an entity with a valid EEK private key can decrypt the
+ *     BCC. To make it feasible to build a provisioning server which cannot use the BCC to track
+ *     devices, the CertificateRequest is structured so that the server can be partitioned into two
+ *     components.  The "decrypter" decrypts the BCC, verifies DK_pub and the device's right to
+ *     receive provisioned certificates, but does not see the public keys to be signed or the
+ *     resulting certificates.  The "certifier" gets informed of the results of the decrypter's
+ *     validation and sees the public keys to be signed and resulting certificates, but does not see
+ *     the BCC.
+ *
+ * Test Mode
+ * =========
+ *
+ * The IRemotelyProvisionedComponent supports a test mode, allowing the generation of test key pairs
+ * and test CertificateRequests. Test keys/requests are annotated as such, and the BCC used for test
+ * CertificateRequests must contain freshly-generated keys, not the real BCC key pairs.
+ */
+@VintfStability
+interface IRemotelyProvisionedComponent {
+    const int STATUS_FAILED = 1;
+    const int STATUS_INVALID_MAC = 2;
+    const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3;
+    const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4;
+    const int STATUS_INVALID_EEK = 5;
+
+    /**
+     * generateKeyPair generates a new ECDSA P-256 key pair that can be certified.  Note that this
+     * method only generates ECDSA P-256 key pairs, but the interface can be extended to add methods
+     * for generating keys for other algorithms, if necessary.
+     *
+     * @param in boolean testMode indicates whether the generated key is for testing only. Test keys
+     *        are marked (see the definition of PublicKey in the MacedPublicKey structure) to
+     *        prevent them from being confused with production keys.
+     *
+     * @param out MacedPublicKey macedPublicKey contains the public key of the generated key pair,
+     *        MACed so that generateCertificateRequest can easily verify, without the
+     *        privateKeyHandle, that the contained public key is for remote certification.
+     *
+     * @return data representing a handle to the private key. The format is implementation-defined,
+     *         but note that specific services may define a required format.
+     */
+    byte[] generateEcdsaP256KeyPair(in boolean testMode, out MacedPublicKey macedPublicKey);
+
+    /**
+     * generateCertificateRequest creates a certificate request to be sent to the provisioning
+     * server.
+     *
+     * @param in boolean testMode indicates whether the generated certificate request is for testing
+     *        only.
+     *
+     * @param in MacedPublicKey[] keysToSign contains the set of keys to certify. The
+     *        IRemotelyProvisionedComponent must validate the MACs on each key.  If any entry in the
+     *        array lacks a valid MAC, the method must return STATUS_INVALID_MAC.
+     *
+     *        If testMode is true, the keysToCertify array must contain only keys flagged as test
+     *        keys. Otherwise, the method must return STATUS_PRODUCTION_KEY_IN_TEST_REQUEST.
+     *
+     *        If testMode is false, the keysToCertify array must not contain any keys flagged as
+     *        test keys. Otherwise, the method must return STATUS_TEST_KEY_IN_PRODUCTION_REQUEST.
+     *
+     * @param in endpointEncryptionKey contains an X25519 public key which will be used to encrypt
+     *        the BCC. For flexibility, this is represented as a certificate chain, represented as a
+     *        CBOR array of COSE_Sign1 objects, ordered from root to leaf. The leaf contains the
+     *        X25519 encryption key, each other element is an Ed25519 key signing the next in the
+     *        chain. The root is self-signed.
+     *
+     *            EekChain = [ + SignedSignatureKey, SignedEek ]
+     *
+     *            SignedSignatureKey = [              // COSE_Sign1
+     *                protected: bstr .cbor {
+     *                    1 : -8,                     // Algorithm : EdDSA
+     *                },
+     *                unprotected: bstr .size 0
+     *                payload: bstr .cbor SignatureKey,
+     *                signature: bstr PureEd25519(.cbor SignatureKeySignatureInput)
+     *            ]
+     *
+     *            SignatureKey = {                    // COSE_Key
+     *                 1 : 1,                         // Key type : Octet Key Pair
+     *                 3 : -8,                        // Algorithm : EdDSA
+     *                 -1 : 6,                        // Curve : Ed25519
+     *                 -2 : bstr                      // Ed25519 public key
+     *            }
+     *
+     *            SignatureKeySignatureInput = [
+     *                context: "Signature1",
+     *                body_protected: bstr .cbor {
+     *                    1 : -8,                     // Algorithm : EdDSA
+     *                },
+     *                external_aad: bstr .size 0,
+     *                payload: bstr .cbor SignatureKey
+     *            ]
+     *
+     *            SignedEek = [                       // COSE_Sign1
+     *                protected: bstr .cbor {
+     *                    1 : -8,                     // Algorithm : EdDSA
+     *                },
+     *                unprotected: bstr .size 0
+     *                payload: bstr .cbor Eek,
+     *                signature: bstr PureEd25519(.cbor EekSignatureInput)
+     *            ]
+     *
+     *            Eek = {                             // COSE_Key
+     *                1 : 1,                          // Key type : Octet Key Pair
+     *                2 : bstr                        // KID : EEK ID
+     *                3 : -25,                        // Algorithm : ECDH-ES + HKDF-256
+     *                -1 : 4,                         // Curve : X25519
+     *                -2 : bstr                       // Ed25519 public key
+     *            }
+     *
+     *            EekSignatureInput = [
+     *                context: "Signature1",
+     *                body_protected: bstr .cbor {
+     *                    1 : -8,                     // Algorithm : EdDSA
+     *                },
+     *                external_aad: bstr .size 0,
+     *                payload: bstr .cbor Eek
+     *            ]
+     *
+     *        If the contents of endpointEncryptionKey do not match the SignedEek structure above,
+     *        the method must return STATUS_INVALID_EEK.
+     *
+     *        If testMode is true, the method must ignore the length and content of the signatures
+     *        in the chain, which implies that it must not attempt to validate the signature.
+     *
+     *        If testMode is false, the method must validate the chain signatures, and must verify
+     *        that the public key in the root certifictate is in its pre-configured set of
+     *        authorized EEK root keys. If the public key is not in the database, or if signature
+     *        verification fails, the method must return STATUS_INVALID_EEK.
+     *
+     * @param in challenge contains a byte string from the provisioning server that must be signed
+     *        by the secure area. See the description of the 'signature' output parameter for
+     *        details.
+     *
+     * @param out keysToSignMac contains the MAC of KeysToSign in the CertificateRequest
+     *        structure. Specifically, it contains:
+     *
+     *            HMAC-256(EK_mac, .cbor KeysToMacStructure)
+     *
+     *        Where EK_mac is an ephemeral MAC key, found in ProtectedData (see below).  The MACed
+     *        data is the "tag" field of a COSE_Mac0 structure like:
+     *
+     *            MacedKeys = [                            // COSE_Mac0
+     *                protected : bstr .cbor {
+     *                    1 : 5,                           // Algorithm : HMAC-256
+     *                },
+     *                unprotected : bstr .size 0,
+     *                // Payload is PublicKeys from keysToSign argument, in provided order.
+     *                payload: bstr .cbor [ * PublicKey ],
+     *                tag: bstr
+     *           ]
+     *
+     *            KeysToMacStructure = [
+     *                context : "MAC0",
+     *                protected : bstr .cbor { 1 : 5 },    // Algorithm : HMAC-256
+     *                external_aad : bstr .size 0,
+     *                // Payload is PublicKeys from keysToSign argument, in provided order.
+     *                payload : bstr .cbor [ * PublicKey ]
+     *            ]
+     *
+     * @param out ProtectedData contains the encrypted BCC and the ephemeral MAC key used to
+     *        authenticate the keysToSign (see keysToSignMac output argument).
+     */
+    void generateCertificateRequest(in boolean testMode, in MacedPublicKey[] keysToSign,
+            in byte[] endpointEncryptionCertChain, in byte[] challenge, out byte[] keysToSignMac,
+            out ProtectedData protectedData);
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl
index 0801868..edd4d8f 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl
@@ -17,25 +17,20 @@
 package android.hardware.security.keymint;
 
 import android.hardware.security.keymint.KeyParameter;
+import android.hardware.security.keymint.SecurityLevel;
 
 /**
- * KeyCharacteristics defines the attributes of a key, including cryptographic parameters, and usage
- * restrictions.  It consits of two vectors of KeyParameters, one for "softwareEnforced" attributes
- * and one for "hardwareEnforced" attributes.
+ * KeyCharacteristics defines the attributes of a key that are enforced by KeyMint, and the security
+ * level (see SecurityLevel.aidl) of that enforcement.
  *
- * KeyCharacteristics objects are returned by generateKey, importKey, importWrappedKey and
- * getKeyCharacteristics.  The IKeyMintDevice secure environment is responsible for allocating the
- * parameters, all of which are Tags with associated values, to the correct vector.  The
- * hardwareEnforced vector must contain only those attributes which are enforced by secure hardware.
- * All others should be in the softwareEnforced vector.  See the definitions of individual Tag enums
- * for specification of which must be hardware-enforced, which may be software-enforced and which
- * must never appear in KeyCharacteristics.
+ * The `generateKey` `importKey` and `importWrappedKey` methods each return an array of
+ * KeyCharacteristics, specifying the security levels of enforcement and the authorizations
+ * enforced.  Note that enforcement at a given security level means that the semantics of the tag
+ * and value are fully enforced.  See the definition of individual tags for specifications of what
+ * must be enforced.
  */
 @VintfStability
 parcelable KeyCharacteristics {
-    /* TODO(seleneh) get rid of the software enforced in keymint.  replace hardware enforced with
-     * tee enforced and strongbox enforced.
-     */
-    KeyParameter[] softwareEnforced;
-    KeyParameter[] hardwareEnforced;
+    SecurityLevel securityLevel;
+    KeyParameter[] authorizations;
 }
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
new file mode 100644
index 0000000..69bec2d7
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
@@ -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.
+ */
+
+package android.hardware.security.keymint;
+
+import android.hardware.security.keymint.Certificate;
+import android.hardware.security.keymint.KeyCharacteristics;
+
+/**
+ * This structure is returned when a new key is created with generateKey(), importKey() or
+ * importWrappedKey().
+ */
+@VintfStability
+parcelable KeyCreationResult {
+    /**
+     * `keyBlob` is an descriptor of the generated/imported key key.
+     */
+    byte[] keyBlob;
+
+    /**
+     * `keyCharacteristics` is a description of the generated key in the form of authorization lists
+     * associated with security levels.  The rules that IKeyMintDevice implementations must use for
+     * deciding whether a given tag from `keyParams` argument to the generation/import method should
+     * be returned in `keyCharacteristics` are:
+     *
+     * - If the semantics of the tag are fully enforced by the IKeyMintDevice, without any
+     *   assistance from components running at other security levels, it should be included in an
+     *   entry with the SecurityLevel of the IKeyMintDevice.
+     * - If the semantics of the tag are fully enforced, but with the assistance of components
+     *   running at another SecurityLevel, it should be included in an entry with the minimum
+     *   SecurityLevel of the involved components.  For example if a StrongBox IKeyMintDevice relies
+     *   on a TEE to validate biometric authentication, biometric authentication tags go in an entry
+     *   with SecurityLevel::TRUSTED_ENVIRONMENT.
+     * - If the semantics are not enforced by KeyMint at all, SecurityLevel::KEYSTORE is used to
+     *   indicate that Keystore should enforce.  Note that in Keymaster (predecessor to KeyMint),
+     *   these tags would have been in SecurityLevel::SOFTWARE.
+     */
+    KeyCharacteristics[] keyCharacteristics;
+
+    /**
+     * If the generated/imported key is an asymmetric key, `certificateChain` will contain a chain
+     * of one or more certificates.  If the key parameters provided to the generate/import method
+     * contains Tag::ATTESTATION_CHALLENGE the first certificate will contain an attestation
+     * extension, and will be signed by a factory-installed attestation key and followed by a chain
+     * of certificates leading to an authoritative root.  If there is no attestation challenge, only
+     * one certificate will be returned, and it will be self-signed or contain a fake signature,
+     * depending on whether the key has KeyPurpose::SIGN.  If the generated key is symmetric,
+     * certificateChain will be empty.
+     */
+    Certificate[] certificateChain;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
index d3d7368..2fcaf4c 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
@@ -22,6 +22,7 @@
  * KeyMintHardwareInfo is the hardware information returned by calling KeyMint getHardwareInfo()
  */
 @VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
 parcelable KeyMintHardwareInfo {
     /**
      * Implementation version of the keymint hardware.  The version number is implementation
@@ -44,4 +45,11 @@
      *         same author.
      */
     @utf8InCpp String keyMintAuthorName;
+
+    /* The timestampTokenRequired is a boolean flag, which when true reflects that IKeyMintDevice
+     * instance will expect a valid TimeStampToken with various operations. This will typically
+     * required by the StrongBox implementations that generally don't have secure clock hardware to
+     * generate timestamp tokens.
+     */
+    boolean timestampTokenRequired;
 }
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl
index cb4682e..978a027 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl
@@ -16,12 +16,11 @@
 
 package android.hardware.security.keymint;
 
-
 /**
  * Possible purposes of a key (or pair).
  */
 @VintfStability
-@Backing(type = "int")
+@Backing(type="int")
 enum KeyPurpose {
     /* Usable with RSA, EC and AES keys. */
     ENCRYPT = 0,
@@ -39,5 +38,10 @@
     /* Usable with wrapping keys. */
     WRAP_KEY = 5,
 
-    /* TODO(seleneh) add AGREE_KEY and ATTEST_KEY and their corresponding codes and tests later*/
+    /* Key Agreement, usable with EC keys. */
+    AGREE_KEY = 6,
+
+    /* Usable as an attestation signing key.  Keys with this purpose must not have any other
+     * purpose. */
+    ATTEST_KEY = 7,
 }
diff --git a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
new file mode 100644
index 0000000..da85a50
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+package android.hardware.security.keymint;
+
+/**
+ * MacedPublicKey contains a CBOR-encoded public key, MACed by an IRemotelyProvisionedComponent, to
+ * prove that the key pair was generated by that component.
+ */
+@VintfStability
+parcelable MacedPublicKey {
+    /**
+     * key is a COSE_Mac0 structure containing the new public key.  It's MACed by a key available
+     * only to the secure environment, as proof that the public key was generated by that
+     * environment. In CDDL, assuming the contained key is an Ed25519 public key:
+     *
+     *     MacedPublicKey = [                     // COSE_Mac0
+     *         protected: bstr .cbor { 1 : 5},    // Algorithm : HMAC-256
+     *         unprotected: bstr .size 0,
+     *         payload : bstr .cbor PublicKey,
+     *         tag : bstr HMAC-256(K_mac, MAC_structure)
+     *     ]
+     *
+     *     PublicKey = {               // COSE_Key
+     *         1 : 1,                  // Key type : octet key pair
+     *         3 : -8                  // Algorithm : EdDSA
+     *         -1 : 6,                 // Curve : Ed25519
+     *         -2 : bstr               // X coordinate, little-endian
+     *         ? -70000 : nil          // Presence indicates this is a test key.  If set, K_mac is
+     *                                 // all zeros.
+     *     },
+     *
+     *     MAC_structure = [
+     *         context : "MAC0",
+     *         protected : bstr .cbor { 1 : 5 },
+     *         external_aad : bstr .size 0,
+     *         payload : bstr .cbor PublicKey
+     *     ]
+     *
+     * if a non-Ed25519 public key were contained, the contents of the PublicKey map would change a
+     * little; see RFC 8152 for details.
+     */
+    byte[] macedKey;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
new file mode 100644
index 0000000..1ec3bf0
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
@@ -0,0 +1,169 @@
+/*
+ * 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.security.keymint;
+
+/**
+ * ProtectedData contains the encrypted BCC and the ephemeral MAC key used to
+ * authenticate the keysToSign (see keysToSignMac output argument).
+ */
+@VintfStability
+parcelable ProtectedData {
+    /**
+     * ProtectedData is a COSE_Encrypt structure, specified by the following CDDL
+     *
+     *     ProtectedData = [               // COSE_Encrypt
+     *         protected: bstr .cbor {
+     *             1 : 3                   // Algorithm : AES-GCM 256
+     *         },
+     *         unprotected: {
+     *             5 : bstr .size 12       // IV
+     *         },
+     *         ciphertext: bstr,           // AES-GCM-128(K, .cbor ProtectedDataPayload)
+     *         recipients : [
+     *             [                       // COSE_Recipient
+     *                 protected : bstr .cbor {
+     *                     1 : -25         // Algorithm : ECDH-ES + HKDF-256
+     *                 },
+     *                 unprotected : {
+     *                     -1 : {          // COSE_Key
+     *                         1 : 1,      // Key type : Octet Key Pair
+     *                         -1 : 4,     // Curve : X25519
+     *                         -2 : bstr   // Sender X25519 public key
+     *                     }
+     *                     4 : bstr,       // KID : EEK ID
+     *                 },
+     *                 ciphertext : nil
+     *             ]
+     *         ]
+     *     ]
+     *
+     *     K = HKDF-256(ECDH(EEK_pub, Ephemeral_priv), Context)
+     *
+     *     Context = [                     // COSE_KDF_Context
+     *         AlgorithmID : 3             // AES-GCM 256
+     *         PartyUInfo : [
+     *             identity : bstr "client"
+     *             nonce : bstr .size 0,
+     *             other : bstr            // Ephemeral pubkey
+     *         ],
+     *         PartyVInfo : [
+     *             identity : bstr "server",
+     *             nonce : bstr .size 0,
+     *             other : bstr            // EEK pubkey
+     *         ],
+     *         SuppPubInfo : [
+     *             128,                    // Output key length
+     *             protected : bstr .size 0
+     *         ]
+     *     ]
+     *
+     *     ProtectedDataPayload [
+     *         SignedMac,
+     *         Bcc,
+     *     ]
+     *
+     *     SignedMac = [                       // COSE_Sign1
+     *         bstr .cbor {                    // Protected params
+     *             1 : -8,                     // Algorithm : EdDSA
+     *         },
+     *         bstr .size 0,                   // Unprotected params
+     *         bstr .size 32,                  // MAC key
+     *         bstr PureEd25519(DK_priv, .cbor SignedMac_structure)
+     *     ]
+     *
+     *     SignedMac_structure = [
+     *         "Signature1",
+     *         bstr .cbor {                    // Protected params
+     *             1 : -8,                     // Algorithm : EdDSA
+     *         },
+     *         bstr .cbor SignedMacAad
+     *         bstr .size 32                   // MAC key
+     *     ]
+     *
+     *     SignedMacAad = [
+     *         challenge : bstr,
+     *         DeviceInfo
+     *     ]
+     *
+     *     Bcc = [
+     *         PubKey,                        // DK_pub
+     *         + BccEntry,                    // Root -> leaf (KM_pub)
+     *     ]
+     *
+     *     BccPayload = {                     // CWT
+     *         1 : tstr,                      // Issuer
+     *         2 : tstr,                      // Subject
+     *         // See the Open Profile for DICE for details on these fields.
+     *         ? -4670545 : bstr,             // Code Hash
+     *         ? -4670546 : bstr,             // Code Descriptor
+     *         ? -4670547 : bstr,             // Configuration Hash
+     *         ? -4670548 : bstr .cbor {      // Configuration Descriptor
+     *             ? -70002 : tstr,           // Component name
+     *             ? -70003 : int,            // Firmware version
+     *             ? -70004 : null,           // Resettable
+     *         },
+     *         ? -4670549 : bstr,             // Authority Hash
+     *         ? -4670550 : bstr,             // Authority Descriptor
+     *         ? -4670551 : bstr,             // Mode
+     *         -4670552 : bstr .cbor PubKey   // Subject Public Key
+     *         -4670553 : bstr                // Key Usage
+     *     }
+     *
+     *     BccEntry = [                       // COSE_Sign1
+     *         protected: bstr .cbor {
+     *             1 : -8,                    // Algorithm : EdDSA
+     *         },
+     *         unprotected: bstr .size 0,
+     *         payload: bstr .cbor BccPayload,
+     *         // First entry in the chain is signed by DK_pub, the others are each signed by their
+     *         // immediate predecessor.  See RFC 8032 for signature representation.
+     *         signature: bstr .cbor PureEd25519(SigningKey, bstr .cbor BccEntryInput)
+     *     ]
+     *
+     *     PubKey = {                         // COSE_Key
+     *         1 : 1,                         // Key type : octet key pair
+     *         3 : -8,                        // Algorithm : EdDSA
+     *         4 : 2,                         // Ops: Verify
+     *         -1 : 6,                        // Curve : Ed25519
+     *         -2 : bstr                      // X coordinate, little-endian
+     *     }
+     *
+     *     BccEntryInput = [
+     *         context: "Signature1",
+     *         protected: bstr .cbor {
+     *             1 : -8,                    // Algorithm : EdDSA
+     *         },
+     *         external_aad: bstr .size 0,
+     *         payload: bstr .cbor BccPayload
+     *     ]
+     *
+     *     DeviceInfo = {
+     *         ? "brand" : tstr,
+     *         ? "manufacturer" : tstr,
+     *         ? "product" : tstr,
+     *         ? "model" : tstr,
+     *         ? "board" : tstr,
+     *         ? "vb_state" : "green" / "yellow" / "orange",
+     *         ? "bootloader_state" : "locked" / "unlocked",
+     *         ? "os_version" : tstr,
+     *         ? "system_patch_level" : uint,        // YYYYMMDD
+     *         ? "boot_patch_level" : uint,          // YYYYMMDD
+     *         ? "vendor_patch_level" : uint,        // YYYYMMDD
+     *     }
+     */
+    byte[] protectedData;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/SecurityLevel.aidl b/security/keymint/aidl/android/hardware/security/keymint/SecurityLevel.aidl
index 10363e9..c63859c 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/SecurityLevel.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/SecurityLevel.aidl
@@ -17,16 +17,59 @@
 package android.hardware.security.keymint;
 
 /**
- * Device security levels.
+ * Device security levels.  These enum values are used in two ways:
+ *
+ * 1.  Returned from IKeyMintDevice::getHardwareInfo to identify the security level of the
+ *     IKeyMintDevice.  This characterizes the sort of environment in which the KeyMint
+ *     implementation runs, and therefore the security of its operations.
+ *
+ * 2.  Associated with individual KeyMint authorization Tags in KeyCharacteristics or in attestation
+ *     certificates.  This specifies the security level of the weakest environment involved in
+ *     enforcing that particular tag, i.e. the sort of security environment an attacker would have
+ *     to subvert in order to break the enforcement of that tag.
  */
 @VintfStability
 @Backing(type="int")
 enum SecurityLevel {
+    /**
+     * The SOFTWARE security level represents a KeyMint implementation that runs in an Android
+     * process, or a tag enforced by such an implementation.  An attacker who can compromise that
+     * process, or obtain root, or subvert the kernel on the device can defeat it.
+     *
+     * Note that the distinction between SOFTWARE and KEYSTORE is only relevant on-device.  For
+     * attestation purposes, these categories are combined into the software-enforced authorization
+     * list.
+     */
     SOFTWARE = 0,
+
+    /**
+     * The TRUSTED_ENVIRONMENT security level represents a KeyMint implementation that runs in an
+     * Android process, or a tag enforced by such an implementation.  An attacker who completely
+     * compromises Android, including the Linux kernel, does not have the ability to subvert it.  At
+     * attacker who can find an exploit that gains them control of the trusted environment, or who
+     * has access to the physical device and can mount a sophisticated hardware attack, may be able
+     * to defeat it.
+     */
     TRUSTED_ENVIRONMENT = 1,
     /**
-     * STRONGBOX specifies that the secure hardware satisfies the requirements specified in CDD
-     * 9.11.2.
+     * The STRONGBOX security level represents a KeyMint implementation that runs in security
+     * hardware that satisfies the requirements specified in CDD 9.11.2.  Roughly speaking, these
+     * are discrete, security-focus computing environments that are hardened against physical and
+     * side channel attack, and have had their security formally validated by a competent
+     * penetration testing lab.
      */
     STRONGBOX = 2,
+
+    /**
+     * KeyMint implementations must never return the KEYSTORE security level from getHardwareInfo.
+     * It is used to specify tags that are not enforced by the IKeyMintDevice, but are instead
+     * to be enforced by Keystore.  An attacker who can subvert the keystore process or gain root or
+     * subvert the kernel can prevent proper enforcement of these tags.
+     *
+     *
+     * Note that the distinction between SOFTWARE and KEYSTORE is only relevant on-device.  When
+     * KeyMint generates an attestation certificate, these categories are combined into the
+     * software-enforced authorization list.
+     */
+    KEYSTORE = 100
 }
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index 3bc3f16..4f58cbe 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -27,7 +27,7 @@
  * data are stored in KeyParameter.
  */
 @VintfStability
-@Backing(type = "int")
+@Backing(type="int")
 enum Tag {
     /**
      * Tag::INVALID should never be set.  It means you hit an error.
@@ -82,7 +82,6 @@
      */
     BLOCK_MODE = (2 << 28) /* TagType:ENUM_REP */ | 4,
 
-
     /**
      * Tag::DIGEST specifies the digest algorithms that may be used with the key to perform signing
      * and verification operations.  This tag is relevant to RSA, ECDSA and HMAC keys.  Possible
@@ -188,6 +187,22 @@
     INCLUDE_UNIQUE_ID = (7 << 28) /* TagType:BOOL */ | 202,
 
     /**
+     * Tag::RSA_OAEP_MGF_DIGEST specifies the MGF1 digest algorithms that may be used with
+     * RSA encryption/decryption with OAEP padding. If the key characteristics supports OAEP
+     * and this tag is absent then SHA1 digest is selected by default for MGF1.
+     *
+     * This tag is repeatable for key generation/import.  If this tag is present in the key
+     * characteristics with one or more values from @4.0::Digest, then for RSA cipher
+     * operations with OAEP Padding, the caller must specify a digest in the additionalParams
+     * argument of begin operation. If this tag is missing or the specified digest is not in
+     * the digests associated with the key then begin operation must fail with
+     * ErrorCode::INCOMPATIBLE_MGF_DIGEST.
+     *
+     * Must be hardware-enforced.
+     */
+    RSA_OAEP_MGF_DIGEST = (2 << 28) /* TagType:ENUM_REP */ | 203,
+
+    /**
      * TODO(seleneh) this tag needs to be deleted from all codes.
      *
      * Tag::BLOB_USAGE_REQUIREMENTS specifies the necessary system environment conditions for the
@@ -317,6 +332,35 @@
     MAX_USES_PER_BOOT = (3 << 28) /* TagType:UINT */ | 404,
 
     /**
+     * Tag::USAGE_COUNT_LIMIT specifies the number of times that a key may be used. This can be
+     * used to limit the use of a key.
+     *
+     * The value is a 32-bit integer representing the current number of attempts left.
+     *
+     * When initializing a limited use key, the value of this tag represents the maximum usage
+     * limit for that key. After the key usage is exhausted, the key blob should be invalidated by
+     * finish() call. Any subsequent attempts to use the key must result in a failure with
+     * ErrorCode::INVALID_KEY_BLOB returned by IKeyMintDevice.
+     *
+     * At this point, if the caller specifies count > 1, it is not expected that any TEE will be
+     * able to enforce this feature in the hardware due to limited resources of secure
+     * storage. In this case, the tag with the value of maximum usage must be added to the key
+     * characteristics with SecurityLevel::KEYSTORE by the IKeyMintDevice.
+     *
+     * On the other hand, if the caller specifies count = 1, some TEEs may have the ability
+     * to enforce this feature in the hardware with its secure storage. If the IKeyMintDevice
+     * implementation can enforce this feature, the tag with value = 1 must be added to the key
+     * characteristics with the SecurityLevel of the IKeyMintDevice. If the IKeyMintDevice can't
+     * enforce this feature even when the count = 1, the tag must be added to the key
+     * characteristics with the SecurityLevel::KEYSTORE.
+     *
+     * When the key is attested, this tag with the same value must also be added to the attestation
+     * record. This tag must have the same SecurityLevel as the tag that is added to the key
+     * characteristics.
+     */
+    USAGE_COUNT_LIMIT = (3 << 28) | 405, /* TagType:UINT */
+
+    /**
      * Tag::USER_ID specifies the ID of the Android user that is permitted to use the key.
      *
      * Must not be hardware-enforced.
@@ -452,7 +496,8 @@
      */
     TRUSTED_USER_PRESENCE_REQUIRED = (7 << 28) /* TagType:BOOL */ | 507,
 
-    /** Tag::TRUSTED_CONFIRMATION_REQUIRED is only applicable to keys with KeyPurpose SIGN, and
+    /**
+     * Tag::TRUSTED_CONFIRMATION_REQUIRED is only applicable to keys with KeyPurpose SIGN, and
      *  specifies that this key must not be usable unless the user provides confirmation of the data
      *  to be signed.  Confirmation is proven to keyMint via an approval token.  See
      *  CONFIRMATION_TOKEN, as well as the ConfirmatinUI HAL.
@@ -496,10 +541,10 @@
 
     /**
      * Tag::APPLICATION_DATA.  When provided to generateKey or importKey, this tag specifies data
-     * that is necessary during all uses of the key.  In particular, calls to exportKey() and
-     * getKeyCharacteristics() must provide the same value to the appData parameter, and calls to
-     * begin must provide this tag and the same associated data as part of the inParams set.  If
-     * the correct data is not provided, the method must return ErrorCode::INVALID_KEY_BLOB.
+     * that is necessary during all uses of the key.  In particular, calls to begin() and
+     * exportKey() must provide the same value to the appData parameter, and calls to begin must
+     * provide this tag and the same associated data as part of the inParams set.  If the correct
+     * data is not provided, the method must return ErrorCode::INVALID_KEY_BLOB.
      *
      * The content of this tag msut be bound to the key cryptographically, meaning it must not be
      * possible for an adversary who has access to all of the secure world secrets but does not have
@@ -888,4 +933,35 @@
      * Must never appear in KeyCharacteristics.
      */
     CONFIRMATION_TOKEN = (9 << 28) /* TagType:BYTES */ | 1005,
+
+    /**
+     * Tag::CERTIFICATE_SERIAL specifies the serial number to be assigned to the
+     * attestation certificate to be generated for the given key.  This parameter should only
+     * be passed to keyMint in the attestation parameters during generateKey() and importKey().
+     */
+    CERTIFICATE_SERIAL = (8 << 28) /* TagType:BIGNUM */ | 1006,
+
+    /**
+     * Tag::CERTIFICATE_SUBJECT the certificate subject. The value is a DER encoded X509 NAME.
+     * This value is used when generating a self signed certificates. This tag may be specified
+     * during generateKey and importKey. If not provided the subject name shall default to
+     * <TODO default subject here>.
+     */
+    CERTIFICATE_SUBJECT = (9 << 28) /* TagType:BYTES */ | 1007,
+
+    /**
+     * Tag::CERTIFICATE_NOT_BEFORE the beginning of the validity of the certificate in UNIX epoch
+     * time in seconds. This value is used when generating attestation or self signed certificates.
+     * ErrorCode::MISSING_NOT_BEFORE must be returned if this tag is not provided if this tag is
+     * not provided to generateKey or importKey.
+     */
+    CERTIFICATE_NOT_BEFORE = (6 << 28) /* TagType:DATE */ | 1008,
+
+    /**
+     * Tag::CERTIFICATE_NOT_AFTER the end of the validity of the certificate in UNIX epoch
+     * time in seconds. This value is used when generating attestation or self signed certificates.
+     * ErrorCode::MISSING_NOT_AFTER must be returned if this tag is not provided to generateKey
+     * or importKey.
+     */
+    CERTIFICATE_NOT_AFTER = (6 << 28) /* TagType:DATE */ | 1009,
 }
diff --git a/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl b/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl
deleted file mode 100644
index f76e6a8..0000000
--- a/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.
- */
-
-package android.hardware.security.keymint;
-
-import android.hardware.security.keymint.SecurityLevel;
-import android.hardware.security.keymint.Timestamp;
-
-/**
- * VerificationToken instances are used for secure environments to authenticate one another.
- *
- * This version of the parcelable currently don't use the parametersVerified field since it's not
- * needed for time-based verification. This can be added in a later version, if needed.
- */
-@VintfStability
-parcelable VerificationToken {
-    /**
-     * The operation handle, used to ensure freshness.
-     */
-    long challenge;
-
-    /**
-     * The current time of the secure environment that generates the VerificationToken.  This can be
-     * checked against auth tokens generated by the same secure environment, which avoids needing to
-     * synchronize clocks.
-     */
-    Timestamp timestamp;
-
-    /**
-     * SecurityLevel of the secure environment that generated the token.
-     */
-    SecurityLevel securityLevel;
-
-    /**
-     * 32-byte HMAC-SHA256 of the above values, computed as:
-     *
-     *    HMAC(H,
-     *         "Auth Verification" || challenge || timestamp || securityLevel)
-     *
-     * where:
-     *
-     *   ``HMAC'' is the shared HMAC key (see computeSharedHmac() in IKeyMint).
-     *
-     *   ``||'' represents concatenation
-     *
-     * The representation of challenge and timestamp is as 64-bit unsigned integers in big-endian
-     * order.  securityLevel is represented as a 32-bit unsigned integer in big-endian order.
-     */
-    byte[] mac;
-}
diff --git a/security/keymint/aidl/default/Android.bp b/security/keymint/aidl/default/Android.bp
index 79697c4..e160548 100644
--- a/security/keymint/aidl/default/Android.bp
+++ b/security/keymint/aidl/default/Android.bp
@@ -2,25 +2,57 @@
     name: "android.hardware.security.keymint-service",
     relative_install_path: "hw",
     init_rc: ["android.hardware.security.keymint-service.rc"],
-    vintf_fragments: ["android.hardware.security.keymint-service.xml"],
+    vintf_fragments: [
+        "android.hardware.security.keymint-service.xml",
+        "android.hardware.security.sharedsecret-service.xml",
+        "android.hardware.security.secureclock-service.xml",
+    ],
     vendor: true,
     cflags: [
         "-Wall",
         "-Wextra",
     ],
     shared_libs: [
-        "android.hardware.security.keymint-unstable-ndk_platform",
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "android.hardware.security.sharedsecret-V1-ndk_platform",
+        "android.hardware.security.secureclock-V1-ndk_platform",
         "libbase",
         "libbinder_ndk",
-        "libcppbor",
+        "libcppbor_external",
         "libcrypto",
         "libkeymaster_portable",
         "libkeymint",
         "liblog",
         "libpuresoftkeymasterdevice",
+        "libremote_provisioner",
         "libutils",
     ],
     srcs: [
         "service.cpp",
     ],
 }
+
+cc_library {
+    name: "libremote_provisioner",
+    vendor_available: true,
+    static_libs: [
+        "libkeymint_remote_prov_support",
+    ],
+    shared_libs: [
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "libbinder_ndk",
+        "libcppbor_external",
+        "libcppcose",
+        "libcrypto",
+        "libkeymaster_portable",
+        "libkeymint",
+        "liblog",
+        "libpuresoftkeymasterdevice",
+    ],
+    export_include_dirs: [
+        ".",
+    ],
+    srcs: [
+        "RemotelyProvisionedComponent.cpp",
+    ],
+}
diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
new file mode 100644
index 0000000..f2651fb
--- /dev/null
+++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
@@ -0,0 +1,430 @@
+/*
+ * 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 "RemotelyProvisionedComponent.h"
+
+#include <assert.h>
+#include <variant>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <KeyMintUtils.h>
+#include <cppcose/cppcose.h>
+#include <keymaster/keymaster_configuration.h>
+#include <remote_prov/remote_prov_utils.h>
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+
+namespace aidl::android::hardware::security::keymint {
+
+using ::std::string;
+using ::std::tuple;
+using ::std::unique_ptr;
+using ::std::variant;
+using ::std::vector;
+using bytevec = ::std::vector<uint8_t>;
+
+using namespace cppcose;
+using namespace keymaster;
+
+namespace {
+
+constexpr auto STATUS_FAILED = RemotelyProvisionedComponent::STATUS_FAILED;
+constexpr auto STATUS_INVALID_EEK = RemotelyProvisionedComponent::STATUS_INVALID_EEK;
+constexpr auto STATUS_INVALID_MAC = RemotelyProvisionedComponent::STATUS_INVALID_MAC;
+constexpr uint32_t kAffinePointLength = 32;
+struct AStatusDeleter {
+    void operator()(AStatus* p) { AStatus_delete(p); }
+};
+
+// TODO(swillden): Remove the dependency on AStatus stuff.  The COSE lib should use something like
+// StatusOr, but it shouldn't depend on AStatus.
+class Status {
+  public:
+    Status() {}
+    Status(int32_t errCode, const std::string& errMsg)
+        : status_(AStatus_fromServiceSpecificErrorWithMessage(errCode, errMsg.c_str())) {}
+    explicit Status(const std::string& errMsg)
+        : status_(AStatus_fromServiceSpecificErrorWithMessage(STATUS_FAILED, errMsg.c_str())) {}
+    Status(AStatus* status) : status_(status) {}
+    Status(Status&&) = default;
+    Status(const Status&) = delete;
+
+    operator ::ndk::ScopedAStatus() && { return ndk::ScopedAStatus(status_.release()); }
+
+    bool isOk() { return !status_; }
+
+    // Don't call getMessage() unless isOk() returns false;
+    const char* getMessage() const { return AStatus_getMessage(status_.get()); }
+
+  private:
+    std::unique_ptr<AStatus, AStatusDeleter> status_;
+};
+
+template <typename T>
+class StatusOr {
+  public:
+    StatusOr(AStatus* status) : status_(status) {}
+    StatusOr(Status status) : status_(std::move(status)) {}
+    StatusOr(T val) : value_(std::move(val)) {}
+
+    bool isOk() { return status_.isOk(); }
+
+    T* operator->() & {
+        assert(isOk());
+        return &value_.value();
+    }
+    T& operator*() & {
+        assert(isOk());
+        return value_.value();
+    }
+    T&& operator*() && {
+        assert(isOk());
+        return std::move(value_).value();
+    }
+
+    const char* getMessage() const {
+        assert(!isOk());
+        return status_.getMessage();
+    }
+
+    Status moveError() {
+        assert(!isOk());
+        return std::move(status_);
+    }
+
+    T moveValue() { return std::move(value_).value(); }
+
+  private:
+    Status status_;
+    std::optional<T> value_;
+};
+
+StatusOr<std::pair<bytevec /* EEK pub */, bytevec /* EEK ID */>> validateAndExtractEekPubAndId(
+        bool testMode, const bytevec& endpointEncryptionCertChain) {
+    auto [item, newPos, errMsg] = cppbor::parse(endpointEncryptionCertChain);
+
+    if (!item || !item->asArray()) {
+        return Status("Error parsing EEK chain" + errMsg);
+    }
+
+    const cppbor::Array* certArr = item->asArray();
+    bytevec lastPubKey;
+    for (int i = 0; i < certArr->size(); ++i) {
+        auto cosePubKey = verifyAndParseCoseSign1(testMode, certArr->get(i)->asArray(),
+                                                  std::move(lastPubKey), bytevec{} /* AAD */);
+        if (!cosePubKey) {
+            return Status(STATUS_INVALID_EEK,
+                          "Failed to validate EEK chain: " + cosePubKey.moveMessage());
+        }
+        lastPubKey = *std::move(cosePubKey);
+    }
+
+    auto eek = CoseKey::parseX25519(lastPubKey, true /* requireKid */);
+    if (!eek) return Status(STATUS_INVALID_EEK, "Failed to get EEK: " + eek.moveMessage());
+
+    return std::make_pair(eek->getBstrValue(CoseKey::PUBKEY_X).value(),
+                          eek->getBstrValue(CoseKey::KEY_ID).value());
+}
+
+StatusOr<bytevec /* pubkeys */> validateAndExtractPubkeys(bool testMode,
+                                                          const vector<MacedPublicKey>& keysToSign,
+                                                          const bytevec& macKey) {
+    auto pubKeysToMac = cppbor::Array();
+    for (auto& keyToSign : keysToSign) {
+        auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(keyToSign.macedKey);
+        if (!macedKeyItem || !macedKeyItem->asArray() ||
+            macedKeyItem->asArray()->size() != kCoseMac0EntryCount) {
+            return Status("Invalid COSE_Mac0 structure");
+        }
+
+        auto protectedParms = macedKeyItem->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
+        auto unprotectedParms = macedKeyItem->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+        auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr();
+        auto tag = macedKeyItem->asArray()->get(kCoseMac0Tag)->asBstr();
+        if (!protectedParms || !unprotectedParms || !payload || !tag) {
+            return Status("Invalid COSE_Mac0 contents");
+        }
+
+        auto [protectedMap, __, errMsg] = cppbor::parse(protectedParms);
+        if (!protectedMap || !protectedMap->asMap()) {
+            return Status("Invalid Mac0 protected: " + errMsg);
+        }
+        auto& algo = protectedMap->asMap()->get(ALGORITHM);
+        if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) {
+            return Status("Unsupported Mac0 algorithm");
+        }
+
+        auto pubKey = CoseKey::parse(payload->value(), EC2, ES256, P256);
+        if (!pubKey) return Status(pubKey.moveMessage());
+
+        bool testKey = static_cast<bool>(pubKey->getMap().get(CoseKey::TEST_KEY));
+        if (testMode && !testKey) {
+            return Status(BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST,
+                          "Production key in test request");
+        } else if (!testMode && testKey) {
+            return Status(BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST,
+                          "Test key in production request");
+        }
+
+        auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value());
+        if (!macTag) return Status(STATUS_INVALID_MAC, macTag.moveMessage());
+        if (macTag->size() != tag->value().size() ||
+            CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) {
+            return Status(STATUS_INVALID_MAC, "MAC tag mismatch");
+        }
+
+        pubKeysToMac.add(pubKey->moveMap());
+    }
+
+    return pubKeysToMac.encode();
+}
+
+StatusOr<std::pair<bytevec, bytevec>> buildCosePublicKeyFromKmCert(
+        const keymaster_blob_t* km_cert) {
+    if (km_cert == nullptr) {
+        return Status(STATUS_FAILED, "km_cert is a nullptr");
+    }
+    const uint8_t* temp = km_cert->data;
+    X509* cert = d2i_X509(NULL, &temp, km_cert->data_length);
+    if (cert == nullptr) {
+        return Status(STATUS_FAILED, "d2i_X509 returned null when attempting to get the cert.");
+    }
+    EVP_PKEY* pubKey = X509_get_pubkey(cert);
+    if (pubKey == nullptr) {
+        return Status(STATUS_FAILED, "Boringssl failed to get the public key from the cert");
+    }
+    EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(pubKey);
+    if (ecKey == nullptr) {
+        return Status(STATUS_FAILED,
+                      "The key in the certificate returned from GenerateKey is not "
+                      "an EC key.");
+    }
+    const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ecKey);
+    BIGNUM x;
+    BIGNUM y;
+    BN_CTX* ctx = BN_CTX_new();
+    if (ctx == nullptr) {
+        return Status(STATUS_FAILED, "Memory allocation failure for BN_CTX");
+    }
+    if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ecKey), jacobian_coords, &x, &y,
+                                             ctx)) {
+        return Status(STATUS_FAILED, "Failed to get affine coordinates");
+    }
+    bytevec x_bytestring(kAffinePointLength);
+    bytevec y_bytestring(kAffinePointLength);
+    if (BN_bn2binpad(&x, x_bytestring.data(), kAffinePointLength) != kAffinePointLength) {
+        return Status(STATUS_FAILED, "Wrote incorrect number of bytes for x coordinate");
+    }
+    if (BN_bn2binpad(&y, y_bytestring.data(), kAffinePointLength) != kAffinePointLength) {
+        return Status(STATUS_FAILED, "Wrote incorrect number of bytes for y coordinate");
+    }
+    BN_CTX_free(ctx);
+    return std::make_pair(x_bytestring, y_bytestring);
+}
+
+cppbor::Array buildCertReqRecipients(const bytevec& pubkey, const bytevec& kid) {
+    return cppbor::Array()                   // Array of recipients
+            .add(cppbor::Array()             // Recipient
+                         .add(cppbor::Map()  // Protected
+                                      .add(ALGORITHM, ECDH_ES_HKDF_256)
+                                      .canonicalize()
+                                      .encode())
+                         .add(cppbor::Map()  // Unprotected
+                                      .add(COSE_KEY, cppbor::Map()
+                                                             .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+                                                             .add(CoseKey::CURVE, cppcose::X25519)
+                                                             .add(CoseKey::PUBKEY_X, pubkey)
+                                                             .canonicalize())
+                                      .add(KEY_ID, kid)
+                                      .canonicalize())
+                         .add(cppbor::Null()));  // No ciphertext
+}
+
+static keymaster_key_param_t kKeyMintEcdsaP256Params[] = {
+        Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_ALGORITHM, KM_ALGORITHM_EC),
+        Authorization(TAG_KEY_SIZE, 256), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256),
+        Authorization(TAG_EC_CURVE, KM_EC_CURVE_P_256), Authorization(TAG_NO_AUTH_REQUIRED),
+        // The certificate generated by KM will be discarded, these values don't matter.
+        Authorization(TAG_CERTIFICATE_NOT_BEFORE, 0), Authorization(TAG_CERTIFICATE_NOT_AFTER, 0)};
+
+}  // namespace
+
+RemotelyProvisionedComponent::RemotelyProvisionedComponent(
+        std::shared_ptr<keymint::AndroidKeyMintDevice> keymint) {
+    std::tie(devicePrivKey_, bcc_) = generateBcc();
+    impl_ = keymint->getKeymasterImpl();
+}
+
+RemotelyProvisionedComponent::~RemotelyProvisionedComponent() {}
+
+ScopedAStatus RemotelyProvisionedComponent::generateEcdsaP256KeyPair(bool testMode,
+                                                                     MacedPublicKey* macedPublicKey,
+                                                                     bytevec* privateKeyHandle) {
+    // TODO(jbires): The following should move from ->GenerateKey to ->GenerateRKPKey and everything
+    //              after the GenerateKey call should basically be moved into that new function call
+    //              as well once the issue with libcppbor in system/keymaster is sorted out
+    GenerateKeyRequest request(impl_->message_version());
+    request.key_description.Reinitialize(kKeyMintEcdsaP256Params,
+                                         array_length(kKeyMintEcdsaP256Params));
+    GenerateKeyResponse response(impl_->message_version());
+    impl_->GenerateKey(request, &response);
+    if (response.error != KM_ERROR_OK) {
+        return km_utils::kmError2ScopedAStatus(response.error);
+    }
+
+    if (response.certificate_chain.entry_count != 1) {
+        // Error: Need the single non-signed certificate with the public key in it.
+        return Status(STATUS_FAILED,
+                      "Expected to receive a single certificate from GenerateKey. Instead got: " +
+                              std::to_string(response.certificate_chain.entry_count));
+    }
+    auto affineCoords = buildCosePublicKeyFromKmCert(response.certificate_chain.begin());
+    if (!affineCoords.isOk()) return affineCoords.moveError();
+    cppbor::Map cosePublicKeyMap = cppbor::Map()
+                                           .add(CoseKey::KEY_TYPE, EC2)
+                                           .add(CoseKey::ALGORITHM, ES256)
+                                           .add(CoseKey::CURVE, cppcose::P256)
+                                           .add(CoseKey::PUBKEY_X, affineCoords->first)
+                                           .add(CoseKey::PUBKEY_Y, affineCoords->second);
+    if (testMode) {
+        cosePublicKeyMap.add(CoseKey::TEST_KEY, cppbor::Null());
+    }
+
+    bytevec cosePublicKey = cosePublicKeyMap.canonicalize().encode();
+
+    auto macedKey = constructCoseMac0(testMode ? remote_prov::kTestMacKey : macKey_,
+                                      {} /* externalAad */, cosePublicKey);
+    if (!macedKey) return Status(macedKey.moveMessage());
+
+    macedPublicKey->macedKey = macedKey->encode();
+    *privateKeyHandle = km_utils::kmBlob2vector(response.key_blob);
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemotelyProvisionedComponent::generateCertificateRequest(
+        bool testMode, const vector<MacedPublicKey>& keysToSign,
+        const bytevec& endpointEncCertChain, const bytevec& challenge, bytevec* keysToSignMac,
+        ProtectedData* protectedData) {
+    auto pubKeysToSign = validateAndExtractPubkeys(testMode, keysToSign,
+                                                   testMode ? remote_prov::kTestMacKey : macKey_);
+    if (!pubKeysToSign.isOk()) return pubKeysToSign.moveError();
+
+    bytevec ephemeralMacKey = remote_prov::randomBytes(SHA256_DIGEST_LENGTH);
+
+    auto pubKeysToSignMac = generateCoseMac0Mac(ephemeralMacKey, bytevec{}, *pubKeysToSign);
+    if (!pubKeysToSignMac) return Status(pubKeysToSignMac.moveMessage());
+    *keysToSignMac = *std::move(pubKeysToSignMac);
+
+    bytevec devicePrivKey;
+    cppbor::Array bcc;
+    if (testMode) {
+        std::tie(devicePrivKey, bcc) = generateBcc();
+    } else {
+        devicePrivKey = devicePrivKey_;
+        bcc = bcc_.clone();
+    }
+
+    auto signedMac = constructCoseSign1(devicePrivKey /* Signing key */,  //
+                                        ephemeralMacKey /* Payload */,
+                                        cppbor::Array() /* AAD */
+                                                .add(challenge)
+                                                .add(createDeviceInfo())
+                                                .encode());
+    if (!signedMac) return Status(signedMac.moveMessage());
+
+    bytevec ephemeralPrivKey(X25519_PRIVATE_KEY_LEN);
+    bytevec ephemeralPubKey(X25519_PUBLIC_VALUE_LEN);
+    X25519_keypair(ephemeralPubKey.data(), ephemeralPrivKey.data());
+
+    auto eek = validateAndExtractEekPubAndId(testMode, endpointEncCertChain);
+    if (!eek.isOk()) return eek.moveError();
+
+    auto sessionKey = x25519_HKDF_DeriveKey(ephemeralPubKey, ephemeralPrivKey, eek->first,
+                                            true /* senderIsA */);
+    if (!sessionKey) return Status(sessionKey.moveMessage());
+
+    auto coseEncrypted =
+            constructCoseEncrypt(*sessionKey, remote_prov::randomBytes(kAesGcmNonceLength),
+                                 cppbor::Array()  // payload
+                                         .add(signedMac.moveValue())
+                                         .add(std::move(bcc))
+                                         .encode(),
+                                 {},  // aad
+                                 buildCertReqRecipients(ephemeralPubKey, eek->second));
+
+    if (!coseEncrypted) return Status(coseEncrypted.moveMessage());
+    protectedData->protectedData = coseEncrypted->encode();
+
+    return ScopedAStatus::ok();
+}
+
+bytevec RemotelyProvisionedComponent::deriveBytesFromHbk(const string& context,
+                                                         size_t numBytes) const {
+    bytevec fakeHbk(32, 0);
+    bytevec result(numBytes);
+
+    // TODO(swillden): Figure out if HKDF can fail.  It doesn't seem like it should be able to,
+    // but the function does return an error code.
+    HKDF(result.data(), numBytes,               //
+         EVP_sha256(),                          //
+         fakeHbk.data(), fakeHbk.size(),        //
+         nullptr /* salt */, 0 /* salt len */,  //
+         reinterpret_cast<const uint8_t*>(context.data()), context.size());
+
+    return result;
+}
+
+bytevec RemotelyProvisionedComponent::createDeviceInfo() const {
+    return cppbor::Map().encode();
+}
+
+std::pair<bytevec /* privKey */, cppbor::Array /* BCC */>
+RemotelyProvisionedComponent::generateBcc() {
+    bytevec privKey(ED25519_PRIVATE_KEY_LEN);
+    bytevec pubKey(ED25519_PUBLIC_KEY_LEN);
+
+    ED25519_keypair(pubKey.data(), privKey.data());
+
+    auto coseKey = cppbor::Map()
+                           .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+                           .add(CoseKey::ALGORITHM, EDDSA)
+                           .add(CoseKey::CURVE, ED25519)
+                           .add(CoseKey::KEY_OPS, VERIFY)
+                           .add(CoseKey::PUBKEY_X, pubKey)
+                           .canonicalize()
+                           .encode();
+    auto sign1Payload = cppbor::Map()
+                                .add(1 /* Issuer */, "Issuer")
+                                .add(2 /* Subject */, "Subject")
+                                .add(-4670552 /* Subject Pub Key */, coseKey)
+                                .add(-4670553 /* Key Usage */,
+                                     std::vector<uint8_t>(0x05) /* Big endian order */)
+                                .canonicalize()
+                                .encode();
+    auto coseSign1 = constructCoseSign1(privKey,       /* signing key */
+                                        cppbor::Map(), /* extra protected */
+                                        sign1Payload, {} /* AAD */);
+    assert(coseSign1);
+
+    return {privKey, cppbor::Array().add(coseKey).add(coseSign1.moveValue())};
+}
+
+}  // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.h b/security/keymint/aidl/default/RemotelyProvisionedComponent.h
new file mode 100644
index 0000000..e8d2343
--- /dev/null
+++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <AndroidKeyMintDevice.h>
+#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+#include <cppbor.h>
+#include <keymaster/UniquePtr.h>
+#include <keymaster/android_keymaster.h>
+
+namespace aidl::android::hardware::security::keymint {
+
+using ::ndk::ScopedAStatus;
+
+class RemotelyProvisionedComponent : public BnRemotelyProvisionedComponent {
+  public:
+    explicit RemotelyProvisionedComponent(std::shared_ptr<keymint::AndroidKeyMintDevice> keymint);
+    virtual ~RemotelyProvisionedComponent();
+
+    ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey,
+                                           std::vector<uint8_t>* privateKeyHandle) override;
+
+    ScopedAStatus generateCertificateRequest(bool testMode,
+                                             const std::vector<MacedPublicKey>& keysToSign,
+                                             const std::vector<uint8_t>& endpointEncCertChain,
+                                             const std::vector<uint8_t>& challenge,
+                                             std::vector<uint8_t>* keysToSignMac,
+                                             ProtectedData* protectedData) override;
+
+  private:
+    // TODO(swillden): Move these into an appropriate Context class.
+    std::vector<uint8_t> deriveBytesFromHbk(const std::string& context, size_t numBytes) const;
+    std::vector<uint8_t> createDeviceInfo() const;
+    std::pair<std::vector<uint8_t>, cppbor::Array> generateBcc();
+
+    std::vector<uint8_t> macKey_ = deriveBytesFromHbk("Key to MAC public keys", 32);
+    std::vector<uint8_t> devicePrivKey_;
+    cppbor::Array bcc_;
+    std::shared_ptr<::keymaster::AndroidKeymaster> impl_;
+};
+
+}  // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/default/android.hardware.security.keymint-service.xml b/security/keymint/aidl/default/android.hardware.security.keymint-service.xml
index 73d15a8..4aa05ef 100644
--- a/security/keymint/aidl/default/android.hardware.security.keymint-service.xml
+++ b/security/keymint/aidl/default/android.hardware.security.keymint-service.xml
@@ -3,4 +3,8 @@
         <name>android.hardware.security.keymint</name>
         <fqname>IKeyMintDevice/default</fqname>
     </hal>
+    <hal format="aidl">
+        <name>android.hardware.security.keymint</name>
+        <fqname>IRemotelyProvisionedComponent/default</fqname>
+    </hal>
 </manifest>
diff --git a/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml b/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml
new file mode 100644
index 0000000..c0ff775
--- /dev/null
+++ b/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.security.secureclock</name>
+        <fqname>ISecureClock/default</fqname>
+    </hal>
+</manifest>
diff --git a/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml b/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml
new file mode 100644
index 0000000..d37981f
--- /dev/null
+++ b/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.security.sharedsecret</name>
+        <fqname>ISharedSecret/default</fqname>
+    </hal>
+</manifest>
diff --git a/security/keymint/aidl/default/service.cpp b/security/keymint/aidl/default/service.cpp
index a710535..bcebbaf 100644
--- a/security/keymint/aidl/default/service.cpp
+++ b/security/keymint/aidl/default/service.cpp
@@ -21,25 +21,42 @@
 #include <android/binder_process.h>
 
 #include <AndroidKeyMintDevice.h>
+#include <AndroidSecureClock.h>
+#include <AndroidSharedSecret.h>
 #include <keymaster/soft_keymaster_logger.h>
 
+#include "RemotelyProvisionedComponent.h"
+
 using aidl::android::hardware::security::keymint::AndroidKeyMintDevice;
+using aidl::android::hardware::security::keymint::RemotelyProvisionedComponent;
 using aidl::android::hardware::security::keymint::SecurityLevel;
+using aidl::android::hardware::security::secureclock::AndroidSecureClock;
+using aidl::android::hardware::security::sharedsecret::AndroidSharedSecret;
+
+template <typename T, class... Args>
+std::shared_ptr<T> addService(Args&&... args) {
+    std::shared_ptr<T> ser = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
+    auto instanceName = std::string(T::descriptor) + "/default";
+    LOG(INFO) << "adding keymint service instance: " << instanceName;
+    binder_status_t status =
+            AServiceManager_addService(ser->asBinder().get(), instanceName.c_str());
+    CHECK(status == STATUS_OK);
+    return ser;
+}
 
 int main() {
     // Zero threads seems like a useless pool, but below we'll join this thread to it, increasing
     // the pool size to 1.
     ABinderProcess_setThreadPoolMaxThreadCount(0);
+    // Add Keymint Service
     std::shared_ptr<AndroidKeyMintDevice> keyMint =
-            ndk::SharedRefBase::make<AndroidKeyMintDevice>(SecurityLevel::SOFTWARE);
-
-    keymaster::SoftKeymasterLogger logger;
-    const auto instanceName = std::string(AndroidKeyMintDevice::descriptor) + "/default";
-    LOG(INFO) << "instance: " << instanceName;
-    binder_status_t status =
-            AServiceManager_addService(keyMint->asBinder().get(), instanceName.c_str());
-    CHECK(status == STATUS_OK);
-
+            addService<AndroidKeyMintDevice>(SecurityLevel::SOFTWARE);
+    // Add Secure Clock Service
+    addService<AndroidSecureClock>(keyMint);
+    // Add Shared Secret Service
+    addService<AndroidSharedSecret>(keyMint);
+    // Add Remotely Provisioned Component Service
+    addService<RemotelyProvisionedComponent>(keyMint);
     ABinderProcess_joinThreadPool();
     return EXIT_FAILURE;  // should not reach
 }
diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp
index c7cc380..24fe616 100644
--- a/security/keymint/aidl/vts/functional/Android.bp
+++ b/security/keymint/aidl/vts/functional/Android.bp
@@ -21,8 +21,8 @@
         "use_libaidlvintf_gtest_helper_static",
     ],
     srcs: [
+        "AttestKeyTest.cpp",
         "KeyMintTest.cpp",
-        "VerificationTokenTest.cpp",
     ],
     shared_libs: [
         "libbinder_ndk",
@@ -31,7 +31,8 @@
         "libkeymint_support",
     ],
     static_libs: [
-        "android.hardware.security.keymint-unstable-ndk_platform",
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "android.hardware.security.secureclock-V1-ndk_platform",
         "libcppbor_external",
         "libkeymint_vts_test_utils",
     ],
@@ -60,7 +61,38 @@
         "libkeymint_support",
     ],
     static_libs: [
-        "android.hardware.security.keymint-unstable-ndk_platform",
-        "libcppbor",
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "android.hardware.security.secureclock-V1-ndk_platform",
+        "libcppbor_external",
+    ],
+}
+
+cc_test {
+    name: "VtsHalRemotelyProvisionedComponentTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "VtsRemotelyProvisionedComponentTests.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libcppbor_external",
+        "libcrypto",
+        "libkeymaster_portable",
+        "libpuresoftkeymasterdevice",
+    ],
+    static_libs: [
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "libcppcose",
+        "libgmock_ndk",
+        "libremote_provisioner",
+        "libkeymint",
+        "libkeymint_remote_prov_support",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
     ],
 }
diff --git a/security/keymint/aidl/vts/functional/AttestKeyTest.cpp b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp
new file mode 100644
index 0000000..7e7a466
--- /dev/null
+++ b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp
@@ -0,0 +1,235 @@
+/*
+ * 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 "keymint_1_attest_key_test"
+#include <cutils/log.h>
+
+#include <keymint_support/key_param_output.h>
+#include <keymint_support/openssl_utils.h>
+
+#include "KeyMintAidlTestBase.h"
+
+namespace aidl::android::hardware::security::keymint::test {
+
+namespace {
+
+vector<uint8_t> make_name_from_str(const string& name) {
+    X509_NAME_Ptr x509_name(X509_NAME_new());
+    EXPECT_TRUE(x509_name.get() != nullptr);
+    if (!x509_name) return {};
+
+    EXPECT_EQ(1, X509_NAME_add_entry_by_txt(x509_name.get(),  //
+                                            "CN",             //
+                                            MBSTRING_ASC,
+                                            reinterpret_cast<const uint8_t*>(name.c_str()),
+                                            -1,  // len
+                                            -1,  // loc
+                                            0 /* set */));
+
+    int len = i2d_X509_NAME(x509_name.get(), nullptr /* only return length */);
+    EXPECT_GT(len, 0);
+
+    vector<uint8_t> retval(len);
+    uint8_t* p = retval.data();
+    i2d_X509_NAME(x509_name.get(), &p);
+
+    return retval;
+}
+
+bool IsSelfSigned(const vector<Certificate>& chain) {
+    if (chain.size() != 1) return false;
+    return ChainSignaturesAreValid(chain);
+}
+
+}  // namespace
+
+using AttestKeyTest = KeyMintAidlTestBase;
+
+TEST_P(AttestKeyTest, AllRsaSizes) {
+    for (auto size : ValidKeySizes(Algorithm::RSA)) {
+        /*
+         * Create attestaton key.
+         */
+        AttestationKey attest_key;
+        vector<KeyCharacteristics> attest_key_characteristics;
+        vector<Certificate> attest_key_cert_chain;
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .RsaSigningKey(size, 65537)
+                                                     .AttestKey()
+                                                     .SetDefaultValidity(),
+                                             {} /* attestation signing key */, &attest_key.keyBlob,
+                                             &attest_key_characteristics, &attest_key_cert_chain));
+
+        EXPECT_EQ(attest_key_cert_chain.size(), 1);
+        EXPECT_TRUE(IsSelfSigned(attest_key_cert_chain)) << "Failed on size " << size;
+
+        /*
+         * Use attestation key to sign RSA key
+         */
+        attest_key.issuerSubjectName = make_name_from_str("Android Keystore Key");
+        vector<uint8_t> attested_key_blob;
+        vector<KeyCharacteristics> attested_key_characteristics;
+        vector<Certificate> attested_key_cert_chain;
+        EXPECT_EQ(ErrorCode::OK,
+                  GenerateKey(AuthorizationSetBuilder()
+                                      .RsaSigningKey(2048, 65537)
+                                      .Authorization(TAG_NO_AUTH_REQUIRED)
+                                      .AttestationChallenge("foo")
+                                      .AttestationApplicationId("bar")
+                                      .SetDefaultValidity(),
+                              attest_key, &attested_key_blob, &attested_key_characteristics,
+                              &attested_key_cert_chain));
+
+        CheckedDeleteKey(&attested_key_blob);
+
+        AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
+        AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
+        EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(),
+                                              attested_key_cert_chain[0].encodedCertificate));
+
+        // Attestation by itself is not valid (last entry is not self-signed).
+        EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+        // Appending the attest_key chain to the attested_key_chain should yield a valid chain.
+        if (attest_key_cert_chain.size() > 0) {
+            attested_key_cert_chain.push_back(attest_key_cert_chain[0]);
+        }
+        EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+        /*
+         * Use attestation key to sign EC key
+         */
+        EXPECT_EQ(ErrorCode::OK,
+                  GenerateKey(AuthorizationSetBuilder()
+                                      .EcdsaSigningKey(EcCurve::P_256)
+                                      .Authorization(TAG_NO_AUTH_REQUIRED)
+                                      .AttestationChallenge("foo")
+                                      .AttestationApplicationId("bar")
+                                      .SetDefaultValidity(),
+                              attest_key, &attested_key_blob, &attested_key_characteristics,
+                              &attested_key_cert_chain));
+
+        CheckedDeleteKey(&attested_key_blob);
+        CheckedDeleteKey(&attest_key.keyBlob);
+
+        hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
+        sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
+        EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(),
+                                              attested_key_cert_chain[0].encodedCertificate));
+
+        // Attestation by itself is not valid (last entry is not self-signed).
+        EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+        // Appending the attest_key chain to the attested_key_chain should yield a valid chain.
+        if (attest_key_cert_chain.size() > 0) {
+            attested_key_cert_chain.push_back(attest_key_cert_chain[0]);
+        }
+        EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+        // Bail early if anything failed.
+        if (HasFailure()) return;
+    }
+}
+
+TEST_P(AttestKeyTest, AllEcCurves) {
+    for (auto curve : ValidCurves()) {
+        /*
+         * Create attestaton key.
+         */
+        AttestationKey attest_key;
+        vector<KeyCharacteristics> attest_key_characteristics;
+        vector<Certificate> attest_key_cert_chain;
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .EcdsaSigningKey(curve)
+                                                     .AttestKey()
+                                                     .SetDefaultValidity(),
+                                             {} /* attestation siging key */, &attest_key.keyBlob,
+                                             &attest_key_characteristics, &attest_key_cert_chain));
+
+        EXPECT_EQ(attest_key_cert_chain.size(), 1);
+        EXPECT_TRUE(IsSelfSigned(attest_key_cert_chain)) << "Failed on curve " << curve;
+
+        /*
+         * Use attestation key to sign RSA key
+         */
+        attest_key.issuerSubjectName = make_name_from_str("Android Keystore Key");
+        vector<uint8_t> attested_key_blob;
+        vector<KeyCharacteristics> attested_key_characteristics;
+        vector<Certificate> attested_key_cert_chain;
+        EXPECT_EQ(ErrorCode::OK,
+                  GenerateKey(AuthorizationSetBuilder()
+                                      .RsaSigningKey(2048, 65537)
+                                      .Authorization(TAG_NO_AUTH_REQUIRED)
+                                      .AttestationChallenge("foo")
+                                      .AttestationApplicationId("bar")
+                                      .SetDefaultValidity(),
+                              attest_key, &attested_key_blob, &attested_key_characteristics,
+                              &attested_key_cert_chain));
+
+        CheckedDeleteKey(&attested_key_blob);
+
+        AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
+        AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
+        EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(),
+                                              attested_key_cert_chain[0].encodedCertificate));
+
+        // Attestation by itself is not valid (last entry is not self-signed).
+        EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+        // Appending the attest_key chain to the attested_key_chain should yield a valid chain.
+        if (attest_key_cert_chain.size() > 0) {
+            attested_key_cert_chain.push_back(attest_key_cert_chain[0]);
+        }
+        EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+        /*
+         * Use attestation key to sign EC key
+         */
+        EXPECT_EQ(ErrorCode::OK,
+                  GenerateKey(AuthorizationSetBuilder()
+                                      .EcdsaSigningKey(EcCurve::P_256)
+                                      .Authorization(TAG_NO_AUTH_REQUIRED)
+                                      .AttestationChallenge("foo")
+                                      .AttestationApplicationId("bar")
+                                      .SetDefaultValidity(),
+                              attest_key, &attested_key_blob, &attested_key_characteristics,
+                              &attested_key_cert_chain));
+
+        CheckedDeleteKey(&attested_key_blob);
+        CheckedDeleteKey(&attest_key.keyBlob);
+
+        hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
+        sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
+        EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(),
+                                              attested_key_cert_chain[0].encodedCertificate));
+
+        // Attestation by itself is not valid (last entry is not self-signed).
+        EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+        // Appending the attest_key chain to the attested_key_chain should yield a valid chain.
+        if (attest_key_cert_chain.size() > 0) {
+            attested_key_cert_chain.push_back(attest_key_cert_chain[0]);
+        }
+        EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+        // Bail early if anything failed.
+        if (HasFailure()) return;
+    }
+}
+
+INSTANTIATE_KEYMINT_AIDL_TEST(AttestKeyTest);
+
+}  // namespace aidl::android::hardware::security::keymint::test
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
index 94bc199..d61a081 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
@@ -17,32 +17,131 @@
 #include "KeyMintAidlTestBase.h"
 
 #include <chrono>
+#include <unordered_set>
 #include <vector>
 
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
+#include <cutils/properties.h>
+#include <openssl/mem.h>
 
+#include <keymint_support/attestation_record.h>
 #include <keymint_support/key_param_output.h>
 #include <keymint_support/keymint_utils.h>
+#include <keymint_support/openssl_utils.h>
 
 namespace aidl::android::hardware::security::keymint {
 
 using namespace std::literals::chrono_literals;
 using std::endl;
 using std::optional;
+using std::unique_ptr;
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
 
 ::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set) {
     if (set.size() == 0)
         os << "(Empty)" << ::std::endl;
     else {
         os << "\n";
-        for (size_t i = 0; i < set.size(); ++i) os << set[i] << ::std::endl;
+        for (auto& entry : set) os << entry << ::std::endl;
     }
     return os;
 }
 
 namespace test {
 
+namespace {
+typedef KeyMintAidlTestBase::KeyData KeyData;
+// Predicate for testing basic characteristics validity in generation or import.
+bool KeyCharacteristicsBasicallyValid(SecurityLevel secLevel,
+                                      const vector<KeyCharacteristics>& key_characteristics) {
+    if (key_characteristics.empty()) return false;
+
+    std::unordered_set<SecurityLevel> levels_seen;
+    for (auto& entry : key_characteristics) {
+        if (entry.authorizations.empty()) return false;
+
+        // Just ignore the SecurityLevel::KEYSTORE as the KM won't do any enforcement on this.
+        if (entry.securityLevel == SecurityLevel::KEYSTORE) continue;
+
+        if (levels_seen.find(entry.securityLevel) != levels_seen.end()) return false;
+        levels_seen.insert(entry.securityLevel);
+
+        // Generally, we should only have one entry, at the same security level as the KM
+        // instance.  There is an exception: StrongBox KM can have some authorizations that are
+        // enforced by the TEE.
+        bool isExpectedSecurityLevel = secLevel == entry.securityLevel ||
+                                       (secLevel == SecurityLevel::STRONGBOX &&
+                                        entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT);
+
+        if (!isExpectedSecurityLevel) return false;
+    }
+    return true;
+}
+
+// Extract attestation record from cert. Returned object is still part of cert; don't free it
+// separately.
+ASN1_OCTET_STRING* get_attestation_record(X509* certificate) {
+    ASN1_OBJECT_Ptr oid(OBJ_txt2obj(kAttestionRecordOid, 1 /* dotted string format */));
+    EXPECT_TRUE(!!oid.get());
+    if (!oid.get()) return nullptr;
+
+    int location = X509_get_ext_by_OBJ(certificate, oid.get(), -1 /* search from beginning */);
+    EXPECT_NE(-1, location) << "Attestation extension not found in certificate";
+    if (location == -1) return nullptr;
+
+    X509_EXTENSION* attest_rec_ext = X509_get_ext(certificate, location);
+    EXPECT_TRUE(!!attest_rec_ext)
+            << "Found attestation extension but couldn't retrieve it?  Probably a BoringSSL bug.";
+    if (!attest_rec_ext) return nullptr;
+
+    ASN1_OCTET_STRING* attest_rec = X509_EXTENSION_get_data(attest_rec_ext);
+    EXPECT_TRUE(!!attest_rec) << "Attestation extension contained no data";
+    return attest_rec;
+}
+
+bool avb_verification_enabled() {
+    char value[PROPERTY_VALUE_MAX];
+    return property_get("ro.boot.vbmeta.device_state", value, "") != 0;
+}
+
+char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
+                       '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+// Attestations don't contain everything in key authorization lists, so we need to filter the key
+// lists to produce the lists that we expect to match the attestations.
+auto kTagsToFilter = {
+        Tag::BLOB_USAGE_REQUIREMENTS,  //
+        Tag::CREATION_DATETIME,        //
+        Tag::EC_CURVE,
+        Tag::HARDWARE_TYPE,
+        Tag::INCLUDE_UNIQUE_ID,
+};
+
+AuthorizationSet filtered_tags(const AuthorizationSet& set) {
+    AuthorizationSet filtered;
+    std::remove_copy_if(
+            set.begin(), set.end(), std::back_inserter(filtered), [](const auto& entry) -> bool {
+                return std::find(kTagsToFilter.begin(), kTagsToFilter.end(), entry.tag) !=
+                       kTagsToFilter.end();
+            });
+    return filtered;
+}
+
+string x509NameToStr(X509_NAME* name) {
+    char* s = X509_NAME_oneline(name, nullptr, 0);
+    string retval(s);
+    OPENSSL_free(s);
+    return retval;
+}
+
+}  // namespace
+
+bool KeyMintAidlTestBase::arm_deleteAllKeys = false;
+bool KeyMintAidlTestBase::dump_Attestations = false;
+
 ErrorCode KeyMintAidlTestBase::GetReturnErrorCode(const Status& result) {
     if (result.isOk()) return ErrorCode::OK;
 
@@ -78,65 +177,83 @@
 }
 
 ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc,
-                                           vector<uint8_t>* keyBlob, KeyCharacteristics* keyChar) {
-    EXPECT_NE(keyBlob, nullptr) << "Key blob pointer must not be null.  Test bug";
-    EXPECT_NE(keyChar, nullptr)
+                                           const optional<AttestationKey>& attest_key,
+                                           vector<uint8_t>* key_blob,
+                                           vector<KeyCharacteristics>* key_characteristics,
+                                           vector<Certificate>* cert_chain) {
+    EXPECT_NE(key_blob, nullptr) << "Key blob pointer must not be null.  Test bug";
+    EXPECT_NE(key_characteristics, nullptr)
             << "Previous characteristics not deleted before generating key.  Test bug.";
 
-    // Aidl does not clear these output parameters if the function returns
-    // error.  This is different from hal where output parameter is always
-    // cleared due to hal returning void.  So now we need to do our own clearing
-    // of the output variables prior to calling keyMint aidl libraries.
-    keyBlob->clear();
-    keyChar->softwareEnforced.clear();
-    keyChar->hardwareEnforced.clear();
-    certChain_.clear();
-
-    Status result;
-    ByteArray blob;
-
-    result = keymint_->generateKey(key_desc.vector_data(), &blob, keyChar, &certChain_);
-
-    // On result, blob & characteristics should be empty.
+    KeyCreationResult creationResult;
+    Status result = keymint_->generateKey(key_desc.vector_data(), attest_key, &creationResult);
     if (result.isOk()) {
-        if (SecLevel() != SecurityLevel::SOFTWARE) {
-            EXPECT_GT(keyChar->hardwareEnforced.size(), 0);
+        EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+                     creationResult.keyCharacteristics);
+        EXPECT_GT(creationResult.keyBlob.size(), 0);
+        *key_blob = std::move(creationResult.keyBlob);
+        *key_characteristics = std::move(creationResult.keyCharacteristics);
+        *cert_chain = std::move(creationResult.certificateChain);
+
+        auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM);
+        EXPECT_TRUE(algorithm);
+        if (algorithm &&
+            (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+            EXPECT_GE(cert_chain->size(), 1);
+            if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) {
+                if (attest_key) {
+                    EXPECT_EQ(cert_chain->size(), 1);
+                } else {
+                    EXPECT_GT(cert_chain->size(), 1);
+                }
+            }
+        } else {
+            // For symmetric keys there should be no certificates.
+            EXPECT_EQ(cert_chain->size(), 0);
         }
-        EXPECT_GT(keyChar->softwareEnforced.size(), 0);
-        // TODO(seleneh) in a later version where we return @nullable
-        // single Certificate, check non-null single certificate is always
-        // non-empty.
-        *keyBlob = blob.data;
     }
 
     return GetReturnErrorCode(result);
 }
 
-ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc) {
-    return GenerateKey(key_desc, &key_blob_, &key_characteristics_);
+ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc,
+                                           const optional<AttestationKey>& attest_key) {
+    return GenerateKey(key_desc, attest_key, &key_blob_, &key_characteristics_, &cert_chain_);
 }
 
 ErrorCode KeyMintAidlTestBase::ImportKey(const AuthorizationSet& key_desc, KeyFormat format,
                                          const string& key_material, vector<uint8_t>* key_blob,
-                                         KeyCharacteristics* key_characteristics) {
+                                         vector<KeyCharacteristics>* key_characteristics) {
     Status result;
 
-    certChain_.clear();
-    key_characteristics->softwareEnforced.clear();
-    key_characteristics->hardwareEnforced.clear();
+    cert_chain_.clear();
+    key_characteristics->clear();
     key_blob->clear();
 
-    ByteArray blob;
+    KeyCreationResult creationResult;
     result = keymint_->importKey(key_desc.vector_data(), format,
-                                 vector<uint8_t>(key_material.begin(), key_material.end()), &blob,
-                                 key_characteristics, &certChain_);
+                                 vector<uint8_t>(key_material.begin(), key_material.end()),
+                                 {} /* attestationSigningKeyBlob */, &creationResult);
 
     if (result.isOk()) {
-        if (SecLevel() != SecurityLevel::SOFTWARE) {
-            EXPECT_GT(key_characteristics->hardwareEnforced.size(), 0);
+        EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+                     creationResult.keyCharacteristics);
+        EXPECT_GT(creationResult.keyBlob.size(), 0);
+
+        *key_blob = std::move(creationResult.keyBlob);
+        *key_characteristics = std::move(creationResult.keyCharacteristics);
+        cert_chain_ = std::move(creationResult.certificateChain);
+
+        auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM);
+        EXPECT_TRUE(algorithm);
+        if (algorithm &&
+            (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+            EXPECT_GE(cert_chain_.size(), 1);
+            if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) EXPECT_GT(cert_chain_.size(), 1);
+        } else {
+            // For symmetric keys there should be no certificates.
+            EXPECT_EQ(cert_chain_.size(), 0);
         }
-        EXPECT_GT(key_characteristics->softwareEnforced.size(), 0);
-        *key_blob = blob.data;
     }
 
     return GetReturnErrorCode(result);
@@ -151,25 +268,39 @@
                                                 const AuthorizationSet& wrapping_key_desc,
                                                 string masking_key,
                                                 const AuthorizationSet& unwrapping_params) {
-    Status result;
     EXPECT_EQ(ErrorCode::OK, ImportKey(wrapping_key_desc, KeyFormat::PKCS8, wrapping_key));
 
-    ByteArray outBlob;
-    key_characteristics_.softwareEnforced.clear();
-    key_characteristics_.hardwareEnforced.clear();
+    key_characteristics_.clear();
 
-    result = keymint_->importWrappedKey(vector<uint8_t>(wrapped_key.begin(), wrapped_key.end()),
-                                        key_blob_,
-                                        vector<uint8_t>(masking_key.begin(), masking_key.end()),
-                                        unwrapping_params.vector_data(), 0 /* passwordSid */,
-                                        0 /* biometricSid */, &outBlob, &key_characteristics_);
+    KeyCreationResult creationResult;
+    Status result = keymint_->importWrappedKey(
+            vector<uint8_t>(wrapped_key.begin(), wrapped_key.end()), key_blob_,
+            vector<uint8_t>(masking_key.begin(), masking_key.end()),
+            unwrapping_params.vector_data(), 0 /* passwordSid */, 0 /* biometricSid */,
+            &creationResult);
 
     if (result.isOk()) {
-        key_blob_ = outBlob.data;
-        if (SecLevel() != SecurityLevel::SOFTWARE) {
-            EXPECT_GT(key_characteristics_.hardwareEnforced.size(), 0);
+        EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+                     creationResult.keyCharacteristics);
+        EXPECT_GT(creationResult.keyBlob.size(), 0);
+
+        key_blob_ = std::move(creationResult.keyBlob);
+        key_characteristics_ = std::move(creationResult.keyCharacteristics);
+        cert_chain_ = std::move(creationResult.certificateChain);
+
+        AuthorizationSet allAuths;
+        for (auto& entry : key_characteristics_) {
+            allAuths.push_back(AuthorizationSet(entry.authorizations));
         }
-        EXPECT_GT(key_characteristics_.softwareEnforced.size(), 0);
+        auto algorithm = allAuths.GetTagValue(TAG_ALGORITHM);
+        EXPECT_TRUE(algorithm);
+        if (algorithm &&
+            (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+            EXPECT_GE(cert_chain_.size(), 1);
+        } else {
+            // For symmetric keys there should be no certificates.
+            EXPECT_EQ(cert_chain_.size(), 0);
+        }
     }
 
     return GetReturnErrorCode(result);
@@ -397,6 +528,34 @@
     }
 }
 
+auto KeyMintAidlTestBase::ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
+                                         const string& message, const AuthorizationSet& in_params)
+        -> std::tuple<ErrorCode, string, AuthorizationSet /* out_params */> {
+    AuthorizationSet begin_out_params;
+    ErrorCode result = Begin(operation, key_blob, in_params, &begin_out_params);
+    AuthorizationSet out_params(std::move(begin_out_params));
+    if (result != ErrorCode::OK) {
+        return {result, {}, out_params};
+    }
+
+    string output;
+    int32_t consumed = 0;
+    AuthorizationSet update_params;
+    AuthorizationSet update_out_params;
+    result = Update(update_params, message, &update_out_params, &output, &consumed);
+    out_params.push_back(update_out_params);
+    if (result != ErrorCode::OK) {
+        return {result, output, out_params};
+    }
+
+    string unused;
+    AuthorizationSet finish_params;
+    AuthorizationSet finish_out_params;
+    result = Finish(finish_params, message.substr(consumed), unused, &finish_out_params, &output);
+    out_params.push_back(finish_out_params);
+    return {result, output, out_params};
+}
+
 string KeyMintAidlTestBase::ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
                                            const string& message, const AuthorizationSet& in_params,
                                            AuthorizationSet* out_params) {
@@ -754,6 +913,310 @@
     return {};
 }
 
+static const vector<KeyParameter> kEmptyAuthList{};
+
+const vector<KeyParameter>& KeyMintAidlTestBase::SecLevelAuthorizations(
+        const vector<KeyCharacteristics>& key_characteristics) {
+    auto found = std::find_if(key_characteristics.begin(), key_characteristics.end(),
+                              [this](auto& entry) { return entry.securityLevel == SecLevel(); });
+    return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations;
+}
+
+const vector<KeyParameter>& KeyMintAidlTestBase::SecLevelAuthorizations(
+        const vector<KeyCharacteristics>& key_characteristics, SecurityLevel securityLevel) {
+    auto found = std::find_if(
+            key_characteristics.begin(), key_characteristics.end(),
+            [securityLevel](auto& entry) { return entry.securityLevel == securityLevel; });
+    return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations;
+}
+
+AuthorizationSet KeyMintAidlTestBase::HwEnforcedAuthorizations(
+        const vector<KeyCharacteristics>& key_characteristics) {
+    AuthorizationSet authList;
+    for (auto& entry : key_characteristics) {
+        if (entry.securityLevel == SecurityLevel::STRONGBOX ||
+            entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT) {
+            authList.push_back(AuthorizationSet(entry.authorizations));
+        }
+    }
+    return authList;
+}
+
+AuthorizationSet KeyMintAidlTestBase::SwEnforcedAuthorizations(
+        const vector<KeyCharacteristics>& key_characteristics) {
+    AuthorizationSet authList;
+    for (auto& entry : key_characteristics) {
+        if (entry.securityLevel == SecurityLevel::SOFTWARE ||
+            entry.securityLevel == SecurityLevel::KEYSTORE) {
+            authList.push_back(AuthorizationSet(entry.authorizations));
+        }
+    }
+    return authList;
+}
+
+ErrorCode KeyMintAidlTestBase::UseAesKey(const vector<uint8_t>& aesKeyBlob) {
+    auto [result, ciphertext, out_params] = ProcessMessage(
+            aesKeyBlob, KeyPurpose::ENCRYPT, "1234567890123456",
+            AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::NONE));
+    return result;
+}
+
+ErrorCode KeyMintAidlTestBase::UseHmacKey(const vector<uint8_t>& hmacKeyBlob) {
+    auto [result, mac, out_params] = ProcessMessage(
+            hmacKeyBlob, KeyPurpose::SIGN, "1234567890123456",
+            AuthorizationSetBuilder().Authorization(TAG_MAC_LENGTH, 128).Digest(Digest::SHA_2_256));
+    return result;
+}
+
+ErrorCode KeyMintAidlTestBase::UseRsaKey(const vector<uint8_t>& rsaKeyBlob) {
+    std::string message(2048 / 8, 'a');
+    auto [result, signature, out_params] = ProcessMessage(
+            rsaKeyBlob, KeyPurpose::SIGN, message,
+            AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE));
+    return result;
+}
+
+ErrorCode KeyMintAidlTestBase::UseEcdsaKey(const vector<uint8_t>& ecdsaKeyBlob) {
+    auto [result, signature, out_params] =
+            ProcessMessage(ecdsaKeyBlob, KeyPurpose::SIGN, "a",
+                           AuthorizationSetBuilder().Digest(Digest::SHA_2_256));
+    return result;
+}
+
+bool verify_attestation_record(const string& challenge,                //
+                               const string& app_id,                   //
+                               AuthorizationSet expected_sw_enforced,  //
+                               AuthorizationSet expected_hw_enforced,  //
+                               SecurityLevel security_level,
+                               const vector<uint8_t>& attestation_cert) {
+    X509_Ptr cert(parse_cert_blob(attestation_cert));
+    EXPECT_TRUE(!!cert.get());
+    if (!cert.get()) return false;
+
+    ASN1_OCTET_STRING* attest_rec = get_attestation_record(cert.get());
+    EXPECT_TRUE(!!attest_rec);
+    if (!attest_rec) return false;
+
+    AuthorizationSet att_sw_enforced;
+    AuthorizationSet att_hw_enforced;
+    uint32_t att_attestation_version;
+    uint32_t att_keymaster_version;
+    SecurityLevel att_attestation_security_level;
+    SecurityLevel att_keymaster_security_level;
+    vector<uint8_t> att_challenge;
+    vector<uint8_t> att_unique_id;
+    vector<uint8_t> att_app_id;
+
+    auto error = parse_attestation_record(attest_rec->data,                 //
+                                          attest_rec->length,               //
+                                          &att_attestation_version,         //
+                                          &att_attestation_security_level,  //
+                                          &att_keymaster_version,           //
+                                          &att_keymaster_security_level,    //
+                                          &att_challenge,                   //
+                                          &att_sw_enforced,                 //
+                                          &att_hw_enforced,                 //
+                                          &att_unique_id);
+    EXPECT_EQ(ErrorCode::OK, error);
+    if (error != ErrorCode::OK) return false;
+
+    EXPECT_GE(att_attestation_version, 3U);
+
+    expected_sw_enforced.push_back(TAG_ATTESTATION_APPLICATION_ID,
+                                   vector<uint8_t>(app_id.begin(), app_id.end()));
+
+    EXPECT_GE(att_keymaster_version, 4U);
+    EXPECT_EQ(security_level, att_keymaster_security_level);
+    EXPECT_EQ(security_level, att_attestation_security_level);
+
+    EXPECT_EQ(challenge.length(), att_challenge.size());
+    EXPECT_EQ(0, memcmp(challenge.data(), att_challenge.data(), challenge.length()));
+
+    char property_value[PROPERTY_VALUE_MAX] = {};
+    // TODO(b/136282179): When running under VTS-on-GSI the TEE-backed
+    // keymaster implementation will report YYYYMM dates instead of YYYYMMDD
+    // for the BOOT_PATCH_LEVEL.
+    if (avb_verification_enabled()) {
+        for (int i = 0; i < att_hw_enforced.size(); i++) {
+            if (att_hw_enforced[i].tag == TAG_BOOT_PATCHLEVEL ||
+                att_hw_enforced[i].tag == TAG_VENDOR_PATCHLEVEL) {
+                std::string date =
+                        std::to_string(att_hw_enforced[i].value.get<KeyParameterValue::dateTime>());
+                // strptime seems to require delimiters, but the tag value will
+                // be YYYYMMDD
+                date.insert(6, "-");
+                date.insert(4, "-");
+                EXPECT_EQ(date.size(), 10);
+                struct tm time;
+                strptime(date.c_str(), "%Y-%m-%d", &time);
+
+                // Day of the month (0-31)
+                EXPECT_GE(time.tm_mday, 0);
+                EXPECT_LT(time.tm_mday, 32);
+                // Months since Jan (0-11)
+                EXPECT_GE(time.tm_mon, 0);
+                EXPECT_LT(time.tm_mon, 12);
+                // Years since 1900
+                EXPECT_GT(time.tm_year, 110);
+                EXPECT_LT(time.tm_year, 200);
+            }
+        }
+    }
+
+    // Check to make sure boolean values are properly encoded. Presence of a boolean tag
+    // indicates true. A provided boolean tag that can be pulled back out of the certificate
+    // indicates correct encoding. No need to check if it's in both lists, since the
+    // AuthorizationSet compare below will handle mismatches of tags.
+    if (security_level == SecurityLevel::SOFTWARE) {
+        EXPECT_TRUE(expected_sw_enforced.Contains(TAG_NO_AUTH_REQUIRED));
+    } else {
+        EXPECT_TRUE(expected_hw_enforced.Contains(TAG_NO_AUTH_REQUIRED));
+    }
+
+    // Alternatively this checks the opposite - a false boolean tag (one that isn't provided in
+    // the authorization list during key generation) isn't being attested to in the certificate.
+    EXPECT_FALSE(expected_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+    EXPECT_FALSE(att_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+    EXPECT_FALSE(expected_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+    EXPECT_FALSE(att_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+
+    if (att_hw_enforced.Contains(TAG_ALGORITHM, Algorithm::EC)) {
+        // For ECDSA keys, either an EC_CURVE or a KEY_SIZE can be specified, but one must be.
+        EXPECT_TRUE(att_hw_enforced.Contains(TAG_EC_CURVE) ||
+                    att_hw_enforced.Contains(TAG_KEY_SIZE));
+    }
+
+    // Test root of trust elements
+    vector<uint8_t> verified_boot_key;
+    VerifiedBoot verified_boot_state;
+    bool device_locked;
+    vector<uint8_t> verified_boot_hash;
+    error = parse_root_of_trust(attest_rec->data, attest_rec->length, &verified_boot_key,
+                                &verified_boot_state, &device_locked, &verified_boot_hash);
+    EXPECT_EQ(ErrorCode::OK, error);
+
+    if (avb_verification_enabled()) {
+        EXPECT_NE(property_get("ro.boot.vbmeta.digest", property_value, ""), 0);
+        string prop_string(property_value);
+        EXPECT_EQ(prop_string.size(), 64);
+        EXPECT_EQ(prop_string, bin2hex(verified_boot_hash));
+
+        EXPECT_NE(property_get("ro.boot.vbmeta.device_state", property_value, ""), 0);
+        if (!strcmp(property_value, "unlocked")) {
+            EXPECT_FALSE(device_locked);
+        } else {
+            EXPECT_TRUE(device_locked);
+        }
+
+        // Check that the device is locked if not debuggable, e.g., user build
+        // images in CTS. For VTS, debuggable images are used to allow adb root
+        // and the device is unlocked.
+        if (!property_get_bool("ro.debuggable", false)) {
+            EXPECT_TRUE(device_locked);
+        } else {
+            EXPECT_FALSE(device_locked);
+        }
+    }
+
+    // Verified boot key should be all 0's if the boot state is not verified or self signed
+    std::string empty_boot_key(32, '\0');
+    std::string verified_boot_key_str((const char*)verified_boot_key.data(),
+                                      verified_boot_key.size());
+    EXPECT_NE(property_get("ro.boot.verifiedbootstate", property_value, ""), 0);
+    if (!strcmp(property_value, "green")) {
+        EXPECT_EQ(verified_boot_state, VerifiedBoot::VERIFIED);
+        EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+                            verified_boot_key.size()));
+    } else if (!strcmp(property_value, "yellow")) {
+        EXPECT_EQ(verified_boot_state, VerifiedBoot::SELF_SIGNED);
+        EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+                            verified_boot_key.size()));
+    } else if (!strcmp(property_value, "orange")) {
+        EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED);
+        EXPECT_EQ(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+                            verified_boot_key.size()));
+    } else if (!strcmp(property_value, "red")) {
+        EXPECT_EQ(verified_boot_state, VerifiedBoot::FAILED);
+    } else {
+        EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED);
+        EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+                            verified_boot_key.size()));
+    }
+
+    att_sw_enforced.Sort();
+    expected_sw_enforced.Sort();
+    auto a = filtered_tags(expected_sw_enforced);
+    auto b = filtered_tags(att_sw_enforced);
+    EXPECT_EQ(a, b);
+
+    att_hw_enforced.Sort();
+    expected_hw_enforced.Sort();
+    EXPECT_EQ(filtered_tags(expected_hw_enforced), filtered_tags(att_hw_enforced));
+
+    return true;
+}
+
+string bin2hex(const vector<uint8_t>& data) {
+    string retval;
+    retval.reserve(data.size() * 2 + 1);
+    for (uint8_t byte : data) {
+        retval.push_back(nibble2hex[0x0F & (byte >> 4)]);
+        retval.push_back(nibble2hex[0x0F & byte]);
+    }
+    return retval;
+}
+
+AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain) {
+    std::stringstream cert_data;
+
+    for (size_t i = 0; i < chain.size(); ++i) {
+        cert_data << bin2hex(chain[i].encodedCertificate) << std::endl;
+
+        X509_Ptr key_cert(parse_cert_blob(chain[i].encodedCertificate));
+        X509_Ptr signing_cert;
+        if (i < chain.size() - 1) {
+            signing_cert = parse_cert_blob(chain[i + 1].encodedCertificate);
+        } else {
+            signing_cert = parse_cert_blob(chain[i].encodedCertificate);
+        }
+        if (!key_cert.get() || !signing_cert.get()) return AssertionFailure() << cert_data.str();
+
+        EVP_PKEY_Ptr signing_pubkey(X509_get_pubkey(signing_cert.get()));
+        if (!signing_pubkey.get()) return AssertionFailure() << cert_data.str();
+
+        if (!X509_verify(key_cert.get(), signing_pubkey.get())) {
+            return AssertionFailure()
+                   << "Verification of certificate " << i << " failed "
+                   << "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL) << '\n'
+                   << cert_data.str();
+        }
+
+        string cert_issuer = x509NameToStr(X509_get_issuer_name(key_cert.get()));
+        string signer_subj = x509NameToStr(X509_get_subject_name(signing_cert.get()));
+        if (cert_issuer != signer_subj) {
+            return AssertionFailure() << "Cert " << i << " has wrong issuer.\n" << cert_data.str();
+        }
+
+        if (i == 0) {
+            string cert_sub = x509NameToStr(X509_get_subject_name(key_cert.get()));
+            if ("/CN=Android Keystore Key" != cert_sub) {
+                return AssertionFailure()
+                       << "Leaf cert has wrong subject, should be CN=Android Keystore Key, was "
+                       << cert_sub << '\n'
+                       << cert_data.str();
+            }
+        }
+    }
+
+    if (KeyMintAidlTestBase::dump_Attestations) std::cout << cert_data.str();
+    return AssertionSuccess();
+}
+
+X509_Ptr parse_cert_blob(const vector<uint8_t>& blob) {
+    const uint8_t* p = blob.data();
+    return X509_Ptr(d2i_X509(nullptr /* allocate new */, &p, blob.size()));
+}
+
 }  // namespace test
 
 }  // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
index f73c26d..452d2b6 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
@@ -21,26 +21,43 @@
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 #include <gtest/gtest.h>
+#include <openssl/x509.h>
 
 #include <aidl/android/hardware/security/keymint/ErrorCode.h>
 #include <aidl/android/hardware/security/keymint/IKeyMintDevice.h>
 
 #include <keymint_support/authorization_set.h>
+#include <keymint_support/openssl_utils.h>
 
-namespace aidl::android::hardware::security::keymint::test {
+namespace aidl::android::hardware::security::keymint {
+
+::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set);
+
+inline bool operator==(const keymint::AuthorizationSet& a, const keymint::AuthorizationSet& b) {
+    return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
+}
+
+namespace test {
 
 using ::android::sp;
 using Status = ::ndk::ScopedAStatus;
+using ::std::optional;
 using ::std::shared_ptr;
 using ::std::string;
 using ::std::vector;
 
 constexpr uint64_t kOpHandleSentinel = 0xFFFFFFFFFFFFFFFF;
 
-::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set);
-
 class KeyMintAidlTestBase : public ::testing::TestWithParam<string> {
   public:
+    struct KeyData {
+        vector<uint8_t> blob;
+        vector<KeyCharacteristics> characteristics;
+    };
+
+    static bool arm_deleteAllKeys;
+    static bool dump_Attestations;
+
     void SetUp() override;
     void TearDown() override {
         if (key_blob_.size()) {
@@ -55,14 +72,22 @@
     uint32_t os_patch_level() { return os_patch_level_; }
 
     ErrorCode GetReturnErrorCode(const Status& result);
-    ErrorCode GenerateKey(const AuthorizationSet& key_desc, vector<uint8_t>* key_blob,
-                          KeyCharacteristics* key_characteristics);
 
-    ErrorCode GenerateKey(const AuthorizationSet& key_desc);
+    ErrorCode GenerateKey(const AuthorizationSet& key_desc, vector<uint8_t>* key_blob,
+                          vector<KeyCharacteristics>* key_characteristics) {
+        return GenerateKey(key_desc, std::nullopt /* attest_key */, key_blob, key_characteristics,
+                           &cert_chain_);
+    }
+    ErrorCode GenerateKey(const AuthorizationSet& key_desc,
+                          const optional<AttestationKey>& attest_key, vector<uint8_t>* key_blob,
+                          vector<KeyCharacteristics>* key_characteristics,
+                          vector<Certificate>* cert_chain);
+    ErrorCode GenerateKey(const AuthorizationSet& key_desc,
+                          const optional<AttestationKey>& attest_key = std::nullopt);
 
     ErrorCode ImportKey(const AuthorizationSet& key_desc, KeyFormat format,
                         const string& key_material, vector<uint8_t>* key_blob,
-                        KeyCharacteristics* key_characteristics);
+                        vector<KeyCharacteristics>* key_characteristics);
     ErrorCode ImportKey(const AuthorizationSet& key_desc, KeyFormat format,
                         const string& key_material);
 
@@ -104,7 +129,9 @@
     string ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
                           const string& message, const AuthorizationSet& in_params,
                           AuthorizationSet* out_params);
-
+    std::tuple<ErrorCode, std::string /* processedMessage */, AuthorizationSet /* out_params */>
+    ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
+                   const std::string& message, const AuthorizationSet& in_params);
     string SignMessage(const vector<uint8_t>& key_blob, const string& message,
                        const AuthorizationSet& params);
     string SignMessage(const string& message, const AuthorizationSet& params);
@@ -147,8 +174,58 @@
 
     std::pair<ErrorCode, vector<uint8_t>> UpgradeKey(const vector<uint8_t>& key_blob);
 
-    bool IsSecure() { return securityLevel_ != SecurityLevel::SOFTWARE; }
-    SecurityLevel SecLevel() { return securityLevel_; }
+    template <typename TagType>
+    std::tuple<KeyData /* aesKey */, KeyData /* hmacKey */, KeyData /* rsaKey */,
+               KeyData /* ecdsaKey */>
+    CreateTestKeys(TagType tagToTest, ErrorCode expectedReturn) {
+        /* AES */
+        KeyData aesKeyData;
+        ErrorCode errorCode = GenerateKey(AuthorizationSetBuilder()
+                                                  .AesEncryptionKey(128)
+                                                  .Authorization(tagToTest)
+                                                  .BlockMode(BlockMode::ECB)
+                                                  .Padding(PaddingMode::NONE)
+                                                  .Authorization(TAG_NO_AUTH_REQUIRED),
+                                          &aesKeyData.blob, &aesKeyData.characteristics);
+        EXPECT_EQ(expectedReturn, errorCode);
+
+        /* HMAC */
+        KeyData hmacKeyData;
+        errorCode = GenerateKey(AuthorizationSetBuilder()
+                                        .HmacKey(128)
+                                        .Authorization(tagToTest)
+                                        .Digest(Digest::SHA_2_256)
+                                        .Authorization(TAG_MIN_MAC_LENGTH, 128)
+                                        .Authorization(TAG_NO_AUTH_REQUIRED),
+                                &hmacKeyData.blob, &hmacKeyData.characteristics);
+        EXPECT_EQ(expectedReturn, errorCode);
+
+        /* RSA */
+        KeyData rsaKeyData;
+        errorCode = GenerateKey(AuthorizationSetBuilder()
+                                        .RsaSigningKey(2048, 65537)
+                                        .Authorization(tagToTest)
+                                        .Digest(Digest::NONE)
+                                        .Padding(PaddingMode::NONE)
+                                        .Authorization(TAG_NO_AUTH_REQUIRED)
+                                        .SetDefaultValidity(),
+                                &rsaKeyData.blob, &rsaKeyData.characteristics);
+        EXPECT_EQ(expectedReturn, errorCode);
+
+        /* ECDSA */
+        KeyData ecdsaKeyData;
+        errorCode = GenerateKey(AuthorizationSetBuilder()
+                                        .EcdsaSigningKey(256)
+                                        .Authorization(tagToTest)
+                                        .Digest(Digest::SHA_2_256)
+                                        .Authorization(TAG_NO_AUTH_REQUIRED)
+                                        .SetDefaultValidity(),
+                                &ecdsaKeyData.blob, &ecdsaKeyData.characteristics);
+        EXPECT_EQ(expectedReturn, errorCode);
+        return {aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData};
+    }
+    bool IsSecure() const { return securityLevel_ != SecurityLevel::SOFTWARE; }
+    SecurityLevel SecLevel() const { return securityLevel_; }
 
     vector<uint32_t> ValidKeySizes(Algorithm algorithm);
     vector<uint32_t> InvalidKeySizes(Algorithm algorithm);
@@ -164,9 +241,26 @@
     }
 
     std::shared_ptr<IKeyMintOperation> op_;
-    vector<Certificate> certChain_;
+    vector<Certificate> cert_chain_;
     vector<uint8_t> key_blob_;
-    KeyCharacteristics key_characteristics_;
+    vector<KeyCharacteristics> key_characteristics_;
+
+    const vector<KeyParameter>& SecLevelAuthorizations(
+            const vector<KeyCharacteristics>& key_characteristics);
+    inline const vector<KeyParameter>& SecLevelAuthorizations() {
+        return SecLevelAuthorizations(key_characteristics_);
+    }
+    const vector<KeyParameter>& SecLevelAuthorizations(
+            const vector<KeyCharacteristics>& key_characteristics, SecurityLevel securityLevel);
+
+    AuthorizationSet HwEnforcedAuthorizations(
+            const vector<KeyCharacteristics>& key_characteristics);
+    AuthorizationSet SwEnforcedAuthorizations(
+            const vector<KeyCharacteristics>& key_characteristics);
+    ErrorCode UseAesKey(const vector<uint8_t>& aesKeyBlob);
+    ErrorCode UseHmacKey(const vector<uint8_t>& hmacKeyBlob);
+    ErrorCode UseRsaKey(const vector<uint8_t>& rsaKeyBlob);
+    ErrorCode UseEcdsaKey(const vector<uint8_t>& ecdsaKeyBlob);
 
   private:
     std::shared_ptr<IKeyMintDevice> keymint_;
@@ -179,9 +273,21 @@
     long challenge_;
 };
 
+bool verify_attestation_record(const string& challenge,                //
+                               const string& app_id,                   //
+                               AuthorizationSet expected_sw_enforced,  //
+                               AuthorizationSet expected_hw_enforced,  //
+                               SecurityLevel security_level,
+                               const vector<uint8_t>& attestation_cert);
+string bin2hex(const vector<uint8_t>& data);
+X509_Ptr parse_cert_blob(const vector<uint8_t>& blob);
+::testing::AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain);
+
 #define INSTANTIATE_KEYMINT_AIDL_TEST(name)                                          \
     INSTANTIATE_TEST_SUITE_P(PerInstance, name,                                      \
                              testing::ValuesIn(KeyMintAidlTestBase::build_params()), \
                              ::android::PrintInstanceNameToString)
 
-}  // namespace aidl::android::hardware::security::keymint::test
+}  // namespace test
+
+}  // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index 3060153..71aae90 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -14,41 +14,30 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "keymint_5_test"
+#define LOG_TAG "keymint_1_test"
 #include <cutils/log.h>
 
 #include <signal.h>
 #include <iostream>
 
+#include <openssl/ec.h>
 #include <openssl/evp.h>
 #include <openssl/mem.h>
-#include <openssl/x509.h>
+#include <openssl/x509v3.h>
 
 #include <cutils/properties.h>
 
 #include <aidl/android/hardware/security/keymint/KeyFormat.h>
 
-#include <keymint_support/attestation_record.h>
 #include <keymint_support/key_param_output.h>
 #include <keymint_support/openssl_utils.h>
 
 #include "KeyMintAidlTestBase.h"
 
-static bool arm_deleteAllKeys = false;
-static bool dump_Attestations = false;
-
 using aidl::android::hardware::security::keymint::AuthorizationSet;
 using aidl::android::hardware::security::keymint::KeyCharacteristics;
 using aidl::android::hardware::security::keymint::KeyFormat;
 
-namespace aidl::android::hardware::security::keymint {
-
-bool operator==(const keymint::AuthorizationSet& a, const keymint::AuthorizationSet& b) {
-    return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
-}
-
-}  // namespace aidl::android::hardware::security::keymint
-
 namespace std {
 
 using namespace aidl::android::hardware::security::keymint;
@@ -56,18 +45,16 @@
 template <>
 struct std::equal_to<KeyCharacteristics> {
     bool operator()(const KeyCharacteristics& a, const KeyCharacteristics& b) const {
-        // This isn't very efficient. Oh, well.
-        AuthorizationSet a_sw(a.softwareEnforced);
-        AuthorizationSet b_sw(b.softwareEnforced);
-        AuthorizationSet a_tee(b.hardwareEnforced);
-        AuthorizationSet b_tee(b.hardwareEnforced);
+        if (a.securityLevel != b.securityLevel) return false;
 
-        a_sw.Sort();
-        b_sw.Sort();
-        a_tee.Sort();
-        b_tee.Sort();
+        // this isn't very efficient. Oh, well.
+        AuthorizationSet a_auths(a.authorizations);
+        AuthorizationSet b_auths(b.authorizations);
 
-        return ((a_sw == b_sw) && (a_tee == b_tee));
+        a_auths.Sort();
+        b_auths.Sort();
+
+        return a_auths == b_auths;
     }
 };
 
@@ -78,7 +65,8 @@
 namespace {
 
 template <TagType tag_type, Tag tag, typename ValueT>
-bool contains(vector<KeyParameter>& set, TypedTag<tag_type, tag> ttag, ValueT expected_value) {
+bool contains(const vector<KeyParameter>& set, TypedTag<tag_type, tag> ttag,
+              ValueT expected_value) {
     auto it = std::find_if(set.begin(), set.end(), [&](const KeyParameter& param) {
         if (auto p = authorizationValue(ttag, param)) {
             return *p == expected_value;
@@ -89,7 +77,7 @@
 }
 
 template <TagType tag_type, Tag tag>
-bool contains(vector<KeyParameter>& set, TypedTag<tag_type, tag>) {
+bool contains(const vector<KeyParameter>& set, TypedTag<tag_type, tag>) {
     auto it = std::find_if(set.begin(), set.end(),
                            [&](const KeyParameter& param) { return param.tag == tag; });
     return (it != set.end());
@@ -182,10 +170,6 @@
     void operator()(RSA* p) { RSA_free(p); }
 };
 
-/* TODO(seleneh) add attestation verification codes like verify_chain() and
- * attestation tests after we decided on the keymint 1 attestation changes.
- */
-
 std::string make_string(const uint8_t* data, size_t length) {
     return std::string(reinterpret_cast<const char*>(data), length);
 }
@@ -229,19 +213,20 @@
 
 class NewKeyGenerationTest : public KeyMintAidlTestBase {
   protected:
-    void CheckBaseParams(const KeyCharacteristics& keyCharacteristics) {
+    void CheckBaseParams(const vector<KeyCharacteristics>& keyCharacteristics) {
         // TODO(swillden): Distinguish which params should be in which auth list.
 
-        AuthorizationSet auths(keyCharacteristics.hardwareEnforced);
-        auths.push_back(AuthorizationSet(keyCharacteristics.softwareEnforced));
+        AuthorizationSet auths;
+        for (auto& entry : keyCharacteristics) {
+            auths.push_back(AuthorizationSet(entry.authorizations));
+        }
 
         EXPECT_TRUE(auths.Contains(TAG_ORIGIN, KeyOrigin::GENERATED));
         EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::SIGN));
         EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::VERIFY));
 
-        // Verify that App ID, App data and ROT are NOT included.
+        // Verify that App data and ROT are NOT included.
         EXPECT_FALSE(auths.Contains(TAG_ROOT_OF_TRUST));
-        EXPECT_FALSE(auths.Contains(TAG_APPLICATION_ID));
         EXPECT_FALSE(auths.Contains(TAG_APPLICATION_DATA));
 
         // Check that some unexpected tags/values are NOT present.
@@ -249,15 +234,13 @@
         EXPECT_FALSE(auths.Contains(TAG_PURPOSE, KeyPurpose::DECRYPT));
         EXPECT_FALSE(auths.Contains(TAG_AUTH_TIMEOUT, 301U));
 
-        // Now check that unspecified, defaulted tags are correct.
-        EXPECT_TRUE(auths.Contains(TAG_CREATION_DATETIME));
+        auto os_ver = auths.GetTagValue(TAG_OS_VERSION);
+        ASSERT_TRUE(os_ver);
+        EXPECT_EQ(*os_ver, os_version());
 
-        EXPECT_TRUE(auths.Contains(TAG_OS_VERSION, os_version()))
-                << "OS version is " << os_version() << " key reported "
-                << auths.GetTagValue(TAG_OS_VERSION)->get();
-        EXPECT_TRUE(auths.Contains(TAG_OS_PATCHLEVEL, os_patch_level()))
-                << "OS patch level is " << os_patch_level() << " key reported "
-                << auths.GetTagValue(TAG_OS_PATCHLEVEL)->get();
+        auto os_pl = auths.GetTagValue(TAG_OS_PATCHLEVEL);
+        ASSERT_TRUE(os_pl);
+        EXPECT_EQ(*os_pl, os_patch_level());
     }
 };
 
@@ -270,22 +253,18 @@
 TEST_P(NewKeyGenerationTest, Rsa) {
     for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
         vector<uint8_t> key_blob;
-        KeyCharacteristics key_characteristics;
+        vector<KeyCharacteristics> key_characteristics;
         ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                      .RsaSigningKey(key_size, 65537)
                                                      .Digest(Digest::NONE)
-                                                     .Padding(PaddingMode::NONE),
+                                                     .Padding(PaddingMode::NONE)
+                                                     .SetDefaultValidity(),
                                              &key_blob, &key_characteristics));
 
         ASSERT_GT(key_blob.size(), 0U);
         CheckBaseParams(key_characteristics);
 
-        AuthorizationSet crypto_params;
-        if (IsSecure()) {
-            crypto_params = key_characteristics.hardwareEnforced;
-        } else {
-            crypto_params = key_characteristics.softwareEnforced;
-        }
+        AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
 
         EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
         EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
@@ -297,6 +276,148 @@
 }
 
 /*
+ * NewKeyGenerationTest.RsaWithAttestation
+ *
+ * Verifies that keymint can generate all required RSA key sizes, and that the resulting keys
+ * have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, RsaWithAttestation) {
+    for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
+        auto challenge = "hello";
+        auto app_id = "foo";
+
+        vector<uint8_t> key_blob;
+        vector<KeyCharacteristics> key_characteristics;
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .RsaSigningKey(key_size, 65537)
+                                                     .Digest(Digest::NONE)
+                                                     .Padding(PaddingMode::NONE)
+                                                     .AttestationChallenge(challenge)
+                                                     .AttestationApplicationId(app_id)
+                                                     .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                     .SetDefaultValidity(),
+                                             &key_blob, &key_characteristics));
+
+        ASSERT_GT(key_blob.size(), 0U);
+        CheckBaseParams(key_characteristics);
+
+        AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+        EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
+        EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+                << "Key size " << key_size << "missing";
+        EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U));
+
+        EXPECT_TRUE(ChainSignaturesAreValid(cert_chain_));
+        ASSERT_GT(cert_chain_.size(), 0);
+
+        AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics);
+        AuthorizationSet sw_enforced = SwEnforcedAuthorizations(key_characteristics);
+        EXPECT_TRUE(verify_attestation_record(challenge, app_id,  //
+                                              sw_enforced, hw_enforced, SecLevel(),
+                                              cert_chain_[0].encodedCertificate));
+
+        CheckedDeleteKey(&key_blob);
+    }
+}
+
+/*
+ * NewKeyGenerationTest.LimitedUsageRsa
+ *
+ * Verifies that KeyMint can generate all required RSA key sizes with limited usage, and that the
+ * resulting keys have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, LimitedUsageRsa) {
+    for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
+        vector<uint8_t> key_blob;
+        vector<KeyCharacteristics> key_characteristics;
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .RsaSigningKey(key_size, 65537)
+                                                     .Digest(Digest::NONE)
+                                                     .Padding(PaddingMode::NONE)
+                                                     .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+                                                     .SetDefaultValidity(),
+                                             &key_blob, &key_characteristics));
+
+        ASSERT_GT(key_blob.size(), 0U);
+        CheckBaseParams(key_characteristics);
+
+        AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+        EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
+        EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+                << "Key size " << key_size << "missing";
+        EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U));
+
+        // Check the usage count limit tag appears in the authorizations.
+        AuthorizationSet auths;
+        for (auto& entry : key_characteristics) {
+            auths.push_back(AuthorizationSet(entry.authorizations));
+        }
+        EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+                << "key usage count limit " << 1U << " missing";
+
+        CheckedDeleteKey(&key_blob);
+    }
+}
+
+/*
+ * NewKeyGenerationTest.LimitedUsageRsaWithAttestation
+ *
+ * Verifies that KeyMint can generate all required RSA key sizes with limited usage, and that the
+ * resulting keys have correct characteristics and attestation.
+ */
+TEST_P(NewKeyGenerationTest, LimitedUsageRsaWithAttestation) {
+    for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
+        auto challenge = "hello";
+        auto app_id = "foo";
+
+        vector<uint8_t> key_blob;
+        vector<KeyCharacteristics> key_characteristics;
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .RsaSigningKey(key_size, 65537)
+                                                     .Digest(Digest::NONE)
+                                                     .Padding(PaddingMode::NONE)
+                                                     .AttestationChallenge(challenge)
+                                                     .AttestationApplicationId(app_id)
+                                                     .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                     .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+                                                     .SetDefaultValidity(),
+                                             &key_blob, &key_characteristics));
+
+        ASSERT_GT(key_blob.size(), 0U);
+        CheckBaseParams(key_characteristics);
+
+        AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+        EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
+        EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+                << "Key size " << key_size << "missing";
+        EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U));
+
+        // Check the usage count limit tag appears in the authorizations.
+        AuthorizationSet auths;
+        for (auto& entry : key_characteristics) {
+            auths.push_back(AuthorizationSet(entry.authorizations));
+        }
+        EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+                << "key usage count limit " << 1U << " missing";
+
+        // Check the usage count limit tag also appears in the attestation.
+        EXPECT_TRUE(ChainSignaturesAreValid(cert_chain_));
+        ASSERT_GT(cert_chain_.size(), 0);
+
+        AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics);
+        AuthorizationSet sw_enforced = SwEnforcedAuthorizations(key_characteristics);
+        EXPECT_TRUE(verify_attestation_record(challenge, app_id,  //
+                                              sw_enforced, hw_enforced, SecLevel(),
+                                              cert_chain_[0].encodedCertificate));
+
+        CheckedDeleteKey(&key_blob);
+    }
+}
+
+/*
  * NewKeyGenerationTest.NoInvalidRsaSizes
  *
  * Verifies that keymint cannot generate any RSA key sizes that are designated as invalid.
@@ -304,12 +425,13 @@
 TEST_P(NewKeyGenerationTest, NoInvalidRsaSizes) {
     for (auto key_size : InvalidKeySizes(Algorithm::RSA)) {
         vector<uint8_t> key_blob;
-        KeyCharacteristics key_characteristics;
+        vector<KeyCharacteristics> key_characteristics;
         ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE,
                   GenerateKey(AuthorizationSetBuilder()
                                       .RsaSigningKey(key_size, 65537)
                                       .Digest(Digest::NONE)
-                                      .Padding(PaddingMode::NONE),
+                                      .Padding(PaddingMode::NONE)
+                                      .SetDefaultValidity(),
                               &key_blob, &key_characteristics));
     }
 }
@@ -325,7 +447,8 @@
               GenerateKey(AuthorizationSetBuilder()
                                   .Authorization(TAG_ALGORITHM, Algorithm::RSA)
                                   .Authorization(TAG_RSA_PUBLIC_EXPONENT, 3U)
-                                  .SigningKey()));
+                                  .SigningKey()
+                                  .SetDefaultValidity()));
 }
 
 /*
@@ -337,20 +460,16 @@
 TEST_P(NewKeyGenerationTest, Ecdsa) {
     for (auto key_size : ValidKeySizes(Algorithm::EC)) {
         vector<uint8_t> key_blob;
-        KeyCharacteristics key_characteristics;
-        ASSERT_EQ(ErrorCode::OK,
-                  GenerateKey(
-                          AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE),
-                          &key_blob, &key_characteristics));
+        vector<KeyCharacteristics> key_characteristics;
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .EcdsaSigningKey(key_size)
+                                                     .Digest(Digest::NONE)
+                                                     .SetDefaultValidity(),
+                                             &key_blob, &key_characteristics));
         ASSERT_GT(key_blob.size(), 0U);
         CheckBaseParams(key_characteristics);
 
-        AuthorizationSet crypto_params;
-        if (IsSecure()) {
-            crypto_params = key_characteristics.hardwareEnforced;
-        } else {
-            crypto_params = key_characteristics.softwareEnforced;
-        }
+        AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
 
         EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::EC));
         EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
@@ -361,6 +480,44 @@
 }
 
 /*
+ * NewKeyGenerationTest.LimitedUsageEcdsa
+ *
+ * Verifies that KeyMint can generate all required EC key sizes with limited usage, and that the
+ * resulting keys have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, LimitedUsageEcdsa) {
+    for (auto key_size : ValidKeySizes(Algorithm::EC)) {
+        vector<uint8_t> key_blob;
+        vector<KeyCharacteristics> key_characteristics;
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .EcdsaSigningKey(key_size)
+                                                     .Digest(Digest::NONE)
+                                                     .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+                                                     .SetDefaultValidity(),
+                                             &key_blob, &key_characteristics));
+
+        ASSERT_GT(key_blob.size(), 0U);
+        CheckBaseParams(key_characteristics);
+
+        AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+        EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::EC));
+        EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+                << "Key size " << key_size << "missing";
+
+        // Check the usage count limit tag appears in the authorizations.
+        AuthorizationSet auths;
+        for (auto& entry : key_characteristics) {
+            auths.push_back(AuthorizationSet(entry.authorizations));
+        }
+        EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+                << "key usage count limit " << 1U << " missing";
+
+        CheckedDeleteKey(&key_blob);
+    }
+}
+
+/*
  * NewKeyGenerationTest.EcdsaDefaultSize
  *
  * Verifies that failing to specify a key size for EC key generation returns
@@ -371,7 +528,8 @@
               GenerateKey(AuthorizationSetBuilder()
                                   .Authorization(TAG_ALGORITHM, Algorithm::EC)
                                   .SigningKey()
-                                  .Digest(Digest::NONE)));
+                                  .Digest(Digest::NONE)
+                                  .SetDefaultValidity()));
 }
 
 /*
@@ -383,15 +541,18 @@
 TEST_P(NewKeyGenerationTest, EcdsaInvalidSize) {
     for (auto key_size : InvalidKeySizes(Algorithm::EC)) {
         vector<uint8_t> key_blob;
-        KeyCharacteristics key_characteristics;
-        ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE,
-                  GenerateKey(
-                          AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE),
-                          &key_blob, &key_characteristics));
+        vector<KeyCharacteristics> key_characteristics;
+        ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, GenerateKey(AuthorizationSetBuilder()
+                                                                       .EcdsaSigningKey(key_size)
+                                                                       .Digest(Digest::NONE)
+                                                                       .SetDefaultValidity(),
+                                                               &key_blob, &key_characteristics));
     }
 
-    ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE,
-              GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(190).Digest(Digest::NONE)));
+    ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, GenerateKey(AuthorizationSetBuilder()
+                                                                   .EcdsaSigningKey(190)
+                                                                   .Digest(Digest::NONE)
+                                                                   .SetDefaultValidity()));
 }
 
 /*
@@ -407,7 +568,8 @@
               GenerateKey(AuthorizationSetBuilder()
                                   .EcdsaSigningKey(224)
                                   .Authorization(TAG_EC_CURVE, EcCurve::P_256)
-                                  .Digest(Digest::NONE)));
+                                  .Digest(Digest::NONE)
+                                  .SetDefaultValidity()));
 }
 
 /*
@@ -418,8 +580,10 @@
 TEST_P(NewKeyGenerationTest, EcdsaAllValidSizes) {
     auto valid_sizes = ValidKeySizes(Algorithm::EC);
     for (size_t size : valid_sizes) {
-        EXPECT_EQ(ErrorCode::OK,
-                  GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(size).Digest(Digest::NONE)))
+        EXPECT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .EcdsaSigningKey(size)
+                                                     .Digest(Digest::NONE)
+                                                     .SetDefaultValidity()))
                 << "Failed to generate size: " << size;
         CheckedDeleteKey();
     }
@@ -438,8 +602,10 @@
         digest = Digest::SHA_2_512;
     }
     for (auto curve : ValidCurves()) {
-        EXPECT_EQ(ErrorCode::OK,
-                  GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(curve).Digest(digest)))
+        EXPECT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .EcdsaSigningKey(curve)
+                                                     .Digest(digest)
+                                                     .SetDefaultValidity()))
                 << "Failed to generate key on curve: " << curve;
         CheckedDeleteKey();
     }
@@ -454,7 +620,7 @@
 TEST_P(NewKeyGenerationTest, Hmac) {
     for (auto digest : ValidDigests(false /* withNone */, true /* withMD5 */)) {
         vector<uint8_t> key_blob;
-        KeyCharacteristics key_characteristics;
+        vector<KeyCharacteristics> key_characteristics;
         constexpr size_t key_size = 128;
         ASSERT_EQ(ErrorCode::OK,
                   GenerateKey(
@@ -465,17 +631,48 @@
         ASSERT_GT(key_blob.size(), 0U);
         CheckBaseParams(key_characteristics);
 
-        AuthorizationSet hardwareEnforced = key_characteristics.hardwareEnforced;
-        AuthorizationSet softwareEnforced = key_characteristics.softwareEnforced;
-        if (IsSecure()) {
-            EXPECT_TRUE(hardwareEnforced.Contains(TAG_ALGORITHM, Algorithm::HMAC));
-            EXPECT_TRUE(hardwareEnforced.Contains(TAG_KEY_SIZE, key_size))
-                    << "Key size " << key_size << "missing";
-        } else {
-            EXPECT_TRUE(softwareEnforced.Contains(TAG_ALGORITHM, Algorithm::HMAC));
-            EXPECT_TRUE(softwareEnforced.Contains(TAG_KEY_SIZE, key_size))
-                    << "Key size " << key_size << "missing";
+        AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+        EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::HMAC));
+        EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+                << "Key size " << key_size << "missing";
+
+        CheckedDeleteKey(&key_blob);
+    }
+}
+
+/*
+ * NewKeyGenerationTest.LimitedUsageHmac
+ *
+ * Verifies that KeyMint supports all required digests with limited usage Hmac, and that the
+ * resulting keys have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, LimitedUsageHmac) {
+    for (auto digest : ValidDigests(false /* withNone */, true /* withMD5 */)) {
+        vector<uint8_t> key_blob;
+        vector<KeyCharacteristics> key_characteristics;
+        constexpr size_t key_size = 128;
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .HmacKey(key_size)
+                                                     .Digest(digest)
+                                                     .Authorization(TAG_MIN_MAC_LENGTH, 128)
+                                                     .Authorization(TAG_USAGE_COUNT_LIMIT, 1),
+                                             &key_blob, &key_characteristics));
+
+        ASSERT_GT(key_blob.size(), 0U);
+        CheckBaseParams(key_characteristics);
+
+        AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+        EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::HMAC));
+        EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+                << "Key size " << key_size << "missing";
+
+        // Check the usage count limit tag appears in the authorizations.
+        AuthorizationSet auths;
+        for (auto& entry : key_characteristics) {
+            auths.push_back(AuthorizationSet(entry.authorizations));
         }
+        EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+                << "key usage count limit " << 1U << " missing";
 
         CheckedDeleteKey(&key_blob);
     }
@@ -591,7 +788,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
                                                  .Padding(PaddingMode::NONE)
-                                                 .Authorization(TAG_NO_AUTH_REQUIRED)));
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .SetDefaultValidity()));
     string message = "12345678901234567890123456789012";
     string signature = SignMessage(
             message, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE));
@@ -600,7 +798,7 @@
 /*
  * SigningOperationsTest.RsaUseRequiresCorrectAppIdAppData
  *
- * Verifies that using an RSA key requires the correct app ID/data.
+ * Verifies that using an RSA key requires the correct app data.
  */
 TEST_P(SigningOperationsTest, RsaUseRequiresCorrectAppIdAppData) {
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
@@ -609,7 +807,8 @@
                                                  .Digest(Digest::NONE)
                                                  .Padding(PaddingMode::NONE)
                                                  .Authorization(TAG_APPLICATION_ID, "clientid")
-                                                 .Authorization(TAG_APPLICATION_DATA, "appdata")));
+                                                 .Authorization(TAG_APPLICATION_DATA, "appdata")
+                                                 .SetDefaultValidity()));
     EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB,
               Begin(KeyPurpose::SIGN,
                     AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)));
@@ -645,7 +844,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::SHA_2_256)
                                                  .Padding(PaddingMode::RSA_PSS)
-                                                 .Authorization(TAG_NO_AUTH_REQUIRED)));
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .SetDefaultValidity()));
     // Use large message, which won't work without digesting.
     string message(1024, 'a');
     string signature = SignMessage(
@@ -664,7 +864,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
     string message = "12345678901234567890123456789012";
     string signature;
 
@@ -683,13 +884,13 @@
  */
 TEST_P(SigningOperationsTest, NoUserConfirmation) {
     if (SecLevel() == SecurityLevel::STRONGBOX) return;
-    ASSERT_EQ(ErrorCode::OK,
-              GenerateKey(AuthorizationSetBuilder()
-                                  .RsaSigningKey(1024, 65537)
-                                  .Digest(Digest::NONE)
-                                  .Padding(PaddingMode::NONE)
-                                  .Authorization(TAG_NO_AUTH_REQUIRED)
-                                  .Authorization(TAG_TRUSTED_CONFIRMATION_REQUIRED)));
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                 .RsaSigningKey(1024, 65537)
+                                                 .Digest(Digest::NONE)
+                                                 .Padding(PaddingMode::NONE)
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .Authorization(TAG_TRUSTED_CONFIRMATION_REQUIRED)
+                                                 .SetDefaultValidity()));
 
     const string message = "12345678901234567890123456789012";
     EXPECT_EQ(ErrorCode::OK,
@@ -709,7 +910,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::SHA_2_256)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
-                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
+                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+                                                 .SetDefaultValidity()));
     string message(1024, 'a');
     string signature = SignMessage(message, AuthorizationSetBuilder()
                                                     .Digest(Digest::SHA_2_256)
@@ -726,7 +928,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
-                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
+                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+                                                 .SetDefaultValidity()));
     string message(53, 'a');
     string signature = SignMessage(message, AuthorizationSetBuilder()
                                                     .Digest(Digest::NONE)
@@ -744,7 +947,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
-                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
+                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+                                                 .SetDefaultValidity()));
     string message(257, 'a');
 
     EXPECT_EQ(ErrorCode::OK,
@@ -774,7 +978,8 @@
                                                  .RsaSigningKey(1024, 65537)
                                                  .Digest(Digest::SHA_2_512)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
-                                                 .Padding(PaddingMode::RSA_PSS)));
+                                                 .Padding(PaddingMode::RSA_PSS)
+                                                 .SetDefaultValidity()));
     EXPECT_EQ(ErrorCode::INCOMPATIBLE_DIGEST,
               Begin(KeyPurpose::SIGN, AuthorizationSetBuilder()
                                               .Digest(Digest::SHA_2_512)
@@ -792,7 +997,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
-                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
+                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+                                                 .SetDefaultValidity()));
     // One byte too long
     string message(2048 / 8 + 1, 'a');
     ASSERT_EQ(ErrorCode::OK,
@@ -826,7 +1032,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
 
     ASSERT_EQ(ErrorCode::OK,
               Begin(KeyPurpose::SIGN,
@@ -851,7 +1058,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .Digest(Digest::SHA_2_256 /* supported digest */)
-                                                 .Padding(PaddingMode::PKCS7)));
+                                                 .Padding(PaddingMode::PKCS7)
+                                                 .SetDefaultValidity()));
     ASSERT_EQ(
             ErrorCode::UNSUPPORTED_PADDING_MODE,
             Begin(KeyPurpose::SIGN,
@@ -868,7 +1076,8 @@
                                                  .RsaSigningKey(2048, 65537)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .Digest(Digest::NONE)
-                                                 .Padding(PaddingMode::RSA_PSS)));
+                                                 .Padding(PaddingMode::RSA_PSS)
+                                                 .SetDefaultValidity()));
     ASSERT_EQ(ErrorCode::INCOMPATIBLE_DIGEST,
               Begin(KeyPurpose::SIGN,
                     AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::RSA_PSS)));
@@ -889,7 +1098,8 @@
                                                  .RsaKey(2048, 65537)
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .SigningKey()
-                                                 .Digest(Digest::NONE)));
+                                                 .Digest(Digest::NONE)
+                                                 .SetDefaultValidity()));
     ASSERT_EQ(ErrorCode::UNSUPPORTED_PADDING_MODE,
               Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE)));
 }
@@ -904,7 +1114,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
 
     // Barely shorter
     string message(2048 / 8 - 1, 'a');
@@ -925,7 +1136,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
                                                  .Digest(Digest::NONE)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
     ASSERT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE,
               Begin(KeyPurpose::SIGN,
                     AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)));
@@ -942,7 +1154,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
 
     // Largest possible message will always be larger than the public modulus.
     string message(2048 / 8, static_cast<char>(0xff));
@@ -965,7 +1178,8 @@
             ErrorCode error = GenerateKey(AuthorizationSetBuilder()
                                                   .Authorization(TAG_NO_AUTH_REQUIRED)
                                                   .EcdsaSigningKey(key_size)
-                                                  .Digest(digest));
+                                                  .Digest(digest)
+                                                  .SetDefaultValidity());
             EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate ECDSA key with size " << key_size
                                             << " and digest " << digest;
             if (error != ErrorCode::OK) continue;
@@ -988,7 +1202,8 @@
         ErrorCode error = GenerateKey(AuthorizationSetBuilder()
                                               .Authorization(TAG_NO_AUTH_REQUIRED)
                                               .EcdsaSigningKey(curve)
-                                              .Digest(Digest::SHA_2_256));
+                                              .Digest(Digest::SHA_2_256)
+                                              .SetDefaultValidity());
         EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate ECDSA key with curve " << curve;
         if (error != ErrorCode::OK) continue;
 
@@ -1010,7 +1225,8 @@
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .EcdsaSigningKey(256)
-                                                 .Digest(Digest::NONE)));
+                                                 .Digest(Digest::NONE)
+                                                 .SetDefaultValidity()));
     string message(1 * 1024, 'a');
     SignMessage(message, AuthorizationSetBuilder().Digest(Digest::NONE));
 }
@@ -1026,7 +1242,8 @@
                                                  .EcdsaSigningKey(256)
                                                  .Digest(Digest::NONE)
                                                  .Authorization(TAG_APPLICATION_ID, "clientid")
-                                                 .Authorization(TAG_APPLICATION_DATA, "appdata")));
+                                                 .Authorization(TAG_APPLICATION_DATA, "appdata")
+                                                 .SetDefaultValidity()));
     EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB,
               Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE)));
     AbortIfNeeded();
@@ -1215,7 +1432,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaSigningKey(2048, 65537)
                                                  .Digest(Digest::NONE)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
     string message = "12345678901234567890123456789012";
     string signature = SignMessage(
             message, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE));
@@ -1235,7 +1453,8 @@
                                   .Digest(ValidDigests(true /* withNone */, true /* withMD5 */))
                                   .Padding(PaddingMode::NONE)
                                   .Padding(PaddingMode::RSA_PSS)
-                                  .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN);
+                                  .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+                                  .SetDefaultValidity();
 
     ASSERT_EQ(ErrorCode::OK, GenerateKey(authorizations));
 
@@ -1332,7 +1551,8 @@
         ErrorCode error = GenerateKey(AuthorizationSetBuilder()
                                               .Authorization(TAG_NO_AUTH_REQUIRED)
                                               .EcdsaSigningKey(curve)
-                                              .Digest(digests));
+                                              .Digest(digests)
+                                              .SetDefaultValidity());
         EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate key for EC curve " << curve;
         if (error != ErrorCode::OK) {
             continue;
@@ -1412,7 +1632,7 @@
     string key_material = "HelloThisIsAKey";
 
     vector<uint8_t> signing_key, verification_key;
-    KeyCharacteristics signing_key_chars, verification_key_chars;
+    vector<KeyCharacteristics> signing_key_chars, verification_key_chars;
     EXPECT_EQ(ErrorCode::OK,
               ImportKey(AuthorizationSetBuilder()
                                 .Authorization(TAG_NO_AUTH_REQUIRED)
@@ -1466,28 +1686,22 @@
     template <TagType tag_type, Tag tag, typename ValueT>
     void CheckCryptoParam(TypedTag<tag_type, tag> ttag, ValueT expected) {
         SCOPED_TRACE("CheckCryptoParam");
-        if (IsSecure()) {
-            EXPECT_TRUE(contains(key_characteristics_.hardwareEnforced, ttag, expected))
-                    << "Tag " << tag << " with value " << expected << " not found";
-            EXPECT_FALSE(contains(key_characteristics_.softwareEnforced, ttag))
-                    << "Tag " << tag << " found";
-        } else {
-            EXPECT_TRUE(contains(key_characteristics_.softwareEnforced, ttag, expected))
-                    << "Tag " << tag << " with value " << expected << " not found";
-            EXPECT_FALSE(contains(key_characteristics_.hardwareEnforced, ttag))
-                    << "Tag " << tag << " found";
+        for (auto& entry : key_characteristics_) {
+            if (entry.securityLevel == SecLevel()) {
+                EXPECT_TRUE(contains(entry.authorizations, ttag, expected))
+                        << "Tag " << tag << " with value " << expected
+                        << " not found at security level" << entry.securityLevel;
+            } else {
+                EXPECT_FALSE(contains(entry.authorizations, ttag, expected))
+                        << "Tag " << tag << " found at security level " << entry.securityLevel;
+            }
         }
     }
 
     void CheckOrigin() {
         SCOPED_TRACE("CheckOrigin");
-        if (IsSecure()) {
-            EXPECT_TRUE(contains(key_characteristics_.hardwareEnforced, TAG_ORIGIN,
-                                 KeyOrigin::IMPORTED));
-        } else {
-            EXPECT_TRUE(contains(key_characteristics_.softwareEnforced, TAG_ORIGIN,
-                                 KeyOrigin::IMPORTED));
-        }
+        // Origin isn't a crypto param, but it always lives with them.
+        return CheckCryptoParam(TAG_ORIGIN, KeyOrigin::IMPORTED);
     }
 };
 
@@ -1501,7 +1715,8 @@
                                                .Authorization(TAG_NO_AUTH_REQUIRED)
                                                .RsaSigningKey(1024, 65537)
                                                .Digest(Digest::SHA_2_256)
-                                               .Padding(PaddingMode::RSA_PSS),
+                                               .Padding(PaddingMode::RSA_PSS)
+                                               .SetDefaultValidity(),
                                        KeyFormat::PKCS8, rsa_key));
 
     CheckCryptoParam(TAG_ALGORITHM, Algorithm::RSA);
@@ -1528,7 +1743,8 @@
               ImportKey(AuthorizationSetBuilder()
                                 .RsaSigningKey(2048 /* Doesn't match key */, 65537)
                                 .Digest(Digest::NONE)
-                                .Padding(PaddingMode::NONE),
+                                .Padding(PaddingMode::NONE)
+                                .SetDefaultValidity(),
                         KeyFormat::PKCS8, rsa_key));
 }
 
@@ -1543,7 +1759,8 @@
               ImportKey(AuthorizationSetBuilder()
                                 .RsaSigningKey(1024, 3 /* Doesn't match key */)
                                 .Digest(Digest::NONE)
-                                .Padding(PaddingMode::NONE),
+                                .Padding(PaddingMode::NONE)
+                                .SetDefaultValidity(),
                         KeyFormat::PKCS8, rsa_key));
 }
 
@@ -1556,7 +1773,8 @@
     ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
                                                .Authorization(TAG_NO_AUTH_REQUIRED)
                                                .EcdsaSigningKey(256)
-                                               .Digest(Digest::SHA_2_256),
+                                               .Digest(Digest::SHA_2_256)
+                                               .SetDefaultValidity(),
                                        KeyFormat::PKCS8, ec_256_key));
 
     CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC);
@@ -1582,7 +1800,8 @@
     ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
                                                .Authorization(TAG_NO_AUTH_REQUIRED)
                                                .EcdsaSigningKey(256)
-                                               .Digest(Digest::SHA_2_256),
+                                               .Digest(Digest::SHA_2_256)
+                                               .SetDefaultValidity(),
                                        KeyFormat::PKCS8, ec_256_key_rfc5915));
 
     CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC);
@@ -1607,7 +1826,8 @@
     ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
                                                .Authorization(TAG_NO_AUTH_REQUIRED)
                                                .EcdsaSigningKey(256)
-                                               .Digest(Digest::SHA_2_256),
+                                               .Digest(Digest::SHA_2_256)
+                                               .SetDefaultValidity(),
                                        KeyFormat::PKCS8, ec_256_key_sec1));
 
     CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC);
@@ -1633,7 +1853,8 @@
     ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
                                                .Authorization(TAG_NO_AUTH_REQUIRED)
                                                .EcdsaSigningKey(521)
-                                               .Digest(Digest::SHA_2_256),
+                                               .Digest(Digest::SHA_2_256)
+                                               .SetDefaultValidity(),
                                        KeyFormat::PKCS8, ec_521_key));
 
     CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC);
@@ -1658,7 +1879,8 @@
     ASSERT_EQ(ErrorCode::IMPORT_PARAMETER_MISMATCH,
               ImportKey(AuthorizationSetBuilder()
                                 .EcdsaSigningKey(224 /* Doesn't match key */)
-                                .Digest(Digest::NONE),
+                                .Digest(Digest::NONE)
+                                .SetDefaultValidity(),
                         KeyFormat::PKCS8, ec_256_key));
 }
 
@@ -1672,7 +1894,8 @@
     ASSERT_EQ(ErrorCode::IMPORT_PARAMETER_MISMATCH,
               ImportKey(AuthorizationSetBuilder()
                                 .EcdsaSigningKey(EcCurve::P_224 /* Doesn't match key */)
-                                .Digest(Digest::NONE),
+                                .Digest(Digest::NONE)
+                                .SetDefaultValidity(),
                         KeyFormat::PKCS8, ec_256_key));
 }
 
@@ -1793,7 +2016,8 @@
                                      .RsaEncryptionKey(2048, 65537)
                                      .Digest(Digest::SHA_2_256)
                                      .Padding(PaddingMode::RSA_OAEP)
-                                     .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
+                                     .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY)
+                                     .SetDefaultValidity();
 
     ASSERT_EQ(ErrorCode::OK,
               ImportWrappedKey(wrapped_key, wrapping_key, wrapping_key_desc, zero_masking_key,
@@ -1813,7 +2037,8 @@
                                      .RsaEncryptionKey(2048, 65537)
                                      .Digest(Digest::SHA_2_256)
                                      .Padding(PaddingMode::RSA_OAEP)
-                                     .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
+                                     .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY)
+                                     .SetDefaultValidity();
 
     ASSERT_EQ(ErrorCode::OK,
               ImportWrappedKey(wrapped_key_masked, wrapping_key, wrapping_key_desc, masking_key,
@@ -1827,7 +2052,8 @@
                                      .RsaEncryptionKey(2048, 65537)
                                      .Digest(Digest::SHA_2_256)
                                      .Padding(PaddingMode::RSA_OAEP)
-                                     .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
+                                     .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY)
+                                     .SetDefaultValidity();
 
     ASSERT_EQ(
             ErrorCode::VERIFICATION_FAILED,
@@ -1841,7 +2067,8 @@
     auto wrapping_key_desc = AuthorizationSetBuilder()
                                      .RsaEncryptionKey(2048, 65537)
                                      .Digest(Digest::SHA_2_256)
-                                     .Padding(PaddingMode::RSA_OAEP);
+                                     .Padding(PaddingMode::RSA_OAEP)
+                                     .SetDefaultValidity();
 
     ASSERT_EQ(
             ErrorCode::INCOMPATIBLE_PURPOSE,
@@ -1864,7 +2091,8 @@
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
 
     string message = string(2048 / 8, 'a');
     auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
@@ -1887,7 +2115,8 @@
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
 
     string message = "1";
     auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
@@ -1916,7 +2145,8 @@
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
 
     string message(2048 / 8 + 1, 'a');
 
@@ -1949,7 +2179,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(key_size, 65537)
                                                  .Padding(PaddingMode::RSA_OAEP)
-                                                 .Digest(digests)));
+                                                 .Digest(digests)
+                                                 .SetDefaultValidity()));
 
     string message = "Hello";
 
@@ -1997,7 +2228,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
                                                  .Padding(PaddingMode::RSA_OAEP)
-                                                 .Digest(Digest::NONE)));
+                                                 .Digest(Digest::NONE)
+                                                 .SetDefaultValidity()));
     string message = "Hello World!";
 
     auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_OAEP).Digest(Digest::NONE);
@@ -2017,7 +2249,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(1024, 65537)
                                                  .Padding(PaddingMode::RSA_OAEP)
-                                                 .Digest(Digest::SHA_2_224, Digest::SHA_2_256)));
+                                                 .Digest(Digest::SHA_2_224, Digest::SHA_2_256)
+                                                 .SetDefaultValidity()));
     string message = "Hello World!";
     string ciphertext = EncryptMessage(
             message,
@@ -2042,7 +2275,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
                                                  .Padding(PaddingMode::RSA_OAEP)
-                                                 .Digest(Digest::SHA_2_256)));
+                                                 .Digest(Digest::SHA_2_256)
+                                                 .SetDefaultValidity()));
     constexpr size_t digest_size = 256 /* SHA_2_256 */ / 8;
     constexpr size_t oaep_overhead = 2 * digest_size + 2;
     string message(2048 / 8 - oaep_overhead + 1, 'a');
@@ -2056,6 +2290,110 @@
 }
 
 /*
+ * EncryptionOperationsTest.RsaOaepWithMGFDigestSuccess
+ *
+ * Verifies that RSA-OAEP encryption operations work, with all SHA 256 digests and all type of MGF1
+ * digests.
+ */
+TEST_P(EncryptionOperationsTest, RsaOaepWithMGFDigestSuccess) {
+    auto digests = ValidDigests(false /* withNone */, true /* withMD5 */);
+
+    size_t key_size = 2048;  // Need largish key for SHA-512 test.
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                 .OaepMGFDigest(digests)
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .RsaEncryptionKey(key_size, 65537)
+                                                 .Padding(PaddingMode::RSA_OAEP)
+                                                 .Digest(Digest::SHA_2_256)
+                                                 .SetDefaultValidity()));
+
+    string message = "Hello";
+
+    for (auto digest : digests) {
+        auto params = AuthorizationSetBuilder()
+                              .Authorization(TAG_RSA_OAEP_MGF_DIGEST, digest)
+                              .Digest(Digest::SHA_2_256)
+                              .Padding(PaddingMode::RSA_OAEP);
+        string ciphertext1 = EncryptMessage(message, params);
+        if (HasNonfatalFailure()) std::cout << "-->" << digest << std::endl;
+        EXPECT_EQ(key_size / 8, ciphertext1.size());
+
+        string ciphertext2 = EncryptMessage(message, params);
+        EXPECT_EQ(key_size / 8, ciphertext2.size());
+
+        // OAEP randomizes padding so every result should be different (with astronomically high
+        // probability).
+        EXPECT_NE(ciphertext1, ciphertext2);
+
+        string plaintext1 = DecryptMessage(ciphertext1, params);
+        EXPECT_EQ(message, plaintext1) << "RSA-OAEP failed with digest " << digest;
+        string plaintext2 = DecryptMessage(ciphertext2, params);
+        EXPECT_EQ(message, plaintext2) << "RSA-OAEP failed with digest " << digest;
+
+        // Decrypting corrupted ciphertext should fail.
+        size_t offset_to_corrupt = random() % ciphertext1.size();
+        char corrupt_byte;
+        do {
+            corrupt_byte = static_cast<char>(random() % 256);
+        } while (corrupt_byte == ciphertext1[offset_to_corrupt]);
+        ciphertext1[offset_to_corrupt] = corrupt_byte;
+
+        EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, params));
+        string result;
+        EXPECT_EQ(ErrorCode::UNKNOWN_ERROR, Finish(ciphertext1, &result));
+        EXPECT_EQ(0U, result.size());
+    }
+}
+
+/*
+ * EncryptionOperationsTest.RsaOaepWithMGFIncompatibleDigest
+ *
+ * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate
+ * with incompatible MGF digest.
+ */
+TEST_P(EncryptionOperationsTest, RsaOaepWithMGFIncompatibleDigest) {
+    ASSERT_EQ(ErrorCode::OK,
+              GenerateKey(AuthorizationSetBuilder()
+                                  .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_256)
+                                  .Authorization(TAG_NO_AUTH_REQUIRED)
+                                  .RsaEncryptionKey(2048, 65537)
+                                  .Padding(PaddingMode::RSA_OAEP)
+                                  .Digest(Digest::SHA_2_256)
+                                  .SetDefaultValidity()));
+    string message = "Hello World!";
+
+    auto params = AuthorizationSetBuilder()
+                          .Padding(PaddingMode::RSA_OAEP)
+                          .Digest(Digest::SHA_2_256)
+                          .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_224);
+    EXPECT_EQ(ErrorCode::INCOMPATIBLE_MGF_DIGEST, Begin(KeyPurpose::ENCRYPT, params));
+}
+
+/*
+ * EncryptionOperationsTest.RsaOaepWithMGFUnsupportedDigest
+ *
+ * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate
+ * with unsupported MGF digest.
+ */
+TEST_P(EncryptionOperationsTest, RsaOaepWithMGFUnsupportedDigest) {
+    ASSERT_EQ(ErrorCode::OK,
+              GenerateKey(AuthorizationSetBuilder()
+                                  .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_256)
+                                  .Authorization(TAG_NO_AUTH_REQUIRED)
+                                  .RsaEncryptionKey(2048, 65537)
+                                  .Padding(PaddingMode::RSA_OAEP)
+                                  .Digest(Digest::SHA_2_256)
+                                  .SetDefaultValidity()));
+    string message = "Hello World!";
+
+    auto params = AuthorizationSetBuilder()
+                          .Padding(PaddingMode::RSA_OAEP)
+                          .Digest(Digest::SHA_2_256)
+                          .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::NONE);
+    EXPECT_EQ(ErrorCode::UNSUPPORTED_MGF_DIGEST, Begin(KeyPurpose::ENCRYPT, params));
+}
+
+/*
  * EncryptionOperationsTest.RsaPkcs1Success
  *
  * Verifies that RSA PKCS encryption/decrypts works.
@@ -2064,7 +2402,8 @@
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
-                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)));
+                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)
+                                                 .SetDefaultValidity()));
 
     string message = "Hello World!";
     auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT);
@@ -2103,7 +2442,8 @@
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
-                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)));
+                                                 .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)
+                                                 .SetDefaultValidity()));
     string message(2048 / 8 - 10, 'a');
 
     auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT);
@@ -2123,7 +2463,8 @@
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .EcdsaSigningKey(256)
-                                                 .Digest(Digest::NONE)));
+                                                 .Digest(Digest::NONE)
+                                                 .SetDefaultValidity()));
     auto params = AuthorizationSetBuilder().Digest(Digest::NONE);
     ASSERT_EQ(ErrorCode::UNSUPPORTED_PURPOSE, Begin(KeyPurpose::ENCRYPT, params));
     ASSERT_EQ(ErrorCode::UNSUPPORTED_PURPOSE, Begin(KeyPurpose::DECRYPT, params));
@@ -3760,7 +4101,7 @@
 }
 
 /*
- * MaxOperationsTest.TestLimitAes
+ * MaxOperationsTest.TestLimitRsa
  *
  * Verifies that the max uses per boot tag works correctly with RSA keys.
  */
@@ -3771,7 +4112,8 @@
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaSigningKey(1024, 65537)
                                                  .NoDigestOrPadding()
-                                                 .Authorization(TAG_MAX_USES_PER_BOOT, 3)));
+                                                 .Authorization(TAG_MAX_USES_PER_BOOT, 3)
+                                                 .SetDefaultValidity()));
 
     string message = "1234567890123456";
 
@@ -3787,6 +4129,239 @@
 
 INSTANTIATE_KEYMINT_AIDL_TEST(MaxOperationsTest);
 
+typedef KeyMintAidlTestBase UsageCountLimitTest;
+
+/*
+ * UsageCountLimitTest.TestSingleUseAes
+ *
+ * Verifies that the usage count limit tag = 1 works correctly with AES keys.
+ */
+TEST_P(UsageCountLimitTest, TestSingleUseAes) {
+    if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .AesEncryptionKey(128)
+                                                 .EcbMode()
+                                                 .Padding(PaddingMode::NONE)
+                                                 .Authorization(TAG_USAGE_COUNT_LIMIT, 1)));
+
+    // Check the usage count limit tag appears in the authorizations.
+    AuthorizationSet auths;
+    for (auto& entry : key_characteristics_) {
+        auths.push_back(AuthorizationSet(entry.authorizations));
+    }
+    EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+            << "key usage count limit " << 1U << " missing";
+
+    string message = "1234567890123456";
+    auto params = AuthorizationSetBuilder().EcbMode().Padding(PaddingMode::NONE);
+
+    AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+    AuthorizationSet keystore_auths =
+            SecLevelAuthorizations(key_characteristics_, SecurityLevel::KEYSTORE);
+
+    // First usage of AES key should work.
+    EncryptMessage(message, params);
+
+    if (hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U)) {
+        // Usage count limit tag is enforced by hardware. After using the key, the key blob
+        // must be invalidated from secure storage (such as RPMB partition).
+        EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::ENCRYPT, params));
+    } else {
+        // Usage count limit tag is enforced by keystore, keymint does nothing.
+        EXPECT_TRUE(keystore_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U));
+        EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, params));
+    }
+}
+
+/*
+ * UsageCountLimitTest.TestLimitedUseAes
+ *
+ * Verifies that the usage count limit tag > 1 works correctly with AES keys.
+ */
+TEST_P(UsageCountLimitTest, TestLimitedUseAes) {
+    if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .AesEncryptionKey(128)
+                                                 .EcbMode()
+                                                 .Padding(PaddingMode::NONE)
+                                                 .Authorization(TAG_USAGE_COUNT_LIMIT, 3)));
+
+    // Check the usage count limit tag appears in the authorizations.
+    AuthorizationSet auths;
+    for (auto& entry : key_characteristics_) {
+        auths.push_back(AuthorizationSet(entry.authorizations));
+    }
+    EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U))
+            << "key usage count limit " << 3U << " missing";
+
+    string message = "1234567890123456";
+    auto params = AuthorizationSetBuilder().EcbMode().Padding(PaddingMode::NONE);
+
+    AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+    AuthorizationSet keystore_auths =
+            SecLevelAuthorizations(key_characteristics_, SecurityLevel::KEYSTORE);
+
+    EncryptMessage(message, params);
+    EncryptMessage(message, params);
+    EncryptMessage(message, params);
+
+    if (hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U)) {
+        // Usage count limit tag is enforced by hardware. After using the key, the key blob
+        // must be invalidated from secure storage (such as RPMB partition).
+        EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::ENCRYPT, params));
+    } else {
+        // Usage count limit tag is enforced by keystore, keymint does nothing.
+        EXPECT_TRUE(keystore_auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U));
+        EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, params));
+    }
+}
+
+/*
+ * UsageCountLimitTest.TestSingleUseRsa
+ *
+ * Verifies that the usage count limit tag = 1 works correctly with RSA keys.
+ */
+TEST_P(UsageCountLimitTest, TestSingleUseRsa) {
+    if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .RsaSigningKey(1024, 65537)
+                                                 .NoDigestOrPadding()
+                                                 .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+                                                 .SetDefaultValidity()));
+
+    // Check the usage count limit tag appears in the authorizations.
+    AuthorizationSet auths;
+    for (auto& entry : key_characteristics_) {
+        auths.push_back(AuthorizationSet(entry.authorizations));
+    }
+    EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+            << "key usage count limit " << 1U << " missing";
+
+    string message = "1234567890123456";
+    auto params = AuthorizationSetBuilder().NoDigestOrPadding();
+
+    AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+    AuthorizationSet keystore_auths =
+            SecLevelAuthorizations(key_characteristics_, SecurityLevel::KEYSTORE);
+
+    // First usage of RSA key should work.
+    SignMessage(message, params);
+
+    if (hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U)) {
+        // Usage count limit tag is enforced by hardware. After using the key, the key blob
+        // must be invalidated from secure storage (such as RPMB partition).
+        EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, params));
+    } else {
+        // Usage count limit tag is enforced by keystore, keymint does nothing.
+        EXPECT_TRUE(keystore_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U));
+        EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::SIGN, params));
+    }
+}
+
+/*
+ * UsageCountLimitTest.TestLimitUseRsa
+ *
+ * Verifies that the usage count limit tag > 1 works correctly with RSA keys.
+ */
+TEST_P(UsageCountLimitTest, TestLimitUseRsa) {
+    if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .RsaSigningKey(1024, 65537)
+                                                 .NoDigestOrPadding()
+                                                 .Authorization(TAG_USAGE_COUNT_LIMIT, 3)
+                                                 .SetDefaultValidity()));
+
+    // Check the usage count limit tag appears in the authorizations.
+    AuthorizationSet auths;
+    for (auto& entry : key_characteristics_) {
+        auths.push_back(AuthorizationSet(entry.authorizations));
+    }
+    EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U))
+            << "key usage count limit " << 3U << " missing";
+
+    string message = "1234567890123456";
+    auto params = AuthorizationSetBuilder().NoDigestOrPadding();
+
+    AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+    AuthorizationSet keystore_auths =
+            SecLevelAuthorizations(key_characteristics_, SecurityLevel::KEYSTORE);
+
+    SignMessage(message, params);
+    SignMessage(message, params);
+    SignMessage(message, params);
+
+    if (hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U)) {
+        // Usage count limit tag is enforced by hardware. After using the key, the key blob
+        // must be invalidated from secure storage (such as RPMB partition).
+        EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, params));
+    } else {
+        // Usage count limit tag is enforced by keystore, keymint does nothing.
+        EXPECT_TRUE(keystore_auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U));
+        EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::SIGN, params));
+    }
+}
+
+/*
+ * UsageCountLimitTest.TestSingleUseKeyAndRollbackResistance
+ *
+ * Verifies that when rollback resistance is supported by the KeyMint implementation with
+ * the secure hardware, the single use key with usage count limit tag = 1 must also be enforced
+ * in hardware.
+ */
+TEST_P(UsageCountLimitTest, TestSingleUseKeyAndRollbackResistance) {
+    if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+    auto error = GenerateKey(AuthorizationSetBuilder()
+                                     .RsaSigningKey(2048, 65537)
+                                     .Digest(Digest::NONE)
+                                     .Padding(PaddingMode::NONE)
+                                     .Authorization(TAG_NO_AUTH_REQUIRED)
+                                     .Authorization(TAG_ROLLBACK_RESISTANCE)
+                                     .SetDefaultValidity());
+    ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK);
+
+    if (error == ErrorCode::OK) {
+        // Rollback resistance is supported by KeyMint, verify it is enforced in hardware.
+        AuthorizationSet hardwareEnforced(SecLevelAuthorizations());
+        ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE));
+        ASSERT_EQ(ErrorCode::OK, DeleteKey());
+
+        // The KeyMint should also enforce single use key in hardware when it supports rollback
+        // resistance.
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                     .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                     .RsaSigningKey(1024, 65537)
+                                                     .NoDigestOrPadding()
+                                                     .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+                                                     .SetDefaultValidity()));
+
+        // Check the usage count limit tag appears in the hardware authorizations.
+        AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+        EXPECT_TRUE(hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+                << "key usage count limit " << 1U << " missing";
+
+        string message = "1234567890123456";
+        auto params = AuthorizationSetBuilder().NoDigestOrPadding();
+
+        // First usage of RSA key should work.
+        SignMessage(message, params);
+
+        // Usage count limit tag is enforced by hardware. After using the key, the key blob
+        // must be invalidated from secure storage (such as RPMB partition).
+        EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, params));
+    }
+}
+
+INSTANTIATE_KEYMINT_AIDL_TEST(UsageCountLimitTest);
+
 typedef KeyMintAidlTestBase AddEntropyTest;
 
 /*
@@ -3820,16 +4395,6 @@
 
 INSTANTIATE_KEYMINT_AIDL_TEST(AddEntropyTest);
 
-typedef KeyMintAidlTestBase AttestationTest;
-
-/*
- * AttestationTest.RsaAttestation
- *
- * Verifies that attesting to RSA keys works and generates the expected output.
- */
-// TODO(seleneh) add attestation tests back after decided on the new attestation
-// behavior under generateKey and importKey
-
 typedef KeyMintAidlTestBase KeyDeletionTest;
 
 /**
@@ -3844,12 +4409,13 @@
                                      .Digest(Digest::NONE)
                                      .Padding(PaddingMode::NONE)
                                      .Authorization(TAG_NO_AUTH_REQUIRED)
-                                     .Authorization(TAG_ROLLBACK_RESISTANCE));
+                                     .Authorization(TAG_ROLLBACK_RESISTANCE)
+                                     .SetDefaultValidity());
     ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK);
 
     // Delete must work if rollback protection is implemented
     if (error == ErrorCode::OK) {
-        AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced);
+        AuthorizationSet hardwareEnforced(SecLevelAuthorizations());
         ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE));
 
         ASSERT_EQ(ErrorCode::OK, DeleteKey(true /* keep key blob */));
@@ -3877,13 +4443,14 @@
                                      .Digest(Digest::NONE)
                                      .Padding(PaddingMode::NONE)
                                      .Authorization(TAG_NO_AUTH_REQUIRED)
-                                     .Authorization(TAG_ROLLBACK_RESISTANCE));
+                                     .Authorization(TAG_ROLLBACK_RESISTANCE)
+                                     .SetDefaultValidity());
     ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK);
 
     // Delete must work if rollback protection is implemented
     if (error == ErrorCode::OK) {
-        AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced);
-        ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE));
+        AuthorizationSet enforced(SecLevelAuthorizations());
+        ASSERT_TRUE(enforced.Contains(TAG_ROLLBACK_RESISTANCE));
 
         // Delete the key we don't care about the result at this point.
         DeleteKey();
@@ -3918,7 +4485,7 @@
 
     // Delete must work if rollback protection is implemented
     if (error == ErrorCode::OK) {
-        AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced);
+        AuthorizationSet hardwareEnforced(SecLevelAuthorizations());
         ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE));
 
         ASSERT_EQ(ErrorCode::OK, DeleteAllKeys());
@@ -3972,7 +4539,8 @@
     ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
                                                  .RsaEncryptionKey(2048, 65537)
-                                                 .Padding(PaddingMode::NONE)));
+                                                 .Padding(PaddingMode::NONE)
+                                                 .SetDefaultValidity()));
 
     auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
     constexpr size_t max_operations = 100;  // set to arbituary large number
@@ -4043,17 +4611,238 @@
 
 INSTANTIATE_KEYMINT_AIDL_TEST(TransportLimitTest);
 
+typedef KeyMintAidlTestBase KeyAgreementTest;
+
+int CurveToOpenSslCurveName(EcCurve curve) {
+    switch (curve) {
+        case EcCurve::P_224:
+            return NID_secp224r1;
+        case EcCurve::P_256:
+            return NID_X9_62_prime256v1;
+        case EcCurve::P_384:
+            return NID_secp384r1;
+        case EcCurve::P_521:
+            return NID_secp521r1;
+    }
+}
+
+/*
+ * KeyAgreementTest.Ecdh
+ *
+ * Verifies that ECDH works for all curves
+ */
+TEST_P(KeyAgreementTest, Ecdh) {
+    // Because it's possible to use this API with keys on different curves, we
+    // check all N^2 combinations where N is the number of supported
+    // curves.
+    //
+    // This is not a big deal as N is 4 so we only do 16 runs. If we end up with a
+    // lot more curves we can be smart about things and just pick |otherCurve| so
+    // it's not |curve| and that way we end up with only 2*N runs
+    //
+    for (auto curve : ValidCurves()) {
+        for (auto localCurve : ValidCurves()) {
+            // Generate EC key locally (with access to private key material)
+            auto ecKey = EC_KEY_Ptr(EC_KEY_new());
+            int curveName = CurveToOpenSslCurveName(localCurve);
+            auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(curveName));
+            ASSERT_NE(group, nullptr);
+            ASSERT_EQ(EC_KEY_set_group(ecKey.get(), group.get()), 1);
+            ASSERT_EQ(EC_KEY_generate_key(ecKey.get()), 1);
+            auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+            ASSERT_EQ(EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()), 1);
+
+            // Get encoded form of the public part of the locally generated key...
+            unsigned char* p = nullptr;
+            int encodedPublicKeySize = i2d_PUBKEY(pkey.get(), &p);
+            ASSERT_GT(encodedPublicKeySize, 0);
+            vector<uint8_t> encodedPublicKey(
+                    reinterpret_cast<const uint8_t*>(p),
+                    reinterpret_cast<const uint8_t*>(p + encodedPublicKeySize));
+            OPENSSL_free(p);
+
+            // Generate EC key in KeyMint (only access to public key material)
+            vector<uint8_t> challenge = {0x41, 0x42};
+            EXPECT_EQ(
+                    ErrorCode::OK,
+                    GenerateKey(AuthorizationSetBuilder()
+                                        .Authorization(TAG_NO_AUTH_REQUIRED)
+                                        .Authorization(TAG_EC_CURVE, curve)
+                                        .Authorization(TAG_PURPOSE, KeyPurpose::AGREE_KEY)
+                                        .Authorization(TAG_ALGORITHM, Algorithm::EC)
+                                        .Authorization(TAG_ATTESTATION_APPLICATION_ID, {0x61, 0x62})
+                                        .Authorization(TAG_ATTESTATION_CHALLENGE, challenge)
+                                        .SetDefaultValidity()))
+                    << "Failed to generate key";
+            ASSERT_GT(cert_chain_.size(), 0);
+            X509_Ptr kmKeyCert(parse_cert_blob(cert_chain_[0].encodedCertificate));
+            ASSERT_NE(kmKeyCert, nullptr);
+            // Check that keyAgreement (bit 4) is set in KeyUsage
+            EXPECT_TRUE((X509_get_key_usage(kmKeyCert.get()) & X509v3_KU_KEY_AGREEMENT) != 0);
+            auto kmPkey = EVP_PKEY_Ptr(X509_get_pubkey(kmKeyCert.get()));
+            ASSERT_NE(kmPkey, nullptr);
+            if (dump_Attestations) {
+                for (size_t n = 0; n < cert_chain_.size(); n++) {
+                    std::cout << bin2hex(cert_chain_[n].encodedCertificate) << std::endl;
+                }
+            }
+
+            // Now that we have the two keys, we ask KeyMint to perform ECDH...
+            if (curve != localCurve) {
+                // If the keys are using different curves KeyMint should fail with
+                // ErrorCode:INVALID_ARGUMENT. Check that.
+                EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::AGREE_KEY, AuthorizationSetBuilder()));
+                string ZabFromKeyMintStr;
+                EXPECT_EQ(ErrorCode::INVALID_ARGUMENT,
+                          Finish(string(encodedPublicKey.begin(), encodedPublicKey.end()),
+                                 &ZabFromKeyMintStr));
+
+            } else {
+                // Otherwise if the keys are using the same curve, it should work.
+                EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::AGREE_KEY, AuthorizationSetBuilder()));
+                string ZabFromKeyMintStr;
+                EXPECT_EQ(ErrorCode::OK,
+                          Finish(string(encodedPublicKey.begin(), encodedPublicKey.end()),
+                                 &ZabFromKeyMintStr));
+                vector<uint8_t> ZabFromKeyMint(ZabFromKeyMintStr.begin(), ZabFromKeyMintStr.end());
+
+                // Perform local ECDH between the two keys so we can check if we get the same Zab..
+                auto ctx = EVP_PKEY_CTX_Ptr(EVP_PKEY_CTX_new(pkey.get(), nullptr));
+                ASSERT_NE(ctx, nullptr);
+                ASSERT_EQ(EVP_PKEY_derive_init(ctx.get()), 1);
+                ASSERT_EQ(EVP_PKEY_derive_set_peer(ctx.get(), kmPkey.get()), 1);
+                size_t ZabFromTestLen = 0;
+                ASSERT_EQ(EVP_PKEY_derive(ctx.get(), nullptr, &ZabFromTestLen), 1);
+                vector<uint8_t> ZabFromTest;
+                ZabFromTest.resize(ZabFromTestLen);
+                ASSERT_EQ(EVP_PKEY_derive(ctx.get(), ZabFromTest.data(), &ZabFromTestLen), 1);
+
+                EXPECT_EQ(ZabFromKeyMint, ZabFromTest);
+            }
+
+            CheckedDeleteKey();
+        }
+    }
+}
+
+INSTANTIATE_KEYMINT_AIDL_TEST(KeyAgreementTest);
+
+typedef KeyMintAidlTestBase EarlyBootKeyTest;
+
+TEST_P(EarlyBootKeyTest, CreateEarlyBootKeys) {
+    auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] =
+            CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::OK);
+
+    CheckedDeleteKey(&aesKeyData.blob);
+    CheckedDeleteKey(&hmacKeyData.blob);
+    CheckedDeleteKey(&rsaKeyData.blob);
+    CheckedDeleteKey(&ecdsaKeyData.blob);
+}
+
+// This is a more comprenhensive test, but it can only be run on a machine which is still in early
+// boot stage, which no proper Android device is by the time we can run VTS.  To use this,
+// un-disable it and modify vold to remove the call to earlyBootEnded().  Running the test will end
+// early boot, so you'll have to reboot between runs.
+TEST_P(EarlyBootKeyTest, DISABLED_FullTest) {
+    auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] =
+            CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::OK);
+    // TAG_EARLY_BOOT_ONLY should be in hw-enforced.
+    EXPECT_TRUE(HwEnforcedAuthorizations(aesKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY));
+    EXPECT_TRUE(
+            HwEnforcedAuthorizations(hmacKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY));
+    EXPECT_TRUE(HwEnforcedAuthorizations(rsaKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY));
+    EXPECT_TRUE(
+            HwEnforcedAuthorizations(ecdsaKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY));
+
+    // Should be able to use keys, since early boot has not ended
+    EXPECT_EQ(ErrorCode::OK, UseAesKey(aesKeyData.blob));
+    EXPECT_EQ(ErrorCode::OK, UseHmacKey(hmacKeyData.blob));
+    EXPECT_EQ(ErrorCode::OK, UseRsaKey(rsaKeyData.blob));
+    EXPECT_EQ(ErrorCode::OK, UseEcdsaKey(ecdsaKeyData.blob));
+
+    // End early boot
+    ErrorCode earlyBootResult = GetReturnErrorCode(keyMint().earlyBootEnded());
+    EXPECT_EQ(earlyBootResult, ErrorCode::OK);
+
+    // Should not be able to use already-created keys.
+    EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseAesKey(aesKeyData.blob));
+    EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseHmacKey(hmacKeyData.blob));
+    EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseRsaKey(rsaKeyData.blob));
+    EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseEcdsaKey(ecdsaKeyData.blob));
+
+    CheckedDeleteKey(&aesKeyData.blob);
+    CheckedDeleteKey(&hmacKeyData.blob);
+    CheckedDeleteKey(&rsaKeyData.blob);
+    CheckedDeleteKey(&ecdsaKeyData.blob);
+
+    // Should not be able to create new keys
+    std::tie(aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData) =
+            CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::EARLY_BOOT_ENDED);
+
+    CheckedDeleteKey(&aesKeyData.blob);
+    CheckedDeleteKey(&hmacKeyData.blob);
+    CheckedDeleteKey(&rsaKeyData.blob);
+    CheckedDeleteKey(&ecdsaKeyData.blob);
+}
+INSTANTIATE_KEYMINT_AIDL_TEST(EarlyBootKeyTest);
+
+typedef KeyMintAidlTestBase UnlockedDeviceRequiredTest;
+
+// This may be a problematic test.  It can't be run repeatedly without unlocking the device in
+// between runs... and on most test devices there are no enrolled credentials so it can't be
+// unlocked at all, meaning the only way to get the test to pass again on a properly-functioning
+// device is to reboot it.  For that reason, this is disabled by default.  It can be used as part of
+// a manual test process, which includes unlocking between runs, which is why it's included here.
+// Well, that and the fact that it's the only test we can do without also making calls into the
+// Gatekeeper HAL.  We haven't written any cross-HAL tests, and don't know what all of the
+// implications might be, so that may or may not be a solution.
+TEST_P(UnlockedDeviceRequiredTest, DISABLED_KeysBecomeUnusable) {
+    auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] =
+            CreateTestKeys(TAG_UNLOCKED_DEVICE_REQUIRED, ErrorCode::OK);
+
+    EXPECT_EQ(ErrorCode::OK, UseAesKey(aesKeyData.blob));
+    EXPECT_EQ(ErrorCode::OK, UseHmacKey(hmacKeyData.blob));
+    EXPECT_EQ(ErrorCode::OK, UseRsaKey(rsaKeyData.blob));
+    EXPECT_EQ(ErrorCode::OK, UseEcdsaKey(ecdsaKeyData.blob));
+
+    ErrorCode rc = GetReturnErrorCode(
+            keyMint().deviceLocked(false /* passwordOnly */, {} /* verificationToken */));
+    ASSERT_EQ(ErrorCode::OK, rc);
+    EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseAesKey(aesKeyData.blob));
+    EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseHmacKey(hmacKeyData.blob));
+    EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseRsaKey(rsaKeyData.blob));
+    EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseEcdsaKey(ecdsaKeyData.blob));
+
+    CheckedDeleteKey(&aesKeyData.blob);
+    CheckedDeleteKey(&hmacKeyData.blob);
+    CheckedDeleteKey(&rsaKeyData.blob);
+    CheckedDeleteKey(&ecdsaKeyData.blob);
+}
+INSTANTIATE_KEYMINT_AIDL_TEST(UnlockedDeviceRequiredTest);
+
 }  // namespace aidl::android::hardware::security::keymint::test
 
 int main(int argc, char** argv) {
+    std::cout << "Testing ";
+    auto halInstances =
+            aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase::build_params();
+    std::cout << "HAL instances:\n";
+    for (auto& entry : halInstances) {
+        std::cout << "    " << entry << '\n';
+    }
+
     ::testing::InitGoogleTest(&argc, argv);
     for (int i = 1; i < argc; ++i) {
         if (argv[i][0] == '-') {
             if (std::string(argv[i]) == "--arm_deleteAllKeys") {
-                arm_deleteAllKeys = true;
+                aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase::
+                        arm_deleteAllKeys = true;
             }
             if (std::string(argv[i]) == "--dump_attestations") {
-                dump_Attestations = true;
+                aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase::
+                        dump_Attestations = true;
+            } else {
+                std::cout << "NOT dumping attestations" << std::endl;
             }
         }
     }
diff --git a/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp b/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp
deleted file mode 100644
index 0b1eccd..0000000
--- a/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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 "KeyMintAidlTestBase.h"
-
-namespace aidl::android::hardware::security::keymint::test {
-
-class VerificationTokenTest : public KeyMintAidlTestBase {
-  protected:
-    struct VerifyAuthorizationResult {
-        ErrorCode error;
-        VerificationToken token;
-    };
-
-    VerifyAuthorizationResult verifyAuthorization(uint64_t operationHandle,
-                                                  const HardwareAuthToken& authToken) {
-        VerifyAuthorizationResult result;
-
-        Status err;
-        err = keyMint().verifyAuthorization(operationHandle,  //
-                                            authToken,        //
-                                            &result.token);
-
-        result.error = GetReturnErrorCode(err);
-        return result;
-    }
-
-    uint64_t getTime() {
-        struct timespec timespec;
-        EXPECT_EQ(0, clock_gettime(CLOCK_BOOTTIME, &timespec));
-        return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000;
-    }
-
-    int sleep_ms(uint32_t milliseconds) {
-        struct timespec sleep_time = {static_cast<time_t>(milliseconds / 1000),
-                                      static_cast<long>(milliseconds % 1000) * 1000000};
-        while (sleep_time.tv_sec || sleep_time.tv_nsec) {
-            if (nanosleep(&sleep_time /* to wait */,
-                          &sleep_time /* remaining (on interrruption) */) == 0) {
-                sleep_time = {};
-            } else {
-                if (errno != EINTR) return errno;
-            }
-        }
-        return 0;
-    }
-};
-
-/*
- * VerificationTokens exist to facilitate cross-KeyMint verification of requirements.  As
- * such, the precise capabilities required will vary depending on the specific vendor
- * implementations. Essentially, VerificationTokens are a "hook" to enable vendor
- * implementations to communicate, so the precise usage is defined by those vendors.  The only
- * thing we really can test is that tokens can be created by TEE keyMints, and that the
- * timestamps increase as expected.
- */
-TEST_P(VerificationTokenTest, TestCreation) {
-    auto result1 = verifyAuthorization(1 /* operation handle */, HardwareAuthToken());
-    auto result1_time = getTime();
-
-    if (SecLevel() == SecurityLevel::STRONGBOX) {
-        // StrongBox should not implement verifyAuthorization.
-        EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error);
-        return;
-    }
-
-    ASSERT_EQ(ErrorCode::OK, result1.error);
-    EXPECT_EQ(1U, result1.token.challenge);
-    EXPECT_EQ(SecLevel(), result1.token.securityLevel);
-    EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
-
-    constexpr uint32_t time_to_sleep = 200;
-    sleep_ms(time_to_sleep);
-
-    auto result2 = verifyAuthorization(2 /* operation handle */, HardwareAuthToken());
-
-    auto result2_time = getTime();
-    ASSERT_EQ(ErrorCode::OK, result2.error);
-    EXPECT_EQ(2U, result2.token.challenge);
-    EXPECT_EQ(SecLevel(), result2.token.securityLevel);
-
-    auto host_time_delta = result2_time - result1_time;
-
-    EXPECT_GE(host_time_delta, time_to_sleep)
-            << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
-    EXPECT_LE(host_time_delta, time_to_sleep + 20)
-            << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep)
-            << " ms?  That's awful!";
-
-    auto km_time_delta =
-            result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
-
-    // If not too much else is going on on the system, the time delta should be quite close.  Allow
-    // 2 ms of slop just to avoid test flakiness.
-    //
-    // TODO(swillden): see if we can output values so they can be gathered across many runs and
-    // report if times aren't nearly always <1ms apart.
-    EXPECT_LE(host_time_delta, km_time_delta + 2);
-    EXPECT_LE(km_time_delta, host_time_delta + 2);
-    ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
-    ASSERT_NE(0,
-              memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
-}
-
-/*
- * Test that the mac changes when the time stamp changes. This is does not guarantee that the time
- * stamp is included in the mac but on failure we know that it is not. Other than in the test
- * case above we call verifyAuthorization with the exact same set of parameters.
- */
-TEST_P(VerificationTokenTest, MacChangesOnChangingTimestamp) {
-    auto result1 = verifyAuthorization(0 /* operation handle */, HardwareAuthToken());
-    auto result1_time = getTime();
-
-    if (SecLevel() == SecurityLevel::STRONGBOX) {
-        // StrongBox should not implement verifyAuthorization.
-        EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error);
-        return;
-    }
-
-    EXPECT_EQ(ErrorCode::OK, result1.error);
-    EXPECT_EQ(0U, result1.token.challenge);
-    EXPECT_EQ(SecLevel(), result1.token.securityLevel);
-    EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
-
-    constexpr uint32_t time_to_sleep = 200;
-    sleep_ms(time_to_sleep);
-
-    auto result2 = verifyAuthorization(0 /* operation handle */, HardwareAuthToken());
-    // ASSERT_TRUE(result2.callSuccessful);
-    auto result2_time = getTime();
-    EXPECT_EQ(ErrorCode::OK, result2.error);
-    EXPECT_EQ(0U, result2.token.challenge);
-    EXPECT_EQ(SecLevel(), result2.token.securityLevel);
-
-    auto host_time_delta = result2_time - result1_time;
-
-    EXPECT_GE(host_time_delta, time_to_sleep)
-            << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
-    EXPECT_LE(host_time_delta, time_to_sleep + 20)
-            << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep)
-            << " ms?  That's awful!";
-
-    auto km_time_delta =
-            result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
-
-    EXPECT_LE(host_time_delta, km_time_delta + 2);
-    EXPECT_LE(km_time_delta, host_time_delta + 2);
-    ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
-    ASSERT_NE(0,
-              memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
-}
-
-INSTANTIATE_KEYMINT_AIDL_TEST(VerificationTokenTest);
-
-}  // namespace aidl::android::hardware::security::keymint::test
diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
new file mode 100644
index 0000000..45f9df6
--- /dev/null
+++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
@@ -0,0 +1,432 @@
+/*
+ * 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 "VtsRemotelyProvisionableComponentTests"
+
+#include <RemotelyProvisionedComponent.h>
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+#include <android/binder_manager.h>
+#include <cppbor_parse.h>
+#include <cppcose/cppcose.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <keymaster/keymaster_configuration.h>
+#include <remote_prov/remote_prov_utils.h>
+
+namespace aidl::android::hardware::security::keymint::test {
+
+using ::std::string;
+using ::std::vector;
+
+namespace {
+
+#define INSTANTIATE_REM_PROV_AIDL_TEST(name)                                         \
+    INSTANTIATE_TEST_SUITE_P(                                                        \
+            PerInstance, name,                                                       \
+            testing::ValuesIn(VtsRemotelyProvisionedComponentTests::build_params()), \
+            ::android::PrintInstanceNameToString)
+
+using bytevec = std::vector<uint8_t>;
+using testing::MatchesRegex;
+using namespace remote_prov;
+using namespace keymaster;
+
+bytevec string_to_bytevec(const char* s) {
+    const uint8_t* p = reinterpret_cast<const uint8_t*>(s);
+    return bytevec(p, p + strlen(s));
+}
+
+}  // namespace
+
+class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::string> {
+  public:
+    virtual void SetUp() override {
+        if (AServiceManager_isDeclared(GetParam().c_str())) {
+            ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+            provisionable_ = IRemotelyProvisionedComponent::fromBinder(binder);
+        }
+        ASSERT_NE(provisionable_, nullptr);
+    }
+
+    static vector<string> build_params() {
+        auto params = ::android::getAidlHalInstanceNames(IRemotelyProvisionedComponent::descriptor);
+        return params;
+    }
+
+  protected:
+    std::shared_ptr<IRemotelyProvisionedComponent> provisionable_;
+};
+
+using GenerateKeyTests = VtsRemotelyProvisionedComponentTests;
+
+INSTANTIATE_REM_PROV_AIDL_TEST(GenerateKeyTests);
+
+/**
+ * Generate and validate a production-mode key.  MAC tag can't be verified.
+ */
+TEST_P(GenerateKeyTests, DISABLED_generateEcdsaP256Key_prodMode) {
+    MacedPublicKey macedPubKey;
+    bytevec privateKeyBlob;
+    bool testMode = false;
+    auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
+    ASSERT_TRUE(status.isOk());
+
+    auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
+    ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;
+
+    ASSERT_NE(coseMac0->asArray(), nullptr);
+    ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);
+
+    auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
+    ASSERT_NE(protParms, nullptr);
+    ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n  1 : 5,\n}");
+
+    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+    ASSERT_NE(unprotParms, nullptr);
+    ASSERT_EQ(unprotParms->value().size(), 0);
+
+    auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
+    ASSERT_NE(payload, nullptr);
+    auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value());
+    ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
+    EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
+                MatchesRegex("{\n"
+                             "  1 : 2,\n"
+                             "  3 : -7,\n"
+                             "  -1 : 1,\n"
+                             // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of
+                             // 32 hexadecimal bytes, enclosed in braces and separated by commas.
+                             // In this case, some Ed25519 public key.
+                             "  -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
+                             "  -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
+                             "}"));
+
+    auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
+    ASSERT_TRUE(coseMac0Tag);
+    auto extractedTag = coseMac0Tag->value();
+    EXPECT_EQ(extractedTag.size(), 32U);
+
+    // Compare with tag generated with kTestMacKey.  Shouldn't match.
+    auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
+                                                payload->value());
+    ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message();
+
+    EXPECT_NE(*testTag, extractedTag);
+}
+
+/**
+ * Generate and validate a test-mode key.
+ */
+TEST_P(GenerateKeyTests, DISABLED_generateEcdsaP256Key_testMode) {
+    MacedPublicKey macedPubKey;
+    bytevec privateKeyBlob;
+    bool testMode = true;
+    auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
+    ASSERT_TRUE(status.isOk());
+
+    auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
+    ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;
+
+    ASSERT_NE(coseMac0->asArray(), nullptr);
+    ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);
+
+    auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
+    ASSERT_NE(protParms, nullptr);
+    ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n  1 : 5,\n}");
+
+    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+    ASSERT_NE(unprotParms, nullptr);
+    ASSERT_EQ(unprotParms->value().size(), 0);
+
+    auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
+    ASSERT_NE(payload, nullptr);
+    auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value());
+    ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
+    EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
+                MatchesRegex("{\n"
+                             "  1 : 2,\n"
+                             "  3 : -7,\n"
+                             "  -1 : 1,\n"
+                             // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of
+                             // 32 hexadecimal bytes, enclosed in braces and separated by commas.
+                             // In this case, some Ed25519 public key.
+                             "  -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
+                             "  -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
+                             "  -70000 : null,\n"
+                             "}"));
+
+    auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
+    ASSERT_TRUE(coseMac0);
+    auto extractedTag = coseMac0Tag->value();
+    EXPECT_EQ(extractedTag.size(), 32U);
+
+    // Compare with tag generated with kTestMacKey.  Should match.
+    auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
+                                                payload->value());
+    ASSERT_TRUE(testTag) << testTag.message();
+
+    EXPECT_EQ(*testTag, extractedTag);
+}
+
+class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
+  protected:
+    CertificateRequestTest() : eekId_(string_to_bytevec("eekid")) {
+        auto chain = generateEekChain(3, eekId_);
+        EXPECT_TRUE(chain) << chain.message();
+        if (chain) eekChain_ = chain.moveValue();
+    }
+
+    void generateKeys(bool testMode, size_t numKeys) {
+        keysToSign_ = std::vector<MacedPublicKey>(numKeys);
+        cborKeysToSign_ = cppbor::Array();
+
+        for (auto& key : keysToSign_) {
+            bytevec privateKeyBlob;
+            auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &key, &privateKeyBlob);
+            ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+            auto [parsedMacedKey, _, parseErr] = cppbor::parse(key.macedKey);
+            ASSERT_TRUE(parsedMacedKey) << "Failed parsing MACed key: " << parseErr;
+            ASSERT_TRUE(parsedMacedKey->asArray()) << "COSE_Mac0 not an array?";
+            ASSERT_EQ(parsedMacedKey->asArray()->size(), kCoseMac0EntryCount);
+
+            auto& payload = parsedMacedKey->asArray()->get(kCoseMac0Payload);
+            ASSERT_TRUE(payload);
+            ASSERT_TRUE(payload->asBstr());
+
+            cborKeysToSign_.add(cppbor::EncodedItem(payload->asBstr()->value()));
+        }
+    }
+
+    bytevec eekId_;
+    EekChain eekChain_;
+    std::vector<MacedPublicKey> keysToSign_;
+    cppbor::Array cborKeysToSign_;
+};
+
+/**
+ * Generate an empty certificate request in test mode, and decrypt and verify the structure and
+ * content.
+ */
+TEST_P(CertificateRequestTest, DISABLED_EmptyRequest_testMode) {
+    bool testMode = true;
+    bytevec keysToSignMac;
+    ProtectedData protectedData;
+    auto challenge = randomBytes(32);
+    auto status = provisionable_->generateCertificateRequest(testMode, {} /* keysToSign */,
+                                                             eekChain_.chain, challenge,
+                                                             &keysToSignMac, &protectedData);
+    ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+    auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
+    ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
+    ASSERT_TRUE(parsedProtectedData->asArray());
+    ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
+
+    auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
+    ASSERT_TRUE(senderPubkey) << senderPubkey.message();
+    EXPECT_EQ(senderPubkey->second, eekId_);
+
+    auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey,
+                                            senderPubkey->first, false /* senderIsA */);
+    ASSERT_TRUE(sessionKey) << sessionKey.message();
+
+    auto protectedDataPayload =
+            decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
+    ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
+
+    auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
+    ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
+    ASSERT_TRUE(parsedPayload->asArray());
+    EXPECT_EQ(parsedPayload->asArray()->size(), 2U);
+
+    auto& signedMac = parsedPayload->asArray()->get(0);
+    auto& bcc = parsedPayload->asArray()->get(1);
+    ASSERT_TRUE(signedMac && signedMac->asArray());
+    ASSERT_TRUE(bcc && bcc->asArray());
+
+    // BCC is [ pubkey, + BccEntry]
+    auto bccContents = validateBcc(bcc->asArray());
+    ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get());
+    ASSERT_GT(bccContents->size(), 0U);
+
+    auto& signingKey = bccContents->back().pubKey;
+    auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
+                                          cppbor::Array()          // DeviceInfo
+                                                  .add(challenge)  //
+                                                  .add(cppbor::Map())
+                                                  .encode());
+    ASSERT_TRUE(macKey) << macKey.message();
+
+    auto coseMac0 = cppbor::Array()
+                            .add(cppbor::Map()  // protected
+                                         .add(ALGORITHM, HMAC_256)
+                                         .canonicalize()
+                                         .encode())
+                            .add(cppbor::Bstr())             // unprotected
+                            .add(cppbor::Array().encode())   // payload (keysToSign)
+                            .add(std::move(keysToSignMac));  // tag
+
+    auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
+    ASSERT_TRUE(macPayload) << macPayload.message();
+}
+
+/**
+ * Generate an empty certificate request in prod mode.  Generation will fail because we don't have a
+ * valid GEEK.
+ *
+ * TODO(swillden): Get a valid GEEK and use it so the generation can succeed, though we won't be
+ * able to decrypt.
+ */
+TEST_P(CertificateRequestTest, DISABLED_EmptyRequest_prodMode) {
+    bool testMode = false;
+    bytevec keysToSignMac;
+    ProtectedData protectedData;
+    auto challenge = randomBytes(32);
+    auto status = provisionable_->generateCertificateRequest(testMode, {} /* keysToSign */,
+                                                             eekChain_.chain, challenge,
+                                                             &keysToSignMac, &protectedData);
+    ASSERT_FALSE(status.isOk());
+    ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
+}
+
+/**
+ * Generate a non-empty certificate request in test mode.  Decrypt, parse and validate the contents.
+ */
+TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_testMode) {
+    bool testMode = true;
+    generateKeys(testMode, 4 /* numKeys */);
+
+    bytevec keysToSignMac;
+    ProtectedData protectedData;
+    auto challenge = randomBytes(32);
+    auto status = provisionable_->generateCertificateRequest(
+            testMode, keysToSign_, eekChain_.chain, challenge, &keysToSignMac, &protectedData);
+    ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+    auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
+    ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
+    ASSERT_TRUE(parsedProtectedData->asArray());
+    ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
+
+    auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
+    ASSERT_TRUE(senderPubkey) << senderPubkey.message();
+    EXPECT_EQ(senderPubkey->second, eekId_);
+
+    auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey,
+                                            senderPubkey->first, false /* senderIsA */);
+    ASSERT_TRUE(sessionKey) << sessionKey.message();
+
+    auto protectedDataPayload =
+            decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
+    ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
+
+    auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
+    ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
+    ASSERT_TRUE(parsedPayload->asArray());
+    EXPECT_EQ(parsedPayload->asArray()->size(), 2U);
+
+    auto& signedMac = parsedPayload->asArray()->get(0);
+    auto& bcc = parsedPayload->asArray()->get(1);
+    ASSERT_TRUE(signedMac && signedMac->asArray());
+    ASSERT_TRUE(bcc);
+
+    auto bccContents = validateBcc(bcc->asArray());
+    ASSERT_TRUE(bccContents) << "\n" << prettyPrint(bcc.get());
+    ASSERT_GT(bccContents->size(), 0U);
+
+    auto& signingKey = bccContents->back().pubKey;
+    auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
+                                          cppbor::Array()          // DeviceInfo
+                                                  .add(challenge)  //
+                                                  .add(cppbor::Array())
+                                                  .encode());
+    ASSERT_TRUE(macKey) << macKey.message();
+
+    auto coseMac0 = cppbor::Array()
+                            .add(cppbor::Map()  // protected
+                                         .add(ALGORITHM, HMAC_256)
+                                         .canonicalize()
+                                         .encode())
+                            .add(cppbor::Bstr())             // unprotected
+                            .add(cborKeysToSign_.encode())   // payload
+                            .add(std::move(keysToSignMac));  // tag
+
+    auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
+    ASSERT_TRUE(macPayload) << macPayload.message();
+}
+
+/**
+ * Generate a non-empty certificate request in prod mode.  Must fail because we don't have a valid
+ * GEEK.
+ *
+ * TODO(swillden): Get a valid GEEK and use it so the generation can succeed, though we won't be
+ * able to decrypt.
+ */
+TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_prodMode) {
+    bool testMode = false;
+    generateKeys(testMode, 4 /* numKeys */);
+
+    bytevec keysToSignMac;
+    ProtectedData protectedData;
+    auto challenge = randomBytes(32);
+    auto status = provisionable_->generateCertificateRequest(
+            testMode, keysToSign_, eekChain_.chain, challenge, &keysToSignMac, &protectedData);
+    ASSERT_FALSE(status.isOk());
+    ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
+}
+
+/**
+ * Generate a non-empty certificate request in test mode, with prod keys.  Must fail with
+ * STATUS_PRODUCTION_KEY_IN_TEST_REQUEST.
+ */
+TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_prodKeyInTestCert) {
+    generateKeys(false /* testMode */, 2 /* numKeys */);
+
+    bytevec keysToSignMac;
+    ProtectedData protectedData;
+    auto challenge = randomBytes(32);
+    auto status = provisionable_->generateCertificateRequest(true /* testMode */, keysToSign_,
+                                                             eekChain_.chain, challenge,
+                                                             &keysToSignMac, &protectedData);
+    ASSERT_FALSE(status.isOk());
+    ASSERT_EQ(status.getServiceSpecificError(),
+              BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST);
+}
+
+/**
+ * Generate a non-empty certificate request in prod mode, with test keys.  Must fail with
+ * STATUS_TEST_KEY_IN_PRODUCTION_REQUEST.
+ */
+TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_testKeyInProdCert) {
+    generateKeys(true /* testMode */, 2 /* numKeys */);
+
+    bytevec keysToSignMac;
+    ProtectedData protectedData;
+    auto status = provisionable_->generateCertificateRequest(
+            false /* testMode */, keysToSign_, eekChain_.chain, randomBytes(32) /* challenge */,
+            &keysToSignMac, &protectedData);
+    ASSERT_FALSE(status.isOk());
+    ASSERT_EQ(status.getServiceSpecificError(),
+              BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST);
+}
+
+INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestTest);
+
+}  // namespace aidl::android::hardware::security::keymint::test
diff --git a/security/keymint/aidl/vts/performance/Android.bp b/security/keymint/aidl/vts/performance/Android.bp
new file mode 100644
index 0000000..03240c3
--- /dev/null
+++ b/security/keymint/aidl/vts/performance/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.
+//
+
+cc_benchmark {
+    name: "VtsAidlKeyMintBenchmarkTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "KeyMintBenchmark.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libcrypto",
+        "libkeymint",
+        "libkeymint_support",
+    ],
+    static_libs: [
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "android.hardware.security.secureclock-V1-ndk_platform",
+        "libcppbor_external",
+        "libchrome",
+    ],
+}
diff --git a/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp b/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp
new file mode 100644
index 0000000..f87ca78
--- /dev/null
+++ b/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp
@@ -0,0 +1,714 @@
+/*
+ * 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 "keymint_benchmark"
+
+#include <base/command_line.h>
+#include <benchmark/benchmark.h>
+#include <iostream>
+
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <keymint_support/authorization_set.h>
+
+#define SMALL_MESSAGE_SIZE 64
+#define MEDIUM_MESSAGE_SIZE 1024
+#define LARGE_MESSAGE_SIZE 131072
+
+namespace aidl::android::hardware::security::keymint::test {
+
+::std::ostream& operator<<(::std::ostream& os, const keymint::AuthorizationSet& set);
+
+using ::android::sp;
+using Status = ::ndk::ScopedAStatus;
+using ::std::optional;
+using ::std::shared_ptr;
+using ::std::string;
+using ::std::vector;
+
+class KeyMintBenchmarkTest {
+  public:
+    KeyMintBenchmarkTest() {
+        message_cache_.push_back(string(SMALL_MESSAGE_SIZE, 'x'));
+        message_cache_.push_back(string(MEDIUM_MESSAGE_SIZE, 'x'));
+        message_cache_.push_back(string(LARGE_MESSAGE_SIZE, 'x'));
+    }
+
+    static KeyMintBenchmarkTest* newInstance(const char* instanceName) {
+        if (AServiceManager_isDeclared(instanceName)) {
+            ::ndk::SpAIBinder binder(AServiceManager_waitForService(instanceName));
+            KeyMintBenchmarkTest* test = new KeyMintBenchmarkTest();
+            test->InitializeKeyMint(IKeyMintDevice::fromBinder(binder));
+            return test;
+        } else {
+            return nullptr;
+        }
+    }
+
+    int getError() { return static_cast<int>(error_); }
+
+    const string& GenerateMessage(int size) {
+        for (const string& message : message_cache_) {
+            if (message.size() == size) {
+                return message;
+            }
+        }
+        string message = string(size, 'x');
+        message_cache_.push_back(message);
+        return std::move(message);
+    }
+
+    optional<BlockMode> getBlockMode(string transform) {
+        if (transform.find("/ECB") != string::npos) {
+            return BlockMode::ECB;
+        } else if (transform.find("/CBC") != string::npos) {
+            return BlockMode::CBC;
+        } else if (transform.find("/CTR") != string::npos) {
+            return BlockMode::CTR;
+        } else if (transform.find("/GCM") != string::npos) {
+            return BlockMode::GCM;
+        }
+        return {};
+    }
+
+    PaddingMode getPadding(string transform, bool sign) {
+        if (transform.find("/PKCS7") != string::npos) {
+            return PaddingMode::PKCS7;
+        } else if (transform.find("/PSS") != string::npos) {
+            return PaddingMode::RSA_PSS;
+        } else if (transform.find("/OAEP") != string::npos) {
+            return PaddingMode::RSA_OAEP;
+        } else if (transform.find("/PKCS1") != string::npos) {
+            return sign ? PaddingMode::RSA_PKCS1_1_5_SIGN : PaddingMode::RSA_PKCS1_1_5_ENCRYPT;
+        } else if (sign && transform.find("RSA") != string::npos) {
+            // RSA defaults to PKCS1 for sign
+            return PaddingMode::RSA_PKCS1_1_5_SIGN;
+        }
+        return PaddingMode::NONE;
+    }
+
+    optional<Algorithm> getAlgorithm(string transform) {
+        if (transform.find("AES") != string::npos) {
+            return Algorithm::AES;
+        } else if (transform.find("Hmac") != string::npos) {
+            return Algorithm::HMAC;
+        } else if (transform.find("DESede") != string::npos) {
+            return Algorithm::TRIPLE_DES;
+        } else if (transform.find("RSA") != string::npos) {
+            return Algorithm::RSA;
+        } else if (transform.find("EC") != string::npos) {
+            return Algorithm::EC;
+        }
+        std::cerr << "Can't find algorithm for " << transform << std::endl;
+        return {};
+    }
+
+    Digest getDigest(string transform) {
+        if (transform.find("MD5") != string::npos) {
+            return Digest::MD5;
+        } else if (transform.find("SHA1") != string::npos ||
+                   transform.find("SHA-1") != string::npos) {
+            return Digest::SHA1;
+        } else if (transform.find("SHA224") != string::npos) {
+            return Digest::SHA_2_224;
+        } else if (transform.find("SHA256") != string::npos) {
+            return Digest::SHA_2_256;
+        } else if (transform.find("SHA384") != string::npos) {
+            return Digest::SHA_2_384;
+        } else if (transform.find("SHA512") != string::npos) {
+            return Digest::SHA_2_512;
+        } else if (transform.find("RSA") != string::npos &&
+                   transform.find("OAEP") != string::npos) {
+            return Digest::SHA1;
+        } else if (transform.find("Hmac") != string::npos) {
+            return Digest::SHA_2_256;
+        }
+        return Digest::NONE;
+    }
+
+    bool GenerateKey(string transform, int keySize, bool sign = false) {
+        if (transform == key_transform_) {
+            return true;
+        } else if (key_transform_ != "") {
+            // Deleting old key first
+            key_transform_ = "";
+            if (DeleteKey() != ErrorCode::OK) {
+                return false;
+            }
+        }
+        std::optional<Algorithm> algorithm = getAlgorithm(transform);
+        if (!algorithm) {
+            std::cerr << "Error: invalid algorithm " << transform << std::endl;
+            return false;
+        }
+        key_transform_ = transform;
+        AuthorizationSetBuilder authSet = AuthorizationSetBuilder()
+                                                  .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                  .Authorization(TAG_PURPOSE, KeyPurpose::ENCRYPT)
+                                                  .Authorization(TAG_PURPOSE, KeyPurpose::DECRYPT)
+                                                  .Authorization(TAG_PURPOSE, KeyPurpose::SIGN)
+                                                  .Authorization(TAG_PURPOSE, KeyPurpose::VERIFY)
+                                                  .Authorization(TAG_KEY_SIZE, keySize)
+                                                  .Authorization(TAG_ALGORITHM, algorithm.value())
+                                                  .Digest(getDigest(transform))
+                                                  .Padding(getPadding(transform, sign));
+        std::optional<BlockMode> blockMode = getBlockMode(transform);
+        if (blockMode) {
+            authSet.BlockMode(blockMode.value());
+            if (blockMode == BlockMode::GCM) {
+                authSet.Authorization(TAG_MIN_MAC_LENGTH, 128);
+            }
+        }
+        if (algorithm == Algorithm::HMAC) {
+            authSet.Authorization(TAG_MIN_MAC_LENGTH, 128);
+        }
+        if (algorithm == Algorithm::RSA) {
+            authSet.Authorization(TAG_RSA_PUBLIC_EXPONENT, 65537U);
+            authSet.SetDefaultValidity();
+        }
+        if (algorithm == Algorithm::EC) {
+            authSet.SetDefaultValidity();
+        }
+        error_ = GenerateKey(authSet);
+        return error_ == ErrorCode::OK;
+    }
+
+    AuthorizationSet getOperationParams(string transform, bool sign = false) {
+        AuthorizationSetBuilder builder = AuthorizationSetBuilder()
+                                                  .Padding(getPadding(transform, sign))
+                                                  .Digest(getDigest(transform));
+        std::optional<BlockMode> blockMode = getBlockMode(transform);
+        if (sign && (transform.find("Hmac") != string::npos)) {
+            builder.Authorization(TAG_MAC_LENGTH, 128);
+        }
+        if (blockMode) {
+            builder.BlockMode(*blockMode);
+            if (blockMode == BlockMode::GCM) {
+                builder.Authorization(TAG_MAC_LENGTH, 128);
+            }
+        }
+        return std::move(builder);
+    }
+
+    optional<string> Process(const string& message, const AuthorizationSet& /*in_params*/,
+                             AuthorizationSet* out_params, const string& signature = "") {
+        static const int HIDL_BUFFER_LIMIT = 1 << 14;  // 16KB
+        ErrorCode result;
+
+        // Update
+        AuthorizationSet update_params;
+        AuthorizationSet update_out_params;
+        string output;
+        string aidl_output;
+        int32_t input_consumed = 0;
+        int32_t aidl_input_consumed = 0;
+        while (message.length() - input_consumed > 0) {
+            result = Update(update_params, message.substr(input_consumed, HIDL_BUFFER_LIMIT),
+                            &update_out_params, &aidl_output, &aidl_input_consumed);
+            if (result != ErrorCode::OK) {
+                error_ = result;
+                return {};
+            }
+            output.append(aidl_output);
+            input_consumed += aidl_input_consumed;
+            aidl_output.clear();
+        }
+
+        // Finish
+        AuthorizationSet finish_params;
+        AuthorizationSet finish_out_params;
+        result = Finish(finish_params, message.substr(input_consumed), signature,
+                        &finish_out_params, &aidl_output);
+        if (result != ErrorCode::OK) {
+            error_ = result;
+            return {};
+        }
+        output.append(aidl_output);
+        out_params->push_back(finish_out_params);
+        return output;
+    }
+
+    ErrorCode DeleteKey() {
+        Status result = keymint_->deleteKey(key_blob_);
+        key_blob_ = vector<uint8_t>();
+        return GetReturnErrorCode(result);
+    }
+
+    ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params,
+                    AuthorizationSet* out_params) {
+        Status result;
+        BeginResult out;
+        result = keymint_->begin(purpose, key_blob_, in_params.vector_data(), HardwareAuthToken(),
+                                 &out);
+        if (result.isOk()) {
+            *out_params = out.params;
+            op_ = out.operation;
+        }
+        return GetReturnErrorCode(result);
+    }
+
+    SecurityLevel securityLevel_;
+    string name_;
+
+  private:
+    ErrorCode GenerateKey(const AuthorizationSet& key_desc,
+                          const optional<AttestationKey>& attest_key = std::nullopt) {
+        key_blob_.clear();
+        KeyCreationResult creationResult;
+        Status result = keymint_->generateKey(key_desc.vector_data(), attest_key, &creationResult);
+        if (result.isOk()) {
+            key_blob_ = std::move(creationResult.keyBlob);
+            creationResult.keyCharacteristics.clear();
+            creationResult.certificateChain.clear();
+        }
+        return GetReturnErrorCode(result);
+    }
+
+    void InitializeKeyMint(std::shared_ptr<IKeyMintDevice> keyMint) {
+        if (!keyMint) {
+            std::cerr << "Trying initialize nullptr in InitializeKeyMint" << std::endl;
+            return;
+        }
+        keymint_ = std::move(keyMint);
+        KeyMintHardwareInfo info;
+        Status result = keymint_->getHardwareInfo(&info);
+        if (!result.isOk()) {
+            std::cerr << "InitializeKeyMint: getHardwareInfo failed with "
+                      << result.getServiceSpecificError() << std::endl;
+        }
+        securityLevel_ = info.securityLevel;
+        name_.assign(info.keyMintName.begin(), info.keyMintName.end());
+    }
+
+    ErrorCode Finish(const AuthorizationSet& in_params, const string& input,
+                     const string& signature, AuthorizationSet* out_params, string* output) {
+        Status result;
+        if (!op_) {
+            std::cerr << "Finish: Operation is nullptr" << std::endl;
+            return ErrorCode::UNEXPECTED_NULL_POINTER;
+        }
+        KeyParameterArray key_params;
+        key_params.params = in_params.vector_data();
+
+        KeyParameterArray in_keyParams;
+        in_keyParams.params = in_params.vector_data();
+
+        std::optional<KeyParameterArray> out_keyParams;
+        std::optional<vector<uint8_t>> o_put;
+
+        vector<uint8_t> oPut;
+        result = op_->finish(in_keyParams, vector<uint8_t>(input.begin(), input.end()),
+                             vector<uint8_t>(signature.begin(), signature.end()), {}, {},
+                             &out_keyParams, &oPut);
+
+        if (result.isOk()) {
+            if (out_keyParams) {
+                out_params->push_back(AuthorizationSet(out_keyParams->params));
+            }
+            output->append(oPut.begin(), oPut.end());
+        }
+        op_.reset();
+        return GetReturnErrorCode(result);
+    }
+
+    ErrorCode Update(const AuthorizationSet& in_params, const string& input,
+                     AuthorizationSet* out_params, string* output, int32_t* input_consumed) {
+        Status result;
+        if (!op_) {
+            std::cerr << "Update: Operation is nullptr" << std::endl;
+            return ErrorCode::UNEXPECTED_NULL_POINTER;
+        }
+
+        KeyParameterArray key_params;
+        key_params.params = in_params.vector_data();
+
+        KeyParameterArray in_keyParams;
+        in_keyParams.params = in_params.vector_data();
+
+        std::optional<KeyParameterArray> out_keyParams;
+        std::optional<ByteArray> o_put;
+        result = op_->update(in_keyParams, vector<uint8_t>(input.begin(), input.end()), {}, {},
+                             &out_keyParams, &o_put, input_consumed);
+
+        if (result.isOk()) {
+            if (o_put) {
+                output->append(o_put->data.begin(), o_put->data.end());
+            }
+
+            if (out_keyParams) {
+                out_params->push_back(AuthorizationSet(out_keyParams->params));
+            }
+        }
+
+        return GetReturnErrorCode(result);
+    }
+
+    ErrorCode GetReturnErrorCode(const Status& result) {
+        error_ = static_cast<ErrorCode>(result.getServiceSpecificError());
+        if (result.isOk()) return ErrorCode::OK;
+
+        if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+            return static_cast<ErrorCode>(result.getServiceSpecificError());
+        }
+
+        return ErrorCode::UNKNOWN_ERROR;
+    }
+
+    std::shared_ptr<IKeyMintOperation> op_;
+    vector<Certificate> cert_chain_;
+    vector<uint8_t> key_blob_;
+    vector<KeyCharacteristics> key_characteristics_;
+    std::shared_ptr<IKeyMintDevice> keymint_;
+    std::vector<string> message_cache_;
+    std::string key_transform_;
+    ErrorCode error_;
+};
+
+KeyMintBenchmarkTest* keymintTest;
+
+static void settings(benchmark::internal::Benchmark* benchmark) {
+    benchmark->Unit(benchmark::kMillisecond);
+}
+
+static void addDefaultLabel(benchmark::State& state) {
+    std::string secLevel;
+    switch (keymintTest->securityLevel_) {
+        case SecurityLevel::STRONGBOX:
+            secLevel = "STRONGBOX";
+            break;
+        case SecurityLevel::SOFTWARE:
+            secLevel = "SOFTWARE";
+            break;
+        case SecurityLevel::TRUSTED_ENVIRONMENT:
+            secLevel = "TEE";
+            break;
+        case SecurityLevel::KEYSTORE:
+            secLevel = "KEYSTORE";
+            break;
+    }
+    state.SetLabel("hardware_name:" + keymintTest->name_ + " sec_level:" + secLevel);
+}
+
+// clang-format off
+#define BENCHMARK_KM(func, transform, keySize) \
+    BENCHMARK_CAPTURE(func, transform/keySize, #transform "/" #keySize, keySize)->Apply(settings);
+#define BENCHMARK_KM_MSG(func, transform, keySize, msgSize)                                      \
+    BENCHMARK_CAPTURE(func, transform/keySize/msgSize, #transform "/" #keySize "/" #msgSize, \
+                      keySize, msgSize)                                                          \
+            ->Apply(settings);
+
+#define BENCHMARK_KM_ALL_MSGS(func, transform, keySize)             \
+    BENCHMARK_KM_MSG(func, transform, keySize, SMALL_MESSAGE_SIZE)  \
+    BENCHMARK_KM_MSG(func, transform, keySize, MEDIUM_MESSAGE_SIZE) \
+    BENCHMARK_KM_MSG(func, transform, keySize, LARGE_MESSAGE_SIZE)
+
+#define BENCHMARK_KM_CIPHER(transform, keySize, msgSize)   \
+    BENCHMARK_KM_MSG(encrypt, transform, keySize, msgSize) \
+    BENCHMARK_KM_MSG(decrypt, transform, keySize, msgSize)
+
+#define BENCHMARK_KM_CIPHER_ALL_MSGS(transform, keySize) \
+    BENCHMARK_KM_ALL_MSGS(encrypt, transform, keySize)   \
+    BENCHMARK_KM_ALL_MSGS(decrypt, transform, keySize)
+
+#define BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, keySize) \
+    BENCHMARK_KM_ALL_MSGS(sign, transform, keySize)         \
+    BENCHMARK_KM_ALL_MSGS(verify, transform, keySize)
+// clang-format on
+
+/*
+ * ============= KeyGen TESTS ==================
+ */
+static void keygen(benchmark::State& state, string transform, int keySize) {
+    addDefaultLabel(state);
+    for (auto _ : state) {
+        if (!keymintTest->GenerateKey(transform, keySize)) {
+            state.SkipWithError(
+                    ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+        }
+        state.PauseTiming();
+
+        keymintTest->DeleteKey();
+        state.ResumeTiming();
+    }
+}
+
+BENCHMARK_KM(keygen, AES, 128);
+BENCHMARK_KM(keygen, AES, 256);
+
+BENCHMARK_KM(keygen, RSA, 2048);
+BENCHMARK_KM(keygen, RSA, 3072);
+BENCHMARK_KM(keygen, RSA, 4096);
+
+BENCHMARK_KM(keygen, EC, 224);
+BENCHMARK_KM(keygen, EC, 256);
+BENCHMARK_KM(keygen, EC, 384);
+BENCHMARK_KM(keygen, EC, 521);
+
+BENCHMARK_KM(keygen, DESede, 168);
+
+BENCHMARK_KM(keygen, Hmac, 64);
+BENCHMARK_KM(keygen, Hmac, 128);
+BENCHMARK_KM(keygen, Hmac, 256);
+BENCHMARK_KM(keygen, Hmac, 512);
+
+/*
+ * ============= SIGNATURE TESTS ==================
+ */
+
+static void sign(benchmark::State& state, string transform, int keySize, int msgSize) {
+    addDefaultLabel(state);
+    if (!keymintTest->GenerateKey(transform, keySize, true)) {
+        state.SkipWithError(
+                ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+        return;
+    }
+
+    auto in_params = keymintTest->getOperationParams(transform, true);
+    AuthorizationSet out_params;
+    string message = keymintTest->GenerateMessage(msgSize);
+
+    for (auto _ : state) {
+        state.PauseTiming();
+        ErrorCode error = keymintTest->Begin(KeyPurpose::SIGN, in_params, &out_params);
+        if (error != ErrorCode::OK) {
+            state.SkipWithError(
+                    ("Error beginning sign, " + std::to_string(keymintTest->getError())).c_str());
+            return;
+        }
+        state.ResumeTiming();
+        out_params.Clear();
+        if (!keymintTest->Process(message, in_params, &out_params)) {
+            state.SkipWithError(("Sign error, " + std::to_string(keymintTest->getError())).c_str());
+            break;
+        }
+    }
+}
+
+static void verify(benchmark::State& state, string transform, int keySize, int msgSize) {
+    addDefaultLabel(state);
+    if (!keymintTest->GenerateKey(transform, keySize, true)) {
+        state.SkipWithError(
+                ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+        return;
+    }
+    AuthorizationSet out_params;
+    auto in_params = keymintTest->getOperationParams(transform, true);
+    string message = keymintTest->GenerateMessage(msgSize);
+    ErrorCode error = keymintTest->Begin(KeyPurpose::SIGN, in_params, &out_params);
+    if (error != ErrorCode::OK) {
+        state.SkipWithError(
+                ("Error beginning sign, " + std::to_string(keymintTest->getError())).c_str());
+        return;
+    }
+    std::optional<string> signature = keymintTest->Process(message, in_params, &out_params);
+    if (!signature) {
+        state.SkipWithError(("Sign error, " + std::to_string(keymintTest->getError())).c_str());
+        return;
+    }
+    out_params.Clear();
+    if (transform.find("Hmac") != string::npos) {
+        in_params = keymintTest->getOperationParams(transform, false);
+    }
+    for (auto _ : state) {
+        state.PauseTiming();
+        error = keymintTest->Begin(KeyPurpose::VERIFY, in_params, &out_params);
+        if (error != ErrorCode::OK) {
+            state.SkipWithError(
+                    ("Verify begin error, " + std::to_string(keymintTest->getError())).c_str());
+            return;
+        }
+        state.ResumeTiming();
+        if (!keymintTest->Process(message, in_params, &out_params, *signature)) {
+            state.SkipWithError(
+                    ("Verify error, " + std::to_string(keymintTest->getError())).c_str());
+            break;
+        }
+    }
+}
+
+// clang-format off
+#define BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(transform) \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 64)      \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 128)     \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 256)     \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 512)
+
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA1)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA256)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA224)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA256)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA384)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA512)
+
+#define BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(transform) \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 224)      \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 256)      \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 384)      \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 521)
+
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(NONEwithECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA1withECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA224withECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA256withECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA384withECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA512withECDSA);
+
+#define BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(transform) \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 2048)   \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 3072)   \
+    BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 4096)
+
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(MD5withRSA);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA1withRSA);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA224withRSA);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA384withRSA);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA512withRSA);
+
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(MD5withRSA/PSS);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA1withRSA/PSS);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA224withRSA/PSS);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA384withRSA/PSS);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA512withRSA/PSS);
+// clang-format on
+
+/*
+ * ============= CIPHER TESTS ==================
+ */
+
+static void encrypt(benchmark::State& state, string transform, int keySize, int msgSize) {
+    addDefaultLabel(state);
+    if (!keymintTest->GenerateKey(transform, keySize)) {
+        state.SkipWithError(
+                ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+        return;
+    }
+    auto in_params = keymintTest->getOperationParams(transform);
+    AuthorizationSet out_params;
+    string message = keymintTest->GenerateMessage(msgSize);
+
+    for (auto _ : state) {
+        state.PauseTiming();
+        auto error = keymintTest->Begin(KeyPurpose::ENCRYPT, in_params, &out_params);
+        if (error != ErrorCode::OK) {
+            state.SkipWithError(
+                    ("Encryption begin error, " + std::to_string(keymintTest->getError())).c_str());
+            return;
+        }
+        out_params.Clear();
+        state.ResumeTiming();
+        if (!keymintTest->Process(message, in_params, &out_params)) {
+            state.SkipWithError(
+                    ("Encryption error, " + std::to_string(keymintTest->getError())).c_str());
+            break;
+        }
+    }
+}
+
+static void decrypt(benchmark::State& state, string transform, int keySize, int msgSize) {
+    addDefaultLabel(state);
+    if (!keymintTest->GenerateKey(transform, keySize)) {
+        state.SkipWithError(
+                ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+        return;
+    }
+    AuthorizationSet out_params;
+    AuthorizationSet in_params = keymintTest->getOperationParams(transform);
+    string message = keymintTest->GenerateMessage(msgSize);
+    auto error = keymintTest->Begin(KeyPurpose::ENCRYPT, in_params, &out_params);
+    if (error != ErrorCode::OK) {
+        state.SkipWithError(
+                ("Encryption begin error, " + std::to_string(keymintTest->getError())).c_str());
+        return;
+    }
+    auto encryptedMessage = keymintTest->Process(message, in_params, &out_params);
+    if (!encryptedMessage) {
+        state.SkipWithError(
+                ("Encryption error, " + std::to_string(keymintTest->getError())).c_str());
+        return;
+    }
+    in_params.push_back(out_params);
+    out_params.Clear();
+    for (auto _ : state) {
+        state.PauseTiming();
+        error = keymintTest->Begin(KeyPurpose::DECRYPT, in_params, &out_params);
+        if (error != ErrorCode::OK) {
+            state.SkipWithError(
+                    ("Decryption begin error, " + std::to_string(keymintTest->getError())).c_str());
+            return;
+        }
+        state.ResumeTiming();
+        if (!keymintTest->Process(*encryptedMessage, in_params, &out_params)) {
+            state.SkipWithError(
+                    ("Decryption error, " + std::to_string(keymintTest->getError())).c_str());
+            break;
+        }
+    }
+}
+
+// clang-format off
+// AES
+#define BENCHMARK_KM_CIPHER_ALL_AES_KEYS(transform) \
+    BENCHMARK_KM_CIPHER_ALL_MSGS(transform, 128)    \
+    BENCHMARK_KM_CIPHER_ALL_MSGS(transform, 256)
+
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CBC/NoPadding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CBC/PKCS7Padding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CTR/NoPadding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/ECB/NoPadding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/ECB/PKCS7Padding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/GCM/NoPadding);
+
+// Triple DES
+BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/CBC/NoPadding, 168);
+BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/CBC/PKCS7Padding, 168);
+BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/ECB/NoPadding, 168);
+BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/ECB/PKCS7Padding, 168);
+
+#define BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(transform, msgSize) \
+    BENCHMARK_KM_CIPHER(transform, 2048, msgSize)            \
+    BENCHMARK_KM_CIPHER(transform, 3072, msgSize)            \
+    BENCHMARK_KM_CIPHER(transform, 4096, msgSize)
+
+BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/NoPadding, SMALL_MESSAGE_SIZE);
+BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/PKCS1Padding, SMALL_MESSAGE_SIZE);
+BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/OAEPPadding, SMALL_MESSAGE_SIZE);
+
+// clang-format on
+}  // namespace aidl::android::hardware::security::keymint::test
+
+int main(int argc, char** argv) {
+    ::benchmark::Initialize(&argc, argv);
+    base::CommandLine::Init(argc, argv);
+    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+    auto service_name = command_line->GetSwitchValueASCII("service_name");
+    if (service_name.empty()) {
+        service_name =
+                std::string(
+                        aidl::android::hardware::security::keymint::IKeyMintDevice::descriptor) +
+                "/default";
+    }
+    std::cerr << service_name << std::endl;
+    aidl::android::hardware::security::keymint::test::keymintTest =
+            aidl::android::hardware::security::keymint::test::KeyMintBenchmarkTest::newInstance(
+                    service_name.c_str());
+    if (!aidl::android::hardware::security::keymint::test::keymintTest) {
+        return 1;
+    }
+    ::benchmark::RunSpecifiedBenchmarks();
+}
diff --git a/security/keymint/aidl/vts/performance/README b/security/keymint/aidl/vts/performance/README
new file mode 100644
index 0000000..1221ad8
--- /dev/null
+++ b/security/keymint/aidl/vts/performance/README
@@ -0,0 +1,28 @@
+# KeyMint Benchmark
+
+The KeyMint Benchmark is a standalone tool for measuring the performance of
+ KeyMint implementations.
+
+## Building
+
+Build:
+`m  VtsAidlKeyMintBenchmarkTest`
+
+Transfer to device/emulator:
+`adb sync data`
+
+The benchmark executable will be located at
+ `data/benchmarktest/VtsAidlKeyMintBenchmarkTest` on the device.
+
+## Usage
+
+KeyMint Benchmark is built on [Google microbenchmark
+library](https://github.com/google/benchmark). All of the commandline arguments
+provided by the microbenchmark library are valid, such as
+`--benchmark_filter=<regex>` or `benchmark_out_format={json|console|csv}`.
+In addition to the command line arguments provided by microbenchmark,
+`--service_name=<service_name>` is provided to allow specification of the KeyMint
+fully qualified service name, e.g. specify
+`--service_name=android.hardware.security.keymint.IKeyMintDevice/default` to
+benchmark default implementation of KeyMint.
+
diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp
index 0cfa798..0255874 100644
--- a/security/keymint/support/Android.bp
+++ b/security/keymint/support/Android.bp
@@ -31,9 +31,46 @@
         "include",
     ],
     shared_libs: [
-        "android.hardware.security.keymint-unstable-ndk_platform",
+        "android.hardware.security.keymint-V1-ndk_platform",
         "libbase",
         "libcrypto",
         "libutils",
     ],
 }
+
+cc_library {
+    name: "libkeymint_remote_prov_support",
+    vendor_available: true,
+    srcs: [
+        "remote_prov_utils.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    shared_libs: [
+        "libcppcose",
+        "libcppbor_external",
+        "libcrypto",
+    ],
+}
+
+cc_library {
+    name: "libcppcose",
+    vendor_available: true,
+    srcs: [
+        "cppcose.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libcppbor_external",
+        "libcrypto",
+        "liblog",
+    ],
+    static_libs: [
+        // TODO(swillden): Remove keymint NDK
+        "android.hardware.security.keymint-V1-ndk_platform",
+    ],
+}
diff --git a/security/keymint/support/attestation_record.cpp b/security/keymint/support/attestation_record.cpp
index 596b097..a48f770 100644
--- a/security/keymint/support/attestation_record.cpp
+++ b/security/keymint/support/attestation_record.cpp
@@ -97,6 +97,7 @@
     ASN1_NULL* device_unique_attestation;
     ASN1_NULL* storage_key;
     ASN1_NULL* identity_credential;
+    ASN1_INTEGER* usage_count_limit;
 } KM_AUTH_LIST;
 
 ASN1_SEQUENCE(KM_AUTH_LIST) = {
@@ -143,7 +144,8 @@
         ASN1_EXP_OPT(KM_AUTH_LIST, storage_key, ASN1_NULL, TAG_STORAGE_KEY.maskedTag()),
         ASN1_EXP_OPT(KM_AUTH_LIST, identity_credential, ASN1_NULL,
                      TAG_IDENTITY_CREDENTIAL_KEY.maskedTag()),
-
+        ASN1_EXP_OPT(KM_AUTH_LIST, usage_count_limit, ASN1_INTEGER,
+                     TAG_USAGE_COUNT_LIMIT.maskedTag()),
 } ASN1_SEQUENCE_END(KM_AUTH_LIST);
 IMPLEMENT_ASN1_FUNCTIONS(KM_AUTH_LIST);
 
@@ -285,6 +287,7 @@
     copyAuthTag(record->device_unique_attestation, TAG_DEVICE_UNIQUE_ATTESTATION, auth_list);
     copyAuthTag(record->storage_key, TAG_STORAGE_KEY, auth_list);
     copyAuthTag(record->identity_credential, TAG_IDENTITY_CREDENTIAL_KEY, auth_list);
+    copyAuthTag(record->usage_count_limit, TAG_USAGE_COUNT_LIMIT, auth_list);
 
     return ErrorCode::OK;
 }
diff --git a/security/keymint/support/authorization_set.cpp b/security/keymint/support/authorization_set.cpp
index f98851c..25eace3 100644
--- a/security/keymint/support/authorization_set.cpp
+++ b/security/keymint/support/authorization_set.cpp
@@ -191,6 +191,10 @@
     return Authorization(TAG_PURPOSE, KeyPurpose::DECRYPT);
 }
 
+AuthorizationSetBuilder& AuthorizationSetBuilder::AttestKey() {
+    return Authorization(TAG_PURPOSE, KeyPurpose::ATTEST_KEY);
+}
+
 AuthorizationSetBuilder& AuthorizationSetBuilder::NoDigestOrPadding() {
     Authorization(TAG_DIGEST, Digest::NONE);
     return Authorization(TAG_PADDING, PaddingMode::NONE);
@@ -227,6 +231,14 @@
     return *this;
 }
 
+AuthorizationSetBuilder& AuthorizationSetBuilder::OaepMGFDigest(
+        const std::vector<android::hardware::security::keymint::Digest>& digests) {
+    for (auto digest : digests) {
+        push_back(TAG_RSA_OAEP_MGF_DIGEST, digest);
+    }
+    return *this;
+}
+
 AuthorizationSetBuilder& AuthorizationSetBuilder::Padding(
         std::initializer_list<PaddingMode> paddingModes) {
     for (auto paddingMode : paddingModes) {
@@ -235,4 +247,12 @@
     return *this;
 }
 
+AuthorizationSetBuilder& AuthorizationSetBuilder::SetDefaultValidity() {
+    // Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to
+    // GeneralizedTime 999912312359559, which is 253402300799000 ms from Jan 1, 1970.
+    constexpr uint64_t kUndefinedExpirationDateTime = 253402300799000;
+    Authorization(TAG_CERTIFICATE_NOT_BEFORE, 0);
+    return Authorization(TAG_CERTIFICATE_NOT_AFTER, kUndefinedExpirationDateTime);
+}
+
 }  // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/cppcose.cpp b/security/keymint/support/cppcose.cpp
new file mode 100644
index 0000000..c626ade
--- /dev/null
+++ b/security/keymint/support/cppcose.cpp
@@ -0,0 +1,467 @@
+/*
+ * 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 <cppcose/cppcose.h>
+
+#include <stdio.h>
+#include <iostream>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <openssl/err.h>
+
+namespace cppcose {
+
+namespace {
+
+ErrMsgOr<bssl::UniquePtr<EVP_CIPHER_CTX>> aesGcmInitAndProcessAad(const bytevec& key,
+                                                                  const bytevec& nonce,
+                                                                  const bytevec& aad,
+                                                                  bool encrypt) {
+    if (key.size() != kAesGcmKeySize) return "Invalid key size";
+
+    bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
+    if (!ctx) return "Failed to allocate cipher context";
+
+    if (!EVP_CipherInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr /* engine */, key.data(),
+                           nonce.data(), encrypt ? 1 : 0)) {
+        return "Failed to initialize cipher";
+    }
+
+    int outlen;
+    if (!aad.empty() && !EVP_CipherUpdate(ctx.get(), nullptr /* out; null means AAD */, &outlen,
+                                          aad.data(), aad.size())) {
+        return "Failed to process AAD";
+    }
+
+    return std::move(ctx);
+}
+
+}  // namespace
+
+ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad,
+                                      const bytevec& payload) {
+    auto macStructure = cppbor::Array()
+                                .add("MAC0")
+                                .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
+                                .add(externalAad)
+                                .add(payload)
+                                .encode();
+
+    bytevec macTag(SHA256_DIGEST_LENGTH);
+    uint8_t* out = macTag.data();
+    unsigned int outLen;
+    out = HMAC(EVP_sha256(),                              //
+               macKey.data(), macKey.size(),              //
+               macStructure.data(), macStructure.size(),  //
+               out, &outLen);
+
+    assert(out != nullptr && outLen == macTag.size());
+    if (out == nullptr || outLen != macTag.size()) {
+        return "Error computing public key MAC";
+    }
+
+    return macTag;
+}
+
+ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad,
+                                          const bytevec& payload) {
+    auto tag = generateCoseMac0Mac(macKey, externalAad, payload);
+    if (!tag) return tag.moveMessage();
+
+    return cppbor::Array()
+            .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
+            .add(cppbor::Bstr() /* unprotected */)
+            .add(payload)
+            .add(tag.moveValue());
+}
+
+ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem) {
+    auto mac = macItem ? macItem->asArray() : nullptr;
+    if (!mac || mac->size() != kCoseMac0EntryCount) {
+        return "Invalid COSE_Mac0";
+    }
+
+    auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
+    auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr();
+    auto payload = mac->get(kCoseMac0Payload)->asBstr();
+    auto tag = mac->get(kCoseMac0Tag)->asBstr();
+    if (!protectedParms || !unprotectedParms || !payload || !tag) {
+        return "Invalid COSE_Mac0 contents";
+    }
+
+    return payload->value();
+}
+
+ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem,
+                                                       const bytevec& macKey) {
+    auto mac = macItem ? macItem->asArray() : nullptr;
+    if (!mac || mac->size() != kCoseMac0EntryCount) {
+        return "Invalid COSE_Mac0";
+    }
+
+    auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
+    auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr();
+    auto payload = mac->get(kCoseMac0Payload)->asBstr();
+    auto tag = mac->get(kCoseMac0Tag)->asBstr();
+    if (!protectedParms || !unprotectedParms || !payload || !tag) {
+        return "Invalid COSE_Mac0 contents";
+    }
+
+    auto [protectedMap, _, errMsg] = cppbor::parse(protectedParms);
+    if (!protectedMap || !protectedMap->asMap()) {
+        return "Invalid Mac0 protected: " + errMsg;
+    }
+    auto& algo = protectedMap->asMap()->get(ALGORITHM);
+    if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) {
+        return "Unsupported Mac0 algorithm";
+    }
+
+    auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value());
+    if (!macTag) return macTag.moveMessage();
+
+    if (macTag->size() != tag->value().size() ||
+        CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) {
+        return "MAC tag mismatch";
+    }
+
+    return payload->value();
+}
+
+ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams,
+                                           const bytevec& payload, const bytevec& aad) {
+    bytevec signatureInput = cppbor::Array()
+                                     .add("Signature1")  //
+                                     .add(protectedParams)
+                                     .add(aad)
+                                     .add(payload)
+                                     .encode();
+
+    if (key.size() != ED25519_PRIVATE_KEY_LEN) return "Invalid signing key";
+    bytevec signature(ED25519_SIGNATURE_LEN);
+    if (!ED25519_sign(signature.data(), signatureInput.data(), signatureInput.size(), key.data())) {
+        return "Signing failed";
+    }
+
+    return signature;
+}
+
+ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map protectedParams,
+                                           const bytevec& payload, const bytevec& aad) {
+    bytevec protParms = protectedParams.add(ALGORITHM, EDDSA).canonicalize().encode();
+    auto signature = createCoseSign1Signature(key, protParms, payload, aad);
+    if (!signature) return signature.moveMessage();
+
+    return cppbor::Array()
+            .add(protParms)
+            .add(bytevec{} /* unprotected parameters */)
+            .add(payload)
+            .add(*signature);
+}
+
+ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
+                                           const bytevec& aad) {
+    return constructCoseSign1(key, {} /* protectedParams */, payload, aad);
+}
+
+ErrMsgOr<bytevec> verifyAndParseCoseSign1(bool ignoreSignature, const cppbor::Array* coseSign1,
+                                          const bytevec& signingCoseKey, const bytevec& aad) {
+    if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) {
+        return "Invalid COSE_Sign1";
+    }
+
+    const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr();
+    const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr();
+    const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr();
+    const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr();
+
+    if (!protectedParams || !unprotectedParams || !payload || !signature) {
+        return "Invalid COSE_Sign1";
+    }
+
+    auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams);
+    if (!parsedProtParams) {
+        return errMsg + " when parsing protected params.";
+    }
+    if (!parsedProtParams->asMap()) {
+        return "Protected params must be a map";
+    }
+
+    auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
+    if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) {
+        return "Unsupported signature algorithm";
+    }
+
+    if (!ignoreSignature) {
+        bool selfSigned = signingCoseKey.empty();
+        auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey);
+        if (!key) return "Bad signing key: " + key.moveMessage();
+
+        bytevec signatureInput = cppbor::Array()
+                                         .add("Signature1")
+                                         .add(*protectedParams)
+                                         .add(aad)
+                                         .add(*payload)
+                                         .encode();
+
+        if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(),
+                            key->getBstrValue(CoseKey::PUBKEY_X)->data())) {
+            return "Signature verification failed";
+        }
+    }
+
+    return payload->value();
+}
+
+ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce,
+                                              const bytevec& protectedParams,
+                                              const bytevec& plaintextPayload, const bytevec& aad) {
+    auto ciphertext = aesGcmEncrypt(key, nonce,
+                                    cppbor::Array()                // Enc strucure as AAD
+                                            .add("Encrypt")        // Context
+                                            .add(protectedParams)  // Protected
+                                            .add(aad)              // External AAD
+                                            .encode(),
+                                    plaintextPayload);
+
+    if (!ciphertext) return ciphertext.moveMessage();
+    return ciphertext.moveValue();
+}
+
+ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce,
+                                             const bytevec& plaintextPayload, const bytevec& aad,
+                                             cppbor::Array recipients) {
+    auto encryptProtectedHeader = cppbor::Map()  //
+                                          .add(ALGORITHM, AES_GCM_256)
+                                          .canonicalize()
+                                          .encode();
+
+    auto ciphertext =
+            createCoseEncryptCiphertext(key, nonce, encryptProtectedHeader, plaintextPayload, aad);
+    if (!ciphertext) return ciphertext.moveMessage();
+
+    return cppbor::Array()
+            .add(encryptProtectedHeader)                       // Protected
+            .add(cppbor::Map().add(IV, nonce).canonicalize())  // Unprotected
+            .add(*ciphertext)                                  // Payload
+            .add(std::move(recipients));
+}
+
+ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt(
+        const cppbor::Item* coseEncrypt) {
+    if (!coseEncrypt || !coseEncrypt->asArray() ||
+        coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) {
+        return "Invalid COSE_Encrypt";
+    }
+
+    auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients);
+    if (!recipients || !recipients->asArray() || recipients->asArray()->size() != 1) {
+        return "Invalid recipients list";
+    }
+
+    auto& recipient = recipients->asArray()->get(0);
+    if (!recipient || !recipient->asArray() || recipient->asArray()->size() != 3) {
+        return "Invalid COSE_recipient";
+    }
+
+    auto& ciphertext = recipient->asArray()->get(2);
+    if (!ciphertext->asSimple() || !ciphertext->asSimple()->asNull()) {
+        return "Unexpected value in recipients ciphertext field " +
+               cppbor::prettyPrint(ciphertext.get());
+    }
+
+    auto& protParms = recipient->asArray()->get(0);
+    if (!protParms || !protParms->asBstr()) return "Invalid protected params";
+    auto [parsedProtParms, _, errMsg] = cppbor::parse(protParms->asBstr());
+    if (!parsedProtParms) return "Failed to parse protected params: " + errMsg;
+    if (!parsedProtParms->asMap()) return "Invalid protected params";
+
+    auto& algorithm = parsedProtParms->asMap()->get(ALGORITHM);
+    if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != ECDH_ES_HKDF_256) {
+        return "Invalid algorithm";
+    }
+
+    auto& unprotParms = recipient->asArray()->get(1);
+    if (!unprotParms || !unprotParms->asMap()) return "Invalid unprotected params";
+
+    auto& senderCoseKey = unprotParms->asMap()->get(COSE_KEY);
+    if (!senderCoseKey || !senderCoseKey->asMap()) return "Invalid sender COSE_Key";
+
+    auto& keyType = senderCoseKey->asMap()->get(CoseKey::KEY_TYPE);
+    if (!keyType || !keyType->asInt() || keyType->asInt()->value() != OCTET_KEY_PAIR) {
+        return "Invalid key type";
+    }
+
+    auto& curve = senderCoseKey->asMap()->get(CoseKey::CURVE);
+    if (!curve || !curve->asInt() || curve->asInt()->value() != X25519) {
+        return "Unsupported curve";
+    }
+
+    auto& pubkey = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X);
+    if (!pubkey || !pubkey->asBstr() ||
+        pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) {
+        return "Invalid X25519 public key";
+    }
+
+    auto& key_id = unprotParms->asMap()->get(KEY_ID);
+    if (key_id && key_id->asBstr()) {
+        return std::make_pair(pubkey->asBstr()->value(), key_id->asBstr()->value());
+    }
+
+    // If no key ID, just return an empty vector.
+    return std::make_pair(pubkey->asBstr()->value(), bytevec{});
+}
+
+ErrMsgOr<bytevec> decryptCoseEncrypt(const bytevec& key, const cppbor::Item* coseEncrypt,
+                                     const bytevec& external_aad) {
+    if (!coseEncrypt || !coseEncrypt->asArray() ||
+        coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) {
+        return "Invalid COSE_Encrypt";
+    }
+
+    auto& protParms = coseEncrypt->asArray()->get(kCoseEncryptProtectedParams);
+    auto& unprotParms = coseEncrypt->asArray()->get(kCoseEncryptUnprotectedParams);
+    auto& ciphertext = coseEncrypt->asArray()->get(kCoseEncryptPayload);
+    auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients);
+
+    if (!protParms || !protParms->asBstr() || !unprotParms || !ciphertext || !recipients) {
+        return "Invalid COSE_Encrypt";
+    }
+
+    auto [parsedProtParams, _, errMsg] = cppbor::parse(protParms->asBstr()->value());
+    if (!parsedProtParams) {
+        return errMsg + " when parsing protected params.";
+    }
+    if (!parsedProtParams->asMap()) {
+        return "Protected params must be a map";
+    }
+
+    auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
+    if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != AES_GCM_256) {
+        return "Unsupported encryption algorithm";
+    }
+
+    if (!unprotParms->asMap() || unprotParms->asMap()->size() != 1) {
+        return "Invalid unprotected params";
+    }
+
+    auto& nonce = unprotParms->asMap()->get(IV);
+    if (!nonce || !nonce->asBstr() || nonce->asBstr()->value().size() != kAesGcmNonceLength) {
+        return "Invalid nonce";
+    }
+
+    if (!ciphertext->asBstr()) return "Invalid ciphertext";
+
+    auto aad = cppbor::Array()                             // Enc strucure as AAD
+                       .add("Encrypt")                     // Context
+                       .add(protParms->asBstr()->value())  // Protected
+                       .add(external_aad)                  // External AAD
+                       .encode();
+
+    return aesGcmDecrypt(key, nonce->asBstr()->value(), aad, ciphertext->asBstr()->value());
+}
+
+ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA,
+                                        const bytevec& pubKeyB, bool senderIsA) {
+    bytevec rawSharedKey(X25519_SHARED_KEY_LEN);
+    if (!::X25519(rawSharedKey.data(), privKeyA.data(), pubKeyB.data())) {
+        return "ECDH operation failed";
+    }
+
+    bytevec kdfContext = cppbor::Array()
+                                 .add(AES_GCM_256)
+                                 .add(cppbor::Array()  // Sender Info
+                                              .add(cppbor::Bstr("client"))
+                                              .add(bytevec{} /* nonce */)
+                                              .add(senderIsA ? pubKeyA : pubKeyB))
+                                 .add(cppbor::Array()  // Recipient Info
+                                              .add(cppbor::Bstr("server"))
+                                              .add(bytevec{} /* nonce */)
+                                              .add(senderIsA ? pubKeyB : pubKeyA))
+                                 .add(cppbor::Array()           // SuppPubInfo
+                                              .add(128)         // output key length
+                                              .add(bytevec{}))  // protected
+                                 .encode();
+
+    bytevec retval(SHA256_DIGEST_LENGTH);
+    bytevec salt{};
+    if (!HKDF(retval.data(), retval.size(),              //
+              EVP_sha256(),                              //
+              rawSharedKey.data(), rawSharedKey.size(),  //
+              salt.data(), salt.size(),                  //
+              kdfContext.data(), kdfContext.size())) {
+        return "ECDH HKDF failed";
+    }
+
+    return retval;
+}
+
+ErrMsgOr<bytevec> aesGcmEncrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad,
+                                const bytevec& plaintext) {
+    auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, true /* encrypt */);
+    if (!ctx) return ctx.moveMessage();
+
+    bytevec ciphertext(plaintext.size() + kAesGcmTagSize);
+    int outlen;
+    if (!EVP_CipherUpdate(ctx->get(), ciphertext.data(), &outlen, plaintext.data(),
+                          plaintext.size())) {
+        return "Failed to encrypt plaintext";
+    }
+    assert(plaintext.size() == outlen);
+
+    if (!EVP_CipherFinal_ex(ctx->get(), ciphertext.data() + outlen, &outlen)) {
+        return "Failed to finalize encryption";
+    }
+    assert(outlen == 0);
+
+    if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize,
+                             ciphertext.data() + plaintext.size())) {
+        return "Failed to retrieve tag";
+    }
+
+    return ciphertext;
+}
+
+ErrMsgOr<bytevec> aesGcmDecrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad,
+                                const bytevec& ciphertextWithTag) {
+    auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, false /* encrypt */);
+    if (!ctx) return ctx.moveMessage();
+
+    if (ciphertextWithTag.size() < kAesGcmTagSize) return "Missing tag";
+
+    bytevec plaintext(ciphertextWithTag.size() - kAesGcmTagSize);
+    int outlen;
+    if (!EVP_CipherUpdate(ctx->get(), plaintext.data(), &outlen, ciphertextWithTag.data(),
+                          ciphertextWithTag.size() - kAesGcmTagSize)) {
+        return "Failed to decrypt plaintext";
+    }
+    assert(plaintext.size() == outlen);
+
+    bytevec tag(ciphertextWithTag.end() - kAesGcmTagSize, ciphertextWithTag.end());
+    if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagSize, tag.data())) {
+        return "Failed to set tag: " + std::to_string(ERR_peek_last_error());
+    }
+
+    if (!EVP_CipherFinal_ex(ctx->get(), nullptr, &outlen)) {
+        return "Failed to finalize encryption";
+    }
+    assert(outlen == 0);
+
+    return plaintext;
+}
+
+}  // namespace cppcose
diff --git a/security/keymint/support/include/cppcose/cppcose.h b/security/keymint/support/include/cppcose/cppcose.h
new file mode 100644
index 0000000..a936bfd
--- /dev/null
+++ b/security/keymint/support/include/cppcose/cppcose.h
@@ -0,0 +1,288 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <openssl/cipher.h>
+#include <openssl/curve25519.h>
+#include <openssl/digest.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/mem.h>
+#include <openssl/sha.h>
+
+namespace cppcose {
+
+using bytevec = std::vector<uint8_t>;
+
+constexpr int kCoseSign1EntryCount = 4;
+constexpr int kCoseSign1ProtectedParams = 0;
+constexpr int kCoseSign1UnprotectedParams = 1;
+constexpr int kCoseSign1Payload = 2;
+constexpr int kCoseSign1Signature = 3;
+
+constexpr int kCoseMac0EntryCount = 4;
+constexpr int kCoseMac0ProtectedParams = 0;
+constexpr int kCoseMac0UnprotectedParams = 1;
+constexpr int kCoseMac0Payload = 2;
+constexpr int kCoseMac0Tag = 3;
+
+constexpr int kCoseEncryptEntryCount = 4;
+constexpr int kCoseEncryptProtectedParams = 0;
+constexpr int kCoseEncryptUnprotectedParams = 1;
+constexpr int kCoseEncryptPayload = 2;
+constexpr int kCoseEncryptRecipients = 3;
+
+enum Label : int {
+    ALGORITHM = 1,
+    KEY_ID = 4,
+    IV = 5,
+    COSE_KEY = -1,
+};
+
+enum CoseKeyAlgorithm : int {
+    AES_GCM_256 = 3,
+    HMAC_256 = 5,
+    ES256 = -7,  // ECDSA with SHA-256
+    EDDSA = -8,
+    ECDH_ES_HKDF_256 = -25,
+};
+
+enum CoseKeyCurve : int { P256 = 1, X25519 = 4, ED25519 = 6 };
+enum CoseKeyType : int { OCTET_KEY_PAIR = 1, EC2 = 2, SYMMETRIC_KEY = 4 };
+enum CoseKeyOps : int { SIGN = 1, VERIFY = 2, ENCRYPT = 3, DECRYPT = 4 };
+
+constexpr int kAesGcmNonceLength = 12;
+constexpr int kAesGcmTagSize = 16;
+constexpr int kAesGcmKeySize = 32;
+
+template <typename T>
+class ErrMsgOr {
+  public:
+    ErrMsgOr(std::string errMsg) : errMsg_(std::move(errMsg)) {}
+    ErrMsgOr(const char* errMsg) : errMsg_(errMsg) {}
+    ErrMsgOr(T val) : value_(std::move(val)) {}
+
+    operator bool() const { return value_.has_value(); }
+
+    T* operator->() & {
+        assert(value_);
+        return &value_.value();
+    }
+    T& operator*() & {
+        assert(value_);
+        return value_.value();
+    };
+    T&& operator*() && {
+        assert(value_);
+        return std::move(value_).value();
+    };
+
+    const std::string& message() { return errMsg_; }
+    std::string moveMessage() { return std::move(errMsg_); }
+
+    T moveValue() {
+        assert(value_);
+        return std::move(value_).value();
+    }
+
+  private:
+    std::string errMsg_;
+    std::optional<T> value_;
+};
+
+class CoseKey {
+  public:
+    CoseKey() {}
+    CoseKey(const CoseKey&) = delete;
+    CoseKey(CoseKey&&) = default;
+
+    enum Label : int {
+        KEY_TYPE = 1,
+        KEY_ID = 2,
+        ALGORITHM = 3,
+        KEY_OPS = 4,
+        CURVE = -1,
+        PUBKEY_X = -2,
+        PUBKEY_Y = -3,
+        PRIVATE_KEY = -4,
+        TEST_KEY = -70000  // Application-defined
+    };
+
+    static ErrMsgOr<CoseKey> parse(const bytevec& coseKey) {
+        auto [parsedKey, _, errMsg] = cppbor::parse(coseKey);
+        if (!parsedKey) return errMsg + " when parsing key";
+        if (!parsedKey->asMap()) return "CoseKey must be a map";
+        return CoseKey(static_cast<cppbor::Map*>(parsedKey.release()));
+    }
+
+    static ErrMsgOr<CoseKey> parse(const bytevec& coseKey, CoseKeyType expectedKeyType,
+                                   CoseKeyAlgorithm expectedAlgorithm, CoseKeyCurve expectedCurve) {
+        auto key = parse(coseKey);
+        if (!key) return key;
+
+        if (!key->checkIntValue(CoseKey::KEY_TYPE, expectedKeyType) ||
+            !key->checkIntValue(CoseKey::ALGORITHM, expectedAlgorithm) ||
+            !key->checkIntValue(CoseKey::CURVE, expectedCurve)) {
+            return "Unexpected key type:";
+        }
+
+        return key;
+    }
+
+    static ErrMsgOr<CoseKey> parseEd25519(const bytevec& coseKey) {
+        auto key = parse(coseKey, OCTET_KEY_PAIR, EDDSA, ED25519);
+        if (!key) return key;
+
+        auto& pubkey = key->getMap().get(PUBKEY_X);
+        if (!pubkey || !pubkey->asBstr() ||
+            pubkey->asBstr()->value().size() != ED25519_PUBLIC_KEY_LEN) {
+            return "Invalid Ed25519 public key";
+        }
+
+        return key;
+    }
+
+    static ErrMsgOr<CoseKey> parseX25519(const bytevec& coseKey, bool requireKid) {
+        auto key = parse(coseKey, OCTET_KEY_PAIR, ECDH_ES_HKDF_256, X25519);
+        if (!key) return key;
+
+        auto& pubkey = key->getMap().get(PUBKEY_X);
+        if (!pubkey || !pubkey->asBstr() ||
+            pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) {
+            return "Invalid X25519 public key";
+        }
+
+        auto& kid = key->getMap().get(KEY_ID);
+        if (requireKid && (!kid || !kid->asBstr())) {
+            return "Missing KID";
+        }
+
+        return key;
+    }
+
+    static ErrMsgOr<CoseKey> parseP256(const bytevec& coseKey) {
+        auto key = parse(coseKey, EC2, ES256, P256);
+        if (!key) return key;
+
+        auto& pubkey_x = key->getMap().get(PUBKEY_X);
+        auto& pubkey_y = key->getMap().get(PUBKEY_Y);
+        if (!pubkey_x || !pubkey_y || !pubkey_x->asBstr() || !pubkey_y->asBstr() ||
+            pubkey_x->asBstr()->value().size() != 32 || pubkey_y->asBstr()->value().size() != 32) {
+            return "Invalid P256 public key";
+        }
+
+        return key;
+    }
+
+    std::optional<int> getIntValue(Label label) {
+        const auto& value = key_->get(label);
+        if (!value || !value->asInt()) return {};
+        return value->asInt()->value();
+    }
+
+    std::optional<bytevec> getBstrValue(Label label) {
+        const auto& value = key_->get(label);
+        if (!value || !value->asBstr()) return {};
+        return value->asBstr()->value();
+    }
+
+    const cppbor::Map& getMap() const { return *key_; }
+    cppbor::Map&& moveMap() { return std::move(*key_); }
+
+    bool checkIntValue(Label label, int expectedValue) {
+        const auto& value = key_->get(label);
+        return value && value->asInt() && value->asInt()->value() == expectedValue;
+    }
+
+    void add(Label label, int value) { key_->add(label, value); }
+    void add(Label label, bytevec value) { key_->add(label, std::move(value)); }
+
+    bytevec encode() { return key_->canonicalize().encode(); }
+
+  private:
+    CoseKey(cppbor::Map* parsedKey) : key_(parsedKey) {}
+
+    // This is the full parsed key structure.
+    std::unique_ptr<cppbor::Map> key_;
+};
+
+ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad,
+                                      const bytevec& payload);
+ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad,
+                                          const bytevec& payload);
+ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem);
+ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem,
+                                                       const bytevec& macKey);
+
+ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams,
+                                           const bytevec& payload, const bytevec& aad);
+ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
+                                           const bytevec& aad);
+ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map extraProtectedFields,
+                                           const bytevec& payload, const bytevec& aad);
+/**
+ * Verify and parse a COSE_Sign1 message, returning the payload.
+ *
+ * @param ignoreSignature indicates whether signature verification should be skipped.  If true, no
+ *        verification of the signature will be done.
+ *
+ * @param coseSign1 is the COSE_Sign1 to verify and parse.
+ *
+ * @param signingCoseKey is a CBOR-encoded COSE_Key to use to verify the signature.  The bytevec may
+ *        be empty, in which case the function assumes that coseSign1's payload is the COSE_Key to
+ *        use, i.e. that coseSign1 is a self-signed "certificate".
+ */
+ErrMsgOr<bytevec /* payload */> verifyAndParseCoseSign1(bool ignoreSignature,
+                                                        const cppbor::Array* coseSign1,
+                                                        const bytevec& signingCoseKey,
+                                                        const bytevec& aad);
+
+ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce,
+                                              const bytevec& protectedParams, const bytevec& aad);
+ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce,
+                                             const bytevec& plaintextPayload, const bytevec& aad,
+                                             cppbor::Array recipients);
+ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt(
+        const cppbor::Item* encryptItem);
+inline ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>>
+getSenderPubKeyFromCoseEncrypt(const std::unique_ptr<cppbor::Item>& encryptItem) {
+    return getSenderPubKeyFromCoseEncrypt(encryptItem.get());
+}
+
+ErrMsgOr<bytevec /* plaintextPayload */> decryptCoseEncrypt(const bytevec& key,
+                                                            const cppbor::Item* encryptItem,
+                                                            const bytevec& aad);
+
+ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& senderPubKey, const bytevec& senderPrivKey,
+                                        const bytevec& recipientPubKey, bool senderIsA);
+
+ErrMsgOr<bytevec /* ciphertextWithTag */> aesGcmEncrypt(const bytevec& key, const bytevec& nonce,
+                                                        const bytevec& aad,
+                                                        const bytevec& plaintext);
+ErrMsgOr<bytevec /* plaintext */> aesGcmDecrypt(const bytevec& key, const bytevec& nonce,
+                                                const bytevec& aad,
+                                                const bytevec& ciphertextWithTag);
+
+}  // namespace cppcose
diff --git a/security/keymint/support/include/keymint_support/authorization_set.h b/security/keymint/support/include/keymint_support/authorization_set.h
index 4ff1705..ca51b08 100644
--- a/security/keymint/support/include/keymint_support/authorization_set.h
+++ b/security/keymint/support/include/keymint_support/authorization_set.h
@@ -259,6 +259,12 @@
                              size - 1);  // drop the terminating '\0'
     }
 
+    template <Tag tag>
+    AuthorizationSetBuilder& Authorization(TypedTag<TagType::BYTES, tag> ttag,
+                                           const std::string& data) {
+        return Authorization(ttag, reinterpret_cast<const uint8_t*>(data.data()), data.size());
+    }
+
     AuthorizationSetBuilder& Authorizations(const AuthorizationSet& set) {
         for (const auto& entry : set) {
             push_back(entry);
@@ -282,6 +288,7 @@
 
     AuthorizationSetBuilder& SigningKey();
     AuthorizationSetBuilder& EncryptionKey();
+    AuthorizationSetBuilder& AttestKey();
 
     AuthorizationSetBuilder& NoDigestOrPadding();
 
@@ -290,9 +297,26 @@
     AuthorizationSetBuilder& GcmModeMacLen(uint32_t macLength);
 
     AuthorizationSetBuilder& BlockMode(std::initializer_list<BlockMode> blockModes);
+    AuthorizationSetBuilder& OaepMGFDigest(const std::vector<Digest>& digests);
     AuthorizationSetBuilder& Digest(std::vector<Digest> digests);
     AuthorizationSetBuilder& Padding(std::initializer_list<PaddingMode> paddings);
 
+    AuthorizationSetBuilder& SetDefaultValidity();
+
+    AuthorizationSetBuilder& AttestationChallenge(const std::string& challenge) {
+        return Authorization(TAG_ATTESTATION_CHALLENGE, challenge);
+    }
+    AuthorizationSetBuilder& AttestationChallenge(std::vector<uint8_t> challenge) {
+        return Authorization(TAG_ATTESTATION_CHALLENGE, challenge);
+    }
+
+    AuthorizationSetBuilder& AttestationApplicationId(const std::string& id) {
+        return Authorization(TAG_ATTESTATION_APPLICATION_ID, id);
+    }
+    AuthorizationSetBuilder& AttestationApplicationId(std::vector<uint8_t> id) {
+        return Authorization(TAG_ATTESTATION_APPLICATION_ID, id);
+    }
+
     template <typename... T>
     AuthorizationSetBuilder& BlockMode(T&&... a) {
         return BlockMode({std::forward<T>(a)...});
diff --git a/security/keymint/support/include/keymint_support/key_param_output.h b/security/keymint/support/include/keymint_support/key_param_output.h
index 5f004fe..c2b0029 100644
--- a/security/keymint/support/include/keymint_support/key_param_output.h
+++ b/security/keymint/support/include/keymint_support/key_param_output.h
@@ -84,8 +84,10 @@
 ::std::ostream& operator<<(::std::ostream& os, const KeyParameter& param);
 
 inline ::std::ostream& operator<<(::std::ostream& os, const KeyCharacteristics& value) {
-    return os << "SW: " << value.softwareEnforced << ::std::endl
-              << "HW: " << value.hardwareEnforced << ::std::endl;
+    for (auto& entry : value.authorizations) {
+        os << value.securityLevel << ": " << entry;
+    }
+    return os;
 }
 
 inline ::std::ostream& operator<<(::std::ostream& os, KeyPurpose value) {
diff --git a/security/keymint/support/include/keymint_support/keymint_tags.h b/security/keymint/support/include/keymint_support/keymint_tags.h
index d932b40..479a11d 100644
--- a/security/keymint/support/include/keymint_support/keymint_tags.h
+++ b/security/keymint/support/include/keymint_support/keymint_tags.h
@@ -119,11 +119,17 @@
 DECLARE_TYPED_TAG(TRUSTED_USER_PRESENCE_REQUIRED);
 DECLARE_TYPED_TAG(UNIQUE_ID);
 DECLARE_TYPED_TAG(UNLOCKED_DEVICE_REQUIRED);
+DECLARE_TYPED_TAG(USAGE_COUNT_LIMIT);
 DECLARE_TYPED_TAG(USAGE_EXPIRE_DATETIME);
 DECLARE_TYPED_TAG(USER_AUTH_TYPE);
 DECLARE_TYPED_TAG(USER_ID);
 DECLARE_TYPED_TAG(USER_SECURE_ID);
 DECLARE_TYPED_TAG(VENDOR_PATCHLEVEL);
+DECLARE_TYPED_TAG(RSA_OAEP_MGF_DIGEST);
+DECLARE_TYPED_TAG(CERTIFICATE_SERIAL);
+DECLARE_TYPED_TAG(CERTIFICATE_SUBJECT);
+DECLARE_TYPED_TAG(CERTIFICATE_NOT_BEFORE);
+DECLARE_TYPED_TAG(CERTIFICATE_NOT_AFTER);
 
 #undef DECLARE_TYPED_TAG
 
@@ -134,15 +140,15 @@
         TAG_INVALID_t, TAG_KEY_SIZE_t, TAG_MAC_LENGTH_t, TAG_CALLER_NONCE_t, TAG_MIN_MAC_LENGTH_t,
         TAG_RSA_PUBLIC_EXPONENT_t, TAG_INCLUDE_UNIQUE_ID_t, TAG_ACTIVE_DATETIME_t,
         TAG_ORIGINATION_EXPIRE_DATETIME_t, TAG_USAGE_EXPIRE_DATETIME_t,
-        TAG_MIN_SECONDS_BETWEEN_OPS_t, TAG_MAX_USES_PER_BOOT_t, TAG_USER_ID_t, TAG_USER_SECURE_ID_t,
-        TAG_NO_AUTH_REQUIRED_t, TAG_AUTH_TIMEOUT_t, TAG_ALLOW_WHILE_ON_BODY_t,
-        TAG_UNLOCKED_DEVICE_REQUIRED_t, TAG_APPLICATION_ID_t, TAG_APPLICATION_DATA_t,
-        TAG_CREATION_DATETIME_t, TAG_ROLLBACK_RESISTANCE_t, TAG_HARDWARE_TYPE_t,
-        TAG_ROOT_OF_TRUST_t, TAG_ASSOCIATED_DATA_t, TAG_NONCE_t, TAG_BOOTLOADER_ONLY_t,
-        TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t, TAG_ATTESTATION_CHALLENGE_t,
-        TAG_ATTESTATION_APPLICATION_ID_t, TAG_ATTESTATION_ID_BRAND_t, TAG_ATTESTATION_ID_DEVICE_t,
-        TAG_ATTESTATION_ID_PRODUCT_t, TAG_ATTESTATION_ID_MANUFACTURER_t, TAG_ATTESTATION_ID_MODEL_t,
-        TAG_ATTESTATION_ID_SERIAL_t, TAG_ATTESTATION_ID_IMEI_t, TAG_ATTESTATION_ID_MEID_t,
+        TAG_MIN_SECONDS_BETWEEN_OPS_t, TAG_MAX_USES_PER_BOOT_t, TAG_USAGE_COUNT_LIMIT_t,
+        TAG_USER_ID_t, TAG_USER_SECURE_ID_t, TAG_NO_AUTH_REQUIRED_t, TAG_AUTH_TIMEOUT_t,
+        TAG_ALLOW_WHILE_ON_BODY_t, TAG_UNLOCKED_DEVICE_REQUIRED_t, TAG_APPLICATION_ID_t,
+        TAG_APPLICATION_DATA_t, TAG_CREATION_DATETIME_t, TAG_ROLLBACK_RESISTANCE_t,
+        TAG_HARDWARE_TYPE_t, TAG_ROOT_OF_TRUST_t, TAG_ASSOCIATED_DATA_t, TAG_NONCE_t,
+        TAG_BOOTLOADER_ONLY_t, TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t,
+        TAG_ATTESTATION_CHALLENGE_t, TAG_ATTESTATION_APPLICATION_ID_t, TAG_ATTESTATION_ID_BRAND_t,
+        TAG_ATTESTATION_ID_DEVICE_t, TAG_ATTESTATION_ID_PRODUCT_t,
+        TAG_ATTESTATION_ID_MANUFACTURER_t, TAG_ATTESTATION_ID_MODEL_t,
         TAG_RESET_SINCE_ID_ROTATION_t, TAG_PURPOSE_t, TAG_ALGORITHM_t, TAG_BLOCK_MODE_t,
         TAG_DIGEST_t, TAG_PADDING_t, TAG_ORIGIN_t, TAG_USER_AUTH_TYPE_t, TAG_EC_CURVE_t,
         TAG_BOOT_PATCHLEVEL_t, TAG_VENDOR_PATCHLEVEL_t, TAG_TRUSTED_CONFIRMATION_REQUIRED_t,
@@ -239,6 +245,7 @@
 MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_PURPOSE, keyPurpose)
 MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_USER_AUTH_TYPE, hardwareAuthenticatorType)
 MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_HARDWARE_TYPE, securityLevel)
+MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_RSA_OAEP_MGF_DIGEST, digest)
 
 #undef MAKE_TAG_ENUM_VALUE_ACCESSOR
 
@@ -323,7 +330,9 @@
 inline std::optional<
         std::reference_wrapper<const typename TypedTag2ValueType<TypedTag<tag_type, tag>>::type>>
 authorizationValue(TypedTag<tag_type, tag> ttag, const KeyParameter& param) {
-    if (TypedTag2ValueType<TypedTag<tag_type, tag>>::unionTag != param.value.getTag()) return {};
+    // We only check if the parameter has the correct tag here; accessTagValue checks if the correct
+    // union field was initialized.
+    if (tag != param.tag) return {};
     return accessTagValue(ttag, param);
 }
 
diff --git a/security/keymint/support/include/keymint_support/openssl_utils.h b/security/keymint/support/include/keymint_support/openssl_utils.h
index 9ae7e52..a0212aa 100644
--- a/security/keymint/support/include/keymint_support/openssl_utils.h
+++ b/security/keymint/support/include/keymint_support/openssl_utils.h
@@ -34,10 +34,14 @@
     typedef std::unique_ptr<type, UniquePtrDeleter<type, type##_free>> type##_Ptr;
 
 MAKE_OPENSSL_PTR_TYPE(ASN1_OBJECT)
+MAKE_OPENSSL_PTR_TYPE(BN_CTX)
+MAKE_OPENSSL_PTR_TYPE(EC_GROUP)
+MAKE_OPENSSL_PTR_TYPE(EC_KEY)
 MAKE_OPENSSL_PTR_TYPE(EVP_PKEY)
+MAKE_OPENSSL_PTR_TYPE(EVP_PKEY_CTX)
 MAKE_OPENSSL_PTR_TYPE(RSA)
 MAKE_OPENSSL_PTR_TYPE(X509)
-MAKE_OPENSSL_PTR_TYPE(BN_CTX)
+MAKE_OPENSSL_PTR_TYPE(X509_NAME)
 
 typedef std::unique_ptr<BIGNUM, UniquePtrDeleter<BIGNUM, BN_free>> BIGNUM_Ptr;
 
diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h
new file mode 100644
index 0000000..5e205a2
--- /dev/null
+++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <cppcose/cppcose.h>
+
+namespace aidl::android::hardware::security::keymint::remote_prov {
+
+using bytevec = std::vector<uint8_t>;
+using namespace cppcose;
+
+extern bytevec kTestMacKey;
+
+/**
+ * Generates random bytes.
+ */
+bytevec randomBytes(size_t numBytes);
+
+struct EekChain {
+    bytevec chain;
+    bytevec last_pubkey;
+    bytevec last_privkey;
+};
+
+/**
+ * Generates an X25518 EEK with the specified eekId and an Ed25519 chain of the
+ * specified length. All keys are generated randomly.
+ */
+ErrMsgOr<EekChain> generateEekChain(size_t length, const bytevec& eekId);
+
+struct BccEntryData {
+    bytevec pubKey;
+};
+
+/**
+ * Validates the provided CBOR-encoded BCC, returning a vector of BccEntryData
+ * structs containing the BCC entry contents.  If an entry contains no firmware
+ * digest, the corresponding BccEntryData.firmwareDigest will have length zero
+ * (there's no way to distinguish between an empty and missing firmware digest,
+ * which seems fine).
+ */
+ErrMsgOr<std::vector<BccEntryData>> validateBcc(const cppbor::Array* bcc);
+
+}  // namespace aidl::android::hardware::security::keymint::remote_prov
diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp
new file mode 100644
index 0000000..111cb30
--- /dev/null
+++ b/security/keymint/support/remote_prov_utils.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2019, 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 <remote_prov/remote_prov_utils.h>
+
+#include <openssl/rand.h>
+
+#include <cppbor.h>
+
+namespace aidl::android::hardware::security::keymint::remote_prov {
+
+bytevec kTestMacKey(32 /* count */, 0 /* byte value */);
+
+bytevec randomBytes(size_t numBytes) {
+    bytevec retval(numBytes);
+    RAND_bytes(retval.data(), numBytes);
+    return retval;
+}
+
+ErrMsgOr<EekChain> generateEekChain(size_t length, const bytevec& eekId) {
+    auto eekChain = cppbor::Array();
+
+    bytevec prev_priv_key;
+    for (size_t i = 0; i < length - 1; ++i) {
+        bytevec pub_key(ED25519_PUBLIC_KEY_LEN);
+        bytevec priv_key(ED25519_PRIVATE_KEY_LEN);
+
+        ED25519_keypair(pub_key.data(), priv_key.data());
+
+        // The first signing key is self-signed.
+        if (prev_priv_key.empty()) prev_priv_key = priv_key;
+
+        auto coseSign1 = constructCoseSign1(prev_priv_key,
+                                            cppbor::Map() /* payload CoseKey */
+                                                    .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+                                                    .add(CoseKey::ALGORITHM, EDDSA)
+                                                    .add(CoseKey::CURVE, ED25519)
+                                                    .add(CoseKey::PUBKEY_X, pub_key)
+                                                    .canonicalize()
+                                                    .encode(),
+                                            {} /* AAD */);
+        if (!coseSign1) return coseSign1.moveMessage();
+        eekChain.add(coseSign1.moveValue());
+    }
+
+    bytevec pub_key(X25519_PUBLIC_VALUE_LEN);
+    bytevec priv_key(X25519_PRIVATE_KEY_LEN);
+    X25519_keypair(pub_key.data(), priv_key.data());
+
+    auto coseSign1 = constructCoseSign1(prev_priv_key,
+                                        cppbor::Map() /* payload CoseKey */
+                                                .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+                                                .add(CoseKey::KEY_ID, eekId)
+                                                .add(CoseKey::ALGORITHM, ECDH_ES_HKDF_256)
+                                                .add(CoseKey::CURVE, cppcose::X25519)
+                                                .add(CoseKey::PUBKEY_X, pub_key)
+                                                .canonicalize()
+                                                .encode(),
+                                        {} /* AAD */);
+    if (!coseSign1) return coseSign1.moveMessage();
+    eekChain.add(coseSign1.moveValue());
+
+    return EekChain{eekChain.encode(), pub_key, priv_key};
+}
+
+ErrMsgOr<bytevec> verifyAndParseCoseSign1Cwt(bool ignoreSignature, const cppbor::Array* coseSign1,
+                                             const bytevec& signingCoseKey, const bytevec& aad) {
+    if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) {
+        return "Invalid COSE_Sign1";
+    }
+
+    const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr();
+    const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr();
+    const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr();
+    const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr();
+
+    if (!protectedParams || !unprotectedParams || !payload || !signature) {
+        return "Invalid COSE_Sign1";
+    }
+
+    auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams);
+    if (!parsedProtParams) {
+        return errMsg + " when parsing protected params.";
+    }
+    if (!parsedProtParams->asMap()) {
+        return "Protected params must be a map";
+    }
+
+    auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
+    if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) {
+        return "Unsupported signature algorithm";
+    }
+
+    // TODO(jbires): Handle CWTs as the CoseSign1 payload in a less hacky way. Since the CWT payload
+    //               is extremely remote provisioning specific, probably just make a separate
+    //               function there.
+    auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(payload);
+    if (!parsedPayload) return payloadErrMsg + " when parsing key";
+    if (!parsedPayload->asMap()) return "CWT must be a map";
+    auto serializedKey = parsedPayload->asMap()->get(-4670552)->clone();
+    if (!serializedKey || !serializedKey->asBstr()) return "Could not find key entry";
+
+    if (!ignoreSignature) {
+        bool selfSigned = signingCoseKey.empty();
+        auto key = CoseKey::parseEd25519(selfSigned ? serializedKey->asBstr()->value()
+                                                    : signingCoseKey);
+        if (!key) return "Bad signing key: " + key.moveMessage();
+
+        bytevec signatureInput = cppbor::Array()
+                                         .add("Signature1")
+                                         .add(*protectedParams)
+                                         .add(aad)
+                                         .add(*payload)
+                                         .encode();
+
+        if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(),
+                            key->getBstrValue(CoseKey::PUBKEY_X)->data())) {
+            return "Signature verification failed";
+        }
+    }
+
+    return serializedKey->asBstr()->value();
+}
+ErrMsgOr<std::vector<BccEntryData>> validateBcc(const cppbor::Array* bcc) {
+    if (!bcc || bcc->size() == 0) return "Invalid BCC";
+
+    std::vector<BccEntryData> result;
+
+    bytevec prevKey;
+    // TODO(jbires): Actually process the pubKey at the start of the new bcc entry
+    for (size_t i = 1; i < bcc->size(); ++i) {
+        const cppbor::Array* entry = bcc->get(i)->asArray();
+        if (!entry || entry->size() != kCoseSign1EntryCount) {
+            return "Invalid BCC entry " + std::to_string(i) + ": " + prettyPrint(entry);
+        }
+        auto payload = verifyAndParseCoseSign1Cwt(false /* ignoreSignature */, entry,
+                                                  std::move(prevKey), bytevec{} /* AAD */);
+        if (!payload) {
+            return "Failed to verify entry " + std::to_string(i) + ": " + payload.moveMessage();
+        }
+
+        auto& certProtParms = entry->get(kCoseSign1ProtectedParams);
+        if (!certProtParms || !certProtParms->asBstr()) return "Invalid prot params";
+        auto [parsedProtParms, _, errMsg] = cppbor::parse(certProtParms->asBstr()->value());
+        if (!parsedProtParms || !parsedProtParms->asMap()) return "Invalid prot params";
+
+        result.push_back(BccEntryData{*payload});
+
+        // This entry's public key is the signing key for the next entry.
+        prevKey = payload.moveValue();
+    }
+
+    return result;
+}
+
+}  // namespace aidl::android::hardware::security::keymint::remote_prov
diff --git a/security/secureclock/aidl/Android.bp b/security/secureclock/aidl/Android.bp
new file mode 100644
index 0000000..5a6d7ae
--- /dev/null
+++ b/security/secureclock/aidl/Android.bp
@@ -0,0 +1,21 @@
+aidl_interface {
+    name: "android.hardware.security.secureclock",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/security/secureclock/*.aidl",
+    ],
+    stability: "vintf",
+    backend: {
+        java: {
+            sdk_version: "module_current",
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+        rust: {
+            enabled: true,
+        },
+    },
+}
diff --git a/security/secureclock/aidl/OWNERS b/security/secureclock/aidl/OWNERS
new file mode 100644
index 0000000..a93b171
--- /dev/null
+++ b/security/secureclock/aidl/OWNERS
@@ -0,0 +1,4 @@
+jbires@google.com
+jdanis@google.com
+seleneh@google.com
+swillden@google.com
diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl
new file mode 100644
index 0000000..3778897
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.secureclock;
+@VintfStability
+interface ISecureClock {
+  android.hardware.security.secureclock.TimeStampToken generateTimeStamp(in long challenge);
+  const String TIME_STAMP_MAC_LABEL = "Auth Verification";
+}
diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl
new file mode 100644
index 0000000..00a8bb2
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.secureclock;
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
+parcelable TimeStampToken {
+  long challenge;
+  android.hardware.security.secureclock.Timestamp timestamp;
+  byte[] mac;
+}
diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl
new file mode 100644
index 0000000..bebeb5c
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.secureclock;
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
+parcelable Timestamp {
+  long milliSeconds;
+}
diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl
new file mode 100644
index 0000000..577dd8f
--- /dev/null
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ * limitations under the License.
+ */
+
+package android.hardware.security.secureclock;
+import android.hardware.security.secureclock.TimeStampToken;
+
+/**
+ * Secure Clock definition.
+ *
+ * An ISecureClock provides a keymint service to generate secure timestamp using a secure platform.
+ * The secure time stamp contains time in milliseconds. This time stamp also contains a 256-bit MAC
+ * which provides integrity protection. The MAC is generated using HMAC-SHA-256 and a shared
+ * secret. The shared secret must be available to secure clock service by implementing
+ * ISharedSecret aidl. Note: ISecureClock depends on the shared secret, without which the secure
+ * time stamp token cannot be generated.
+ */
+
+@VintfStability
+interface ISecureClock {
+    /**
+     * String used as context in the HMAC computation signing the generated time stamp.
+     * See TimeStampToken.mac for details.
+     */
+    const String TIME_STAMP_MAC_LABEL = "Auth Verification";
+
+    /**
+     * Generates an authenticated timestamp.
+     *
+     * @param A challenge value provided by the relying party. It will be included in the generated
+     *        TimeStampToken to ensure freshness. The relying service must ensure that the
+     *        challenge cannot be specified or predicted by an attacker.
+     *
+     * @return the TimeStampToken, see the definition for details.
+     */
+    TimeStampToken generateTimeStamp(in long challenge);
+}
diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl
new file mode 100644
index 0000000..dd95732
--- /dev/null
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+package android.hardware.security.secureclock;
+
+import android.hardware.security.secureclock.Timestamp;
+
+/**
+ * TimeStampToken instances are used for secure environments that requires secure time information.
+ */
+
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
+parcelable TimeStampToken {
+    /**
+     * The challenge that was provided as argument to ISecureClock.generateTimeStamp by the client.
+     */
+    long challenge;
+
+    /**
+     * The current time of the secure environment that generates the TimeStampToken.
+     */
+    Timestamp timestamp;
+
+    /**
+     * 32-byte HMAC-SHA256 of the above values, computed as:
+     *
+     *    HMAC(H,
+     *         ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp || securityLevel )
+     *
+     * where:
+     *
+     *   ``ISecureClock.TIME_STAMP_MAC_LABEL'' is a sting constant defined in ISecureClock.aidl.
+     *
+     *   ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedSecret).
+     *
+     *   ``||'' represents concatenation
+     *
+     * The representation of challenge and timestamp is as 64-bit unsigned integers in big-endian
+     * order. SecurityLevel is represented as a 32-bit unsigned integer in big-endian order as
+     * described in android.hardware.security.keymint.SecurityLevel. It represents the security
+     * level of the secure clock environment.
+     */
+    byte[] mac;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
similarity index 88%
rename from security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl
rename to security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
index ebb3684..27758e1 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.security.keymint;
+package android.hardware.security.secureclock;
 
 /**
  * Time in milliseconds since some arbitrary point in time.  Time must be monotonically increasing,
@@ -23,6 +23,7 @@
  * by setting the clock to zero during each boot, and then counting time accurately).
  */
 @VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
 parcelable Timestamp {
     long milliSeconds;
 }
diff --git a/security/secureclock/aidl/vts/functional/Android.bp b/security/secureclock/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..1619eab
--- /dev/null
+++ b/security/secureclock/aidl/vts/functional/Android.bp
@@ -0,0 +1,43 @@
+//
+// 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.
+//
+
+cc_test {
+    name: "VtsAidlSecureClockTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+    ],
+    srcs: [
+        "SecureClockAidlTest.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libcrypto",
+        "libkeymint",
+    ],
+    static_libs: [
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "android.hardware.security.secureclock-V1-ndk_platform",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/security/secureclock/aidl/vts/functional/AndroidTest.xml b/security/secureclock/aidl/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..4861c7c
--- /dev/null
+++ b/security/secureclock/aidl/vts/functional/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<configuration description="Runs VtsAidlSecureClockTargetTest.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push"
+                value="VtsAidlSecureClockTargetTest->/data/local/tmp/VtsAidlSecureClockTargetTest" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="VtsAidlSecureClockTargetTest" />
+        <option name="native-test-timeout" value="900000"/>
+    </test>
+</configuration>
diff --git a/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp
new file mode 100644
index 0000000..9ca1ee8
--- /dev/null
+++ b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp
@@ -0,0 +1,193 @@
+/*
+ * 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 "secureclock_test"
+#include <android-base/logging.h>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/secureclock/ISecureClock.h>
+#include <android/binder_manager.h>
+#include <binder/ProcessState.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace aidl::android::hardware::security::secureclock::test {
+using Status = ::ndk::ScopedAStatus;
+using ::aidl::android::hardware::security::keymint::ErrorCode;
+using ::std::shared_ptr;
+using ::std::string;
+using ::std::vector;
+
+class SecureClockAidlTest : public ::testing::TestWithParam<string> {
+  public:
+    struct TimestampTokenResult {
+        ErrorCode error;
+        TimeStampToken token;
+    };
+
+    TimestampTokenResult getTimestampToken(int64_t in_challenge) {
+        TimestampTokenResult result;
+        result.error =
+                GetReturnErrorCode(secureClock_->generateTimeStamp(in_challenge, &result.token));
+        return result;
+    }
+
+    uint64_t getTime() {
+        struct timespec timespec;
+        EXPECT_EQ(0, clock_gettime(CLOCK_BOOTTIME, &timespec));
+        return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000;
+    }
+
+    int sleep_ms(uint32_t milliseconds) {
+        struct timespec sleep_time = {static_cast<time_t>(milliseconds / 1000),
+                                      static_cast<long>(milliseconds % 1000) * 1000000};
+        while (sleep_time.tv_sec || sleep_time.tv_nsec) {
+            if (nanosleep(&sleep_time /* to wait */,
+                          &sleep_time /* remaining (on interrruption) */) == 0) {
+                sleep_time = {};
+            } else {
+                if (errno != EINTR) return errno;
+            }
+        }
+        return 0;
+    }
+
+    ErrorCode GetReturnErrorCode(const Status& result) {
+        if (result.isOk()) return ErrorCode::OK;
+
+        if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+            return static_cast<ErrorCode>(result.getServiceSpecificError());
+        }
+
+        return ErrorCode::UNKNOWN_ERROR;
+    }
+
+    void InitializeSecureClock(std::shared_ptr<ISecureClock> secureClock) {
+        ASSERT_NE(secureClock, nullptr);
+        secureClock_ = secureClock;
+    }
+
+    ISecureClock& secureClock() { return *secureClock_; }
+
+    static vector<string> build_params() {
+        auto params = ::android::getAidlHalInstanceNames(ISecureClock::descriptor);
+        return params;
+    }
+
+    void SetUp() override {
+        if (AServiceManager_isDeclared(GetParam().c_str())) {
+            ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+            InitializeSecureClock(ISecureClock::fromBinder(binder));
+        } else {
+            InitializeSecureClock(nullptr);
+        }
+    }
+
+    void TearDown() override {}
+
+  private:
+    std::shared_ptr<ISecureClock> secureClock_;
+};
+
+/*
+ * The precise capabilities required to generate TimeStampToken will vary depending on the specific
+ * vendor implementations. The only thing we really can test is that tokens can be created by
+ * secureclock services, and that the timestamps increase as expected.
+ */
+TEST_P(SecureClockAidlTest, TestCreation) {
+    auto result1 = getTimestampToken(1 /* challenge */);
+    auto result1_time = getTime();
+    EXPECT_EQ(ErrorCode::OK, result1.error);
+    EXPECT_EQ(1U, result1.token.challenge);
+    EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
+
+    unsigned long time_to_sleep = 200;
+    sleep_ms(time_to_sleep);
+
+    auto result2 = getTimestampToken(2 /* challenge */);
+    auto result2_time = getTime();
+    EXPECT_EQ(ErrorCode::OK, result2.error);
+    EXPECT_EQ(2U, result2.token.challenge);
+    EXPECT_GT(result2.token.timestamp.milliSeconds, 0U);
+
+    auto host_time_delta = result2_time - result1_time;
+
+    EXPECT_GE(host_time_delta, time_to_sleep)
+            << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
+    EXPECT_LE(host_time_delta, time_to_sleep + 100)
+            << "The getTimestampToken call took " << (host_time_delta - time_to_sleep)
+            << " ms?  That's awful!";
+    EXPECT_GE(result2.token.timestamp.milliSeconds, result1.token.timestamp.milliSeconds);
+    unsigned long km_time_delta =
+            result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
+    // 20 ms of slop just to avoid test flakiness.
+    EXPECT_LE(host_time_delta, km_time_delta + 20);
+    EXPECT_LE(km_time_delta, host_time_delta + 20);
+    ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
+    ASSERT_NE(0,
+              memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
+}
+
+/*
+ * Test that the mac changes when the time stamp changes. This is does not guarantee that the time
+ * stamp is included in the mac but on failure we know that it is not. Other than in the test
+ * case above we call getTimestampToken with the exact same set of parameters.
+ */
+TEST_P(SecureClockAidlTest, MacChangesOnChangingTimestamp) {
+    auto result1 = getTimestampToken(0 /* challenge */);
+    auto result1_time = getTime();
+    EXPECT_EQ(ErrorCode::OK, result1.error);
+    EXPECT_EQ(0U, result1.token.challenge);
+    EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
+
+    unsigned long time_to_sleep = 200;
+    sleep_ms(time_to_sleep);
+
+    auto result2 = getTimestampToken(1 /* challenge */);
+    auto result2_time = getTime();
+    EXPECT_EQ(ErrorCode::OK, result2.error);
+    EXPECT_EQ(1U, result2.token.challenge);
+    EXPECT_GT(result2.token.timestamp.milliSeconds, 0U);
+
+    auto host_time_delta = result2_time - result1_time;
+
+    EXPECT_GE(host_time_delta, time_to_sleep)
+            << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
+    EXPECT_LE(host_time_delta, time_to_sleep + 100)
+            << "The getTimestampToken call took " << (host_time_delta - time_to_sleep)
+            << " ms?  That's awful!";
+
+    EXPECT_GE(result2.token.timestamp.milliSeconds, result1.token.timestamp.milliSeconds);
+    unsigned long km_time_delta =
+            result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
+
+    EXPECT_LE(host_time_delta, km_time_delta + 20);
+    EXPECT_LE(km_time_delta, host_time_delta + 20);
+    ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
+    ASSERT_NE(0,
+              memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, SecureClockAidlTest,
+                         testing::ValuesIn(SecureClockAidlTest::build_params()),
+                         ::android::PrintInstanceNameToString);
+}  // namespace aidl::android::hardware::security::secureclock::test
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/security/sharedsecret/aidl/Android.bp b/security/sharedsecret/aidl/Android.bp
new file mode 100644
index 0000000..ab44110
--- /dev/null
+++ b/security/sharedsecret/aidl/Android.bp
@@ -0,0 +1,21 @@
+aidl_interface {
+    name: "android.hardware.security.sharedsecret",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/security/sharedsecret/*.aidl",
+    ],
+    stability: "vintf",
+    backend: {
+        java: {
+            sdk_version: "module_current",
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+        rust: {
+            enabled: true,
+        },
+    },
+}
diff --git a/security/sharedsecret/aidl/OWNERS b/security/sharedsecret/aidl/OWNERS
new file mode 100644
index 0000000..a93b171
--- /dev/null
+++ b/security/sharedsecret/aidl/OWNERS
@@ -0,0 +1,4 @@
+jbires@google.com
+jdanis@google.com
+seleneh@google.com
+swillden@google.com
diff --git a/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl
new file mode 100644
index 0000000..2509936
--- /dev/null
+++ b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.sharedsecret;
+@VintfStability
+interface ISharedSecret {
+  android.hardware.security.sharedsecret.SharedSecretParameters getSharedSecretParameters();
+  byte[] computeSharedSecret(in android.hardware.security.sharedsecret.SharedSecretParameters[] params);
+  const String KEY_AGREEMENT_LABEL = "KeymasterSharedMac";
+  const String KEY_CHECK_LABEL = "Keymaster HMAC Verification";
+}
diff --git a/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl
new file mode 100644
index 0000000..9b65046
--- /dev/null
+++ b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.sharedsecret;
+@VintfStability
+parcelable SharedSecretParameters {
+  byte[] seed;
+  byte[] nonce;
+}
diff --git a/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl
new file mode 100644
index 0000000..906303f
--- /dev/null
+++ b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ * limitations under the License.
+ */
+
+package android.hardware.security.sharedsecret;
+import android.hardware.security.sharedsecret.SharedSecretParameters;
+
+/**
+ * Shared Secret definition.
+ *
+ * An ISharedSecret enables any service that implements this interface to establish a shared secret
+ * with one or more other services such as ISecureClock, TEE IKeymintDevice, StrongBox
+ * IKeymintDevice, etc. The shared secret is a 256-bit HMAC key and it is further used to generate
+ * secure tokens with integrity protection. There are two steps to establish a shared secret between
+ * the collaborating services:
+ *
+ * Step 1: During Android startup the system calls each service that implements this interface to
+ * get the shared secret parameters. This is done using getSharedSecretParameters method defined
+ * below.
+ * Step 2: The system lexicographically sorts the shared secret parameters received from each
+ * service and then sends these sorted parameter list to each service in a computeSharedSecret
+ * method defined below. The services individually computes the shared secret and returns back
+ * the 32 byte sharing check hash value generated by using the computed shared secret.
+ * Step 3: The system collects sharing check hash values from each service and evaluates them. If
+ * they are all equal, then the shared secret generation is considered to be successful else it is
+ * considered to have failed.
+ */
+
+@VintfStability
+interface ISharedSecret {
+    /**
+     * String used as label in the shared key derivation. See computeSharedSecret below.
+     */
+    const String KEY_AGREEMENT_LABEL = "KeymasterSharedMac";
+
+    /**
+     * String used as context in the computation of the sharingCheck. See computeSharedSecret
+     * below.
+     */
+    const String KEY_CHECK_LABEL = "Keymaster HMAC Verification";
+
+    /**
+     * This method is the first step in the process for agreeing on a shared key.  It is called by
+     * Android during startup.  The system calls it on each of the HAL instances and collects the
+     * results in preparation for the second step.
+     *
+     * @return The SharedSecretParameters to use.  As specified in the SharedSecretParameters
+     *         documentation, the seed must contain the same value in every invocation
+     *         of the method on a given device, and the nonce must return the same value for every
+     *         invocation during a boot session.
+     */
+    SharedSecretParameters getSharedSecretParameters();
+
+    /**
+     * This method is the second and final step in the process for agreeing on a shared key.  It is
+     * called by Android during startup.  The system calls it on each of the keymint services, and
+     * sends to it all of the SharedSecretParameters returned by all keymint services.
+     *
+     * This method computes the shared 32-byte HMAC key ``H'' as follows (all keymint services
+     * instances perform the same computation to arrive at the same result):
+     *
+     *     H = CKDF(key = K,
+     *              context = P1 || P2 || ... || Pn,
+     *              label = KEY_AGREEMENT_LABEL)
+     *
+     * where:
+     *
+     *     ``CKDF'' is the standard AES-CMAC KDF from NIST SP 800-108 in counter mode (see Section
+     *           5.1 of the referenced publication).  ``key'', ``context'', and ``label'' are
+     *           defined in the standard.  The counter is prefixed and length L appended, as shown
+     *           in the construction on page 12 of the standard.  The label string is UTF-8 encoded.
+     *
+     *     ``K'' is a pre-established shared secret, set up during factory reset.  The mechanism for
+     *           establishing this shared secret is implementation-defined.Any method of securely
+     *           establishing K that ensures that an attacker cannot obtain or derive its value is
+     *           acceptable.
+     *
+     *           CRITICAL SECURITY REQUIREMENT: All keys created by a IKeymintDevice instance must
+     *           be cryptographically bound to the value of K, such that establishing a new K
+     *           permanently destroys them.
+     *
+     *     ``||'' represents concatenation.
+     *
+     *     ``Pi'' is the i'th SharedSecretParameters value in the params vector. Encoding of an
+     *           SharedSecretParameters is the concatenation of its two fields, i.e. seed || nonce.
+     *
+     * Note that the label "KeymasterSharedMac" is the 18-byte UTF-8 encoding of the string.
+     *
+     * @param params is an array of SharedSecretParameters The lexicographically sorted
+     * SharedSecretParameters data returned by all keymint services when getSharedSecretParameters
+     * was called.
+     *
+     * @return sharingCheck A 32-byte value used to verify that all the keymint services have
+     *         computed the same shared HMAC key.  The sharingCheck value is computed as follows:
+     *
+     *             sharingCheck = HMAC(H, KEY_CHECK_LABEL)
+     *
+     *         The string is UTF-8 encoded, 27 bytes in length.  If the returned values of all
+     *         keymint services don't match, clients must assume that HMAC agreement
+     *         failed.
+     */
+    byte[] computeSharedSecret(in SharedSecretParameters[] params);
+}
diff --git a/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl
new file mode 100644
index 0000000..691b3f1
--- /dev/null
+++ b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package android.hardware.security.sharedsecret;
+
+/**
+ * SharedSecretParameters holds the data used in the process of establishing a shared secret i.e.
+ * HMAC key between multiple keymint services.  These parameters are returned in by
+ * getSharedSecretParameters() and send to computeShareSecret().  See the named methods in
+ * ISharedSecret for details of usage.
+ */
+
+@VintfStability
+parcelable SharedSecretParameters {
+    /**
+     * Either empty or contains a non zero persistent value that is associated with the pre-shared
+     * HMAC agreement key.  It is either empty or 32 bytes in length.
+     */
+    byte[] seed;
+
+    /**
+     * A 32-byte value which is guaranteed to be different each time
+     * getSharedSecretParameters() is called.  Probabilistic uniqueness (i.e. random) is acceptable,
+     * though a stronger uniqueness guarantee (e.g. counter) is recommended where possible.
+     */
+    byte[] nonce;
+}
diff --git a/security/sharedsecret/aidl/vts/functional/Android.bp b/security/sharedsecret/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..76bf7ff
--- /dev/null
+++ b/security/sharedsecret/aidl/vts/functional/Android.bp
@@ -0,0 +1,43 @@
+//
+// 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.
+//
+
+cc_test {
+    name: "VtsAidlSharedSecretTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "SharedSecretAidlTest.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libcrypto",
+        "libkeymint",
+    ],
+    static_libs: [
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "android.hardware.security.sharedsecret-V1-ndk_platform",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/security/sharedsecret/aidl/vts/functional/AndroidTest.xml b/security/sharedsecret/aidl/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..c6697bc
--- /dev/null
+++ b/security/sharedsecret/aidl/vts/functional/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<configuration description="Runs VtsAidlSharedSecretTargetTest.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push"
+                value="VtsAidlSharedSecretTargetTest->/data/local/tmp/VtsAidlSharedSecretTargetTest" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="VtsAidlSharedSecretTargetTest" />
+        <option name="native-test-timeout" value="900000"/>
+    </test>
+</configuration>
diff --git a/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp b/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp
new file mode 100644
index 0000000..83f6ef3
--- /dev/null
+++ b/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp
@@ -0,0 +1,268 @@
+/*
+ * 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 "sharedsecret_test"
+#include <android-base/logging.h>
+
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/sharedsecret/ISharedSecret.h>
+#include <android/binder_manager.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace aidl::android::hardware::security::sharedsecret::test {
+using ::aidl::android::hardware::security::keymint::ErrorCode;
+using ::std::shared_ptr;
+using ::std::vector;
+using Status = ::ndk::ScopedAStatus;
+
+class SharedSecretAidlTest : public ::testing::Test {
+  public:
+    struct GetParamsResult {
+        ErrorCode error;
+        SharedSecretParameters params;
+        auto tie() { return std::tie(error, params); }
+    };
+
+    struct ComputeResult {
+        ErrorCode error;
+        vector<uint8_t> sharing_check;
+        auto tie() { return std::tie(error, sharing_check); }
+    };
+
+    GetParamsResult getSharedSecretParameters(shared_ptr<ISharedSecret>& sharedSecret) {
+        SharedSecretParameters params;
+        auto error = GetReturnErrorCode(sharedSecret->getSharedSecretParameters(&params));
+        EXPECT_EQ(ErrorCode::OK, error);
+        GetParamsResult result;
+        result.tie() = std::tie(error, params);
+        return result;
+    }
+
+    vector<SharedSecretParameters> getAllSharedSecretParameters() {
+        vector<SharedSecretParameters> paramsVec;
+        for (auto& sharedSecret : allSharedSecrets_) {
+            auto result = getSharedSecretParameters(sharedSecret);
+            EXPECT_EQ(ErrorCode::OK, result.error);
+            if (result.error == ErrorCode::OK) paramsVec.push_back(std::move(result.params));
+        }
+        return paramsVec;
+    }
+
+    ComputeResult computeSharedSecret(shared_ptr<ISharedSecret>& sharedSecret,
+                                      const vector<SharedSecretParameters>& params) {
+        std::vector<uint8_t> sharingCheck;
+        auto error = GetReturnErrorCode(sharedSecret->computeSharedSecret(params, &sharingCheck));
+        ComputeResult result;
+        result.tie() = std::tie(error, sharingCheck);
+        return result;
+    }
+
+    vector<ComputeResult> computeAllSharedSecrets(const vector<SharedSecretParameters>& params) {
+        vector<ComputeResult> result;
+        for (auto& sharedSecret : allSharedSecrets_) {
+            result.push_back(computeSharedSecret(sharedSecret, params));
+        }
+        return result;
+    }
+
+    vector<vector<uint8_t>> copyNonces(const vector<SharedSecretParameters>& paramsVec) {
+        vector<vector<uint8_t>> nonces;
+        for (auto& param : paramsVec) {
+            nonces.push_back(param.nonce);
+        }
+        return nonces;
+    }
+
+    void verifyResponses(const vector<uint8_t>& expected, const vector<ComputeResult>& responses) {
+        for (auto& response : responses) {
+            EXPECT_EQ(ErrorCode::OK, response.error);
+            EXPECT_EQ(expected, response.sharing_check) << "Sharing check values should match.";
+        }
+    }
+
+    ErrorCode GetReturnErrorCode(const Status& result) {
+        if (result.isOk()) return ErrorCode::OK;
+        if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+            return static_cast<ErrorCode>(result.getServiceSpecificError());
+        }
+        return ErrorCode::UNKNOWN_ERROR;
+    }
+
+    static shared_ptr<ISharedSecret> getSharedSecretService(const char* name) {
+        if (AServiceManager_isDeclared(name)) {
+            ::ndk::SpAIBinder binder(AServiceManager_waitForService(name));
+            return ISharedSecret::fromBinder(binder);
+        }
+        return nullptr;
+    }
+
+    const vector<shared_ptr<ISharedSecret>>& allSharedSecrets() { return allSharedSecrets_; }
+
+    static void SetUpTestCase() {
+        if (allSharedSecrets_.empty()) {
+            auto names = ::android::getAidlHalInstanceNames(ISharedSecret::descriptor);
+            for (const auto& name : names) {
+                auto servicePtr = getSharedSecretService(name.c_str());
+                if (servicePtr != nullptr) allSharedSecrets_.push_back(std::move(servicePtr));
+            }
+        }
+    }
+    static void TearDownTestCase() {}
+    void SetUp() override {}
+    void TearDown() override {}
+
+  private:
+    static vector<shared_ptr<ISharedSecret>> allSharedSecrets_;
+};
+
+vector<shared_ptr<ISharedSecret>> SharedSecretAidlTest::allSharedSecrets_;
+
+TEST_F(SharedSecretAidlTest, GetParameters) {
+    auto sharedSecrets = allSharedSecrets();
+    for (auto sharedSecret : sharedSecrets) {
+        auto result1 = getSharedSecretParameters(sharedSecret);
+        EXPECT_EQ(ErrorCode::OK, result1.error);
+        auto result2 = getSharedSecretParameters(sharedSecret);
+        EXPECT_EQ(ErrorCode::OK, result2.error);
+        ASSERT_EQ(result1.params.seed, result2.params.seed)
+                << "A given shared secret service should always return the same seed.";
+        ASSERT_EQ(result1.params.nonce, result2.params.nonce)
+                << "A given shared secret service should always return the same nonce until "
+                   "restart.";
+    }
+}
+
+TEST_F(SharedSecretAidlTest, ComputeSharedSecret) {
+    auto params = getAllSharedSecretParameters();
+    ASSERT_EQ(allSharedSecrets().size(), params.size())
+            << "One or more shared secret services failed to provide parameters.";
+    auto nonces = copyNonces(params);
+    EXPECT_EQ(allSharedSecrets().size(), nonces.size());
+    std::sort(nonces.begin(), nonces.end());
+    std::unique(nonces.begin(), nonces.end());
+    EXPECT_EQ(allSharedSecrets().size(), nonces.size());
+
+    auto responses = computeAllSharedSecrets(params);
+    ASSERT_GT(responses.size(), 0U);
+    verifyResponses(responses[0].sharing_check, responses);
+
+    // Do it a second time.  Should get the same answers.
+    params = getAllSharedSecretParameters();
+    ASSERT_EQ(allSharedSecrets().size(), params.size())
+            << "One or more shared secret services failed to provide parameters.";
+
+    responses = computeAllSharedSecrets(params);
+    ASSERT_GT(responses.size(), 0U);
+    ASSERT_EQ(32U, responses[0].sharing_check.size());
+    verifyResponses(responses[0].sharing_check, responses);
+}
+
+template <class F>
+class final_action {
+  public:
+    explicit final_action(F f) : f_(std::move(f)) {}
+    ~final_action() { f_(); }
+
+  private:
+    F f_;
+};
+
+template <class F>
+inline final_action<F> finally(const F& f) {
+    return final_action<F>(f);
+}
+
+TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptNonce) {
+    auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); });
+
+    auto params = getAllSharedSecretParameters();
+    ASSERT_EQ(allSharedSecrets().size(), params.size())
+            << "One or more shared secret services failed to provide parameters.";
+
+    // All should be well in the normal case
+    auto responses = computeAllSharedSecrets(params);
+
+    ASSERT_GT(responses.size(), 0U);
+    vector<uint8_t> correct_response = responses[0].sharing_check;
+    verifyResponses(correct_response, responses);
+
+    // Pick a random param, a random byte within the param's nonce, and a random bit within
+    // the byte.  Flip that bit.
+    size_t param_to_tweak = rand() % params.size();
+    uint8_t byte_to_tweak = rand() % sizeof(params[param_to_tweak].nonce);
+    uint8_t bit_to_tweak = rand() % 8;
+    params[param_to_tweak].nonce[byte_to_tweak] ^= (1 << bit_to_tweak);
+
+    responses = computeAllSharedSecrets(params);
+    for (size_t i = 0; i < responses.size(); ++i) {
+        if (i == param_to_tweak) {
+            EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error)
+                    << "Shared secret service that provided tweaked param should fail to compute "
+                       "shared secret";
+        } else {
+            EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed";
+            EXPECT_NE(correct_response, responses[i].sharing_check)
+                    << "Others should calculate a different shared secret, due to the tweaked "
+                       "nonce.";
+        }
+    }
+}
+
+TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptSeed) {
+    auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); });
+    auto params = getAllSharedSecretParameters();
+    ASSERT_EQ(allSharedSecrets().size(), params.size())
+            << "One or more shared secret service failed to provide parameters.";
+
+    // All should be well in the normal case
+    auto responses = computeAllSharedSecrets(params);
+
+    ASSERT_GT(responses.size(), 0U);
+    vector<uint8_t> correct_response = responses[0].sharing_check;
+    verifyResponses(correct_response, responses);
+
+    // Pick a random param and modify the seed.  We just increase the seed length by 1.  It doesn't
+    // matter what value is in the additional byte; it changes the seed regardless.
+    auto param_to_tweak = rand() % params.size();
+    auto& to_tweak = params[param_to_tweak].seed;
+    ASSERT_TRUE(to_tweak.size() == 32 || to_tweak.size() == 0);
+    if (!to_tweak.size()) {
+        to_tweak.resize(32);  // Contents don't matter; a little randomization is nice.
+    }
+    to_tweak[0]++;
+
+    responses = computeAllSharedSecrets(params);
+    for (size_t i = 0; i < responses.size(); ++i) {
+        if (i == param_to_tweak) {
+            EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error)
+                    << "Shared secret service that provided tweaked param should fail to compute "
+                       "shared secret";
+        } else {
+            EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed";
+            EXPECT_NE(correct_response, responses[i].sharing_check)
+                    << "Others should calculate a different shared secret, due to the tweaked "
+                       "nonce.";
+        }
+    }
+}
+}  // namespace aidl::android::hardware::security::sharedsecret::test
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/sensors/common/default/2.X/Sensor.cpp b/sensors/common/default/2.X/Sensor.cpp
index 642fc89..1a7c628 100644
--- a/sensors/common/default/2.X/Sensor.cpp
+++ b/sensors/common/default/2.X/Sensor.cpp
@@ -313,7 +313,7 @@
     mSensorInfo.maxRange = 1000.0f * M_PI / 180.0f;
     mSensorInfo.resolution = 1000.0f * M_PI / (180.0f * 32768.0f);
     mSensorInfo.power = 0.001f;
-    mSensorInfo.minDelay = 2.5f * 1000;  // microseconds
+    mSensorInfo.minDelay = 10 * 1000;  // microseconds
     mSensorInfo.maxDelay = kDefaultMaxDelayUs;
     mSensorInfo.fifoReservedEventCount = 0;
     mSensorInfo.fifoMaxEventCount = 0;
diff --git a/tests/extension/vibrator/aidl/client/Android.bp b/tests/extension/vibrator/aidl/client/Android.bp
index b0d8238..108d000 100644
--- a/tests/extension/vibrator/aidl/client/Android.bp
+++ b/tests/extension/vibrator/aidl/client/Android.bp
@@ -14,11 +14,11 @@
     shared_libs: [
         "libbinder",
         "libutils",
-        "android.hardware.vibrator-cpp",
-        "android.hardware.tests.extension.vibrator-cpp",
+        "android.hardware.vibrator-V1-cpp",
+        "android.hardware.tests.extension.vibrator-V1-cpp",
 
         "libbinder_ndk",
-        "android.hardware.vibrator-ndk_platform",
-        "android.hardware.tests.extension.vibrator-ndk_platform",
+        "android.hardware.vibrator-V1-ndk_platform",
+        "android.hardware.tests.extension.vibrator-V1-ndk_platform",
     ],
 }
diff --git a/tests/extension/vibrator/aidl/default/Android.bp b/tests/extension/vibrator/aidl/default/Android.bp
index ed40d25..fd19f13 100644
--- a/tests/extension/vibrator/aidl/default/Android.bp
+++ b/tests/extension/vibrator/aidl/default/Android.bp
@@ -19,7 +19,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.vibrator-ndk_platform",
-        "android.hardware.tests.extension.vibrator-ndk_platform",
+        "android.hardware.vibrator-V1-ndk_platform",
+        "android.hardware.tests.extension.vibrator-V1-ndk_platform",
     ],
 }
diff --git a/tests/lazy/1.1/ILazy.hal b/tests/lazy/1.1/ILazy.hal
index a15e0e3..eb48fd3 100644
--- a/tests/lazy/1.1/ILazy.hal
+++ b/tests/lazy/1.1/ILazy.hal
@@ -18,4 +18,12 @@
 
 import android.hardware.tests.lazy@1.0;
 
-interface ILazy extends @1.0::ILazy {};
+interface ILazy extends @1.0::ILazy {
+    /**
+     * Ask the process hosting the service to install a callback that notifies if there are
+     * services with clients.
+     * For testing purposes, this callback exercises the code to unregister/re-register
+     * the services and eventually shuts down the process.
+     */
+    setCustomActiveServicesCallback();
+};
diff --git a/tests/msgq/1.0/ITestMsgQ.hal b/tests/msgq/1.0/ITestMsgQ.hal
index bd10237..0cf9c7c 100644
--- a/tests/msgq/1.0/ITestMsgQ.hal
+++ b/tests/msgq/1.0/ITestMsgQ.hal
@@ -41,12 +41,15 @@
      *
      * @param configureFmq The server sets up a new unsynchronized FMQ if
      * this parameter is true.
+     * @param userFd True to initialize the message queue with a user supplied
+     * file descriptor for the ring buffer.
+     * False to let the message queue use a single FD for everything.
      *
      * @return ret True if successful.
      * @return mqDesc This structure describes the unsynchronized FMQ that was
      * set up by the service. Client can use it to set up the FMQ at its end.
      */
-    getFmqUnsyncWrite(bool configureFmq) generates(bool ret, fmq_unsync<int32_t> mqDesc);
+    getFmqUnsyncWrite(bool configureFmq, bool userFd) generates(bool ret, fmq_unsync<int32_t> mqDesc);
 
     /**
      * This method request the service to write into the synchronized read/write
diff --git a/tests/msgq/1.0/default/Android.bp b/tests/msgq/1.0/default/Android.bp
index 6e7cd44..a0d6f31 100644
--- a/tests/msgq/1.0/default/Android.bp
+++ b/tests/msgq/1.0/default/Android.bp
@@ -91,9 +91,10 @@
     // These are static libs only for testing purposes and portability. Shared
     // libs should be used on device.
     static_libs: [
+        "android.hardware.common-V2-ndk_platform",
+        "android.hardware.common.fmq-V1-ndk_platform",
         "android.hardware.tests.msgq@1.0",
         "android.fmq.test-ndk_platform",
-        "android.hardware.common.fmq-unstable-ndk_platform",
     ],
     whole_static_libs: [
         "android.hardware.tests.msgq@1.0-impl",
diff --git a/tests/msgq/1.0/default/TestMsgQ.cpp b/tests/msgq/1.0/default/TestMsgQ.cpp
index 4473737..4726ffe 100644
--- a/tests/msgq/1.0/default/TestMsgQ.cpp
+++ b/tests/msgq/1.0/default/TestMsgQ.cpp
@@ -41,10 +41,19 @@
     return true;
 }
 
-Return<void> TestMsgQ::getFmqUnsyncWrite(bool configureFmq, getFmqUnsyncWrite_cb _hidl_cb) {
+Return<void> TestMsgQ::getFmqUnsyncWrite(bool configureFmq, bool userFd,
+                                         getFmqUnsyncWrite_cb _hidl_cb) {
     if (configureFmq) {
         static constexpr size_t kNumElementsInQueue = 1024;
-        mFmqUnsynchronized.reset(new (std::nothrow) MessageQueueUnsync(kNumElementsInQueue));
+        static constexpr size_t kElementSizeBytes = sizeof(int32_t);
+        android::base::unique_fd ringbufferFd;
+        if (userFd) {
+            ringbufferFd.reset(
+                    ::ashmem_create_region("UnsyncWrite", kNumElementsInQueue * kElementSizeBytes));
+        }
+        mFmqUnsynchronized.reset(new (std::nothrow) MessageQueueUnsync(
+                kNumElementsInQueue, false, std::move(ringbufferFd),
+                kNumElementsInQueue * kElementSizeBytes));
     }
     if ((mFmqUnsynchronized == nullptr) ||
         (mFmqUnsynchronized->isValid() == false)) {
diff --git a/tests/msgq/1.0/default/TestMsgQ.h b/tests/msgq/1.0/default/TestMsgQ.h
index 8a204b7..49675fe 100644
--- a/tests/msgq/1.0/default/TestMsgQ.h
+++ b/tests/msgq/1.0/default/TestMsgQ.h
@@ -56,7 +56,8 @@
 
     // Methods from ::android::hardware::tests::msgq::V1_0::ITestMsgQ follow.
     Return<bool> configureFmqSyncReadWrite(const MQDescriptorSync<int32_t>& mqDesc) override;
-    Return<void> getFmqUnsyncWrite(bool configureFmq, getFmqUnsyncWrite_cb _hidl_cb) override;
+    Return<void> getFmqUnsyncWrite(bool configureFmq, bool userFd,
+                                   getFmqUnsyncWrite_cb _hidl_cb) override;
     Return<bool> requestWriteFmqSync(int32_t count) override;
     Return<bool> requestReadFmqSync(int32_t count) override;
     Return<bool> requestWriteFmqUnsync(int32_t count) override;
diff --git a/tetheroffload/control/1.0/vts/functional/Android.bp b/tetheroffload/control/1.0/vts/functional/Android.bp
index c397df4..ddf3826 100644
--- a/tetheroffload/control/1.0/vts/functional/Android.bp
+++ b/tetheroffload/control/1.0/vts/functional/Android.bp
@@ -30,3 +30,17 @@
         "vts",
     ],
 }
+
+cc_test_library {
+    name: "VtsHalTetheroffloadControlV1_0TargetTest-lib",
+    defaults: ["VtsHalTargetTestDefaults"],
+    export_include_dirs: ["include"],
+    static_libs: [
+        "android.hardware.tetheroffload.config@1.0",
+        "android.hardware.tetheroffload.control@1.0",
+    ],
+    srcs: [
+        "OffloadControlTestBase.cpp",
+        "OffloadControlTestUtils.cpp",
+    ],
+}
diff --git a/tetheroffload/control/1.0/vts/functional/OffloadControlTestBase.cpp b/tetheroffload/control/1.0/vts/functional/OffloadControlTestBase.cpp
index bd0dad7..e392e96 100644
--- a/tetheroffload/control/1.0/vts/functional/OffloadControlTestBase.cpp
+++ b/tetheroffload/control/1.0/vts/functional/OffloadControlTestBase.cpp
@@ -53,29 +53,6 @@
     ASSERT_TRUE(ret.isOk());
 }
 
-void OffloadControlTestBase::prepareControlHal() {
-    control = createControl(std::get<1>(GetParam()));
-    ASSERT_NE(nullptr, control.get()) << "Could not get HIDL instance";
-
-    control_cb = new TetheringOffloadCallback();
-    ASSERT_NE(nullptr, control_cb.get()) << "Could not get get offload callback";
-}
-
-void OffloadControlTestBase::initOffload(const bool expected_result) {
-    auto init_cb = [&](bool success, std::string errMsg) {
-        std::string msg = StringPrintf("Unexpectedly %s to init offload: %s",
-                                       success ? "succeeded" : "failed", errMsg.c_str());
-        ASSERT_EQ(expected_result, success) << msg;
-    };
-    const Return<void> ret = control->initOffload(control_cb, init_cb);
-    ASSERT_TRUE(ret.isOk());
-}
-
-void OffloadControlTestBase::setupControlHal() {
-    prepareControlHal();
-    initOffload(true);
-}
-
 void OffloadControlTestBase::stopOffload(const ExpectBoolean value) {
     auto cb = [&](bool success, const hidl_string& errMsg) {
         switch (value) {
diff --git a/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestBase.h b/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestBase.h
index 004019a..994c808 100644
--- a/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestBase.h
+++ b/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestBase.h
@@ -36,7 +36,6 @@
 using android::hardware::Return;
 using android::hardware::Void;
 using android::hardware::tetheroffload::config::V1_0::IOffloadConfig;
-using android::hardware::tetheroffload::control::V1_0::IOffloadControl;
 using android::hardware::tetheroffload::control::V1_0::ITetheringOffloadCallback;
 using android::hardware::tetheroffload::control::V1_0::NatTimeoutUpdate;
 using android::hardware::tetheroffload::control::V1_0::OffloadCallbackEvent;
@@ -64,17 +63,21 @@
 
     // Called once in setup stage to retrieve correct version of
     // IOffloadControl object.
-    virtual sp<IOffloadControl> createControl(const std::string& serviceName) = 0;
+    virtual sp<android::hardware::tetheroffload::control::V1_0::IOffloadControl> createControl(
+            const std::string& serviceName) = 0;
 
     // The IOffloadConfig HAL is tested more thoroughly elsewhere. Here the
     // class just setup everything correctly and verify basic readiness.
     void setupConfigHal();
 
-    void prepareControlHal();
+    virtual void prepareControlHal() = 0;
 
-    void initOffload(const bool expected_result);
+    virtual void initOffload(const bool expected_result) = 0;
 
-    void setupControlHal();
+    void setupControlHal() {
+        prepareControlHal();
+        initOffload(true);
+    };
 
     void stopOffload(const ExpectBoolean value);
 
@@ -100,6 +103,6 @@
     };
 
     sp<IOffloadConfig> config;
-    sp<IOffloadControl> control;
+    sp<android::hardware::tetheroffload::control::V1_0::IOffloadControl> control;
     sp<TetheringOffloadCallback> control_cb;
 };
\ No newline at end of file
diff --git a/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestV1_0.h b/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestV1_0.h
index 7492f8a..9189d71 100644
--- a/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestV1_0.h
+++ b/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestV1_0.h
@@ -26,8 +26,28 @@
         prepareControlHal();
     }
 
-    virtual sp<IOffloadControl> createControl(const std::string& serviceName) override {
-        return IOffloadControl::getService(serviceName);
+    virtual sp<android::hardware::tetheroffload::control::V1_0::IOffloadControl> createControl(
+            const std::string& serviceName) override {
+        return android::hardware::tetheroffload::control::V1_0::IOffloadControl::getService(
+                serviceName);
+    }
+
+    virtual void prepareControlHal() override {
+        control = createControl(std::get<1>(GetParam()));
+        ASSERT_NE(nullptr, control.get()) << "Could not get HIDL instance";
+
+        control_cb = new TetheringOffloadCallback();
+        ASSERT_NE(nullptr, control_cb.get()) << "Could not get get offload callback";
+    }
+
+    virtual void initOffload(const bool expected_result) override {
+        auto init_cb = [&](bool success, std::string errMsg) {
+            std::string msg = StringPrintf("Unexpectedly %s to init offload: %s",
+                                           success ? "succeeded" : "failed", errMsg.c_str());
+            ASSERT_EQ(expected_result, success) << msg;
+        };
+        const Return<void> ret = control->initOffload(control_cb, init_cb);
+        ASSERT_TRUE(ret.isOk());
     }
 };
 
diff --git a/tetheroffload/control/1.1/Android.bp b/tetheroffload/control/1.1/Android.bp
new file mode 100644
index 0000000..18c8ea9
--- /dev/null
+++ b/tetheroffload/control/1.1/Android.bp
@@ -0,0 +1,16 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+    name: "android.hardware.tetheroffload.control@1.1",
+    root: "android.hardware",
+    srcs: [
+        "types.hal",
+        "IOffloadControl.hal",
+        "ITetheringOffloadCallback.hal",
+    ],
+    interfaces: [
+        "android.hardware.tetheroffload.control@1.0",
+        "android.hidl.base@1.0",
+    ],
+    gen_java: true,
+}
diff --git a/tetheroffload/control/1.1/IOffloadControl.hal b/tetheroffload/control/1.1/IOffloadControl.hal
new file mode 100644
index 0000000..2eb54c8
--- /dev/null
+++ b/tetheroffload/control/1.1/IOffloadControl.hal
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package android.hardware.tetheroffload.control@1.1;
+
+import @1.0::IOffloadControl;
+
+/**
+ * Interface used to control the lifecycle of tethering offload. Note that callbacks of 1.1 HAL
+ * can be registered with the existing callback registration methods from 1.0 HAL.
+ */
+interface IOffloadControl extends @1.0::IOffloadControl {
+    /**
+     * Instruct hardware to send callbacks after certain number of bytes have been transferred in
+     * either direction on this upstream interface.
+     *
+     * The specified quota bytes must be applied to all traffic on the given upstream interface.
+     * This includes hardware forwarded traffic, software forwarded traffic, and AP-originated
+     * traffic. IPv4 and IPv6 traffic both count towards the same quota. IP headers are included
+     * in the byte count quota, but, link-layer headers are not.
+     *
+     * This API may only be called while offload is occurring on this upstream. The hardware
+     * management process MUST NOT store the values when offload is not started and applies once
+     * offload is started. This is because the quota values would likely become stale over
+     * time and would not reflect any new traffic that has occurred.
+     *
+     * This API replaces {@link @1.0::IOffloadControl::setDataLimit}, the framework will always
+     * call setDataWarningAndLimit on 1.1 implementations, and setDataLimit on 1.0 implementations.
+     * Thus, no interaction between the two APIs need to be addressed.
+     *
+     * The specified quota bytes MUST replace any previous quotas set by
+     * {@code setDataWarningAndLimit} specified on the same interface. It may be interpreted as
+     * "tell me when either <warningBytes> or <limitBytes> bytes have been transferred
+     * (in either direction), and stop offload when <limitBytes> bytes have been transferred,
+     * starting now and counting from zero on <upstream>."
+     *
+     * Once the {@code warningBytes} is reached, the callback registered in initOffload must be
+     * called with {@code OFFLOAD_WARNING_REACHED} to indicate this event. Once the event fires
+     * for this upstream, no further {@code OFFLOAD_WARNING_REACHED} event will be fired for this
+     * upstream unless this method is called again with the same interface. Note that there is
+     * no need to call initOffload again to resume offload if stopOffload was not called by the
+     * client.
+     *
+     * Similarly, Once the {@code limitBytes} is reached, the callback registered in initOffload
+     * must be called with {@code OFFLOAD_STOPPED_LIMIT_REACHED} to indicate this event. Once
+     * the event fires for this upstream, no further {@code OFFLOAD_STOPPED_LIMIT_REACHED}
+     * event will be fired for this upstream unless this method is called again with the same
+     * interface. However, unlike {@code warningBytes}, when {@code limitBytes} is reached,
+     * all offload must be stopped. If offload is desired again, the hardware management
+     * process must be completely reprogrammed by calling setUpstreamParameters and
+     * addDownstream again.
+     *
+     * Note that when one of the quota bytes is reached, the other one is still considered valid
+     * unless this method is called again with the same interface.
+     *
+     * @param upstream Upstream interface name that quota must apply to.
+     * @param warningBytes The quota of warning, defined as the number of bytes, starting from
+     *                     zero and counting from now.
+     * @param limitBytes The quota of limit, defined as the number of bytes, starting from zero
+     *                   and counting from now.
+     *
+     * @return success true if quota is applied, false otherwise
+     * @return errMsg a human readable string if error has occurred.
+     */
+    setDataWarningAndLimit(string upstream, uint64_t warningBytes, uint64_t limitBytes)
+        generates (bool success, string errMsg);
+};
diff --git a/tetheroffload/control/1.1/ITetheringOffloadCallback.hal b/tetheroffload/control/1.1/ITetheringOffloadCallback.hal
new file mode 100644
index 0000000..7a7d56d
--- /dev/null
+++ b/tetheroffload/control/1.1/ITetheringOffloadCallback.hal
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package android.hardware.tetheroffload.control@1.1;
+
+import @1.0::ITetheringOffloadCallback;
+import OffloadCallbackEvent;
+
+/**
+ * Callback providing information about status of hardware management process
+ * as well as providing a way to keep offloaded connections from timing out.
+ */
+interface ITetheringOffloadCallback extends @1.0::ITetheringOffloadCallback {
+    /**
+     * Called when an asynchronous event is generated by the hardware
+     * management process. Events which are common for 1.0 and 1.1 HAL
+     * MUST be fired on both 1.0 and 1.1 callback.
+     */
+    oneway onEvent_1_1(OffloadCallbackEvent event);
+};
diff --git a/tetheroffload/control/1.1/types.hal b/tetheroffload/control/1.1/types.hal
new file mode 100644
index 0000000..30e6af3
--- /dev/null
+++ b/tetheroffload/control/1.1/types.hal
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package android.hardware.tetheroffload.control@1.1;
+
+import @1.0::OffloadCallbackEvent;
+
+enum OffloadCallbackEvent : @1.0::OffloadCallbackEvent {
+    /**
+     * This event is fired when the quota, applied in setDataWarning, has expired. It is
+     * recommended that the client query for statistics immediately after receiving this event.
+     * Note that hardware acceleration must not be stopped upon receiving this event.
+     */
+    OFFLOAD_WARNING_REACHED = 6,
+};
diff --git a/tetheroffload/control/1.1/vts/functional/Android.bp b/tetheroffload/control/1.1/vts/functional/Android.bp
new file mode 100644
index 0000000..ab29350
--- /dev/null
+++ b/tetheroffload/control/1.1/vts/functional/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+cc_test {
+    name: "VtsHalTetheroffloadControlV1_1TargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: ["VtsHalTetheroffloadControlV1_1TargetTest.cpp"],
+    local_include_dirs: ["include"],
+    static_libs: [
+        "android.hardware.tetheroffload.config@1.0",
+        "android.hardware.tetheroffload.control@1.0",
+        "android.hardware.tetheroffload.control@1.1",
+        "VtsHalTetheroffloadControlV1_0TargetTest-lib",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/tetheroffload/control/1.1/vts/functional/VtsHalTetheroffloadControlV1_1TargetTest.cpp b/tetheroffload/control/1.1/vts/functional/VtsHalTetheroffloadControlV1_1TargetTest.cpp
new file mode 100644
index 0000000..b8c9e53
--- /dev/null
+++ b/tetheroffload/control/1.1/vts/functional/VtsHalTetheroffloadControlV1_1TargetTest.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 <OffloadControlTestV1_1.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+
+using android::hardware::tetheroffload::control::V1_1::IOffloadControl;
+
+const hidl_string TEST_IFACE("rmnet_data0");
+
+// Check that calling setDataWarningAndLimit() without first having called initOffload() returns
+// false.
+TEST_P(OffloadControlTestV1_1_HalNotStarted, SetDataWarningAndLimitWithoutInitReturnsFalse) {
+    const Return<void> ret = getControlV1_1()->setDataWarningAndLimit(TEST_IFACE, 5000ULL, 5000ULL,
+                                                                      ASSERT_FALSE_CALLBACK);
+    EXPECT_TRUE(ret.isOk());
+}
+
+/*
+ * Tests for IOffloadControl::setDataWarningAndLimit().
+ */
+
+// Test that setDataWarningAndLimit() for an empty interface name fails.
+TEST_P(OffloadControlTestV1_1_HalStarted, SetDataWarningAndLimitEmptyUpstreamIfaceFails) {
+    const Return<void> ret = getControlV1_1()->setDataWarningAndLimit(
+            hidl_string(""), 12345ULL, 67890ULL, ASSERT_FALSE_CALLBACK);
+    EXPECT_TRUE(ret.isOk());
+}
+
+// TEST_IFACE is presumed to exist on the device and be up. No packets
+// are ever actually caused to be forwarded.
+TEST_P(OffloadControlTestV1_1_HalStarted, SetDataWarningAndLimitNonZeroOk) {
+    const Return<void> ret = getControlV1_1()->setDataWarningAndLimit(TEST_IFACE, 4000ULL, 5000ULL,
+                                                                      ASSERT_TRUE_CALLBACK);
+    EXPECT_TRUE(ret.isOk());
+}
+
+// TEST_IFACE is presumed to exist on the device and be up. No packets
+// are ever actually caused to be forwarded.
+TEST_P(OffloadControlTestV1_1_HalStarted, SetDataWarningAndLimitZeroOk) {
+    const Return<void> ret =
+            getControlV1_1()->setDataWarningAndLimit(TEST_IFACE, 0ULL, 0ULL, ASSERT_TRUE_CALLBACK);
+    EXPECT_TRUE(ret.isOk());
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OffloadControlTestV1_1_HalNotStarted);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OffloadControlTestV1_1_HalStarted);
+
+INSTANTIATE_TEST_CASE_P(
+        PerInstance, OffloadControlTestV1_1_HalNotStarted,
+        testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+                                 IOffloadConfig::descriptor)),
+                         testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+                                 IOffloadControl::descriptor))),
+        android::hardware::PrintInstanceTupleNameToString<>);
+
+INSTANTIATE_TEST_CASE_P(
+        PerInstance, OffloadControlTestV1_1_HalStarted,
+        testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+                                 IOffloadConfig::descriptor)),
+                         testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+                                 IOffloadControl::descriptor))),
+        android::hardware::PrintInstanceTupleNameToString<>);
diff --git a/tetheroffload/control/1.1/vts/functional/include/OffloadControlTestV1_1.h b/tetheroffload/control/1.1/vts/functional/include/OffloadControlTestV1_1.h
new file mode 100644
index 0000000..a3bc1b4
--- /dev/null
+++ b/tetheroffload/control/1.1/vts/functional/include/OffloadControlTestV1_1.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <OffloadControlTestV1_0.h>
+#include <android/hardware/tetheroffload/control/1.1/IOffloadControl.h>
+#include <android/hardware/tetheroffload/control/1.1/ITetheringOffloadCallback.h>
+#include <gtest/gtest.h>
+
+constexpr char kCallbackOnEvent_1_1[] = "onEvent_1_1";
+
+class TetheringOffloadCallbackArgsV1_1 {
+  public:
+    android::hardware::tetheroffload::control::V1_1::OffloadCallbackEvent last_event;
+};
+
+class OffloadControlTestV1_1_HalNotStarted : public OffloadControlTestV1_0_HalNotStarted {
+  public:
+    virtual sp<android::hardware::tetheroffload::control::V1_0::IOffloadControl> createControl(
+            const std::string& serviceName) override {
+        return android::hardware::tetheroffload::control::V1_1::IOffloadControl::getService(
+                serviceName);
+    };
+
+    void prepareControlHal() override {
+        control = createControl(std::get<1>(GetParam()));
+        ASSERT_NE(nullptr, control.get()) << "Could not get HIDL instance";
+
+        control_cb_1_1 = new TetheringOffloadCallbackV1_1();
+        ASSERT_NE(nullptr, control_cb_1_1.get()) << "Could not get offload callback";
+    };
+
+    void initOffload(const bool expected_result) override {
+        auto init_cb = [&](bool success, std::string errMsg) {
+            std::string msg = StringPrintf("Unexpectedly %s to init offload: %s",
+                                           success ? "succeeded" : "failed", errMsg.c_str());
+            ASSERT_EQ(expected_result, success) << msg;
+        };
+        auto control = getControlV1_1();
+        ASSERT_NE(control, nullptr);
+        const Return<void> ret = control->initOffload(control_cb_1_1, init_cb);
+        ASSERT_TRUE(ret.isOk());
+    };
+
+    sp<android::hardware::tetheroffload::control::V1_1::IOffloadControl> getControlV1_1() {
+        // The cast is safe since only devices with V1.1+ HAL will be enumerated and pass in to the
+        // test.
+        return android::hardware::tetheroffload::control::V1_1::IOffloadControl::castFrom(control)
+                .withDefault(nullptr);
+    };
+
+    // Callback class for both new events.
+    class TetheringOffloadCallbackV1_1
+        : public testing::VtsHalHidlTargetCallbackBase<TetheringOffloadCallbackArgsV1_1>,
+          public android::hardware::tetheroffload::control::V1_1::ITetheringOffloadCallback {
+      public:
+        Return<void> onEvent_1_1(
+                android::hardware::tetheroffload::control::V1_1::OffloadCallbackEvent event)
+                override {
+            const TetheringOffloadCallbackArgsV1_1 args{.last_event = event};
+            NotifyFromCallback(kCallbackOnEvent_1_1, args);
+            return Void();
+        };
+
+        Return<void> onEvent([[maybe_unused]] OffloadCallbackEvent event) override {
+            // Tested only in IOffloadControl 1.0.
+            return Void();
+        };
+
+        Return<void> updateTimeout([[maybe_unused]] const NatTimeoutUpdate& params) override {
+            // Tested only in IOffloadControl 1.0.
+            return Void();
+        };
+    };
+
+    sp<TetheringOffloadCallbackV1_1> control_cb_1_1;
+};
+
+class OffloadControlTestV1_1_HalStarted : public OffloadControlTestV1_1_HalNotStarted {
+  public:
+    virtual void SetUp() override {
+        setupConfigHal();
+        setupControlHal();
+    }
+};
\ No newline at end of file
diff --git a/tv/cec/2.0/Android.bp b/tv/cec/2.0/Android.bp
deleted file mode 100644
index 5463b6d..0000000
--- a/tv/cec/2.0/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-// This file is autogenerated by hidl-gen -Landroidbp.
-
-hidl_interface {
-    name: "android.hardware.tv.cec@2.0",
-    root: "android.hardware",
-    srcs: [
-        "types.hal",
-        "IHdmiCec.hal",
-        "IHdmiCecCallback.hal",
-    ],
-    interfaces: [
-        "android.hidl.base@1.0",
-        "android.hidl.safe_union@1.0",
-    ],
-    gen_java: true,
-}
diff --git a/tv/cec/2.0/IHdmiCec.hal b/tv/cec/2.0/IHdmiCec.hal
deleted file mode 100644
index 0723bad..0000000
--- a/tv/cec/2.0/IHdmiCec.hal
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2019 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.cec@2.0;
-
-import IHdmiCecCallback;
-
-/**
- * HDMI-CEC HAL interface definition.
- */
-interface IHdmiCec {
-    /**
-     * Passes Primary Device Type that must be used in this system.
-     *
-     * HAL must use it to allocate logical address as specified in CEC section
-     * 11.3.2 of the CEC spec 2.0b. Then CEC commands addressed the given
-     * logical address can be filtered in.
-     * This method shall be able to be called up to twice to support two Primary
-     * Device Type as specified in CEC Table 11-8 of the CEC spec 2.0b.
-     *
-     * @param deviceType that must be used in this system. It must be a valid
-     *        value in CecDeviceType for the call to succeed.
-     * @return result Result status of the operation. SUCCESS if successful,
-     *         FAILURE_INVALID_ARGS if the given device type is invalid,
-     *         FAILURE_BUSY if device or resource is busy
-     */
-    @callflow(next={"*"})
-    addDeviceType(CecDeviceType deviceType) generates (Result result);
-
-    /**
-     * Clears all Primary Device Types.
-     *
-     * It is used when the system plan to reconfigure Primary Device Type,
-     * hence to tell HAL to release all logical address associated to them,
-     * and change the state back to the beginning.
-     */
-    @callflow(next="addDeviceType")
-    @exit
-    clearDeviceTypes();
-
-    /**
-     * Set All Device Types for a Primary Device Type.
-     *
-     * This value must be used in REPORT_FEATURES message to response
-     * GIVE_FEATURES message in HAL.
-     *
-     * @param allDeviceTypes device all device types for a Primary Device Type.
-     */
-    @callflow(next="addDeviceType")
-    setAllDeviceTypes(CecAllDeviceTypes allDeviceTypes);
-
-    /**
-     * Set Device Features for a Primary Device Type.
-     *
-     * This value must be used in REPORT_FEATURES message to response
-     * GIVE_FEATURES message in HAL.
-     *
-     * @param deviceType The device Primary Device Type.
-     * @param deviceFeatures device features for a Primary Device Type.
-     */
-    @callflow(next="addDeviceType")
-    setDeviceFeatures(CecDeviceType deviceType,
-                      CecDeviceFeatures deviceFeatures);
-
-    /**
-     * Set Remote Control Profile for a Primary Device Type.
-     *
-     * This value must be used in REPORT_FEATURES message to response
-     * GIVE_FEATURES message in HAL.
-     *
-     * @param deviceType The device Primary Device Type.
-     * @param rcProliles remote control profiles for a Primary Device Type.
-     */
-    @callflow(next="addDeviceType")
-    setRcProfile(CecDeviceType deviceType, CecRcProfile rcProfile);
-
-    /**
-     * Retrieve CEC device information.
-     *
-     * CEC section 11.3 of the CEC spec 2.0b specify that a device should not
-     * ask for static information that another device has already supplied.
-     * Therefore, CEC 2.0 software stack need a map to store all cec
-     * devices’ information of current CEC network.
-     * The device information is broadcasted by a device after it allocates a
-     * logical address.  Messages used to send out these information are
-     * REPORT_FEATURES, REPORT_PHYSICAL_ADDRESS, DEVICE_VENDOR_ID.
-     * The spec also requires less than 1 second between REPORT_FEATURES and
-     * REPORT_PHYSICAL_ADDRESS message, and less than 2 second between
-     * REPORT_PHYSICAL_ADDRESS and DEVICE_VENDOR_ID. An Implementation of
-     * device information map in hal can help to meet the timing constraints.
-     * Logical addressing is part of the process to build this map, so the
-     * implementation shall include allocating logical address too.
-     * Whenever a device plug/unplug, the topology of CEC network changes.
-     * The hal implementation shall update devices’ information map, and
-     * send out onTopologyEvent to Android system. Then Android system
-     * will use readDeviceInfo to retreive latest devices’ information of CEC
-     * network.
-     * If SYSTEM_CEC_CONTROL is false, the hal implementation need continue to
-     * maintain and update device information map, and send out pending
-     * onTopologyEvent to Android system when SYSTEM_CEC_CONTROL is
-     * changed to true.
-     *
-     * @param logicalAddress logical address of CEC device.
-     * @param physicalAddress physical address of CEC device.
-     * @return CecDeviceInfo from device information map.
-     * @return result Result status of the operation. SUCCESS if successful,
-     *         FAILURE_INVALID_ARGS if logical or physical address is invalid.
-     *         FAILURE_INVALID_STATE if device information isn't available yet.
-     */
-    @callflow(next="onTopologyChangeEvent")
-    readDeviceInfo(CecLogicalAddress logicalAddress,
-                   CecPhysicalAddress physicalAddress)
-        generates (Result result, CecDeviceInfo deviceInfo);
-
-   /**
-     * Transmits HDMI-CEC message to other HDMI device.
-     *
-     * The method must be designed to return in a certain amount of time and not
-     * hanging forever. This method MUST complete with in 1 second.
-     *
-     * It must try retransmission at least once as specified in the section '7.1
-     * Frame Re-transmissions' of the CEC Spec 1.4b.
-     *
-     * @param message CEC message to be sent to other HDMI device.
-     * @return result Result status of the operation. SUCCESS if successful,
-     *         NACK if the sent message is not acknowledged,
-     *         BUSY if the CEC bus is busy.
-     */
-    @callflow(next="*")
-    sendMessage(CecMessage message) generates (SendMessageResult result);
-
-    /**
-     * Set the callback
-     *
-     * It is used by the framework to receive CecMessages, HDMI hotplug event
-     * and topology update event. Only one callback client is supported.
-     *
-     * @param callback Callback object to pass hdmi events to the system. The
-     *        previously registered callback must be replaced with this one.
-     */
-    @callflow(next={"*"})
-    @entry
-    setCallback(IHdmiCecCallback callback);
-
-   /**
-     * Gets the hdmi port information of underlying hardware.
-     *
-     * @return infos The list of HDMI port information
-     */
-    @callflow(next={"*"})
-    getPortInfo() generates (vec<HdmiPortInfo> infos);
-
-    /**
-     * Sets flags controlling the way HDMI-CEC service works down to HAL
-     * implementation. Those flags must be used in case the feature needs update
-     * in HAL itself, firmware or microcontroller.
-     *
-     * @param key The key of the option to be updated with a new value.
-     * @param value Value to be set.
-     */
-    @callflow(next="*")
-    setOption(OptionKey key, bool value);
-
-    /**
-     * Passes the updated language information of Android system. Contains
-     * three-letter code as defined in ISO/FDIS 639-2. Must be used for HAL to
-     * respond to <Get Menu Language> while in standby mode.
-     *
-     * @param language Three-letter code defined in ISO/FDIS 639-2. Must be
-     *        lowercase letters. (e.g., eng for English)
-     */
-    @callflow(next="*")
-    setLanguage(string language);
-
-    /**
-     * Configures ARC circuit in the hardware logic to start or stop the
-     * feature.
-     *
-     * @param portId Port id to be configured.
-     * @param enable Flag must be either true to start the feature or false to
-     *        stop it.
-     */
-    @callflow(next="*")
-    enableAudioReturnChannel(HdmiPortId portId, bool enable);
-
-    /**
-     * Gets the connection status of the specified port.
-     *
-     * It's specified in CEC section 10.8 of the CEC spec 2.0b
-     *
-     * @param portId Port id to be inspected for the connection status.
-     * @return status True if a device is connected, otherwise false.
-     */
-    @callflow(next="*")
-    isConnected(HdmiPortId portId) generates (bool connected);
-};
diff --git a/tv/cec/2.0/IHdmiCecCallback.hal b/tv/cec/2.0/IHdmiCecCallback.hal
deleted file mode 100644
index 1a8a489..0000000
--- a/tv/cec/2.0/IHdmiCecCallback.hal
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 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.cec@2.0;
-
-interface IHdmiCecCallback {
-    /**
-     * The callback function that must be called by HAL implementation to notify
-     * the system of new CEC message arrival.
-     */
-    oneway onCecMessage(CecMessage message);
-
-    /**
-     * The callback function that must be called by HAL implementation to notify
-     * the system of new hotplug event.
-     */
-    oneway onHotplugEvent(HotplugEvent event);
-
-    /**
-     * The callback function must be called by HAL implementation to notify the
-     * system whenever CEC device information of CEC network change.
-     * HAL shall be ready for readDeviceInfo call before invoke this callback.
-     * This event is triggered by topology change of whole CEC network. It's
-     * different from HotplugEvent which is triggered between devices which are
-     * connected directly through HDMI cable.
-     */
-    oneway onTopologyEvent(CecTopologyEvent event);
-};
diff --git a/tv/cec/2.0/default/Android.bp b/tv/cec/2.0/default/Android.bp
deleted file mode 100644
index d3d5342..0000000
--- a/tv/cec/2.0/default/Android.bp
+++ /dev/null
@@ -1,40 +0,0 @@
-cc_library_shared {
-    name: "android.hardware.tv.cec@2.0-impl",
-    defaults: ["hidl_defaults"],
-    vendor: true,
-    relative_install_path: "hw",
-    srcs: ["HdmiCec.cpp"],
-
-    shared_libs: [
-        "libhidlbase",
-        "liblog",
-        "libbase",
-        "libutils",
-        "libhardware",
-        "android.hardware.tv.cec@2.0",
-    ],
-
-}
-
-cc_binary {
-    name: "android.hardware.tv.cec@2.0-service",
-    vintf_fragments: ["android.hardware.tv.cec@2.0-service.xml"],
-    defaults: ["hidl_defaults"],
-    relative_install_path: "hw",
-    vendor: true,
-    init_rc: ["android.hardware.tv.cec@2.0-service.rc"],
-    srcs: ["service.cpp"],
-
-    shared_libs: [
-        "liblog",
-        "libcutils",
-        "libdl",
-        "libbase",
-        "libutils",
-        "libhardware_legacy",
-        "libhardware",
-        "libhidlbase",
-        "android.hardware.tv.cec@2.0",
-    ],
-
-}
diff --git a/tv/cec/2.0/default/HdmiCec.cpp b/tv/cec/2.0/default/HdmiCec.cpp
deleted file mode 100644
index f451719..0000000
--- a/tv/cec/2.0/default/HdmiCec.cpp
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright (C) 2019 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@2.0-impl"
-#include <android-base/logging.h>
-
-#include <hardware/hardware.h>
-#include <hardware/hdmi_cec.h>
-#include "HdmiCec.h"
-
-namespace android {
-namespace hardware {
-namespace tv {
-namespace cec {
-namespace V2_0 {
-namespace implementation {
-
-static_assert(CEC_DEVICE_INACTIVE == static_cast<int>(CecDeviceType::INACTIVE),
-              "CecDeviceType::INACTIVE must match legacy value.");
-static_assert(CEC_DEVICE_TV == static_cast<int>(CecDeviceType::TV),
-              "CecDeviceType::TV must match legacy value.");
-static_assert(CEC_DEVICE_RECORDER == static_cast<int>(CecDeviceType::RECORDER),
-              "CecDeviceType::RECORDER must match legacy value.");
-static_assert(CEC_DEVICE_TUNER == static_cast<int>(CecDeviceType::TUNER),
-              "CecDeviceType::TUNER must match legacy value.");
-static_assert(CEC_DEVICE_PLAYBACK == static_cast<int>(CecDeviceType::PLAYBACK),
-              "CecDeviceType::PLAYBACK must match legacy value.");
-static_assert(CEC_DEVICE_AUDIO_SYSTEM == static_cast<int>(CecDeviceType::AUDIO_SYSTEM),
-              "CecDeviceType::AUDIO_SYSTEM must match legacy value.");
-/* TODO: Adjust for cec@2.0
-static_assert(CEC_DEVICE_MAX == static_cast<int>(CecDeviceType::MAX),
-        "CecDeviceType::MAX must match legacy value.");
-*/
-static_assert(CEC_ADDR_TV == static_cast<int>(CecLogicalAddress::TV),
-              "CecLogicalAddress::TV must match legacy value.");
-static_assert(CEC_ADDR_RECORDER_1 == static_cast<int>(CecLogicalAddress::RECORDER_1),
-              "CecLogicalAddress::RECORDER_1 must match legacy value.");
-static_assert(CEC_ADDR_RECORDER_2 == static_cast<int>(CecLogicalAddress::RECORDER_2),
-              "CecLogicalAddress::RECORDER_2 must match legacy value.");
-static_assert(CEC_ADDR_TUNER_1 == static_cast<int>(CecLogicalAddress::TUNER_1),
-              "CecLogicalAddress::TUNER_1 must match legacy value.");
-static_assert(CEC_ADDR_PLAYBACK_1 == static_cast<int>(CecLogicalAddress::PLAYBACK_1),
-              "CecLogicalAddress::PLAYBACK_1 must match legacy value.");
-static_assert(CEC_ADDR_AUDIO_SYSTEM == static_cast<int>(CecLogicalAddress::AUDIO_SYSTEM),
-              "CecLogicalAddress::AUDIO_SYSTEM must match legacy value.");
-static_assert(CEC_ADDR_TUNER_2 == static_cast<int>(CecLogicalAddress::TUNER_2),
-              "CecLogicalAddress::TUNER_2 must match legacy value.");
-static_assert(CEC_ADDR_TUNER_3 == static_cast<int>(CecLogicalAddress::TUNER_3),
-              "CecLogicalAddress::TUNER_3 must match legacy value.");
-static_assert(CEC_ADDR_PLAYBACK_2 == static_cast<int>(CecLogicalAddress::PLAYBACK_2),
-              "CecLogicalAddress::PLAYBACK_2 must match legacy value.");
-static_assert(CEC_ADDR_RECORDER_3 == static_cast<int>(CecLogicalAddress::RECORDER_3),
-              "CecLogicalAddress::RECORDER_3 must match legacy value.");
-static_assert(CEC_ADDR_TUNER_4 == static_cast<int>(CecLogicalAddress::TUNER_4),
-              "CecLogicalAddress::TUNER_4 must match legacy value.");
-static_assert(CEC_ADDR_PLAYBACK_3 == static_cast<int>(CecLogicalAddress::PLAYBACK_3),
-              "CecLogicalAddress::PLAYBACK_3 must match legacy value.");
-/* TODO: Adjust for cec@2.0
-static_assert(CEC_ADDR_FREE_USE == static_cast<int>(CecLogicalAddress::FREE_USE),
-        "CecLogicalAddress::FREE_USE must match legacy value.");
-*/
-static_assert(CEC_ADDR_UNREGISTERED == static_cast<int>(CecLogicalAddress::UNREGISTERED),
-              "CecLogicalAddress::UNREGISTERED must match legacy value.");
-static_assert(CEC_ADDR_BROADCAST == static_cast<int>(CecLogicalAddress::BROADCAST),
-              "CecLogicalAddress::BROADCAST must match legacy value.");
-
-static_assert(CEC_MESSAGE_FEATURE_ABORT == static_cast<int>(CecMessageType::FEATURE_ABORT),
-              "CecMessageType::FEATURE_ABORT must match legacy value.");
-static_assert(CEC_MESSAGE_IMAGE_VIEW_ON == static_cast<int>(CecMessageType::IMAGE_VIEW_ON),
-              "CecMessageType::IMAGE_VIEW_ON must match legacy value.");
-static_assert(CEC_MESSAGE_TUNER_STEP_INCREMENT ==
-                      static_cast<int>(CecMessageType::TUNER_STEP_INCREMENT),
-              "CecMessageType::TUNER_STEP_INCREMENT must match legacy value.");
-static_assert(CEC_MESSAGE_TUNER_STEP_DECREMENT ==
-                      static_cast<int>(CecMessageType::TUNER_STEP_DECREMENT),
-              "CecMessageType::TUNER_STEP_DECREMENT must match legacy value.");
-static_assert(CEC_MESSAGE_TUNER_DEVICE_STATUS ==
-                      static_cast<int>(CecMessageType::TUNER_DEVICE_STATUS),
-              "CecMessageType::TUNER_DEVICE_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_TUNER_DEVICE_STATUS ==
-                      static_cast<int>(CecMessageType::GIVE_TUNER_DEVICE_STATUS),
-              "CecMessageType::GIVE_TUNER_DEVICE_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_RECORD_ON == static_cast<int>(CecMessageType::RECORD_ON),
-              "CecMessageType::RECORD_ON must match legacy value.");
-static_assert(CEC_MESSAGE_RECORD_STATUS == static_cast<int>(CecMessageType::RECORD_STATUS),
-              "CecMessageType::RECORD_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_RECORD_OFF == static_cast<int>(CecMessageType::RECORD_OFF),
-              "CecMessageType::RECORD_OFF must match legacy value.");
-static_assert(CEC_MESSAGE_TEXT_VIEW_ON == static_cast<int>(CecMessageType::TEXT_VIEW_ON),
-              "CecMessageType::TEXT_VIEW_ON must match legacy value.");
-static_assert(CEC_MESSAGE_RECORD_TV_SCREEN == static_cast<int>(CecMessageType::RECORD_TV_SCREEN),
-              "CecMessageType::RECORD_TV_SCREEN must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_DECK_STATUS == static_cast<int>(CecMessageType::GIVE_DECK_STATUS),
-              "CecMessageType::GIVE_DECK_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_STANDBY == static_cast<int>(CecMessageType::STANDBY),
-              "CecMessageType::STANDBY must match legacy value.");
-static_assert(CEC_MESSAGE_PLAY == static_cast<int>(CecMessageType::PLAY),
-              "CecMessageType::PLAY must match legacy value.");
-static_assert(CEC_MESSAGE_DECK_CONTROL == static_cast<int>(CecMessageType::DECK_CONTROL),
-              "CecMessageType::DECK_CONTROL must match legacy value.");
-static_assert(CEC_MESSAGE_TIMER_CLEARED_STATUS ==
-                      static_cast<int>(CecMessageType::TIMER_CLEARED_STATUS),
-              "CecMessageType::TIMER_CLEARED_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_USER_CONTROL_PRESSED ==
-                      static_cast<int>(CecMessageType::USER_CONTROL_PRESSED),
-              "CecMessageType::USER_CONTROL_PRESSED must match legacy value.");
-static_assert(CEC_MESSAGE_USER_CONTROL_RELEASED ==
-                      static_cast<int>(CecMessageType::USER_CONTROL_RELEASED),
-              "CecMessageType::USER_CONTROL_RELEASED must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_OSD_NAME == static_cast<int>(CecMessageType::GIVE_OSD_NAME),
-              "CecMessageType::GIVE_OSD_NAME must match legacy value.");
-static_assert(CEC_MESSAGE_SET_OSD_NAME == static_cast<int>(CecMessageType::SET_OSD_NAME),
-              "CecMessageType::SET_OSD_NAME must match legacy value.");
-static_assert(CEC_MESSAGE_SYSTEM_AUDIO_MODE_REQUEST ==
-                      static_cast<int>(CecMessageType::SYSTEM_AUDIO_MODE_REQUEST),
-              "CecMessageType::SYSTEM_AUDIO_MODE_REQUEST must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_AUDIO_STATUS == static_cast<int>(CecMessageType::GIVE_AUDIO_STATUS),
-              "CecMessageType::GIVE_AUDIO_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_SET_SYSTEM_AUDIO_MODE ==
-                      static_cast<int>(CecMessageType::SET_SYSTEM_AUDIO_MODE),
-              "CecMessageType::SET_SYSTEM_AUDIO_MODE must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_AUDIO_STATUS ==
-                      static_cast<int>(CecMessageType::REPORT_AUDIO_STATUS),
-              "CecMessageType::REPORT_AUDIO_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS ==
-                      static_cast<int>(CecMessageType::GIVE_SYSTEM_AUDIO_MODE_STATUS),
-              "CecMessageType::GIVE_SYSTEM_AUDIO_MODE_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_SYSTEM_AUDIO_MODE_STATUS ==
-                      static_cast<int>(CecMessageType::SYSTEM_AUDIO_MODE_STATUS),
-              "CecMessageType::SYSTEM_AUDIO_MODE_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_ROUTING_CHANGE == static_cast<int>(CecMessageType::ROUTING_CHANGE),
-              "CecMessageType::ROUTING_CHANGE must match legacy value.");
-static_assert(CEC_MESSAGE_ROUTING_INFORMATION ==
-                      static_cast<int>(CecMessageType::ROUTING_INFORMATION),
-              "CecMessageType::ROUTING_INFORMATION must match legacy value.");
-static_assert(CEC_MESSAGE_ACTIVE_SOURCE == static_cast<int>(CecMessageType::ACTIVE_SOURCE),
-              "CecMessageType::ACTIVE_SOURCE must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_PHYSICAL_ADDRESS ==
-                      static_cast<int>(CecMessageType::GIVE_PHYSICAL_ADDRESS),
-              "CecMessageType::GIVE_PHYSICAL_ADDRESS must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_PHYSICAL_ADDRESS ==
-                      static_cast<int>(CecMessageType::REPORT_PHYSICAL_ADDRESS),
-              "CecMessageType::REPORT_PHYSICAL_ADDRESS must match legacy value.");
-static_assert(CEC_MESSAGE_REQUEST_ACTIVE_SOURCE ==
-                      static_cast<int>(CecMessageType::REQUEST_ACTIVE_SOURCE),
-              "CecMessageType::REQUEST_ACTIVE_SOURCE must match legacy value.");
-static_assert(CEC_MESSAGE_SET_STREAM_PATH == static_cast<int>(CecMessageType::SET_STREAM_PATH),
-              "CecMessageType::SET_STREAM_PATH must match legacy value.");
-static_assert(CEC_MESSAGE_DEVICE_VENDOR_ID == static_cast<int>(CecMessageType::DEVICE_VENDOR_ID),
-              "CecMessageType::DEVICE_VENDOR_ID must match legacy value.");
-static_assert(CEC_MESSAGE_VENDOR_COMMAND == static_cast<int>(CecMessageType::VENDOR_COMMAND),
-              "CecMessageType::VENDOR_COMMAND must match legacy value.");
-static_assert(CEC_MESSAGE_VENDOR_REMOTE_BUTTON_DOWN ==
-                      static_cast<int>(CecMessageType::VENDOR_REMOTE_BUTTON_DOWN),
-              "CecMessageType::VENDOR_REMOTE_BUTTON_DOWN must match legacy value.");
-static_assert(CEC_MESSAGE_VENDOR_REMOTE_BUTTON_UP ==
-                      static_cast<int>(CecMessageType::VENDOR_REMOTE_BUTTON_UP),
-              "CecMessageType::VENDOR_REMOTE_BUTTON_UP must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_DEVICE_VENDOR_ID ==
-                      static_cast<int>(CecMessageType::GIVE_DEVICE_VENDOR_ID),
-              "CecMessageType::GIVE_DEVICE_VENDOR_ID must match legacy value.");
-static_assert(CEC_MESSAGE_MENU_REQUEST == static_cast<int>(CecMessageType::MENU_REQUEST),
-              "CecMessageType::MENU_REQUEST must match legacy value.");
-static_assert(CEC_MESSAGE_MENU_STATUS == static_cast<int>(CecMessageType::MENU_STATUS),
-              "CecMessageType::MENU_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_DEVICE_POWER_STATUS ==
-                      static_cast<int>(CecMessageType::GIVE_DEVICE_POWER_STATUS),
-              "CecMessageType::GIVE_DEVICE_POWER_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_POWER_STATUS ==
-                      static_cast<int>(CecMessageType::REPORT_POWER_STATUS),
-              "CecMessageType::REPORT_POWER_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_GET_MENU_LANGUAGE == static_cast<int>(CecMessageType::GET_MENU_LANGUAGE),
-              "CecMessageType::GET_MENU_LANGUAGE must match legacy value.");
-static_assert(CEC_MESSAGE_SELECT_ANALOG_SERVICE ==
-                      static_cast<int>(CecMessageType::SELECT_ANALOG_SERVICE),
-              "CecMessageType::SELECT_ANALOG_SERVICE must match legacy value.");
-static_assert(CEC_MESSAGE_SELECT_DIGITAL_SERVICE ==
-                      static_cast<int>(CecMessageType::SELECT_DIGITAL_SERVICE),
-              "CecMessageType::SELECT_DIGITAL_SERVICE must match legacy value.");
-static_assert(CEC_MESSAGE_SET_DIGITAL_TIMER == static_cast<int>(CecMessageType::SET_DIGITAL_TIMER),
-              "CecMessageType::SET_DIGITAL_TIMER must match legacy value.");
-static_assert(CEC_MESSAGE_CLEAR_DIGITAL_TIMER ==
-                      static_cast<int>(CecMessageType::CLEAR_DIGITAL_TIMER),
-              "CecMessageType::CLEAR_DIGITAL_TIMER must match legacy value.");
-static_assert(CEC_MESSAGE_SET_AUDIO_RATE == static_cast<int>(CecMessageType::SET_AUDIO_RATE),
-              "CecMessageType::SET_AUDIO_RATE must match legacy value.");
-static_assert(CEC_MESSAGE_INACTIVE_SOURCE == static_cast<int>(CecMessageType::INACTIVE_SOURCE),
-              "CecMessageType::INACTIVE_SOURCE must match legacy value.");
-static_assert(CEC_MESSAGE_CEC_VERSION == static_cast<int>(CecMessageType::CEC_VERSION),
-              "CecMessageType::CEC_VERSION must match legacy value.");
-static_assert(CEC_MESSAGE_GET_CEC_VERSION == static_cast<int>(CecMessageType::GET_CEC_VERSION),
-              "CecMessageType::GET_CEC_VERSION must match legacy value.");
-static_assert(CEC_MESSAGE_VENDOR_COMMAND_WITH_ID ==
-                      static_cast<int>(CecMessageType::VENDOR_COMMAND_WITH_ID),
-              "CecMessageType::VENDOR_COMMAND_WITH_ID must match legacy value.");
-static_assert(CEC_MESSAGE_CLEAR_EXTERNAL_TIMER ==
-                      static_cast<int>(CecMessageType::CLEAR_EXTERNAL_TIMER),
-              "CecMessageType::CLEAR_EXTERNAL_TIMER must match legacy value.");
-static_assert(CEC_MESSAGE_SET_EXTERNAL_TIMER ==
-                      static_cast<int>(CecMessageType::SET_EXTERNAL_TIMER),
-              "CecMessageType::SET_EXTERNAL_TIMER must match legacy value.");
-static_assert(CEC_MESSAGE_INITIATE_ARC == static_cast<int>(CecMessageType::INITIATE_ARC),
-              "CecMessageType::INITIATE_ARC must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_ARC_INITIATED ==
-                      static_cast<int>(CecMessageType::REPORT_ARC_INITIATED),
-              "CecMessageType::REPORT_ARC_INITIATED must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_ARC_TERMINATED ==
-                      static_cast<int>(CecMessageType::REPORT_ARC_TERMINATED),
-              "CecMessageType::REPORT_ARC_TERMINATED must match legacy value.");
-static_assert(CEC_MESSAGE_REQUEST_ARC_INITIATION ==
-                      static_cast<int>(CecMessageType::REQUEST_ARC_INITIATION),
-              "CecMessageType::REQUEST_ARC_INITIATION must match legacy value.");
-static_assert(CEC_MESSAGE_REQUEST_ARC_TERMINATION ==
-                      static_cast<int>(CecMessageType::REQUEST_ARC_TERMINATION),
-              "CecMessageType::REQUEST_ARC_TERMINATION must match legacy value.");
-static_assert(CEC_MESSAGE_TERMINATE_ARC == static_cast<int>(CecMessageType::TERMINATE_ARC),
-              "CecMessageType::TERMINATE_ARC must match legacy value.");
-static_assert(CEC_MESSAGE_ABORT == static_cast<int>(CecMessageType::ABORT),
-              "CecMessageType::ABORT must match legacy value.");
-
-static_assert(ABORT_UNRECOGNIZED_MODE == static_cast<int>(AbortReason::UNRECOGNIZED_MODE),
-              "AbortReason::UNRECOGNIZED_MODE must match legacy value.");
-static_assert(ABORT_NOT_IN_CORRECT_MODE == static_cast<int>(AbortReason::NOT_IN_CORRECT_MODE),
-              "AbortReason::NOT_IN_CORRECT_MODE must match legacy value.");
-static_assert(ABORT_CANNOT_PROVIDE_SOURCE == static_cast<int>(AbortReason::CANNOT_PROVIDE_SOURCE),
-              "AbortReason::CANNOT_PROVIDE_SOURCE must match legacy value.");
-static_assert(ABORT_INVALID_OPERAND == static_cast<int>(AbortReason::INVALID_OPERAND),
-              "AbortReason::INVALID_OPERAND must match legacy value.");
-static_assert(ABORT_REFUSED == static_cast<int>(AbortReason::REFUSED),
-              "AbortReason::REFUSED must match legacy value.");
-static_assert(ABORT_UNABLE_TO_DETERMINE == static_cast<int>(AbortReason::UNABLE_TO_DETERMINE),
-              "AbortReason::UNABLE_TO_DETERMINE must match legacy value.");
-
-static_assert(HDMI_RESULT_SUCCESS == static_cast<int>(SendMessageResult::SUCCESS),
-              "SendMessageResult::SUCCESS must match legacy value.");
-static_assert(HDMI_RESULT_NACK == static_cast<int>(SendMessageResult::NACK),
-              "SendMessageResult::NACK must match legacy value.");
-static_assert(HDMI_RESULT_BUSY == static_cast<int>(SendMessageResult::BUSY),
-              "SendMessageResult::BUSY must match legacy value.");
-static_assert(HDMI_RESULT_FAIL == static_cast<int>(SendMessageResult::FAIL),
-              "SendMessageResult::FAIL must match legacy value.");
-
-static_assert(HDMI_INPUT == static_cast<int>(HdmiPortType::INPUT),
-              "HdmiPortType::INPUT must match legacy value.");
-static_assert(HDMI_OUTPUT == static_cast<int>(HdmiPortType::OUTPUT),
-              "HdmiPortType::OUTPUT must match legacy value.");
-
-static_assert(HDMI_OPTION_WAKEUP == static_cast<int>(OptionKey::WAKEUP),
-              "OptionKey::WAKEUP must match legacy value.");
-static_assert(HDMI_OPTION_ENABLE_CEC == static_cast<int>(OptionKey::ENABLE_CEC),
-              "OptionKey::ENABLE_CEC must match legacy value.");
-static_assert(HDMI_OPTION_SYSTEM_CEC_CONTROL == static_cast<int>(OptionKey::SYSTEM_CEC_CONTROL),
-              "OptionKey::SYSTEM_CEC_CONTROL must match legacy value.");
-
-sp<IHdmiCecCallback> HdmiCec::mCallback = nullptr;
-
-HdmiCec::HdmiCec(hdmi_cec_device_t* device) : mDevice(device) {}
-
-// Methods from ::android::hardware::tv::cec::V2_0::IHdmiCec follow.
-Return<Result> HdmiCec::addDeviceType(CecDeviceType deviceType) {
-    // TODO implement
-    if (deviceType <= CecDeviceType::MAX) {
-        return Result::SUCCESS;
-    } else {
-        return Result::FAILURE_INVALID_ARGS;
-    }
-}
-
-Return<void> HdmiCec::clearDeviceTypes() {
-    // TODO implement
-    return Void();
-}
-
-Return<void> HdmiCec::setAllDeviceTypes(CecAllDeviceTypes allDeviceTypes) {
-    // TODO implement
-    if (allDeviceTypes == 1) {
-    }
-    return Void();
-}
-
-Return<void> HdmiCec::setDeviceFeatures(CecDeviceType deviceType,
-                                        CecDeviceFeatures /* deviceFeatures */) {
-    // TODO implement
-    if (deviceType != CecDeviceType::MAX) {
-    }
-    return Void();
-}
-
-Return<void> HdmiCec::setRcProfile(CecDeviceType deviceType, const CecRcProfile& /* rcProfile */) {
-    // TODO implement
-    if (deviceType != CecDeviceType::MAX) {
-    }
-    return Void();
-}
-
-Return<void> HdmiCec::readDeviceInfo(CecLogicalAddress logicalAddress,
-                                     CecPhysicalAddress physicalAddress,
-                                     const readDeviceInfo_cb _hidl_cb) {
-    // TODO implement
-    CecDeviceInfo deviceInfo;
-
-    if (logicalAddress == CecLogicalAddress::TV) {
-        _hidl_cb(Result::SUCCESS, deviceInfo);
-        if (physicalAddress) {
-        }
-    }
-    return Void();
-}
-
-Return<SendMessageResult> HdmiCec::sendMessage(const CecMessage& message) {
-    cec_message_t legacyMessage{
-            .initiator = static_cast<cec_logical_address_t>(message.initiator),
-            .destination = static_cast<cec_logical_address_t>(message.destination),
-            .length = message.body.size(),
-    };
-    for (size_t i = 0; i < message.body.size(); ++i) {
-        legacyMessage.body[i] = static_cast<unsigned char>(message.body[i]);
-    }
-    return static_cast<SendMessageResult>(mDevice->send_message(mDevice, &legacyMessage));
-}
-
-Return<void> HdmiCec::setCallback(const sp<IHdmiCecCallback>& callback) {
-    if (mCallback != nullptr) {
-        mCallback->unlinkToDeath(this);
-        mCallback = nullptr;
-    }
-
-    if (callback != nullptr) {
-        mCallback = callback;
-        mCallback->linkToDeath(this, 0 /*cookie*/);
-        mDevice->register_event_callback(mDevice, eventCallback, nullptr);
-    }
-    return Void();
-}
-
-Return<void> HdmiCec::getPortInfo(getPortInfo_cb _hidl_cb) {
-    struct hdmi_port_info* legacyPorts;
-    int numPorts;
-    hidl_vec<HdmiPortInfo> portInfos;
-    mDevice->get_port_info(mDevice, &legacyPorts, &numPorts);
-    portInfos.resize(numPorts);
-    for (int i = 0; i < numPorts; ++i) {
-        portInfos[i] = {.type = static_cast<HdmiPortType>(legacyPorts[i].type),
-                        .portId = static_cast<HdmiPortId>(legacyPorts[i].port_id),
-                        .cecSupported = legacyPorts[i].cec_supported != 0,
-                        .arcSupported = legacyPorts[i].arc_supported != 0,
-                        .physicalAddress = legacyPorts[i].physical_address};
-    }
-    _hidl_cb(portInfos);
-    return Void();
-}
-
-Return<void> HdmiCec::setOption(OptionKey key, bool value) {
-    mDevice->set_option(mDevice, static_cast<int>(key), value ? 1 : 0);
-    return Void();
-}
-
-Return<void> HdmiCec::setLanguage(const hidl_string& language) {
-    if (language.size() != 3) {
-        LOG(ERROR) << "Wrong language code: expected 3 letters, but it was " << language.size()
-                   << ".";
-        return Void();
-    }
-    const char* languageStr = language.c_str();
-    int convertedLanguage = ((languageStr[0] & 0xFF) << 16) | ((languageStr[1] & 0xFF) << 8) |
-                            (languageStr[2] & 0xFF);
-    mDevice->set_option(mDevice, HDMI_OPTION_SET_LANG, convertedLanguage);
-    return Void();
-}
-
-Return<void> HdmiCec::enableAudioReturnChannel(HdmiPortId portId, bool enable) {
-    mDevice->set_audio_return_channel(mDevice, portId, enable ? 1 : 0);
-    return Void();
-}
-
-Return<bool> HdmiCec::isConnected(HdmiPortId portId) {
-    return mDevice->is_connected(mDevice, portId) > 0;
-}
-
-IHdmiCec* HIDL_FETCH_IHdmiCec(const char* hal) {
-    hdmi_cec_device_t* hdmi_cec_device;
-    int ret = 0;
-    const hw_module_t* hw_module = nullptr;
-
-    ret = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID, &hw_module);
-    if (ret == 0) {
-        ret = hdmi_cec_open(hw_module, &hdmi_cec_device);
-        if (ret != 0) {
-            LOG(ERROR) << "hdmi_cec_open " << hal << " failed: " << ret;
-        }
-    } else {
-        LOG(ERROR) << "hw_get_module " << hal << " failed: " << ret;
-    }
-
-    if (ret == 0) {
-        return new HdmiCec(hdmi_cec_device);
-    } else {
-        LOG(ERROR) << "Passthrough failed to load legacy HAL.";
-        return nullptr;
-    }
-}
-
-}  // namespace implementation
-}  // namespace V2_0
-}  // namespace cec
-}  // namespace tv
-}  // namespace hardware
-}  // namespace android
diff --git a/tv/cec/2.0/default/HdmiCec.h b/tv/cec/2.0/default/HdmiCec.h
deleted file mode 100644
index ab54770..0000000
--- a/tv/cec/2.0/default/HdmiCec.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2019 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_TV_CEC_V2_0_HDMICEC_H
-#define ANDROID_HARDWARE_TV_CEC_V2_0_HDMICEC_H
-
-#include <algorithm>
-
-#include <android/hardware/tv/cec/2.0/IHdmiCec.h>
-#include <hardware/hardware.h>
-#include <hardware/hdmi_cec.h>
-#include <hidl/Status.h>
-
-#include <hidl/MQDescriptor.h>
-namespace android {
-namespace hardware {
-namespace tv {
-namespace cec {
-namespace V2_0 {
-namespace implementation {
-
-using ::android::sp;
-using ::android::hardware::hidl_string;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::tv::cec::V2_0::CecLogicalAddress;
-using ::android::hardware::tv::cec::V2_0::CecMessage;
-using ::android::hardware::tv::cec::V2_0::CecPhysicalAddress;
-using ::android::hardware::tv::cec::V2_0::HdmiPortId;
-using ::android::hardware::tv::cec::V2_0::HdmiPortInfo;
-using ::android::hardware::tv::cec::V2_0::IHdmiCec;
-using ::android::hardware::tv::cec::V2_0::IHdmiCecCallback;
-using ::android::hardware::tv::cec::V2_0::MaxLength;
-using ::android::hardware::tv::cec::V2_0::OptionKey;
-using ::android::hardware::tv::cec::V2_0::Result;
-using ::android::hardware::tv::cec::V2_0::SendMessageResult;
-
-struct HdmiCec : public IHdmiCec, public hidl_death_recipient {
-    HdmiCec(hdmi_cec_device_t* device);
-    // Methods from ::android::hardware::tv::cec::V2_0::IHdmiCec follow.
-    Return<Result> addDeviceType(CecDeviceType deviceType) override;
-    Return<void> clearDeviceTypes() override;
-    Return<void> setAllDeviceTypes(CecAllDeviceTypes allDeviceTypes) override;
-    Return<void> setDeviceFeatures(CecDeviceType deviceType,
-                                   CecDeviceFeatures /* deviceFeatures */) override;
-    Return<void> setRcProfile(CecDeviceType deviceType,
-                              const CecRcProfile& /* rcProfile */) override;
-    Return<void> readDeviceInfo(CecLogicalAddress logicalAddress,
-                                CecPhysicalAddress physicalAddress,
-                                const readDeviceInfo_cb _hidl_cb) override;
-    Return<SendMessageResult> sendMessage(const CecMessage& message) override;
-    Return<void> setCallback(const sp<IHdmiCecCallback>& callback) override;
-    Return<void> getPortInfo(getPortInfo_cb _hidl_cb) override;
-    Return<void> setOption(OptionKey key, bool value) override;
-    Return<void> setLanguage(const hidl_string& language) override;
-    Return<void> enableAudioReturnChannel(HdmiPortId portId, bool enable) override;
-    Return<bool> isConnected(HdmiPortId portId) override;
-
-    static void eventCallback(const hdmi_event_t* event, void* /* arg */) {
-        if (mCallback != nullptr && event != nullptr) {
-            if (event->type == HDMI_EVENT_CEC_MESSAGE) {
-                size_t length =
-                        std::min(event->cec.length, static_cast<size_t>(MaxLength::MESSAGE_BODY));
-                CecMessage cecMessage{
-                        .initiator = static_cast<CecLogicalAddress>(event->cec.initiator),
-                        .destination = static_cast<CecLogicalAddress>(event->cec.destination),
-                };
-                cecMessage.body.resize(length);
-                for (size_t i = 0; i < length; ++i) {
-                    cecMessage.body[i] = static_cast<uint8_t>(event->cec.body[i]);
-                }
-                mCallback->onCecMessage(cecMessage);
-            } else if (event->type == HDMI_EVENT_HOT_PLUG) {
-                HotplugEvent hotplugEvent{
-                        .connected = event->hotplug.connected > 0,
-                        .portId = static_cast<HdmiPortId>(event->hotplug.port_id)};
-                mCallback->onHotplugEvent(hotplugEvent);
-            }
-        }
-    }
-
-    virtual void serviceDied(uint64_t /*cookie*/,
-                             const wp<::android::hidl::base::V1_0::IBase>& /*who*/) {
-        setCallback(nullptr);
-    }
-
-   private:
-    static sp<IHdmiCecCallback> mCallback;
-    const hdmi_cec_device_t* mDevice;
-};
-
-extern "C" IHdmiCec* HIDL_FETCH_IHdmiCec(const char* name);
-
-}  // namespace implementation
-}  // namespace V2_0
-}  // namespace cec
-}  // namespace tv
-}  // namespace hardware
-}  // namespace android
-
-#endif  // ANDROID_HARDWARE_TV_CEC_V2_0_HDMICEC_H
diff --git a/tv/cec/2.0/default/OWNERS b/tv/cec/2.0/default/OWNERS
deleted file mode 100644
index 1b3d095..0000000
--- a/tv/cec/2.0/default/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-nchalko@google.com
-amyjojo@google.com
-shubang@google.com
-quxiangfang@google.com
diff --git a/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.rc b/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.rc
deleted file mode 100644
index 1e8cd80..0000000
--- a/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.rc
+++ /dev/null
@@ -1,4 +0,0 @@
-service vendor.cec-hal-2-0 /vendor/bin/hw/android.hardware.tv.cec@2.0-service
-    class hal
-    user system
-    group system
diff --git a/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.xml b/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.xml
deleted file mode 100644
index 61fb1bb..0000000
--- a/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest version="1.0" type="device">
-    <hal format="hidl">
-        <name>android.hardware.tv.cec</name>
-        <transport>hwbinder</transport>
-        <version>2.0</version>
-        <interface>
-            <name>IHdmiCec</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
-</manifest>
diff --git a/tv/cec/2.0/default/service.cpp b/tv/cec/2.0/default/service.cpp
deleted file mode 100644
index dacc38c..0000000
--- a/tv/cec/2.0/default/service.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2019 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@2.0-service"
-
-#include <android/hardware/tv/cec/2.0/IHdmiCec.h>
-#include <hidl/LegacySupport.h>
-
-using android::hardware::defaultPassthroughServiceImplementation;
-using android::hardware::tv::cec::V2_0::IHdmiCec;
-
-int main() {
-    return defaultPassthroughServiceImplementation<IHdmiCec>();
-}
diff --git a/tv/cec/2.0/types.hal b/tv/cec/2.0/types.hal
deleted file mode 100644
index cad6c39..0000000
--- a/tv/cec/2.0/types.hal
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2019 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.cec@2.0;
-
-import android.hidl.safe_union@1.0;
-
-/**
- * CEC device type as specified in CEC Table 11-7 of the CEC spec 2.0b.
- */
-enum CecDeviceType : int32_t {
-    INACTIVE = -1,
-    TV = 0,
-    RECORDER = 1,
-    TUNER = 3,
-    PLAYBACK = 4,
-    AUDIO_SYSTEM = 5,
-    PURE_CEC_SWITCH = 6,
-    PROCESSOR = 7,
-    MAX = PROCESSOR,
-};
-
-/**
- * CEC logical address as specified in CEC Table 11-9 of the CEC spec 2.0b.
- */
-enum CecLogicalAddress : int32_t {
-    TV = 0,
-    RECORDER_1 = 1,
-    RECORDER_2 = 2,
-    TUNER_1 = 3,
-    PLAYBACK_1 = 4,
-    AUDIO_SYSTEM = 5,
-    TUNER_2 = 6,
-    TUNER_3 = 7,
-    PLAYBACK_2 = 8,
-    RECORDER_3 = 9,
-    TUNER_4 = 10,
-    PLAYBACK_3 = 11,
-    BACKUP_1 = 12, // backup1 for Playback/Recording/Tuner/Processor device
-    BACKUP_2 = 13, // backup2 for Playback/Recording/Tuner/Processor device
-    SPECIFIC_USE = 14,
-    UNREGISTERED = 15, // as Initiator address
-    BROADCAST = 15, // as Destination address
-};
-
-/**
- * HDMI CEC message types.
- *
- * The assigned values represent opcode used in CEC frame as specified in
- * Section 11.10 of the CEC spec 2.0b on top of Section CEC 15 of the CEC
- * Spec 1.4b.
- */
-enum CecMessageType : int32_t {
-    FEATURE_ABORT = 0x00,
-    IMAGE_VIEW_ON = 0x04,
-    TUNER_STEP_INCREMENT = 0x05,
-    TUNER_STEP_DECREMENT = 0x06,
-    TUNER_DEVICE_STATUS = 0x07,
-    GIVE_TUNER_DEVICE_STATUS = 0x08,
-    RECORD_ON = 0x09,
-    RECORD_STATUS = 0x0A,
-    RECORD_OFF = 0x0B,
-    TEXT_VIEW_ON = 0x0D,
-    RECORD_TV_SCREEN = 0x0F,
-    GIVE_DECK_STATUS = 0x1A,
-    DECK_STATUS = 0x1B,
-    SET_MENU_LANGUAGE = 0x32,
-    CLEAR_ANALOG_TIMER = 0x33,
-    SET_ANALOG_TIMER = 0x34,
-    TIMER_STATUS = 0x35,
-    STANDBY = 0x36,
-    PLAY = 0x41,
-    DECK_CONTROL = 0x42,
-    TIMER_CLEARED_STATUS = 0x43,
-    USER_CONTROL_PRESSED = 0x44,
-    USER_CONTROL_RELEASED = 0x45,
-    GIVE_OSD_NAME = 0x46,
-    SET_OSD_NAME = 0x47,
-    SET_OSD_STRING = 0x64,
-    SET_TIMER_PROGRAM_TITLE = 0x67,
-    SYSTEM_AUDIO_MODE_REQUEST = 0x70,
-    GIVE_AUDIO_STATUS = 0x71,
-    SET_SYSTEM_AUDIO_MODE = 0x72,
-    REPORT_AUDIO_STATUS = 0x7A,
-    GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D,
-    SYSTEM_AUDIO_MODE_STATUS = 0x7E,
-    ROUTING_CHANGE = 0x80,
-    ROUTING_INFORMATION = 0x81,
-    ACTIVE_SOURCE = 0x82,
-    GIVE_PHYSICAL_ADDRESS = 0x83,
-    REPORT_PHYSICAL_ADDRESS = 0x84,
-    REQUEST_ACTIVE_SOURCE = 0x85,
-    SET_STREAM_PATH = 0x86,
-    DEVICE_VENDOR_ID = 0x87,
-    VENDOR_COMMAND = 0x89,
-    VENDOR_REMOTE_BUTTON_DOWN = 0x8A,
-    VENDOR_REMOTE_BUTTON_UP = 0x8B,
-    GIVE_DEVICE_VENDOR_ID = 0x8C,
-    MENU_REQUEST = 0x8D,
-    MENU_STATUS = 0x8E,
-    GIVE_DEVICE_POWER_STATUS = 0x8F,
-    REPORT_POWER_STATUS = 0x90,
-    GET_MENU_LANGUAGE = 0x91,
-    SELECT_ANALOG_SERVICE = 0x92,
-    SELECT_DIGITAL_SERVICE = 0x93,
-    SET_DIGITAL_TIMER = 0x97,
-    CLEAR_DIGITAL_TIMER = 0x99,
-    SET_AUDIO_RATE = 0x9A,
-    INACTIVE_SOURCE = 0x9D,
-    CEC_VERSION = 0x9E,
-    GET_CEC_VERSION = 0x9F,
-    VENDOR_COMMAND_WITH_ID = 0xA0,
-    CLEAR_EXTERNAL_TIMER = 0xA1,
-    SET_EXTERNAL_TIMER = 0xA2,
-    REPORT_SHORT_AUDIO_DESCRIPTOR = 0xA3,
-    REQUEST_SHORT_AUDIO_DESCRIPTOR = 0xA4,
-    GIVE_FEATURES = 0XA5,
-    REPORT_FEATURES = 0xA6,
-    REQUEST_CURRENT_LATENCY = 0xA7,
-    REPORT_CURRENT_LATENCY = 0xA8,
-    INITIATE_ARC = 0xC0,
-    REPORT_ARC_INITIATED = 0xC1,
-    REPORT_ARC_TERMINATED = 0xC2,
-    REQUEST_ARC_INITIATION = 0xC3,
-    REQUEST_ARC_TERMINATION = 0xC4,
-    TERMINATE_ARC = 0xC5,
-    ABORT = 0xFF,
-    POLLING_MESSAGE = 0xFFFFFF00, // used for cec polling message
-};
-
-/**
- * Abort Reason as specified in CEC Table 29 of the CEC spec 1.4b.
- */
-enum AbortReason : int32_t {
-    UNRECOGNIZED_MODE = 0,
-    NOT_IN_CORRECT_MODE = 1,
-    CANNOT_PROVIDE_SOURCE = 2,
-    INVALID_OPERAND = 3,
-    REFUSED = 4,
-    UNABLE_TO_DETERMINE = 5,
-};
-
-enum MaxLength : int32_t {
-    MESSAGE_BODY = 14,
-};
-
-struct CecMessage {
-    /** logical address of sender */
-    CecLogicalAddress initiator;
-
-    /** logical address of receiver */
-    CecLogicalAddress destination;
-
-    /** cec message type */
-    CecMessageType cecMessageType;
-
-    /**
-     * The maximum size of body is 14 (MaxLength::MESSAGE_BODY) as specified in
-     * the section 6 of the CEC Spec 1.4b. Overflowed data must be ignored.
-     */
-    vec<uint8_t> body;
-};
-
-/**
- * error code used for send_message.
- */
-enum SendMessageResult : int32_t {
-    SUCCESS = 0,
-    NACK = 1, // not acknowledged
-    BUSY = 2, // bus is busy
-    FAIL = 3,
-};
-
-/**
- * CEC All Device Type Value as specified in Table 11-30 of the CEC spec 2.0b.
- */
-enum CecAllDeviceTypeValue : uint8_t {
-    RESERVED_DEVICE_2 = 1 << 0,
-    RESERVED_DEVICE_1 = 1 << 1,
-    CEC_SWITCH_DEVICE = 1 << 2,
-    AUDIO_DEVICE = 1 << 3,
-    PLAYBACK_DEVICE = 1 << 4,
-    TUNER_DEVICE = 1 << 5,
-    RECORDING_DEVICE = 1 << 6,
-    TV_DEVICE   = 1 << 7,
-};
-
-/**
- * CEC All Device Types
- *
- * It is a combination of all supported type from CecAllDeviceTypeValue.
- * For example a record with tuner functionalitye,
- * cecAllDeviceTypes = ((CecAllDeviceTypeValue::RECORDING_DEVICE)
- *                     |(CecAllDeviceTypeValue::TUNER_DEVICE))
- */
-typedef bitfield<CecAllDeviceTypeValue> CecAllDeviceTypes;
-
-/**
- * CEC Versions as specified in CEC Table 11-30 of the CEC spec 2.0b.
- */
-enum CecVersion : int32_t {
-    V_1_3_A   = 0x04,
-    V_1_4   = 0x05, // indicate CEC 1.4, 1.4a or 1.4b
-    V_2_0   = 0x06,
-};
-
-/**
- * Device Feature
- *
- * It is specified in CEC Table 11-30 of the CEC spec 2.0b. As a uint32 there
- * is room for future extensions aka DeviceFeature2 through DeviceFeature4.
- */
-enum CecDeviceFeature : uint32_t {
-    RESERVED = 1 << 0,
-    SOURCE_SUPPORT_ARC_RX = 1 << 1,
-    SINK_SUPPORT_ARC_TX = 1 << 2,
-    SOURCE_SUPPORT_SET_AUDIO_RATE = 1 << 3,
-    SUPPORT_CONTROLLED_BY_DECK = 1 << 4,
-    TV_SUPPORT_SET_OSD_STRINGS = 1 << 5,
-    TV_SUPPORT_RECORD_TV_SCREEN = 1 << 6,
-};
-
-/**
- * CEC Device Features
- *
- * It is a combination of all supported features from CecDeviceFeature.
- * For example a TV with OSD and ARC capabilities,
- *   CecDeviceFeatures = ((CecDeviceFeature::TV_SUPPORT_SET_OSD_STRINGS)
- *                       |(CecDeviceFeature::SINK_SUPPORT_ARC_TX))
- */
-typedef bitfield<CecDeviceFeature> CecDeviceFeatures;
-
-/**
- * Remote Control Profile
- *
- * It is specified in CEC Table 11-30 of the CEC spec 2.0b.
- */
-enum CecRcProfileId : uint8_t {
-    NONE = 0,   // TV doesn’t support any of these profiles
-    RC_PROFILE_1 = 0x02, // minimalistic zapper (low button count)
-    RC_PROFILE_2 = 0x06, // intermediate between profile 1 and profile 3
-    RC_PROFILE_3 = 0x0A, // typical TV remote
-    RC_PROFILE_4 = 0x0E, // extended form of profile 3
-};
-
-/**
- * Remote Control Profile Source
- *
- * It is specified in CEC Table 11-30 of the CEC spec 2.0b.
- */
-enum CecRcProfileSource : uint8_t {
-    MEDIA_CONTEXT_SENSITIVE = 1 << 0, // source can handle UI command 0x11
-    MEDIA_TO = 1 << 1, // source can handle UI command 0x10
-    CONTENTS = 1 << 2, // source can handle UI command 0x0B
-    DEVICE_SETUP = 1 << 3, // source can handle UI command 0x0A
-    DEVICE_ROOT = 1 << 4, // source can handle UI command 0x09
-    SOURCE_FLAG = 1 << 6, // Indicate the profile is for source
-};
-
-/**
- * Remote Control Profile for either TV or Source.
- */
-safe_union CecRcProfile1 {
-    /** CEC remote control profile for TV. */
-    CecRcProfileId profileId;
-
-    /* CEC remote control profile for source
-     *
-     * It is a combination of all supported profiles from CecRcProfileSource.
-     * For example a playback device support root menu and setup menu,
-     * profileSource = ((CecRcProfileSource::DEVICE_ROOT)
-     *                  |(CecRcProfileSource::DEVICE_SETUP)
-     *                  |(CecRcProfileSource::SOURCE_FLAG))
-     */
-    bitfield<CecRcProfileSource> profileSource;
-};
-
-/**
- * CEC Remote Control Profiles
- *
- * CEC 2.0 only use one byte to represent Remote Control Profile.
- */
-struct CecRcProfile {
-   CecRcProfile1 rcProfile1;
-};
-
-/**
- * CEC device power states as specified in CEC Table 11-10 of the CEC spec 2.0b
- */
-enum CecPowerState : int8_t {
-    ON = 0,
-    STANDBY = 1,
-    ON_TO_STANDBY = 2,
-    STANDBY_TO_ON = 4,
-    UNKNOWN = 0xFF, // some devices may not report power status
-};
-
-/** CEC physical address of device */
-typedef uint16_t CecPhysicalAddress;
-
-/**
- * CEC device information
- *
- * It is initially built during addressing specified in CEC section 11.3 of
- * the CEC spec 2.0b. It may be updated with cec devices's status changed.
- */
-struct CecDeviceInfo {
-    /** CEC version which device supports */
-    CecVersion version;
-
-    /** CEC device primary type */
-    CecDeviceType devceType;
-
-    /** CEC all device types */
-    CecAllDeviceTypes allDeviceTypes;
-
-    /** CEC device features */
-    CecDeviceFeatures deviceFeatures;
-
-    /** CEC Device Remote Control Profile */
-    CecRcProfile rcProfile;
-
-    /** CEC Device Vendor ID */
-    uint32_t vendorId;
-
-    /** logical address of device */
-    CecLogicalAddress logicalAddress;
-
-    /** physical of device */
-    CecPhysicalAddress physicalAddress;
-
-    /** power status of device */
-    CecPowerState powerState;
-};
-
-/**
- * Topology Event Type.
- */
-enum CecTopologyEventType : int32_t {
-    DEVICE_ADDED,
-    DEVICE_REMOVED,
-    DEVICE_UPDATED,
-};
-
-/**
- * Topology Event.
- */
-struct CecTopologyEvent {
-    CecTopologyEventType eventType;
-    CecLogicalAddress logicalAddress;
-    CecPhysicalAddress physicalAddress;
-
-    /** true if the event is about the device which the system run on */
-    bool isHostDevice;
-};
-
-
-/**
- * CEC UI Command Codes as specified in CEC Table 11-31 of the CEC spec 2.0b
- */
-enum CecUICommandCodes : int32_t {
-    SELECT_OK = 0x00,
-    UP = 0x01,
-    DOWN = 0x02,
-    LEFT = 0x03,
-    RIGHT = 0x04,
-    RIGHT_UP = 0x05,
-    RIGHT_DOWN = 0x06,
-    LEFT_UP = 0x07,
-    LEFT_DOWN = 0x08,
-    DEVICE_ROOT_MENU = 0x09,
-    DEVICE_SETUP_MENU = 0x0A,
-    CONTENTS_MENU = 0x0B,
-    FAVORITE_MENU = 0x0C,
-    BACK = 0x0D,
-    MEDIA_TOP_MENU = 0x10,
-    MEDIA_CONTEXT_SENSITIVE_MENU = 0x11,
-    NUMBER_ENTRY_MODE = 0x1D,
-    NUMBER_11 = 0x1E,
-    NUMBER_12 = 0x1F,
-    NUMBER_0 = 0x20, // or NUMBER 10
-    NUMBER_1 = 0x21,
-    NUMBER_2 = 0x22,
-    NUMBER_3 = 0x23,
-    NUMBER_4 = 0x24,
-    NUMBER_5 = 0x25,
-    NUMBER_6 = 0x26,
-    NUMBER_7 = 0x27,
-    NUMBER_8 = 0x28,
-    NUMBER_9 = 0x29,
-    DOT = 0x2A,
-    ENTER = 0x2B,
-    CLEAR = 0x2C,
-    NEXT_FAVORITE = 0x2F,
-    CHANNEL_UP = 0x30,
-    CHANNEL_DOWN = 0x31,
-    PREVIOUS_CHANNEL = 0x32,
-    SOUND_SELECT = 0x33,
-    INPUT_SELECT = 0x34,
-    DISPLAY_INFORMATION = 0x35,
-    HELP = 0x36,
-    PAGE_UP = 0x37,
-    PAGE_DOWN = 0x38,
-    POWER = 0x40,
-    VOLUME_UP = 0x41,
-    VOLUME_DOWN = 0x42,
-    MUTE = 0x43,
-    PLAY = 0x44,
-    STOP = 0x45,
-    PAUSE = 0x46,
-    RECORD = 0x47,
-    REWIND = 0x48,
-    FAST_FORWARD = 0x49,
-    EJECT = 0x4A,
-    SKIP_FORWARD = 0x4B,
-    SKIP_BACKWARD = 0x4C,
-    STOP_RECORD = 0x4D,
-    PAUSE_RECORD = 0x4E,
-    ANGLE = 0x50,
-    SUB_PICTURE = 0x51,
-    VIDEO_ON_DEMAND = 0x52,
-    ELECTRONIC_PROGRAM_GUIDE = 0x53,
-    TIMER_PROGRAMMING = 0x54,
-    INITIAL_CONFIGURATION = 0x55,
-    SELECT_BROADCAST_TYPE = 0x56,
-    SELECT_SOUND_PRESENTATION = 0x57,
-    AUDIO_DESCRIPTION = 0x58,
-    INTERNET = 0x59,
-    THREE_DIMENSIONAL_MODE = 0x5A,
-    PLAY_FUNCTION = 0x60,
-    PAUSE_PLAY_FUNCTION = 0x61,
-    RECORD_FUNCTION = 0x62,
-    PAUSE_RECORD_FUNCTION = 0x63,
-    STOP_FUNCTION = 0x64,
-    MUTE_FUNCTION = 0x65,
-    RESTORE_VOLUME_FUNCTION = 0x66,
-    TUNE_FUNCTION = 0x67,
-    SELECT_MEDIA_FUNCTION = 0x68,
-    SELECT_AV_INPUT_FUNCTION = 0x69,
-    SELECT_AUDIO_INPUT_FUNCTION = 0x6A,
-    POWER_TOGGLE_FUNCTION = 0x6B,
-    POWER_OFF_FUNCTION = 0x6C,
-    POWER_ON_FUNCTION = 0x6D,
-    F1 = 0x71, // BLUE
-    F2 = 0x72, // RED
-    F3 = 0x73, // GREEN
-    F4 = 0x74, // YELLOW
-    F5 = 0x75,
-    DATA = 0x76,
-};
-
-/**
- * HDMI port type.
- */
-enum HdmiPortType : int32_t {
-    INPUT = 0,
-    OUTPUT = 1,
-};
-
-/**
- * Options used for IHdmiCec.setOption()
- */
-enum OptionKey : int32_t {
-    /**
-     * When set to false, HAL does not wake up the system upon receiving <Image
-     * View On> or <Text View On>. Used when user changes the TV settings to
-     * disable the auto TV on functionality.
-     * Deprecated since <Image View On> and <Text View On> become mandatory
-     * featrues for CEC device. Use ENABLE_CEC OptionKey to disable CEC
-     * functionality instead.
-     * True by Default
-     */
-    WAKEUP = 1,
-
-    /**
-     * When set to false, all the CEC commands are discarded. if logical address
-     * is ever used, it shall be released. Used when user changes the TV
-     * settings to disable CEC functionality.
-     * True by default.
-     *
-     */
-    ENABLE_CEC = 2,
-
-    /**
-     * Setting this flag to false means Android system must stop handling CEC
-     * service and yield the control over to the microprocessor that is powered
-     * on through the standby mode.The microprocessor shall keep current logical
-     * and physical address. It shall response POLLING_MESSAGE, GIVE_FEATURES,
-     * GIVE_DEVICE_POWER_STATUS,GIVE_DEVICE_VENDOR_ID and GIVE_PHYSICAL_ADDRESS
-     * to allow other CEC devices to build CEC devices map specified in CEC
-     * section 11.3 of the CEC spec 2.0b.
-     * When set to true, the system must gain the control over, hence telling
-     * the microprocessor to start forwarding CEC messages to Android system.
-     * For example, this may be called when system goes in and out of
-     * standby mode to notify the microprocessor that it should start/stop
-     * handling CEC commands on behalf of the system.
-     * True by default.
-     */
-    SYSTEM_CEC_CONTROL = 3,
-
-    /* Option 4 not used */
-};
-
-/**
- * Hdmi port ID.
- *
- * It shall start from 1 which corresponds to HDMI "port 1".
- */
-typedef uint32_t HdmiPortId;
-
-/** Hdmi hotplug event */
-struct HotplugEvent {
-    bool connected;
-    HdmiPortId portId;
-};
-
-/**
- * HDMI port descriptor
- */
-struct HdmiPortInfo {
-    HdmiPortType type;
-    HdmiPortId portId;
-    bool cecSupported;
-    bool arcSupported;
-    CecPhysicalAddress physicalAddress;
-};
-
-enum Result : int32_t {
-    SUCCESS = 0,
-    FAILURE_UNKNOWN = 1,
-    FAILURE_INVALID_ARGS = 2,
-    FAILURE_INVALID_STATE = 3,
-    FAILURE_NOT_SUPPORTED = 4,
-    FAILURE_BUSY = 5,
-};
diff --git a/tv/input/1.0/default/TvInput.cpp b/tv/input/1.0/default/TvInput.cpp
index 4ea1dec..7583a67 100644
--- a/tv/input/1.0/default/TvInput.cpp
+++ b/tv/input/1.0/default/TvInput.cpp
@@ -142,7 +142,7 @@
 
 // static
 void TvInput::notify(struct tv_input_device* __unused, tv_input_event_t* event,
-        void* __unused) {
+                     void* optionalStatus) {
     if (mCallback != nullptr && event != nullptr) {
         // Capturing is no longer supported.
         if (event->type >= TV_INPUT_EVENT_CAPTURE_SUCCEEDED) {
@@ -154,7 +154,17 @@
         tvInputEvent.deviceInfo.type = static_cast<TvInputType>(
                 event->device_info.type);
         tvInputEvent.deviceInfo.portId = event->device_info.hdmi.port_id;
-        tvInputEvent.deviceInfo.cableConnectionStatus = CableConnectionStatus::UNKNOWN;
+        CableConnectionStatus connectionStatus = CableConnectionStatus::UNKNOWN;
+        if (optionalStatus != nullptr &&
+            ((event->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) ||
+             (event->type == TV_INPUT_EVENT_DEVICE_AVAILABLE))) {
+            int newStatus = *reinterpret_cast<int*>(optionalStatus);
+            if (newStatus <= static_cast<int>(CableConnectionStatus::DISCONNECTED) &&
+                newStatus >= static_cast<int>(CableConnectionStatus::UNKNOWN)) {
+                connectionStatus = static_cast<CableConnectionStatus>(newStatus);
+            }
+        }
+        tvInputEvent.deviceInfo.cableConnectionStatus = connectionStatus;
         // TODO: Ensure the legacy audio type code is the same once audio HAL default
         // implementation is ready.
         tvInputEvent.deviceInfo.audioType = static_cast<AudioDevice>(
diff --git a/tv/tuner/1.0/vts/functional/DvrTests.cpp b/tv/tuner/1.0/vts/functional/DvrTests.cpp
index 0dfc032..ba21189 100644
--- a/tv/tuner/1.0/vts/functional/DvrTests.cpp
+++ b/tv/tuner/1.0/vts/functional/DvrTests.cpp
@@ -55,6 +55,7 @@
     uint8_t* buffer;
     ALOGW("[vts] playback thread loop start %s", mInputDataFile.c_str());
     if (fd < 0) {
+        EXPECT_TRUE(fd >= 0) << "Failed to open: " + mInputDataFile;
         mPlaybackThreadRunning = false;
         ALOGW("[vts] Error %s", strerror(errno));
     }
@@ -178,7 +179,7 @@
             // Our current implementation filter the data and write it into the filter FMQ
             // immediately after the DATA_READY from the VTS/framework
             if (!readRecordFMQ()) {
-                ALOGD("[vts] record data failed to be filtered. Ending thread");
+                ALOGW("[vts] record data failed to be filtered. Ending thread");
                 mRecordThreadRunning = false;
                 break;
             }
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.cpp b/tv/tuner/1.0/vts/functional/FilterTests.cpp
index 0ecdf73..a354c78 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.cpp
+++ b/tv/tuner/1.0/vts/functional/FilterTests.cpp
@@ -70,6 +70,10 @@
 }
 
 bool FilterCallback::readFilterEventData() {
+    if (mFilterMQ == NULL) {
+        ALOGW("[vts] FMQ is not configured and does not need to be tested.");
+        return true;
+    }
     bool result = false;
     DemuxFilterEvent filterEvent = mFilterEvent;
     ALOGW("[vts] reading from filter FMQ or buffer %d", mFilterId);
@@ -218,7 +222,11 @@
     return AssertionResult(status == Result::SUCCESS);
 }
 
-AssertionResult FilterTests::getFilterMQDescriptor(uint32_t filterId) {
+AssertionResult FilterTests::getFilterMQDescriptor(uint32_t filterId, bool getMqDesc) {
+    if (!getMqDesc) {
+        ALOGE("[vts] Filter does not need FMQ.");
+        return success();
+    }
     Result status;
     EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
     EXPECT_TRUE(mFilterCallbacks[filterId]) << "Test with getNewlyOpenedFilterId first.";
@@ -279,16 +287,14 @@
 AssertionResult FilterTests::closeFilter(uint32_t filterId) {
     EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
     Result status = mFilters[filterId]->close();
-    if (status == Result::SUCCESS) {
-        for (int i = 0; i < mUsedFilterIds.size(); i++) {
-            if (mUsedFilterIds[i] == filterId) {
-                mUsedFilterIds.erase(mUsedFilterIds.begin() + i);
-                break;
-            }
+    for (int i = 0; i < mUsedFilterIds.size(); i++) {
+        if (mUsedFilterIds[i] == filterId) {
+            mUsedFilterIds.erase(mUsedFilterIds.begin() + i);
+            break;
         }
-        mFilterCallbacks.erase(filterId);
-        mFilters.erase(filterId);
     }
+    mFilterCallbacks.erase(filterId);
+    mFilters.erase(filterId);
     return AssertionResult(status == Result::SUCCESS);
 }
 
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.h b/tv/tuner/1.0/vts/functional/FilterTests.h
index a76a6b9..75c59b3 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.h
+++ b/tv/tuner/1.0/vts/functional/FilterTests.h
@@ -157,7 +157,7 @@
     AssertionResult getTimeStamp();
     AssertionResult getNewlyOpenedFilterId(uint32_t& filterId);
     AssertionResult configFilter(DemuxFilterSettings setting, uint32_t filterId);
-    AssertionResult getFilterMQDescriptor(uint32_t filterId);
+    AssertionResult getFilterMQDescriptor(uint32_t filterId, bool getMqDesc);
     AssertionResult setFilterDataSource(uint32_t sourceFilterId, uint32_t sinkFilterId);
     AssertionResult setFilterDataSourceToDemux(uint32_t filterId);
     AssertionResult startFilter(uint32_t filterId);
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
index 2be68b8..22ba271 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
@@ -48,7 +48,7 @@
     ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
     ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
     ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
-    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
     ASSERT_TRUE(mFilterTests.startFilter(filterId));
     ASSERT_TRUE(mFilterTests.stopFilter(filterId));
     ASSERT_TRUE(mFilterTests.closeFilter(filterId));
@@ -75,6 +75,9 @@
 
 void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf,
                                                        FrontendConfig frontendConf) {
+    if (!frontendConf.enable) {
+        return;
+    }
     uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
@@ -99,7 +102,7 @@
     ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
     ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
     ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
-    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
     ASSERT_TRUE(mFilterTests.startFilter(filterId));
     // tune test
     ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
@@ -145,7 +148,7 @@
     ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
     ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
     ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
-    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
     mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback());
     ASSERT_TRUE(mDvrTests.startDvrPlayback());
     ASSERT_TRUE(mFilterTests.startFilter(filterId));
@@ -160,6 +163,9 @@
 
 void TunerRecordHidlTest::recordSingleFilterTest(FilterConfig filterConf,
                                                  FrontendConfig frontendConf, DvrConfig dvrConf) {
+    if (!frontendConf.enable) {
+        return;
+    }
     uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
@@ -184,7 +190,7 @@
     ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
     ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
     ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
-    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
     filter = mFilterTests.getFilterById(filterId);
     ASSERT_TRUE(filter != nullptr);
     mDvrTests.startRecordOutputThread(dvrConf.settings.record());
@@ -247,7 +253,7 @@
     ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
     ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
     ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
-    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+    ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
     filter = mFilterTests.getFilterById(filterId);
     ASSERT_TRUE(filter != nullptr);
     ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter));
@@ -265,6 +271,9 @@
 void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> mediaFilterConfs,
                                                       FrontendConfig frontendConf,
                                                       DescramblerConfig descConfig) {
+    if (!frontendConf.enable) {
+        return;
+    }
     uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
@@ -328,17 +337,17 @@
 
 TEST_P(TunerFrontendHidlTest, TuneFrontend) {
     description("Tune one Frontend with specific setting and check Lock event");
-    mFrontendTests.tuneTest(frontendArray[DVBT]);
+    mFrontendTests.tuneTest(frontendArray[defaultFrontend]);
 }
 
 TEST_P(TunerFrontendHidlTest, AutoScanFrontend) {
     description("Run an auto frontend scan with specific setting and check lock scanMessage");
-    mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_AUTO);
+    mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_AUTO);
 }
 
 TEST_P(TunerFrontendHidlTest, BlindScanFrontend) {
     description("Run an blind frontend scan with specific setting and check lock scanMessage");
-    mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_BLIND);
+    mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_BLIND);
 }
 
 TEST_P(TunerLnbHidlTest, OpenLnbByName) {
@@ -374,7 +383,7 @@
     uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
-    mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+    mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
     ASSERT_TRUE(feId != INVALID_ID);
     ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
     ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -394,7 +403,7 @@
     uint32_t avSyncHwId;
     sp<IFilter> mediaFilter;
 
-    mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+    mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
     ASSERT_TRUE(feId != INVALID_ID);
     ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
     ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -422,7 +431,7 @@
 TEST_P(TunerFilterHidlTest, StartFilterInDemux) {
     description("Open and start a filter in Demux.");
     // TODO use paramterized tests
-    configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[DVBT]);
+    configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]);
 }
 
 TEST_P(TunerFilterHidlTest, SetFilterLinkage) {
@@ -463,22 +472,22 @@
 
 TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowVideoFilterTest) {
     description("Test Video Filter functionality in Broadcast use case.");
-    broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[DVBT]);
+    broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[defaultFrontend]);
 }
 
 TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowAudioFilterTest) {
     description("Test Audio Filter functionality in Broadcast use case.");
-    broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[DVBT]);
+    broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[defaultFrontend]);
 }
 
 TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowSectionFilterTest) {
     description("Test Section Filter functionality in Broadcast use case.");
-    broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[DVBT]);
+    broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[defaultFrontend]);
 }
 
 TEST_P(TunerBroadcastHidlTest, IonBufferTest) {
     description("Test the av filter data bufferring.");
-    broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[DVBT]);
+    broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]);
 }
 
 TEST_P(TunerBroadcastHidlTest, LnbBroadcastDataFlowVideoFilterTest) {
@@ -494,13 +503,14 @@
 TEST_P(TunerRecordHidlTest, AttachFiltersToRecordTest) {
     description("Attach a single filter to the record dvr test.");
     // TODO use paramterized tests
-    attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[DVBT],
+    attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend],
                                       dvrArray[DVR_RECORD0]);
 }
 
 TEST_P(TunerRecordHidlTest, RecordDataFlowWithTsRecordFilterTest) {
     description("Feed ts data from frontend to recording and test with ts record filter");
-    recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[DVBT], dvrArray[DVR_RECORD0]);
+    recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend],
+                           dvrArray[DVR_RECORD0]);
 }
 
 TEST_P(TunerRecordHidlTest, LnbRecordDataFlowWithTsRecordFilterTest) {
@@ -513,7 +523,7 @@
     uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
-    mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+    mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
     ASSERT_TRUE(feId != INVALID_ID);
     ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
     ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -530,7 +540,7 @@
     set<FilterConfig> filterConfs;
     filterConfs.insert(filterArray[TS_AUDIO0]);
     filterConfs.insert(filterArray[TS_VIDEO1]);
-    scrambledBroadcastTest(filterConfs, frontendArray[DVBT], descramblerArray[DESC_0]);
+    scrambledBroadcastTest(filterConfs, frontendArray[defaultFrontend], descramblerArray[DESC_0]);
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
index 6c68e35..92a8130 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
@@ -55,6 +55,7 @@
 
 using namespace std;
 
+const uint32_t FMQ_SIZE_512K = 0x80000;
 const uint32_t FMQ_SIZE_1M = 0x100000;
 const uint32_t FMQ_SIZE_4M = 0x400000;
 const uint32_t FMQ_SIZE_16M = 0x1000000;
@@ -134,6 +135,7 @@
     uint32_t bufferSize;
     DemuxFilterType type;
     DemuxFilterSettings settings;
+    bool getMqDesc;
 
     bool operator<(const FilterConfig& /*c*/) const { return false; }
 };
@@ -144,6 +146,7 @@
 };
 
 struct FrontendConfig {
+    bool enable;
     bool isSoftwareFe;
     FrontendType type;
     FrontendSettings settings;
@@ -191,6 +194,8 @@
 static DvrConfig dvrArray[DVR_MAX];
 static DescramblerConfig descramblerArray[DESC_MAX];
 static vector<string> goldenOutputFiles;
+static int defaultFrontend = DVBT;
+static int defaultScanFrontend = SCAN_DVBT;
 
 /** Configuration array for the frontend tune test */
 inline void initFrontendConfig() {
@@ -216,7 +221,9 @@
     frontendArray[DVBT].tuneStatusTypes = types;
     frontendArray[DVBT].expectTuneStatuses = statuses;
     frontendArray[DVBT].isSoftwareFe = true;
+    frontendArray[DVBS].enable = true;
     frontendArray[DVBS].type = FrontendType::DVBS;
+    frontendArray[DVBS].enable = true;
     frontendArray[DVBS].isSoftwareFe = true;
 };
 
@@ -283,6 +290,7 @@
             .isRaw = false,
             .streamId = 0xbd,
     });
+    filterArray[TS_PES0].getMqDesc = true;
     // TS PCR filter setting
     filterArray[TS_PCR0].type.mainType = DemuxFilterMainType::TS;
     filterArray[TS_PCR0].type.subType.tsFilterType(DemuxTsFilterType::PCR);
@@ -303,6 +311,7 @@
     filterArray[TS_SECTION0].settings.ts().filterSettings.section({
             .isRaw = false,
     });
+    filterArray[TS_SECTION0].getMqDesc = true;
     // TS RECORD filter setting
     filterArray[TS_RECORD0].type.mainType = DemuxFilterMainType::TS;
     filterArray[TS_RECORD0].type.subType.tsFilterType(DemuxTsFilterType::RECORD);
diff --git a/vibrator/aidl/default/Android.bp b/vibrator/aidl/default/Android.bp
index 9e6d9cf..70175dd 100644
--- a/vibrator/aidl/default/Android.bp
+++ b/vibrator/aidl/default/Android.bp
@@ -4,7 +4,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.vibrator-ndk_platform",
+        "android.hardware.vibrator-V1-ndk_platform",
     ],
     export_include_dirs: ["include"],
     srcs: ["Vibrator.cpp"],
@@ -23,7 +23,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.vibrator-ndk_platform",
+        "android.hardware.vibrator-V1-ndk_platform",
     ],
     static_libs: [
         "libvibratorexampleimpl",
diff --git a/vibrator/aidl/vts/Android.bp b/vibrator/aidl/vts/Android.bp
index 28cb4d9..a06dae3 100644
--- a/vibrator/aidl/vts/Android.bp
+++ b/vibrator/aidl/vts/Android.bp
@@ -9,7 +9,7 @@
         "libbinder",
     ],
     static_libs: [
-        "android.hardware.vibrator-cpp",
+        "android.hardware.vibrator-V1-cpp",
     ],
     test_suites: [
         "general-tests",
diff --git a/weaver/aidl/Android.bp b/weaver/aidl/Android.bp
new file mode 100644
index 0000000..5637e0a
--- /dev/null
+++ b/weaver/aidl/Android.bp
@@ -0,0 +1,16 @@
+aidl_interface {
+    name: "android.hardware.weaver",
+    vendor_available: true,
+    srcs: ["android/hardware/weaver/*.aidl"],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl
new file mode 100644
index 0000000..29bd9a9
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.weaver;
+@VintfStability
+interface IWeaver {
+  android.hardware.weaver.WeaverConfig getConfig();
+  android.hardware.weaver.WeaverReadResponse read(in int slotId, in byte[] key);
+  void write(in int slotId, in byte[] key, in byte[] value);
+  const int STATUS_FAILED = 1;
+  const int INCORRECT_KEY = 2;
+  const int THROTTLE = 3;
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl
new file mode 100644
index 0000000..239cdac
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.weaver;
+@VintfStability
+parcelable WeaverConfig {
+  long slots;
+  long keySize;
+  long valueSize;
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl
new file mode 100644
index 0000000..7e5db59
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.weaver;
+@VintfStability
+parcelable WeaverReadResponse {
+  long timeout;
+  byte[] value;
+}
diff --git a/weaver/aidl/android/hardware/weaver/IWeaver.aidl b/weaver/aidl/android/hardware/weaver/IWeaver.aidl
new file mode 100644
index 0000000..ebbfabe
--- /dev/null
+++ b/weaver/aidl/android/hardware/weaver/IWeaver.aidl
@@ -0,0 +1,94 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.weaver;
+
+import android.hardware.weaver.WeaverConfig;
+import android.hardware.weaver.WeaverReadResponse;
+
+/**
+ * Weaver provides secure storage of secret values that may only be read if the
+ * corresponding key has been presented.
+ *
+ * The storage must be secure as the device's user authentication and encryption
+ * relies on the security of these values. The cardinality of the domains of the
+ * key and value must be suitably large such that they cannot be easily guessed.
+ *
+ * Weaver is structured as an array of slots, each containing a key-value pair.
+ * Slots are uniquely identified by an ID in the range [0, `getConfig().slots`).
+ */
+@VintfStability
+interface IWeaver {
+    /**
+     * Retrieves the config information for this implementation of Weaver.
+     *
+     * The config is static i.e. every invocation returns the same information.
+     *
+     * @return config data for this implementation of Weaver if status is OK,
+     *         otherwise undefined.
+     */
+    WeaverConfig getConfig();
+
+    /**
+     * Read binder calls may return a ServiceSpecificException with the following error codes.
+     */
+    const int STATUS_FAILED = 1;
+    const int INCORRECT_KEY = 2;
+    const int THROTTLE = 3;
+
+    /**
+     * Attempts to retrieve the value stored in the identified slot.
+     *
+     * The value is only returned if the provided key matches the key stored in
+     * the slot. The value is never returned if the wrong key is provided.
+     *
+     * Throttling must be used to limit the frequency of failed read attempts.
+     * The value is only returned when throttling is not active, even if the
+     * correct key is provided. If called when throttling is active, the time
+     * until the next attempt can be made is returned.
+     *
+     * Service status return:
+     *
+     * OK if the value was successfully read from slot.
+     * INCORRECT_KEY if the key does not match the key in the slot.
+     * THROTTLE if throttling is active.
+     * STATUS_FAILED if the read was unsuccessful for another reason.
+     *
+     * @param slotId of the slot to read from, this must be positive to be valid.
+     * @param key that is stored in the slot.
+     * @return The WeaverReadResponse for this read request. If the status is OK,
+     * value is set to the value in the slot and timeout is 0. Otherwise, value is
+     * empty and timeout is set accordingly.
+     */
+    WeaverReadResponse read(in int slotId, in byte[] key);
+
+    /**
+     * Overwrites the identified slot with the provided key and value.
+     *
+     * The new values are written regardless of the current state of the slot in
+     * order to remain idempotent.
+     *
+     * Service status return:
+     *
+     * OK if the write was successfully completed.
+     * FAILED if the write was unsuccessful.
+     *
+     * @param slotId of the slot to write to.
+     * @param key to write to the slot.
+     * @param value to write to slot.
+     */
+    void write(in int slotId, in byte[] key, in byte[] value);
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
similarity index 61%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
index 127f98e..75d961e 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 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.
@@ -14,15 +14,21 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.weaver;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
+@VintfStability
+parcelable WeaverConfig {
+    /**
+     * The number of slots available.
+     */
+    long slots;
+    /**
+     * The number of bytes used for a key.
+     */
+    long keySize;
+    /**
+     * The number of bytes used for a value.
+     */
+    long valueSize;
 }
 
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
-}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
similarity index 61%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
index 127f98e..ec006e8 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 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.
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-#include <healthd/healthd.h>
+package android.hardware.weaver;
 
-void healthd_board_init(struct healthd_config*)
-{
-    // use defaults
+@VintfStability
+parcelable WeaverReadResponse {
+    /**
+     * The time to wait, in milliseconds, before making the next request.
+     */
+    long timeout;
+    /**
+     * The value read from the slot or empty if the value was not read.
+     */
+    byte[] value;
 }
 
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
-}
diff --git a/weaver/aidl/default/Android.bp b/weaver/aidl/default/Android.bp
new file mode 100644
index 0000000..8440670
--- /dev/null
+++ b/weaver/aidl/default/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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.
+//
+
+cc_binary {
+    name: "android.hardware.weaver-service.example",
+    relative_install_path: "hw",
+    init_rc: ["android.hardware.weaver-service.example.rc"],
+    vintf_fragments: ["android.hardware.weaver-service.example.xml"],
+    vendor: true,
+    srcs: [
+        "service.cpp",
+        "Weaver.cpp",
+    ],
+    shared_libs: [
+        "android.hardware.weaver-V1-ndk_platform",
+        "libbase",
+        "libbinder_ndk",
+    ],
+}
diff --git a/weaver/aidl/default/Weaver.cpp b/weaver/aidl/default/Weaver.cpp
new file mode 100644
index 0000000..56d9c4d
--- /dev/null
+++ b/weaver/aidl/default/Weaver.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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 "Weaver.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace weaver {
+
+// Methods from ::android::hardware::weaver::IWeaver follow.
+
+::ndk::ScopedAStatus Weaver::getConfig(WeaverConfig* out_config) {
+    (void)out_config;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Weaver::read(int32_t in_slotId, const std::vector<uint8_t>& in_key, WeaverReadResponse* out_response) {
+    (void)in_slotId;
+    (void)in_key;
+    (void)out_response;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Weaver::write(int32_t in_slotId, const std::vector<uint8_t>& in_key, const std::vector<uint8_t>& in_value) {
+    (void)in_slotId;
+    (void)in_key;
+    (void)in_value;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+} //namespace weaver
+} //namespace hardware
+} //namespace android
+} //namespace aidl
diff --git a/weaver/aidl/default/Weaver.h b/weaver/aidl/default/Weaver.h
new file mode 100644
index 0000000..b50018e
--- /dev/null
+++ b/weaver/aidl/default/Weaver.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/weaver/BnWeaver.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace weaver {
+
+using ::aidl::android::hardware::weaver::WeaverConfig;
+using ::aidl::android::hardware::weaver::WeaverReadResponse;
+
+struct Weaver : public BnWeaver {
+public:
+    Weaver() = default;
+
+    // Methods from ::android::hardware::weaver::IWeaver follow.
+    ::ndk::ScopedAStatus getConfig(WeaverConfig* _aidl_return) override;
+    ::ndk::ScopedAStatus read(int32_t in_slotId, const std::vector<uint8_t>& in_key, WeaverReadResponse* _aidl_return) override;
+    ::ndk::ScopedAStatus write(int32_t in_slotId, const std::vector<uint8_t>& in_key, const std::vector<uint8_t>& in_value) override;
+};
+
+}  // namespace weaver
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/weaver/aidl/default/android.hardware.weaver-service.example.rc b/weaver/aidl/default/android.hardware.weaver-service.example.rc
new file mode 100644
index 0000000..ec77774
--- /dev/null
+++ b/weaver/aidl/default/android.hardware.weaver-service.example.rc
@@ -0,0 +1,4 @@
+service vendor.weaver_default /vendor/bin/hw/android.hardware.weaver-service.example
+    class hal
+    user hsm
+    group hsm
diff --git a/weaver/aidl/default/android.hardware.weaver-service.example.xml b/weaver/aidl/default/android.hardware.weaver-service.example.xml
new file mode 100644
index 0000000..ed291cd
--- /dev/null
+++ b/weaver/aidl/default/android.hardware.weaver-service.example.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.weaver</name>
+        <version>1</version>
+        <interface>
+            <name>IWeaver</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/weaver/aidl/default/service.cpp b/weaver/aidl/default/service.cpp
new file mode 100644
index 0000000..1495bc9
--- /dev/null
+++ b/weaver/aidl/default/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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 <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Weaver.h"
+
+using ::aidl::android::hardware::weaver::Weaver;
+
+int main() {
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+    std::shared_ptr<Weaver> weaver = ndk::SharedRefBase::make<Weaver>();
+
+    const std::string instance = std::string() + Weaver::descriptor + "/default";
+    binder_status_t status = AServiceManager_addService(weaver->asBinder().get(), instance.c_str());
+    CHECK(status == STATUS_OK);
+
+    ABinderProcess_joinThreadPool();
+    return -1; // Should never be reached
+}
diff --git a/weaver/aidl/vts/Android.bp b/weaver/aidl/vts/Android.bp
new file mode 100644
index 0000000..7daad8d
--- /dev/null
+++ b/weaver/aidl/vts/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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.
+//
+
+cc_test {
+    name: "VtsHalWeaverTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: ["VtsHalWeaverTargetTest.cpp"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbase",
+    ],
+    static_libs: ["android.hardware.weaver-V1-ndk_platform"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/weaver/aidl/vts/OWNERS b/weaver/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/weaver/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp b/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp
new file mode 100644
index 0000000..7d8daa2
--- /dev/null
+++ b/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp
@@ -0,0 +1,277 @@
+/*
+ * 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 <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/weaver/IWeaver.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <limits>
+
+using ::aidl::android::hardware::weaver::IWeaver;
+using ::aidl::android::hardware::weaver::WeaverConfig;
+using ::aidl::android::hardware::weaver::WeaverReadResponse;
+
+using ::ndk::SpAIBinder;
+
+const std::vector<uint8_t> KEY{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+const std::vector<uint8_t> WRONG_KEY{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const std::vector<uint8_t> VALUE{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+const std::vector<uint8_t> OTHER_VALUE{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 255, 255};
+
+struct WeaverAidlTest : public ::testing::TestWithParam<std::string> {
+    virtual void SetUp() override {
+        weaver = IWeaver::fromBinder(
+            SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+        ASSERT_NE(weaver, nullptr);
+    }
+
+    virtual void TearDown() override {}
+
+    std::shared_ptr<IWeaver> weaver;
+};
+
+/*
+ * Checks config values are suitably large
+ */
+TEST_P(WeaverAidlTest, GetConfig) {
+    WeaverConfig config;
+
+    auto ret = weaver->getConfig(&config);
+
+    ASSERT_TRUE(ret.isOk());
+
+    EXPECT_GE(config.slots, 16u);
+    EXPECT_GE(config.keySize, 16u);
+    EXPECT_GE(config.valueSize, 16u);
+}
+
+/*
+ * Gets the config twice and checks they are the same
+ */
+TEST_P(WeaverAidlTest, GettingConfigMultipleTimesGivesSameResult) {
+    WeaverConfig config1;
+    WeaverConfig config2;
+
+    auto ret = weaver->getConfig(&config1);
+    ASSERT_TRUE(ret.isOk());
+
+    ret = weaver->getConfig(&config2);
+    ASSERT_TRUE(ret.isOk());
+
+    EXPECT_EQ(config1, config2);
+}
+
+/*
+ * Gets the number of slots from the config and writes a key and value to the last one
+ */
+TEST_P(WeaverAidlTest, WriteToLastSlot) {
+    WeaverConfig config;
+    const auto configRet = weaver->getConfig(&config);
+
+    ASSERT_TRUE(configRet.isOk());
+
+    const uint32_t lastSlot = config.slots - 1;
+    const auto writeRet = weaver->write(lastSlot, KEY, VALUE);
+    ASSERT_TRUE(writeRet.isOk());
+}
+
+/*
+ * Writes a key and value to a slot
+ * Reads the slot with the same key and receives the value that was previously written
+ */
+TEST_P(WeaverAidlTest, WriteFollowedByReadGivesTheSameValue) {
+    constexpr uint32_t slotId = 0;
+    const auto ret = weaver->write(slotId, KEY, VALUE);
+    ASSERT_TRUE(ret.isOk());
+
+    WeaverReadResponse response;
+    std::vector<uint8_t> readValue;
+    uint32_t timeout;
+    const auto readRet = weaver->read(slotId, KEY, &response);
+
+    readValue = response.value;
+    timeout = response.timeout;
+
+    ASSERT_TRUE(readRet.isOk());
+    EXPECT_EQ(readValue, VALUE);
+    EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writes a key and value to a slot
+ * Overwrites the slot with a new key and value
+ * Reads the slot with the new key and receives the new value
+ */
+TEST_P(WeaverAidlTest, OverwritingSlotUpdatesTheValue) {
+    constexpr uint32_t slotId = 0;
+    const auto initialWriteRet = weaver->write(slotId, WRONG_KEY, VALUE);
+    ASSERT_TRUE(initialWriteRet.isOk());
+
+    const auto overwriteRet = weaver->write(slotId, KEY, OTHER_VALUE);
+    ASSERT_TRUE(overwriteRet.isOk());
+
+    WeaverReadResponse response;
+    std::vector<uint8_t> readValue;
+    uint32_t timeout;
+    const auto readRet = weaver->read(slotId, KEY, &response);
+
+    readValue = response.value;
+    timeout = response.timeout;
+
+    ASSERT_TRUE(readRet.isOk());
+    EXPECT_EQ(readValue, OTHER_VALUE);
+    EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writes a key and value to a slot
+ * Reads the slot with a different key so does not receive the value
+ */
+TEST_P(WeaverAidlTest, WriteFollowedByReadWithWrongKeyDoesNotGiveTheValue) {
+    constexpr uint32_t slotId = 0;
+    const auto ret = weaver->write(slotId, KEY, VALUE);
+    ASSERT_TRUE(ret.isOk());
+
+    WeaverReadResponse response;
+    std::vector<uint8_t> readValue;
+    const auto readRet =
+        weaver->read(slotId, WRONG_KEY, &response);
+
+    readValue = response.value;
+
+    ASSERT_FALSE(readRet.isOk());
+    ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+    ASSERT_EQ(IWeaver::INCORRECT_KEY, readRet.getServiceSpecificError());
+    EXPECT_TRUE(readValue.empty());
+}
+
+/*
+ * Writing to an invalid slot fails
+ */
+TEST_P(WeaverAidlTest, WritingToInvalidSlotFails) {
+    WeaverConfig config;
+    const auto configRet = weaver->getConfig(&config);
+    ASSERT_TRUE(configRet.isOk());
+
+    if (config.slots == std::numeric_limits<uint32_t>::max()) {
+        // If there are no invalid slots then pass
+        return;
+    }
+
+    const auto writeRet = weaver->write(config.slots, KEY, VALUE);
+    ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Reading from an invalid slot fails rather than incorrect key
+ */
+TEST_P(WeaverAidlTest, ReadingFromInvalidSlotFails) {
+    WeaverConfig config;
+    const auto configRet = weaver->getConfig(&config);
+    ASSERT_TRUE(configRet.isOk());
+
+    if (config.slots == std::numeric_limits<uint32_t>::max()) {
+        // If there are no invalid slots then pass
+        return;
+    }
+
+    WeaverReadResponse response;
+    std::vector<uint8_t> readValue;
+    uint32_t timeout;
+    const auto readRet =
+        weaver->read(config.slots, KEY, &response);
+
+    readValue = response.value;
+    timeout = response.timeout;
+
+    ASSERT_FALSE(readRet.isOk());
+    ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+    ASSERT_EQ(IWeaver::STATUS_FAILED, readRet.getServiceSpecificError());
+    EXPECT_TRUE(readValue.empty());
+    EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writing a key that is too large fails
+ */
+TEST_P(WeaverAidlTest, WriteWithTooLargeKeyFails) {
+    WeaverConfig config;
+    const auto configRet = weaver->getConfig(&config);
+    ASSERT_TRUE(configRet.isOk());
+
+    std::vector<uint8_t> bigKey(config.keySize + 1);
+
+    constexpr uint32_t slotId = 0;
+    const auto writeRet = weaver->write(slotId, bigKey, VALUE);
+    ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Writing a value that is too large fails
+ */
+TEST_P(WeaverAidlTest, WriteWithTooLargeValueFails) {
+    WeaverConfig config;
+    const auto configRet = weaver->getConfig(&config);
+    ASSERT_TRUE(configRet.isOk());
+
+    std::vector<uint8_t> bigValue(config.valueSize + 1);
+
+    constexpr uint32_t slotId = 0;
+    const auto writeRet = weaver->write(slotId, KEY, bigValue);
+    ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Reading with a key that is loo large fails
+ */
+TEST_P(WeaverAidlTest, ReadWithTooLargeKeyFails) {
+    WeaverConfig config;
+    const auto configRet = weaver->getConfig(&config);
+    ASSERT_TRUE(configRet.isOk());
+
+    std::vector<uint8_t> bigKey(config.keySize + 1);
+
+    constexpr uint32_t slotId = 0;
+    WeaverReadResponse response;
+    std::vector<uint8_t> readValue;
+    uint32_t timeout;
+    const auto readRet =
+        weaver->read(slotId, bigKey, &response);
+
+    readValue = response.value;
+    timeout = response.timeout;
+
+    ASSERT_FALSE(readRet.isOk());
+    ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+    ASSERT_EQ(IWeaver::STATUS_FAILED, readRet.getServiceSpecificError());
+    EXPECT_TRUE(readValue.empty());
+    EXPECT_EQ(timeout, 0u);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WeaverAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+        PerInstance, WeaverAidlTest,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IWeaver::descriptor)),
+        android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/wifi/1.4/default/wifi_legacy_hal.h b/wifi/1.4/default/wifi_legacy_hal.h
index 9964460..822f83a 100644
--- a/wifi/1.4/default/wifi_legacy_hal.h
+++ b/wifi/1.4/default/wifi_legacy_hal.h
@@ -23,15 +23,9 @@
 #include <thread>
 #include <vector>
 
+#include <hardware_legacy/wifi_hal.h>
 #include <wifi_system/interface_tool.h>
 
-// HACK: The include inside the namespace below also transitively includes a
-// bunch of libc headers into the namespace, which leads to functions like
-// socketpair being defined in
-// android::hardware::wifi::V1_1::implementation::legacy_hal. Include this one
-// particular header as a hacky workaround until that's fixed.
-#include <sys/socket.h>
-
 namespace android {
 namespace hardware {
 namespace wifi {
@@ -40,9 +34,274 @@
 // This is in a separate namespace to prevent typename conflicts between
 // the legacy HAL types and the HIDL interface types.
 namespace legacy_hal {
-// Wrap all the types defined inside the legacy HAL header files inside this
+// Import all the types defined inside the legacy HAL header files into this
 // namespace.
-#include <hardware_legacy/wifi_hal.h>
+using ::FRAME_TYPE_80211_MGMT;
+using ::FRAME_TYPE_ETHERNET_II;
+using ::FRAME_TYPE_UNKNOWN;
+using ::NAN_CHANNEL_24G_BAND;
+using ::NAN_CHANNEL_5G_BAND_HIGH;
+using ::NAN_CHANNEL_5G_BAND_LOW;
+using ::NAN_DISABLE_RANGE_REPORT;
+using ::NAN_DO_NOT_USE_SRF;
+using ::NAN_DP_CHANNEL_NOT_REQUESTED;
+using ::NAN_DP_CONFIG_NO_SECURITY;
+using ::NAN_DP_CONFIG_SECURITY;
+using ::NAN_DP_END;
+using ::NAN_DP_FORCE_CHANNEL_SETUP;
+using ::NAN_DP_INITIATOR_RESPONSE;
+using ::NAN_DP_INTERFACE_CREATE;
+using ::NAN_DP_INTERFACE_DELETE;
+using ::NAN_DP_REQUEST_ACCEPT;
+using ::NAN_DP_REQUEST_CHANNEL_SETUP;
+using ::NAN_DP_REQUEST_REJECT;
+using ::NAN_DP_RESPONDER_RESPONSE;
+using ::NAN_GET_CAPABILITIES;
+using ::NAN_MATCH_ALG_MATCH_CONTINUOUS;
+using ::NAN_MATCH_ALG_MATCH_NEVER;
+using ::NAN_MATCH_ALG_MATCH_ONCE;
+using ::NAN_PUBLISH_TYPE_SOLICITED;
+using ::NAN_PUBLISH_TYPE_UNSOLICITED;
+using ::NAN_PUBLISH_TYPE_UNSOLICITED_SOLICITED;
+using ::NAN_RANGING_AUTO_RESPONSE_DISABLE;
+using ::NAN_RANGING_AUTO_RESPONSE_ENABLE;
+using ::NAN_RANGING_DISABLE;
+using ::NAN_RANGING_ENABLE;
+using ::NAN_RESPONSE_BEACON_SDF_PAYLOAD;
+using ::NAN_RESPONSE_CONFIG;
+using ::NAN_RESPONSE_DISABLED;
+using ::NAN_RESPONSE_ENABLED;
+using ::NAN_RESPONSE_ERROR;
+using ::NAN_RESPONSE_PUBLISH;
+using ::NAN_RESPONSE_PUBLISH_CANCEL;
+using ::NAN_RESPONSE_STATS;
+using ::NAN_RESPONSE_SUBSCRIBE;
+using ::NAN_RESPONSE_SUBSCRIBE_CANCEL;
+using ::NAN_RESPONSE_TCA;
+using ::NAN_RESPONSE_TRANSMIT_FOLLOWUP;
+using ::NAN_SECURITY_KEY_INPUT_PASSPHRASE;
+using ::NAN_SECURITY_KEY_INPUT_PASSPHRASE;
+using ::NAN_SECURITY_KEY_INPUT_PMK;
+using ::NAN_SECURITY_KEY_INPUT_PMK;
+using ::NAN_SERVICE_ACCEPT_POLICY_ALL;
+using ::NAN_SERVICE_ACCEPT_POLICY_NONE;
+using ::NAN_SRF_ATTR_BLOOM_FILTER;
+using ::NAN_SRF_ATTR_PARTIAL_MAC_ADDR;
+using ::NAN_SRF_INCLUDE_DO_NOT_RESPOND;
+using ::NAN_SRF_INCLUDE_RESPOND;
+using ::NAN_SSI_NOT_REQUIRED_IN_MATCH_IND;
+using ::NAN_SSI_REQUIRED_IN_MATCH_IND;
+using ::NAN_STATUS_ALREADY_ENABLED;
+using ::NAN_STATUS_FOLLOWUP_QUEUE_FULL;
+using ::NAN_STATUS_INTERNAL_FAILURE;
+using ::NAN_STATUS_INVALID_NDP_ID;
+using ::NAN_STATUS_INVALID_PARAM;
+using ::NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID;
+using ::NAN_STATUS_INVALID_REQUESTOR_INSTANCE_ID;
+using ::NAN_STATUS_NAN_NOT_ALLOWED;
+using ::NAN_STATUS_NO_OTA_ACK;
+using ::NAN_STATUS_NO_RESOURCE_AVAILABLE;
+using ::NAN_STATUS_PROTOCOL_FAILURE;
+using ::NAN_STATUS_SUCCESS;
+using ::NAN_STATUS_UNSUPPORTED_CONCURRENCY_NAN_DISABLED;
+using ::NAN_SUBSCRIBE_TYPE_ACTIVE;
+using ::NAN_SUBSCRIBE_TYPE_PASSIVE;
+using ::NAN_TRANSMIT_IN_DW;
+using ::NAN_TRANSMIT_IN_FAW;
+using ::NAN_TX_PRIORITY_HIGH;
+using ::NAN_TX_PRIORITY_NORMAL;
+using ::NAN_TX_TYPE_BROADCAST;
+using ::NAN_TX_TYPE_UNICAST;
+using ::NAN_USE_SRF;
+using ::NanBeaconSdfPayloadInd;
+using ::NanCapabilities;
+using ::NanChannelInfo;
+using ::NanConfigRequest;
+using ::NanDataPathChannelCfg;
+using ::NanDataPathConfirmInd;
+using ::NanDataPathEndInd;
+using ::NanDataPathIndicationResponse;
+using ::NanDataPathInitiatorRequest;
+using ::NanDataPathRequestInd;
+using ::NanDataPathScheduleUpdateInd;
+using ::NanDisabledInd;
+using ::NanDiscEngEventInd;
+using ::NanEnableRequest;
+using ::NanFollowupInd;
+using ::NanMatchAlg;
+using ::NanMatchExpiredInd;
+using ::NanMatchInd;
+using ::NanPublishCancelRequest;
+using ::NanPublishRequest;
+using ::NanPublishTerminatedInd;
+using ::NanPublishType;
+using ::NanRangeReportInd;
+using ::NanRangeRequestInd;
+using ::NanResponseMsg;
+using ::NanSRFType;
+using ::NanStatusType;
+using ::NanSubscribeCancelRequest;
+using ::NanSubscribeRequest;
+using ::NanSubscribeTerminatedInd;
+using ::NanSubscribeType;
+using ::NanTransmitFollowupInd;
+using ::NanTransmitFollowupRequest;
+using ::NanTxType;
+using ::ROAMING_DISABLE;
+using ::ROAMING_ENABLE;
+using ::RTT_PEER_AP;
+using ::RTT_PEER_NAN;
+using ::RTT_PEER_P2P_CLIENT;
+using ::RTT_PEER_P2P_GO;
+using ::RTT_PEER_STA;
+using ::RTT_STATUS_ABORTED;
+using ::RTT_STATUS_FAILURE;
+using ::RTT_STATUS_FAIL_AP_ON_DIFF_CHANNEL;
+using ::RTT_STATUS_FAIL_BUSY_TRY_LATER;
+using ::RTT_STATUS_FAIL_FTM_PARAM_OVERRIDE;
+using ::RTT_STATUS_FAIL_INVALID_TS;
+using ::RTT_STATUS_FAIL_NOT_SCHEDULED_YET;
+using ::RTT_STATUS_FAIL_NO_CAPABILITY;
+using ::RTT_STATUS_FAIL_NO_RSP;
+using ::RTT_STATUS_FAIL_PROTOCOL;
+using ::RTT_STATUS_FAIL_REJECTED;
+using ::RTT_STATUS_FAIL_SCHEDULE;
+using ::RTT_STATUS_FAIL_TM_TIMEOUT;
+using ::RTT_STATUS_INVALID_REQ;
+using ::RTT_STATUS_NAN_RANGING_CONCURRENCY_NOT_SUPPORTED;
+using ::RTT_STATUS_NAN_RANGING_PROTOCOL_FAILURE;
+using ::RTT_STATUS_NO_WIFI;
+using ::RTT_STATUS_SUCCESS;
+using ::RTT_TYPE_1_SIDED;
+using ::RTT_TYPE_2_SIDED;
+using ::RX_PKT_FATE_DRV_DROP_FILTER;
+using ::RX_PKT_FATE_DRV_DROP_INVALID;
+using ::RX_PKT_FATE_DRV_DROP_NOBUFS;
+using ::RX_PKT_FATE_DRV_DROP_OTHER;
+using ::RX_PKT_FATE_DRV_QUEUED;
+using ::RX_PKT_FATE_FW_DROP_FILTER;
+using ::RX_PKT_FATE_FW_DROP_INVALID;
+using ::RX_PKT_FATE_FW_DROP_NOBUFS;
+using ::RX_PKT_FATE_FW_DROP_OTHER;
+using ::RX_PKT_FATE_FW_QUEUED;
+using ::RX_PKT_FATE_SUCCESS;
+using ::TX_PKT_FATE_ACKED;
+using ::TX_PKT_FATE_DRV_DROP_INVALID;
+using ::TX_PKT_FATE_DRV_DROP_NOBUFS;
+using ::TX_PKT_FATE_DRV_DROP_OTHER;
+using ::TX_PKT_FATE_DRV_QUEUED;
+using ::TX_PKT_FATE_FW_DROP_INVALID;
+using ::TX_PKT_FATE_FW_DROP_NOBUFS;
+using ::TX_PKT_FATE_FW_DROP_OTHER;
+using ::TX_PKT_FATE_FW_QUEUED;
+using ::TX_PKT_FATE_SENT;
+using ::WIFI_AC_BE;
+using ::WIFI_AC_BK;
+using ::WIFI_AC_VI;
+using ::WIFI_AC_VO;
+using ::WIFI_BAND_A;
+using ::WIFI_BAND_ABG;
+using ::WIFI_BAND_ABG_WITH_DFS;
+using ::WIFI_BAND_A_DFS;
+using ::WIFI_BAND_A_WITH_DFS;
+using ::WIFI_BAND_BG;
+using ::WIFI_BAND_UNSPECIFIED;
+using ::WIFI_CHAN_WIDTH_10;
+using ::WIFI_CHAN_WIDTH_160;
+using ::WIFI_CHAN_WIDTH_20;
+using ::WIFI_CHAN_WIDTH_40;
+using ::WIFI_CHAN_WIDTH_5;
+using ::WIFI_CHAN_WIDTH_5;
+using ::WIFI_CHAN_WIDTH_80;
+using ::WIFI_CHAN_WIDTH_80P80;
+using ::WIFI_CHAN_WIDTH_INVALID;
+using ::WIFI_ERROR_BUSY;
+using ::WIFI_ERROR_INVALID_ARGS;
+using ::WIFI_ERROR_INVALID_REQUEST_ID;
+using ::WIFI_ERROR_NONE;
+using ::WIFI_ERROR_NOT_AVAILABLE;
+using ::WIFI_ERROR_NOT_SUPPORTED;
+using ::WIFI_ERROR_OUT_OF_MEMORY;
+using ::WIFI_ERROR_TIMED_OUT;
+using ::WIFI_ERROR_TOO_MANY_REQUESTS;
+using ::WIFI_ERROR_UNINITIALIZED;
+using ::WIFI_ERROR_UNKNOWN;
+using ::WIFI_INTERFACE_TYPE_AP;
+using ::WIFI_INTERFACE_TYPE_NAN;
+using ::WIFI_INTERFACE_TYPE_P2P;
+using ::WIFI_INTERFACE_TYPE_STA;
+using ::WIFI_LATENCY_MODE_LOW;
+using ::WIFI_LATENCY_MODE_NORMAL;
+using ::WIFI_LOGGER_CONNECT_EVENT_SUPPORTED;
+using ::WIFI_LOGGER_DRIVER_DUMP_SUPPORTED;
+using ::WIFI_LOGGER_MEMORY_DUMP_SUPPORTED;
+using ::WIFI_LOGGER_PACKET_FATE_SUPPORTED;
+using ::WIFI_LOGGER_POWER_EVENT_SUPPORTED;
+using ::WIFI_LOGGER_WAKE_LOCK_SUPPORTED;
+using ::WIFI_MOTION_EXPECTED;
+using ::WIFI_MOTION_NOT_EXPECTED;
+using ::WIFI_MOTION_UNKNOWN;
+using ::WIFI_POWER_SCENARIO_ON_BODY_CELL_OFF;
+using ::WIFI_POWER_SCENARIO_ON_BODY_CELL_ON;
+using ::WIFI_POWER_SCENARIO_ON_HEAD_CELL_OFF;
+using ::WIFI_POWER_SCENARIO_ON_HEAD_CELL_ON;
+using ::WIFI_POWER_SCENARIO_VOICE_CALL;
+using ::WIFI_RTT_BW_10;
+using ::WIFI_RTT_BW_160;
+using ::WIFI_RTT_BW_20;
+using ::WIFI_RTT_BW_40;
+using ::WIFI_RTT_BW_5;
+using ::WIFI_RTT_BW_80;
+using ::WIFI_RTT_PREAMBLE_HE;
+using ::WIFI_RTT_PREAMBLE_HT;
+using ::WIFI_RTT_PREAMBLE_LEGACY;
+using ::WIFI_RTT_PREAMBLE_VHT;
+using ::WIFI_SCAN_FLAG_INTERRUPTED;
+using ::WIFI_SUCCESS;
+using ::WLAN_MAC_2_4_BAND;
+using ::WLAN_MAC_5_0_BAND;
+using ::WLAN_MAC_6_0_BAND;
+using ::frame_info;
+using ::frame_type;
+using ::fw_roaming_state_t;
+using ::mac_addr;
+using ::rtt_peer_type;
+using ::ssid_t;
+using ::transaction_id;
+using ::wifi_band;
+using ::wifi_cached_scan_results;
+using ::wifi_channel_info;
+using ::wifi_channel_stat;
+using ::wifi_channel_width;
+using ::wifi_error;
+using ::wifi_gscan_capabilities;
+using ::wifi_information_element;
+using ::wifi_interface_type;
+using ::wifi_latency_mode;
+using ::wifi_lci_information;
+using ::wifi_lcr_information;
+using ::wifi_motion_pattern;
+using ::wifi_power_scenario;
+using ::wifi_rate;
+using ::wifi_request_id;
+using ::wifi_ring_buffer_status;
+using ::wifi_roaming_capabilities;
+using ::wifi_roaming_config;
+using ::wifi_rtt_bw;
+using ::wifi_rtt_capabilities;
+using ::wifi_rtt_config;
+using ::wifi_rtt_preamble;
+using ::wifi_rtt_responder;
+using ::wifi_rtt_result;
+using ::wifi_rtt_status;
+using ::wifi_rtt_type;
+using ::wifi_rx_packet_fate;
+using ::wifi_rx_report;
+using ::wifi_scan_bucket_spec;
+using ::wifi_scan_cmd_params;
+using ::wifi_scan_result;
+using ::wifi_tx_packet_fate;
+using ::wifi_tx_report;
 
 // APF capabilities supported by the iface.
 struct PacketFilterCapabilities {
diff --git a/wifi/1.4/default/wifi_legacy_hal_stubs.h b/wifi/1.4/default/wifi_legacy_hal_stubs.h
index 577a545..c709680 100644
--- a/wifi/1.4/default/wifi_legacy_hal_stubs.h
+++ b/wifi/1.4/default/wifi_legacy_hal_stubs.h
@@ -17,13 +17,14 @@
 #ifndef WIFI_LEGACY_HAL_STUBS_H_
 #define WIFI_LEGACY_HAL_STUBS_H_
 
+#include <hardware_legacy/wifi_hal.h>
+
 namespace android {
 namespace hardware {
 namespace wifi {
 namespace V1_4 {
 namespace implementation {
 namespace legacy_hal {
-#include <hardware_legacy/wifi_hal.h>
 
 bool initHalFuncTableWithStubs(wifi_hal_fn* hal_fn);
 }  // namespace legacy_hal