Merge "Add CTS/GTS tests for TH presumbit."
diff --git a/automotive/OWNERS b/automotive/OWNERS
index 3cf4489..83ee63c 100644
--- a/automotive/OWNERS
+++ b/automotive/OWNERS
@@ -1,4 +1,5 @@
-randolphs@google.com
 pirozzoj@google.com
 twasilczyk@google.com
 pfg@google.com
+gurunagarajan@google.com
+keunyoung@google.com
diff --git a/automotive/can/1.0/default/Android.bp b/automotive/can/1.0/default/Android.bp
index 0a4afd6..8aa1d6b 100644
--- a/automotive/can/1.0/default/Android.bp
+++ b/automotive/can/1.0/default/Android.bp
@@ -47,7 +47,6 @@
     shared_libs: [
         "android.hardware.automotive.can@1.0",
         "libhidlbase",
-        "libhidltransport",
     ],
     static_libs: [
         "android.hardware.automotive.can@libnetdevice",
diff --git a/automotive/can/1.0/tools/Android.bp b/automotive/can/1.0/tools/Android.bp
index 8c26985..21f364b 100644
--- a/automotive/can/1.0/tools/Android.bp
+++ b/automotive/can/1.0/tools/Android.bp
@@ -23,7 +23,6 @@
     shared_libs: [
         "android.hardware.automotive.can@1.0",
         "libhidlbase",
-        "libhidltransport",
     ],
     header_libs: [
         "android.hardware.automotive.can@hidl-utils-lib",
@@ -39,7 +38,6 @@
     shared_libs: [
         "android.hardware.automotive.can@1.0",
         "libhidlbase",
-        "libhidltransport",
     ],
     header_libs: [
         "android.hardware.automotive.can@hidl-utils-lib",
@@ -55,6 +53,5 @@
     shared_libs: [
         "android.hardware.automotive.can@1.0",
         "libhidlbase",
-        "libhidltransport",
     ],
 }
diff --git a/automotive/evs/1.1/default/Android.bp b/automotive/evs/1.1/default/Android.bp
index 411f0ff..a463471 100644
--- a/automotive/evs/1.1/default/Android.bp
+++ b/automotive/evs/1.1/default/Android.bp
@@ -19,7 +19,6 @@
         "libcutils",
         "libhardware",
         "libhidlbase",
-        "libhidltransport",
         "liblog",
         "libui",
         "libutils",
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
index c8e11e3..094a372 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
@@ -786,7 +786,7 @@
          .initialValue = {.int32Values = {toInt(VehicleApPowerStateReq::ON), 0}}},
 
         {.config = {.prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
-                    .access = VehiclePropertyAccess::WRITE,
+                    .access = VehiclePropertyAccess::READ_WRITE,
                     .changeMode = VehiclePropertyChangeMode::ON_CHANGE},
          .initialValue = {.int32Values = {toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL), 0}}},
 
diff --git a/automotive/vehicle/2.0/types.hal b/automotive/vehicle/2.0/types.hal
index 8c84c0a..1355d9f 100644
--- a/automotive/vehicle/2.0/types.hal
+++ b/automotive/vehicle/2.0/types.hal
@@ -1310,7 +1310,7 @@
 
      *
      * @change_mode VehiclePropertyChangeMode:ON_CHANGE
-     * @access VehiclePropertyAccess:WRITE
+     * @access VehiclePropertyAccess:READ_WRITE
      */
     AP_POWER_STATE_REPORT = (
         0x0A01
@@ -2537,7 +2537,7 @@
      * power controller must change power state to this state to shutdown
      * system.
      *
-     * int32Values[1] : one of enum_vehicle_ap_power_state_shutdown_param_type
+     * int32Values[1] : one of VehicleApPowerStateShutdownParam
      *
      * SHUTDOWN_PRPARE may be requested from either WAIT_FOR_VHAL or ON states.
      */
diff --git a/boot/1.1/Android.bp b/boot/1.1/Android.bp
new file mode 100644
index 0000000..6a8d57a
--- /dev/null
+++ b/boot/1.1/Android.bp
@@ -0,0 +1,18 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+    name: "android.hardware.boot@1.1",
+    root: "android.hardware",
+    vndk: {
+        enabled: true,
+    },
+    srcs: [
+        "types.hal",
+        "IBootControl.hal",
+    ],
+    interfaces: [
+        "android.hardware.boot@1.0",
+        "android.hidl.base@1.0",
+    ],
+    gen_java: true,
+}
diff --git a/boot/1.1/IBootControl.hal b/boot/1.1/IBootControl.hal
new file mode 100644
index 0000000..939dfb3
--- /dev/null
+++ b/boot/1.1/IBootControl.hal
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package android.hardware.boot@1.1;
+
+import @1.0::IBootControl;
+
+interface IBootControl extends @1.0::IBootControl {
+    /**
+     * Sets whether a snapshot-merge of any dynamic partition is in progress.
+     *
+     * After the merge status is set to a given value, subsequent calls to
+     * getSnapshotMergeStatus must return the set value.
+     *
+     * The merge status must be persistent across reboots. That is, getSnapshotMergeStatus
+     * must return the same value after a reboot if the merge status is not altered in any way
+     * (e.g. set by setSnapshotMergeStatus or set to CANCELLED by bootloader).
+     *
+     * Read/write access to the merge status must be atomic. When the HAL is processing a
+     * setSnapshotMergeStatus call, all subsequent calls to getSnapshotMergeStatus must block until
+     * setSnapshotMergeStatus has returned.
+     *
+     * A MERGING state indicates that dynamic partitions are partially comprised by blocks in the
+     * userdata partition.
+     *
+     * When the merge status is set to MERGING, the following operations must be prohibited from the
+     * bootloader:
+     *  - Flashing or erasing "userdata" or "metadata".
+     *
+     * The following operations may be prohibited when the status is set to MERGING. If not
+     * prohibited, it is recommended that the user receive a warning.
+     *  - Changing the active slot (e.g. via "fastboot set_active")
+     *
+     * @param status Merge status.
+     *
+     * @return success True on success, false otherwise.
+     */
+    setSnapshotMergeStatus(MergeStatus status) generates (bool success);
+
+    /**
+     * Returns whether a snapshot-merge of any dynamic partition is in progress.
+     *
+     * This function must return the merge status set by the last setSnapshotMergeStatus call and
+     * recorded by the bootloader with one exception. If the partitions are being flashed from the
+     * bootloader such that the pending merge must be canceled (for example, if the super partition
+     * is being flashed), this function must return CANCELLED.
+     *
+     * @return success True if the merge status is read successfully, false otherwise.
+     * @return status Merge status.
+     */
+    getSnapshotMergeStatus() generates (MergeStatus status);
+};
+
diff --git a/boot/1.1/types.hal b/boot/1.1/types.hal
new file mode 100644
index 0000000..6346078
--- /dev/null
+++ b/boot/1.1/types.hal
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package android.hardware.boot@1.1;
+
+enum MergeStatus : int32_t {
+    /**
+     * No snapshot or merge is in progress.
+     */
+    NONE = 0,
+
+    /**
+     * The merge status could not be determined.
+     */
+    UNKNOWN,
+
+    /**
+     * Partitions are being snapshotted, but no merge has been started.
+     */
+    SNAPSHOTTED,
+
+    /**
+     * At least one partition has merge is in progress.
+     */
+    MERGING,
+
+    /**
+     * A merge was in progress, but it was canceled by the bootloader.
+     */
+    CANCELLED,
+};
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index ef1cd75..a409650 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -89,7 +89,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.boot</name>
-        <version>1.0</version>
+        <version>1.1</version>
         <interface>
             <name>IBootControl</name>
             <instance>default</instance>
@@ -204,7 +204,7 @@
     </hal>
     <hal format="hidl" optional="false">
         <name>android.hardware.graphics.composer</name>
-        <version>2.1-3</version>
+        <version>2.1-4</version>
         <interface>
             <name>IComposer</name>
             <instance>default</instance>
@@ -307,7 +307,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.neuralnetworks</name>
-        <version>1.0-2</version>
+        <version>1.0-3</version>
         <interface>
             <name>IDevice</name>
             <regex-instance>.*</regex-instance>
@@ -467,7 +467,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.vibrator</name>
-        <version>1.0-3</version>
+        <version>1.0-4</version>
         <interface>
             <name>IVibrator</name>
             <instance>default</instance>
@@ -507,7 +507,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.wifi.supplicant</name>
-        <version>1.0-2</version>
+        <version>1.0-3</version>
         <interface>
             <name>ISupplicant</name>
             <instance>default</instance>
diff --git a/confirmationui/1.0/vts/functional/Android.bp b/confirmationui/1.0/vts/functional/Android.bp
index d19d702..fd088cd 100644
--- a/confirmationui/1.0/vts/functional/Android.bp
+++ b/confirmationui/1.0/vts/functional/Android.bp
@@ -23,7 +23,7 @@
     static_libs: [
         "android.hardware.confirmationui@1.0",
         "android.hardware.keymaster@4.0",
-        "libcrypto",
+        "libcrypto_static",
         "libcn-cbor",
         "android.hardware.confirmationui-support-lib",
     ],
diff --git a/contexthub/1.0/default/OWNERS b/contexthub/1.0/default/OWNERS
index 5373073..90c2330 100644
--- a/contexthub/1.0/default/OWNERS
+++ b/contexthub/1.0/default/OWNERS
@@ -1,4 +1,3 @@
-aarossig@google.com
 arthuri@google.com
 bduddie@google.com
-bstack@google.com
+stange@google.com
diff --git a/contexthub/1.0/vts/functional/OWNERS b/contexthub/1.0/vts/functional/OWNERS
index ee01441..045cc4e 100644
--- a/contexthub/1.0/vts/functional/OWNERS
+++ b/contexthub/1.0/vts/functional/OWNERS
@@ -1,8 +1,7 @@
 #Context Hub team
-aarossig@google.com
 arthuri@google.com
 bduddie@google.com
-bstack@google.com
+stange@google.com
 
 #VTS team
 yim@google.com
diff --git a/current.txt b/current.txt
index 83657b2..87498a1 100644
--- a/current.txt
+++ b/current.txt
@@ -575,7 +575,23 @@
 2410dd02d67786a732d36e80b0f8ccf55086604ef37f9838e2013ff2c571e404 android.hardware.camera.device@3.5::types
 b69a7615c508acf5c5201efd1bfa3262167874fc3594e2db5a3ff93addd8ac75 android.hardware.keymaster@4.0::IKeymasterDevice
 eb2fa0c883c2185d514be0b84c179b283753ef0c1b77b45b4f359bd23bba8b75 android.hardware.neuralnetworks@1.0::IPreparedModel
+f1109cbb10297b7429a11fab42afa912710b303c9bf20bd5cdb8bd57b9c84186 android.hardware.neuralnetworks@1.0::types
+9d8ee57c490ffeaa28f702eaea8d198cb510e4bbfb99e6cb5f63e73341057c7c android.hardware.neuralnetworks@1.1::types
 fb382e986c10b8fbb797a8546e8f9ea6d1107bfe6f3fb7e57f6bbbf1f807a906 android.hardware.neuralnetworks@1.2::IDevice
 40e71cd693de5b832325c5d8f081f2ff20a7ba2b89d401cee5b4b3eb0e241681 android.hardware.neuralnetworks@1.2::IPreparedModel
+71c0f7127335e5b74d1615d5e7f129831b43ffbae5318ad0924d7d8d8910a859 android.hardware.neuralnetworks@1.2::types
+a785a57447a81e9c130eef6904c3a5c256076c6a04588c40620ebd6fa2660d77 android.hardware.radio@1.2::types
 1a6e2bd289f22931c526b21916910f1d4c436b7acb9556e4243de4ce8e6cc2e4 android.hardware.soundtrigger@2.0::ISoundTriggerHwCallback
 fd65298e1e09e0e3c781ab18305920d757dbe55a3b459ce17814ec5cf6dfee99 android.hardware.wifi@1.0::IWifiP2pIface
+
+# HALs released in Android R
+07d0a252b2d8fa35887908a996ba395cf392968395fc30afab791f46e0c22a52 android.hardware.boot@1.1::IBootControl
+74049a402be913963edfdd80828a53736570e9d8124a1bf18166b6ed46a6b0ab android.hardware.boot@1.1::types
+34515afa2bb792d3c6d8495a5f5d907d179c8507ca5e55c10050d02ae1d516ef android.hardware.neuralnetworks@1.3::IDevice
+b74fe72cfe438f50e772e6a307657ff449d5bde83c15dd1f140ff2edbe73499c android.hardware.neuralnetworks@1.3::types
+04395b26be33db17747c3d3b0e8066d323f891ff4f9f3b3ddb490b2f3f844a18 android.hardware.wifi@1.4::IWifi
+270f0eb670dfd9bc5cd718e09711f2534fa8425f54d06c1a46523ca156b509e2 android.hardware.wifi.supplicant@1.3::ISupplicant
+dd4b7cfbb6e1c6ff011c33920762ad89dd02240c63a4d3a3d5037f154eae3e3b android.hardware.wifi.supplicant@1.3::ISupplicantStaIface
+619fc9839ec6e369cfa9b28e3e9412e6885720ff8f9b5750c1b6ffb905120391 android.hardware.wifi.supplicant@1.3::ISupplicantStaIfaceCallback
+6fe09b18e913608579638594788198ec45bb2369e567d7df661db46c4f0e5f08 android.hardware.wifi.supplicant@1.3::ISupplicantStaNetwork
+91931b05bd70ea6bdffbe075086183f803379571788564e28854207620eb75cf android.hardware.wifi.supplicant@1.3::types
diff --git a/drm/1.0/vts/functional/Android.bp b/drm/1.0/vts/functional/Android.bp
index d6ebfdd..61d4d58 100644
--- a/drm/1.0/vts/functional/Android.bp
+++ b/drm/1.0/vts/functional/Android.bp
@@ -30,7 +30,7 @@
         "libhidlmemory",
         "libnativehelper",
         "libssl",
-        "libcrypto",
+        "libcrypto_static",
     ],
     test_suites: ["general-tests"],
 }
diff --git a/drm/1.2/vts/functional/Android.bp b/drm/1.2/vts/functional/Android.bp
index 6b4a4c0..95883bf 100644
--- a/drm/1.2/vts/functional/Android.bp
+++ b/drm/1.2/vts/functional/Android.bp
@@ -34,7 +34,7 @@
         "libhidlmemory",
         "libnativehelper",
         "libssl",
-        "libcrypto",
+        "libcrypto_static",
     ],
     test_suites: ["general-tests"],
 }
diff --git a/dumpstate/1.0/default/DumpstateDevice.cpp b/dumpstate/1.0/default/DumpstateDevice.cpp
index 25d92b0..c57bf43 100644
--- a/dumpstate/1.0/default/DumpstateDevice.cpp
+++ b/dumpstate/1.0/default/DumpstateDevice.cpp
@@ -37,11 +37,6 @@
     // NOTE: this is just an example on how to use the DumpstateUtil.h functions to implement
     // this interface.
 
-    // Exit when dump is completed since this is a lazy HAL.
-    addPostCommandTask([]() {
-        exit(0);
-    });
-
     if (handle == nullptr || handle->numFds < 1) {
         ALOGE("no FDs\n");
         return Void();
diff --git a/dumpstate/1.0/default/service.cpp b/dumpstate/1.0/default/service.cpp
index 4f276b7..76c72b5 100644
--- a/dumpstate/1.0/default/service.cpp
+++ b/dumpstate/1.0/default/service.cpp
@@ -15,22 +15,26 @@
  */
 #define LOG_TAG "android.hardware.dumpstate@1.0-service"
 
+#include <hidl/HidlLazyUtils.h>
 #include <hidl/HidlSupport.h>
 #include <hidl/HidlTransportSupport.h>
 
 #include "DumpstateDevice.h"
 
-using ::android::hardware::configureRpcThreadpool;
-using ::android::hardware::dumpstate::V1_0::IDumpstateDevice;
-using ::android::hardware::dumpstate::V1_0::implementation::DumpstateDevice;
-using ::android::hardware::joinRpcThreadpool;
 using ::android::OK;
 using ::android::sp;
+using ::android::hardware::configureRpcThreadpool;
+using ::android::hardware::joinRpcThreadpool;
+using ::android::hardware::LazyServiceRegistrar;
+using ::android::hardware::dumpstate::V1_0::IDumpstateDevice;
+using ::android::hardware::dumpstate::V1_0::implementation::DumpstateDevice;
 
 int main(int /* argc */, char* /* argv */ []) {
     sp<IDumpstateDevice> dumpstate = new DumpstateDevice;
     configureRpcThreadpool(1, true /* will join */);
-    if (dumpstate->registerAsService() != OK) {
+
+    auto registrar = LazyServiceRegistrar::getInstance();
+    if (registrar.registerService(dumpstate) != OK) {
         ALOGE("Could not register service.");
         return 1;
     }
diff --git a/graphics/composer/2.2/vts/functional/Android.bp b/graphics/composer/2.2/vts/functional/Android.bp
index 2872880..21ba9f3 100644
--- a/graphics/composer/2.2/vts/functional/Android.bp
+++ b/graphics/composer/2.2/vts/functional/Android.bp
@@ -30,8 +30,6 @@
         "libfmq",
         "libgui",
         "libhidlbase",
-        "libhidltransport",
-        "libhwbinder",
         "libprocessgroup",
         "libsync",
         "libui",
diff --git a/graphics/composer/2.4/IComposer.hal b/graphics/composer/2.4/IComposer.hal
index 34801da..d3b3cb6 100644
--- a/graphics/composer/2.4/IComposer.hal
+++ b/graphics/composer/2.4/IComposer.hal
@@ -17,12 +17,10 @@
 package android.hardware.graphics.composer@2.4;
 
 import IComposerClient;
-
 import @2.1::Error;
 import @2.3::IComposer;
 
 interface IComposer extends @2.3::IComposer {
-
     /**
      * Creates a v2.4 client of the composer. Supersedes @2.3::createClient.
      *
diff --git a/graphics/composer/2.4/IComposerClient.hal b/graphics/composer/2.4/IComposerClient.hal
index 8fe0976..60445f5 100644
--- a/graphics/composer/2.4/IComposerClient.hal
+++ b/graphics/composer/2.4/IComposerClient.hal
@@ -21,13 +21,12 @@
 import @2.3::IComposerClient;
 
 interface IComposerClient extends @2.3::IComposerClient {
-
     /**
      * Required capabilities which are supported by the display. The
      * particular set of supported capabilities for a given display may be
      * retrieved using getDisplayCapabilities.
      */
-    enum DisplayCapability : uint32_t {
+    enum DisplayCapability : @2.3::IComposerClient.DisplayCapability {
         /**
          * Indicates that the display supports protected contents.
          * When returned, hardware composer must be able to accept client target
@@ -37,6 +36,20 @@
     };
 
     /**
+     * Supersedes {@link @2.1::IComposerClient.DisplayType}.
+     */
+    enum DisplayConnectionType : uint32_t {
+        /**
+         * Display is connected through internal port, e.g. DSI, eDP.
+         */
+        INTERNAL = 0,
+        /**
+         * Display is connected through external port, e.g. HDMI, DisplayPort.
+         */
+        EXTERNAL = 1,
+    };
+
+    /**
      * Provides a list of supported capabilities (as described in the
      * definition of DisplayCapability above). This list must not change after
      * initialization.
@@ -46,6 +59,14 @@
      * @return capabilities is a list of supported capabilities.
      */
     getDisplayCapabilities_2_4(Display display)
-              generates (Error error,
-                         vec<DisplayCapability> capabilities);
+        generates (Error error, vec<DisplayCapability> capabilities);
+
+    /**
+     * Returns whether the given physical display is internal or external.
+     *
+     * @return error is NONE upon success. Otherwise,
+     *     BAD_DISPLAY when the given display is invalid or virtual.
+     * @return type is the connection type of the display.
+     */
+    getDisplayConnectionType(Display display) generates (Error error, DisplayConnectionType type);
 };
diff --git a/graphics/composer/2.4/default/Android.bp b/graphics/composer/2.4/default/Android.bp
index a44e687..a30609b 100644
--- a/graphics/composer/2.4/default/Android.bp
+++ b/graphics/composer/2.4/default/Android.bp
@@ -37,7 +37,6 @@
         "libfmq",
         "libhardware",
         "libhidlbase",
-        "libhidltransport",
         "libhwc2on1adapter",
         "libhwc2onfbadapter",
         "liblog",
diff --git a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h
index 7110c80..c810186 100644
--- a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h
+++ b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h
@@ -46,6 +46,14 @@
         return Void();
     }
 
+    Return<void> getDisplayConnectionType(
+            Display display, IComposerClient::getDisplayConnectionType_cb hidl_cb) override {
+        IComposerClient::DisplayConnectionType type;
+        Error error = mHal->getDisplayConnectionType(display, &type);
+        hidl_cb(error, type);
+        return Void();
+    }
+
     static std::unique_ptr<ComposerClientImpl> create(Hal* hal) {
         auto client = std::make_unique<ComposerClientImpl>(hal);
         return client->init() ? std::move(client) : nullptr;
diff --git a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerHal.h b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerHal.h
index 0074808..c3bb535 100644
--- a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerHal.h
+++ b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerHal.h
@@ -38,6 +38,8 @@
   public:
     virtual Error getDisplayCapabilities_2_4(
             Display display, std::vector<IComposerClient::DisplayCapability>* outCapabilities) = 0;
+    virtual Error getDisplayConnectionType(Display display,
+                                           IComposerClient::DisplayConnectionType* outType) = 0;
 };
 
 }  // namespace hal
diff --git a/graphics/composer/2.4/utils/passthrough/include/composer-passthrough/2.4/HwcHal.h b/graphics/composer/2.4/utils/passthrough/include/composer-passthrough/2.4/HwcHal.h
index 65d47d7..fd05f66 100644
--- a/graphics/composer/2.4/utils/passthrough/include/composer-passthrough/2.4/HwcHal.h
+++ b/graphics/composer/2.4/utils/passthrough/include/composer-passthrough/2.4/HwcHal.h
@@ -62,15 +62,34 @@
         return Error::NONE;
     }
 
+    Error getDisplayConnectionType(Display display,
+                                   IComposerClient::DisplayConnectionType* outType) override {
+        if (!mDispatch.getDisplayConnectionType) {
+            return Error::UNSUPPORTED;
+        }
+
+        uint32_t type = HWC2_DISPLAY_CONNECTION_TYPE_INTERNAL;
+        int32_t error = mDispatch.getDisplayConnectionType(mDevice, display, &type);
+        *outType = static_cast<IComposerClient::DisplayConnectionType>(type);
+        return static_cast<Error>(error);
+    }
+
   protected:
     bool initDispatch() override {
         if (!BaseType2_3::initDispatch()) {
             return false;
         }
+
+        this->initOptionalDispatch(HWC2_FUNCTION_GET_DISPLAY_CONNECTION_TYPE,
+                                   &mDispatch.getDisplayConnectionType);
         return true;
     }
 
   private:
+    struct {
+        HWC2_PFN_GET_DISPLAY_CONNECTION_TYPE getDisplayConnectionType;
+    } mDispatch = {};
+
     using BaseType2_1 = V2_1::passthrough::detail::HwcHalImpl<Hal>;
     using BaseType2_3 = V2_3::passthrough::detail::HwcHalImpl<Hal>;
     using BaseType2_1::mDevice;
diff --git a/graphics/composer/2.4/utils/vts/ComposerVts.cpp b/graphics/composer/2.4/utils/vts/ComposerVts.cpp
index ee4f3a3..937b50e 100644
--- a/graphics/composer/2.4/utils/vts/ComposerVts.cpp
+++ b/graphics/composer/2.4/utils/vts/ComposerVts.cpp
@@ -51,7 +51,6 @@
 
 Error ComposerClient::getDisplayCapabilities(
         Display display, std::vector<IComposerClient::DisplayCapability>* outCapabilities) {
-    std::vector<IComposerClient::DisplayCapability> capabilities;
     Error error = Error::NONE;
     mClient->getDisplayCapabilities_2_4(display,
                                         [&](const auto& tmpError, const auto& tmpCapabilities) {
@@ -61,6 +60,16 @@
     return error;
 }
 
+Error ComposerClient::getDisplayConnectionType(Display display,
+                                               IComposerClient::DisplayConnectionType* outType) {
+    Error error = Error::NONE;
+    mClient->getDisplayConnectionType(display, [&](const auto& tmpError, const auto& tmpType) {
+        error = tmpError;
+        *outType = tmpType;
+    });
+    return error;
+}
+
 }  // namespace vts
 }  // namespace V2_4
 }  // namespace composer
diff --git a/graphics/composer/2.4/utils/vts/include/composer-vts/2.4/ComposerVts.h b/graphics/composer/2.4/utils/vts/include/composer-vts/2.4/ComposerVts.h
index 0a301c6..a7d7f86 100644
--- a/graphics/composer/2.4/utils/vts/include/composer-vts/2.4/ComposerVts.h
+++ b/graphics/composer/2.4/utils/vts/include/composer-vts/2.4/ComposerVts.h
@@ -71,6 +71,9 @@
             Display display,
             std::vector<IComposerClient::DisplayCapability>* outDisplayCapabilities);
 
+    Error getDisplayConnectionType(Display display,
+                                   IComposerClient::DisplayConnectionType* outType);
+
   private:
     const sp<IComposerClient> mClient;
 };
diff --git a/graphics/composer/2.4/vts/functional/Android.bp b/graphics/composer/2.4/vts/functional/Android.bp
index 6ee7873..921c421 100644
--- a/graphics/composer/2.4/vts/functional/Android.bp
+++ b/graphics/composer/2.4/vts/functional/Android.bp
@@ -22,7 +22,6 @@
     // TODO(b/64437680): Assume these libs are always available on the device.
     shared_libs: [
         "libfmq",
-        "libhidltransport",
         "libsync",
     ],
     static_libs: [
diff --git a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
index 0fccc58..76c0039 100644
--- a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
+++ b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
@@ -179,6 +179,16 @@
     EXPECT_EQ(Error::BAD_DISPLAY, error);
 }
 
+TEST_F(GraphicsComposerHidlTest, getDisplayConnectionType) {
+    IComposerClient::DisplayConnectionType type;
+    EXPECT_EQ(Error::BAD_DISPLAY,
+              mComposerClient->getDisplayConnectionType(mInvalidDisplayId, &type));
+
+    for (Display display : mComposerCallback->getDisplays()) {
+        EXPECT_EQ(Error::NONE, mComposerClient->getDisplayConnectionType(display, &type));
+    }
+}
+
 }  // namespace
 }  // namespace vts
 }  // namespace V2_4
diff --git a/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h b/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h
index d9beb4f..db7e67d 100644
--- a/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h
+++ b/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h
@@ -282,14 +282,16 @@
         }
 
         if (flex.planes[0].component != FLEX_COMPONENT_Y ||
-            flex.planes[1].component != FLEX_COMPONENT_Cb ||
-            flex.planes[2].component != FLEX_COMPONENT_Cr) {
+           ((flex.planes[1].component != FLEX_COMPONENT_Cb || flex.planes[2].component != FLEX_COMPONENT_Cr) &&
+           (flex.planes[2].component != FLEX_COMPONENT_Cb || flex.planes[1].component != FLEX_COMPONENT_Cr))) {
             return false;
         }
 
         const auto& y = flex.planes[0];
-        const auto& cb = flex.planes[1];
-        const auto& cr = flex.planes[2];
+        const auto& cb = (flex.planes[1].component == FLEX_COMPONENT_Cb)?
+                          flex.planes[1] : flex.planes[2];
+        const auto& cr = (flex.planes[2].component == FLEX_COMPONENT_Cr)?
+                          flex.planes[2] : flex.planes[1];
 
         if (cb.h_increment != cr.h_increment || cb.v_increment != cr.v_increment) {
             return false;
diff --git a/graphics/mapper/2.1/utils/passthrough/include/mapper-passthrough/2.1/Gralloc0Hal.h b/graphics/mapper/2.1/utils/passthrough/include/mapper-passthrough/2.1/Gralloc0Hal.h
index 18fbb6d..8540068 100644
--- a/graphics/mapper/2.1/utils/passthrough/include/mapper-passthrough/2.1/Gralloc0Hal.h
+++ b/graphics/mapper/2.1/utils/passthrough/include/mapper-passthrough/2.1/Gralloc0Hal.h
@@ -37,6 +37,10 @@
      Error validateBufferSize(const native_handle_t* bufferHandle,
                               const IMapper::BufferDescriptorInfo& descriptorInfo,
                               uint32_t stride) override {
+         if (descriptorInfo.layerCount != 1) {
+             return Error::BAD_VALUE;
+         }
+
          if (!mModule->validateBufferSize) {
              return Error::NONE;
          }
diff --git a/keymaster/3.0/vts/functional/Android.bp b/keymaster/3.0/vts/functional/Android.bp
index b0371c7..69aa56d 100644
--- a/keymaster/3.0/vts/functional/Android.bp
+++ b/keymaster/3.0/vts/functional/Android.bp
@@ -26,7 +26,7 @@
     ],
     static_libs: [
         "android.hardware.keymaster@3.0",
-        "libcrypto",
+        "libcrypto_static",
         "libsoftkeymasterdevice",
     ],
     test_suites: ["general-tests"],
diff --git a/keymaster/4.0/vts/functional/Android.bp b/keymaster/4.0/vts/functional/Android.bp
index 333e408..0401362 100644
--- a/keymaster/4.0/vts/functional/Android.bp
+++ b/keymaster/4.0/vts/functional/Android.bp
@@ -25,7 +25,7 @@
     ],
     static_libs: [
         "android.hardware.keymaster@4.0",
-        "libcrypto",
+        "libcrypto_static",
         "libkeymaster4support",
         "libsoftkeymasterdevice",
     ],
diff --git a/keymaster/4.0/vts/functional/KeymasterHidlTest.cpp b/keymaster/4.0/vts/functional/KeymasterHidlTest.cpp
index 3af1df3..4838e7e 100644
--- a/keymaster/4.0/vts/functional/KeymasterHidlTest.cpp
+++ b/keymaster/4.0/vts/functional/KeymasterHidlTest.cpp
@@ -48,10 +48,11 @@
 SecurityLevel KeymasterHidlTest::securityLevel_;
 hidl_string KeymasterHidlTest::name_;
 hidl_string KeymasterHidlTest::author_;
+string KeymasterHidlTest::service_name_;
 
-void KeymasterHidlTest::SetUpTestCase() {
-    string service_name = KeymasterHidlEnvironment::Instance()->getServiceName<IKeymasterDevice>();
-    keymaster_ = ::testing::VtsHalHidlTargetTestBase::getService<IKeymasterDevice>(service_name);
+void KeymasterHidlTest::InitializeKeymaster() {
+    service_name_ = KeymasterHidlEnvironment::Instance()->getServiceName<IKeymasterDevice>();
+    keymaster_ = ::testing::VtsHalHidlTargetTestBase::getService<IKeymasterDevice>(service_name_);
     ASSERT_NE(keymaster_, nullptr);
 
     ASSERT_TRUE(keymaster_
@@ -62,18 +63,22 @@
                         author_ = author;
                     })
                     .isOk());
+}
+
+void KeymasterHidlTest::SetUpTestCase() {
+
+    InitializeKeymaster();
 
     os_version_ = ::keymaster::GetOsVersion();
     os_patch_level_ = ::keymaster::GetOsPatchlevel();
 
     auto service_manager = android::hidl::manager::V1_0::IServiceManager::getService();
     ASSERT_NE(nullptr, service_manager.get());
-
     all_keymasters_.push_back(keymaster_);
     service_manager->listByInterface(
         IKeymasterDevice::descriptor, [&](const hidl_vec<hidl_string>& names) {
             for (auto& name : names) {
-                if (name == service_name) continue;
+                if (name == service_name_) continue;
                 auto keymaster =
                     ::testing::VtsHalHidlTargetTestBase::getService<IKeymasterDevice>(name);
                 ASSERT_NE(keymaster, nullptr);
@@ -269,6 +274,13 @@
     return GetCharacteristics(key_blob, client_id, app_data, key_characteristics);
 }
 
+ErrorCode KeymasterHidlTest::GetDebugInfo(DebugInfo* debug_info) {
+    EXPECT_TRUE(keymaster_->getDebugInfo([&](const DebugInfo& hidl_debug_info) {
+      *debug_info = hidl_debug_info;
+    }).isOk());
+    return ErrorCode::OK;
+}
+
 ErrorCode KeymasterHidlTest::Begin(KeyPurpose purpose, const HidlBuf& key_blob,
                                    const AuthorizationSet& in_params, AuthorizationSet* out_params,
                                    OperationHandle* op_handle) {
@@ -611,6 +623,20 @@
     return ciphertext;
 }
 
+string KeymasterHidlTest::EncryptMessage(const string& message, BlockMode block_mode,
+                                         PaddingMode padding, uint8_t mac_length_bits,
+                                         const HidlBuf& iv_in) {
+    SCOPED_TRACE("EncryptMessage");
+    auto params = AuthorizationSetBuilder()
+                          .BlockMode(block_mode)
+                          .Padding(padding)
+                          .Authorization(TAG_MAC_LENGTH, mac_length_bits)
+                          .Authorization(TAG_NONCE, iv_in);
+    AuthorizationSet out_params;
+    string ciphertext = EncryptMessage(message, params, &out_params);
+    return ciphertext;
+}
+
 string KeymasterHidlTest::DecryptMessage(const HidlBuf& key_blob, const string& ciphertext,
                                          const AuthorizationSet& params) {
     SCOPED_TRACE("DecryptMessage");
diff --git a/keymaster/4.0/vts/functional/KeymasterHidlTest.h b/keymaster/4.0/vts/functional/KeymasterHidlTest.h
index 015fc43..b09da45 100644
--- a/keymaster/4.0/vts/functional/KeymasterHidlTest.h
+++ b/keymaster/4.0/vts/functional/KeymasterHidlTest.h
@@ -37,6 +37,7 @@
 
 using ::android::sp;
 using ::std::string;
+using hidl::base::V1_0::DebugInfo;
 
 class HidlBuf : public hidl_vec<uint8_t> {
     typedef hidl_vec<uint8_t> super;
@@ -95,6 +96,7 @@
 
     // SetUpTestCase runs only once per test case, not once per test.
     static void SetUpTestCase();
+    static void InitializeKeymaster();
     static void TearDownTestCase() {
         keymaster_.clear();
         all_keymasters_.clear();
@@ -140,6 +142,8 @@
                                  const HidlBuf& app_data, KeyCharacteristics* key_characteristics);
     ErrorCode GetCharacteristics(const HidlBuf& key_blob, KeyCharacteristics* key_characteristics);
 
+    ErrorCode GetDebugInfo(DebugInfo* debug_info);
+
     ErrorCode Begin(KeyPurpose purpose, const HidlBuf& key_blob, const AuthorizationSet& in_params,
                     AuthorizationSet* out_params, OperationHandle* op_handle);
     ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params,
@@ -201,6 +205,8 @@
                           HidlBuf* iv_out);
     string EncryptMessage(const string& message, BlockMode block_mode, PaddingMode padding,
                           const HidlBuf& iv_in);
+    string EncryptMessage(const string& message, BlockMode block_mode, PaddingMode padding,
+                          uint8_t mac_length_bits, const HidlBuf& iv_in);
 
     string DecryptMessage(const HidlBuf& key_blob, const string& ciphertext,
                           const AuthorizationSet& params);
@@ -235,6 +241,7 @@
     static SecurityLevel securityLevel_;
     static hidl_string name_;
     static hidl_string author_;
+    static string service_name_;
 };
 
 }  // namespace test
diff --git a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
index 9e6cce7..0ac7e48 100644
--- a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
+++ b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
@@ -18,6 +18,7 @@
 #include <cutils/log.h>
 
 #include <iostream>
+#include <signal.h>
 
 #include <openssl/evp.h>
 #include <openssl/mem.h>
@@ -2706,6 +2707,40 @@
 }
 
 /*
+ * EncryptionOperationsTest.AesWrongPurpose
+ *
+ * Verifies that AES encryption fails in the correct way when an unauthorized purpose is specified.
+ */
+TEST_F(EncryptionOperationsTest, AesWrongPurpose) {
+    auto err = GenerateKey(AuthorizationSetBuilder()
+                                   .Authorization(TAG_NO_AUTH_REQUIRED)
+                                   .AesKey(128)
+                                   .Authorization(TAG_PURPOSE, KeyPurpose::ENCRYPT)
+                                   .Authorization(TAG_BLOCK_MODE, BlockMode::GCM)
+                                   .Authorization(TAG_MIN_MAC_LENGTH, 128)
+                                   .Padding(PaddingMode::NONE));
+    ASSERT_EQ(ErrorCode::OK, err) << "Got " << err;
+
+    err = Begin(KeyPurpose::DECRYPT,
+                AuthorizationSetBuilder().BlockMode(BlockMode::GCM).Padding(PaddingMode::NONE));
+    EXPECT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE, err) << "Got " << err;
+
+    CheckedDeleteKey();
+
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .AesKey(128)
+                                                 .Authorization(TAG_PURPOSE, KeyPurpose::DECRYPT)
+                                                 .Authorization(TAG_BLOCK_MODE, BlockMode::GCM)
+                                                 .Authorization(TAG_MIN_MAC_LENGTH, 128)
+                                                 .Padding(PaddingMode::NONE)));
+
+    err = Begin(KeyPurpose::ENCRYPT,
+                AuthorizationSetBuilder().BlockMode(BlockMode::GCM).Padding(PaddingMode::NONE));
+    EXPECT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE, err) << "Got " << err;
+}
+
+/*
  * EncryptionOperationsTest.AesEcbNoPaddingWrongInputSize
  *
  * Verifies that AES encryption fails in the correct way when provided an input that is not a
@@ -3225,6 +3260,92 @@
 }
 
 /*
+ * EncryptionOperationsTest.AesGcmRoundTripWithDelaySuccess
+ *
+ * Verifies that AES GCM mode works, even when there's a long delay
+ * between operations.
+ */
+TEST_F(EncryptionOperationsTest, AesGcmRoundTripWithDelaySuccess) {
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                             .Authorization(TAG_NO_AUTH_REQUIRED)
+                                             .AesEncryptionKey(128)
+                                             .Authorization(TAG_BLOCK_MODE, BlockMode::GCM)
+                                             .Padding(PaddingMode::NONE)
+                                             .Authorization(TAG_MIN_MAC_LENGTH, 128)));
+
+    string aad = "foobar";
+    string message = "123456789012345678901234567890123456";
+
+    auto begin_params = AuthorizationSetBuilder()
+                            .BlockMode(BlockMode::GCM)
+                            .Padding(PaddingMode::NONE)
+                            .Authorization(TAG_MAC_LENGTH, 128);
+
+    auto update_params =
+        AuthorizationSetBuilder().Authorization(TAG_ASSOCIATED_DATA, aad.data(), aad.size());
+
+    // Encrypt
+    AuthorizationSet begin_out_params;
+    ASSERT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, begin_params, &begin_out_params))
+        << "Begin encrypt";
+    string ciphertext;
+    AuthorizationSet update_out_params;
+    sleep(5);
+    ASSERT_EQ(ErrorCode::OK,
+              Finish(op_handle_, update_params, message, "", &update_out_params, &ciphertext));
+
+    ASSERT_EQ(ciphertext.length(), message.length() + 16);
+
+    // Grab nonce
+    begin_params.push_back(begin_out_params);
+
+    // Decrypt.
+    ASSERT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, begin_params)) << "Begin decrypt";
+    string plaintext;
+    size_t input_consumed;
+    sleep(5);
+    ASSERT_EQ(ErrorCode::OK, Update(op_handle_, update_params, ciphertext, &update_out_params,
+                                    &plaintext, &input_consumed));
+    EXPECT_EQ(ciphertext.size(), input_consumed);
+    sleep(5);
+    EXPECT_EQ(ErrorCode::OK, Finish("", &plaintext));
+    EXPECT_EQ(message.length(), plaintext.length());
+    EXPECT_EQ(message, plaintext);
+}
+
+/*
+ * EncryptionOperationsTest.AesGcmDifferentNonces
+ *
+ * Verifies that encrypting the same data with different nonces produces different outputs.
+ */
+TEST_F(EncryptionOperationsTest, AesGcmDifferentNonces) {
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                                 .Authorization(TAG_NO_AUTH_REQUIRED)
+                                                 .AesEncryptionKey(128)
+                                                 .Authorization(TAG_BLOCK_MODE, BlockMode::GCM)
+                                                 .Padding(PaddingMode::NONE)
+                                                 .Authorization(TAG_MIN_MAC_LENGTH, 128)
+                                                 .Authorization(TAG_CALLER_NONCE)));
+
+    string aad = "foobar";
+    string message = "123456789012345678901234567890123456";
+    string nonce1 = "000000000000";
+    string nonce2 = "111111111111";
+    string nonce3 = "222222222222";
+
+    string ciphertext1 =
+            EncryptMessage(message, BlockMode::GCM, PaddingMode::NONE, 128, HidlBuf(nonce1));
+    string ciphertext2 =
+            EncryptMessage(message, BlockMode::GCM, PaddingMode::NONE, 128, HidlBuf(nonce2));
+    string ciphertext3 =
+            EncryptMessage(message, BlockMode::GCM, PaddingMode::NONE, 128, HidlBuf(nonce3));
+
+    ASSERT_NE(ciphertext1, ciphertext2);
+    ASSERT_NE(ciphertext1, ciphertext3);
+    ASSERT_NE(ciphertext2, ciphertext3);
+}
+
+/*
  * EncryptionOperationsTest.AesGcmTooShortTag
  *
  * Verifies that AES GCM mode fails correctly when a too-short tag length is specified.
@@ -4456,6 +4577,84 @@
     EXPECT_EQ(result, std::make_pair(ErrorCode::OK, HidlBuf()));
 }
 
+
+using ClearOperationsTest = KeymasterHidlTest;
+
+/*
+ * ClearSlotsTest.TooManyOperations
+ *
+ * Verifies that TOO_MANY_OPERATIONS is returned after the max number of
+ * operations are started without being finished or aborted. Also verifies
+ * that aborting the operations clears the operations.
+ *
+ */
+TEST_F(ClearOperationsTest, TooManyOperations) {
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                             .Authorization(TAG_NO_AUTH_REQUIRED)
+                                             .RsaEncryptionKey(2048, 65537)
+                                             .Padding(PaddingMode::NONE)));
+
+    auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
+    int max_operations = SecLevel() == SecurityLevel::STRONGBOX ? 4 : 16;
+    OperationHandle op_handles[max_operations];
+    AuthorizationSet out_params;
+    for(int i=0; i<max_operations; i++) {
+        EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &(op_handles[i])));
+    }
+    EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS,
+         Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_));
+    // Try again just in case there's a weird overflow bug
+    EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS,
+         Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_));
+    for(int i=0; i<max_operations; i++) {
+        EXPECT_EQ(ErrorCode::OK, Abort(op_handles[i]));
+    }
+    EXPECT_EQ(ErrorCode::OK,
+         Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_));
+    AbortIfNeeded();
+}
+
+/*
+ * ClearSlotsTest.ServiceDeath
+ *
+ * Verifies that the service is restarted after death and the ongoing
+ * operations are cleared.
+ */
+TEST_F(ClearOperationsTest, ServiceDeath) {
+
+    ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+                                             .Authorization(TAG_NO_AUTH_REQUIRED)
+                                             .RsaEncryptionKey(2048, 65537)
+                                             .Padding(PaddingMode::NONE)));
+
+    auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
+    int max_operations = SecLevel() == SecurityLevel::STRONGBOX ? 4 : 16;
+    OperationHandle op_handles[max_operations];
+    AuthorizationSet out_params;
+    for(int i=0; i<max_operations; i++) {
+        EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &(op_handles[i])));
+    }
+    EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS,
+         Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_));
+
+    DebugInfo debug_info;
+    GetDebugInfo(&debug_info);
+    kill(debug_info.pid, SIGKILL);
+    // wait 1 second for keymaster to restart
+    sleep(1);
+    InitializeKeymaster();
+
+    for(int i=0; i<max_operations; i++) {
+        EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &(op_handles[i])));
+    }
+    EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS,
+         Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_));
+    for(int i=0; i<max_operations; i++) {
+        EXPECT_EQ(ErrorCode::OK, Abort(op_handles[i]));
+    }
+}
+
+
 }  // namespace test
 }  // namespace V4_0
 }  // namespace keymaster
diff --git a/light/utils/main.cpp b/light/utils/main.cpp
index d07e799..b834132 100644
--- a/light/utils/main.cpp
+++ b/light/utils/main.cpp
@@ -25,7 +25,7 @@
     std::cerr << msg << std::endl;
 }
 
-int main() {
+int main(int argc, char* argv[]) {
     using ::android::hardware::hidl_vec;
     using ::android::hardware::light::V2_0::Brightness;
     using ::android::hardware::light::V2_0::Flash;
@@ -41,10 +41,29 @@
         return -1;
     }
 
-    const static LightState off = {
-        .color = 0u, .flashMode = Flash::NONE, .brightnessMode = Brightness::USER,
+    static LightState off = {
+            .color = 0u,
+            .flashMode = Flash::NONE,
+            .brightnessMode = Brightness::USER,
     };
 
+    if (argc > 2) {
+        error("Usage: blank_screen [color]");
+        return -1;
+    }
+
+    if (argc > 1) {
+        char* col_ptr;
+        unsigned int col_new;
+
+        col_new = strtoul(argv[1], &col_ptr, 0);
+        if (*col_ptr != '\0') {
+            error("Failed to convert " + std::string(argv[1]) + " to number");
+            return -1;
+        }
+        off.color = col_new;
+    }
+
     service->getSupportedTypes([&](const hidl_vec<Type>& types) {
         for (Type type : types) {
             Status ret = service->setLight(type, off);
diff --git a/neuralnetworks/1.0/types.hal b/neuralnetworks/1.0/types.hal
index 02db063..ba9d068 100644
--- a/neuralnetworks/1.0/types.hal
+++ b/neuralnetworks/1.0/types.hal
@@ -25,25 +25,24 @@
  * with at least one dimension). Types not prefaced by TENSOR_* represent
  * scalar values and must have no dimensions.
  *
- * Although many types are defined, most operators accept just a few
+ * Although we define many types, most operators accept just a few
  * types. Most used are {@link OperandType::TENSOR_FLOAT32},
  * {@link OperandType::TENSOR_QUANT8_ASYMM},
  * and {@link OperandType::INT32}.
  */
 enum OperandType : int32_t {
     /** A 32 bit floating point scalar value. */
-    FLOAT32             = 0,
+    FLOAT32 = 0,
     /** A signed 32 bit integer scalar value. */
-    INT32               = 1,
+    INT32 = 1,
     /** An unsigned 32 bit integer scalar value. */
-    UINT32              = 2,
-
+    UINT32 = 2,
     /** A tensor of 32 bit floating point values. */
-    TENSOR_FLOAT32      = 3,
+    TENSOR_FLOAT32 = 3,
     /** A tensor of 32 bit integer values. */
-    TENSOR_INT32        = 4,
+    TENSOR_INT32 = 4,
     /**
-     * A tensor of 8 bit integers that represent real numbers.
+     * 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:
@@ -51,21 +50,21 @@
      * - zeroPoint: a 32 bit integer, in range [0, 255].
      *
      * The formula is:
-     * real_value = (integer_value - zeroPoint) * scale.
+     *   real_value = (integer_value - zeroPoint) * scale.
      */
     TENSOR_QUANT8_ASYMM = 5,
 
     /**
-     * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to
-     * OEM operation and data types.
+     * DEPRECATED. Since HAL version 1.2, extensions are the preferred
+     * alternative to OEM operation and data types.
      *
      * OEM specific scalar value.
      */
     OEM                 = 10000,
 
     /**
-     * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to
-     * OEM operation and data types.
+     * DEPRECATED. Since HAL version 1.2, extensions are the preferred
+     * alternative to OEM operation and data types.
      *
      * A tensor of OEM specific values.
      */
@@ -78,7 +77,6 @@
  * The type of an operation in a model.
  */
 enum OperationType : int32_t {
-
     /**
      * Adds two tensors, element-wise.
      *
@@ -110,14 +108,16 @@
      * * 0: A tensor.
      * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
      *      as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} 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.
      *
      * Outputs:
      * * 0: The sum, a tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
      */
     ADD = 0,
 
@@ -187,8 +187,8 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, out_height, out_width, depth].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     AVERAGE_POOL_2D = 1,
 
@@ -206,22 +206,23 @@
      *
      * Inputs:
      * * 0 ~ n-1: The list of n input tensors, of shape
-     *            [D0, D1, ..., Daxis(i), ..., Dm]. For inputs of
-     *            {@link OperandType::TENSOR_QUANT8_ASYMM}, all input tensors
-     *            must have the same scale and zeroPoint.
+     *            [D0, D1, ..., Daxis(i), ..., Dm].
+     *            All input tensors of
+     *            {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *            must have the same scale and zeroPoint as the output tensor.
      * * 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].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, the scale and zeroPoint
+     *      values must be the same as the input tensors'.
      */
     CONCATENATION = 2,
 
     /**
-     * Performs an 2-D convolution operation.
+     * 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
@@ -238,11 +239,17 @@
      *             filter[channel, di, dj, k]
      *         ) + bias[channel]
      *
-     * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * Supported tensor {@link OperandType} configurations:
+     * * 32 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
      *
-     * Supported tensor rank: 4, with "NHWC" data layout.
+     * * 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).
+     *
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
      * Both explicit padding and implicit padding are supported.
      *
@@ -252,12 +259,12 @@
      * * 1: A 4-D tensor, of shape
      *      [depth_out, filter_height, filter_width, depth_in], specifying the
      *      filter.
-     * * 2: A 1-D tensor, of shape [depth_out], 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}, the bias
-     *      should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of
-     *      0 and bias_scale == input_scale * filter_scale.
+     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32}
+     *      the bias must be of the same
+     *      type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      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, specifying the padding on
      *      the left, in the ‘width’ dimension.
      * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
@@ -281,11 +288,11 @@
      *      [depth_out, filter_height, filter_width, depth_in], specifying the
      *      filter.
      * * 2: A 1-D tensor, of shape [depth_out], 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}, the bias should be
-     *      of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 and
-     *      bias_scale == input_scale * filter_scale.
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32}
+     *      the bias must be of the same
+     *      type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      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, specifying the implicit
      *      padding scheme, has to be one of the
      *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
@@ -299,11 +306,9 @@
      *
      * Outputs:
      * * 0: The output 4-D tensor, of shape
-     *      [batches, out_height, out_width, depth_out]. For output tensor of
-     *      {@link OperandType::TENSOR_QUANT8_ASYMM}, the following condition
-     *      must be satisfied: output_scale > input_scale * filter_scale.
-     *
-     * Available since API level 27.
+     *      [batches, out_height, out_width, depth_out].
+     *      For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      the following condition must be satisfied: output_scale > input_scale * filter_scale
      */
     CONV_2D = 3,
 
@@ -329,11 +334,17 @@
      *             filter[1, di, dj, k * channel_multiplier + q]
      *         ) + bias[k * channel_multiplier + q]
      *
-     * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     * Supported tensor {@link OperandType} configurations:
+     * * 32 bit floating point:
+     * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
      *
-     * Supported tensor rank: 4, with "NHWC" data layout.
+     * * 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).
+     *
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
      * Both explicit padding and implicit padding are supported.
      *
@@ -343,11 +354,11 @@
      * * 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 {@link OperandType::TENSOR_FLOAT32}, the bias should
-     *      also be of {@link OperandType::TENSOR_FLOAT32}. For input tensor
-     *      of {@link OperandType::TENSOR_QUANT8_ASYMM}, the bias should be
-     *      of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 and
-     *      bias_scale == input_scale * filter_scale.
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32}
+     *      the bias must be of the same
+     *      type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      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, specifying the padding on
      *      the left, in the ‘width’ dimension.
      * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
@@ -372,11 +383,11 @@
      * * 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 {@link OperandType::TENSOR_FLOAT32}, the bias should
-     *      also be of {@link OperandType::TENSOR_FLOAT32}. For input tensor
-     *      of {@link OperandType::TENSOR_QUANT8_ASYMM}, the bias should be
-     *      of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 and
-     *      bias_scale == input_scale * filter_scale.
+     *      tensor of type {@link OperandType::TENSOR_FLOAT32}
+     *      the bias must be of the same
+     *      type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+     *      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, specifying the implicit
      *      padding scheme, has to be one of the
      *      following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
@@ -392,11 +403,10 @@
      *
      * Outputs:
      * * 0: The output 4-D tensor, of shape
-     *      [batches, out_height, out_width, depth_out]. For output tensor of
-     *      {@link OperandType::TENSOR_QUANT8_ASYMM}, the following condition
-     *      must be satisfied: output_scale > input_scale * filter_scale.
-     *
-     * Available since API level 27.
+     *      [batches, out_height, out_width, depth_out]. 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,
 
@@ -419,7 +429,8 @@
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
-     * Supported tensor rank: 4, with "NHWC" data layout.
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
      * Inputs:
      * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
@@ -431,8 +442,8 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape [batch, height*block_size,
      *      width*block_size, depth/(block_size*block_size)].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     DEPTH_TO_SPACE = 5,
 
@@ -443,19 +454,19 @@
      *
      *     output = (input - zeroPoint) * scale.
      *
-     * Supported tensor {@link OperandType}:
+     * Supported input tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
+     * Supported output tensor {@link OperandType}:
+     * * {@link OperandType::TENSOR_FLOAT32}.
+     *
      * Supported tensor rank: up to 4
      *
      * Inputs:
-     * * 0: A tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}.
+     * * 0: A tensor.
      *
      * Outputs:
-     * * 0: The output tensor of same shape as input0, but with
-     *      {@link OperandType::TENSOR_FLOAT32}.
-     *
-     * Available since API level 27.
+     * * 0: A tensor with the same shape as input0.
      */
     DEQUANTIZE = 6,
 
@@ -479,6 +490,13 @@
      * 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_FLOAT32}
+     * * {@link OperandType::TENSOR_INT32}
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *
+     * 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.
@@ -489,8 +507,8 @@
      * * 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.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input1.
      */
     EMBEDDING_LOOKUP = 7,
 
@@ -508,8 +526,6 @@
      * Outputs:
      * * 0: The output tensor, of the same {@link OperandType} and dimensions as
      *      the input tensor.
-     *
-     * Available since API level 27.
      */
     FLOOR = 8,
 
@@ -549,12 +565,9 @@
      *      invoke on the result.
      *
      * Outputs:
-     * * 0: The output tensor, of shape [batch_size, num_units]. For output
-     *      tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the following
-     *      condition must be satisfied:
-     *      output_scale > input_scale * filter_scale.
-     *
-     * Available since API level 27.
+     * * 0: The output tensor, of shape [batch_size, num_units]. For
+     *      output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the following
+     *      condition must be satisfied: output_scale > input_scale * filter_scale.
      */
     FULLY_CONNECTED = 9,
 
@@ -585,6 +598,13 @@
      * 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 ].
@@ -598,13 +618,13 @@
      *
      * 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.
-     *
-     * Available since API level 27.
      */
     HASHTABLE_LOOKUP = 10,
 
@@ -617,9 +637,6 @@
      *         input[batch, row, col, channel] /
      *         sqrt(sum_{c} pow(input[batch, row, col, c], 2))
      *
-     * For input tensor with more dimensions, independently normalizes each 1-D
-     * slice along dimension dim.
-     *
      * Supported tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_FLOAT32}
      *
@@ -627,13 +644,10 @@
      * Height, Width, and Channels).
      *
      * Inputs:
-     * * 0: A 4-D tensor, of shape [batches, height, width, depth].
+     * * 0: A 4-D tensor, specifying the tensor to be normalized.
      *
      * Outputs:
-     * * 0: The output 4-D tensor, of the same shape as input
-     *      [batches, height, width, depth].
-     *
-     * Available since API level 27.
+     * * 0: A tensor of the same {@link OperandType} and same shape as input0.
      */
     L2_NORMALIZATION = 11,
 
@@ -652,7 +666,8 @@
      * Supported tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_FLOAT32}
      *
-     * Supported tensor rank: 4, with "NHWC" data layout.
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
      * Both explicit padding and implicit padding are supported.
      *
@@ -700,8 +715,6 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, out_height, out_width, depth].
-     *
-     * Available since API level 27.
      */
     L2_POOL_2D = 12,
 
@@ -729,17 +742,18 @@
      *      the input.
      * * 1: An {@link OperandType::INT32} scalar, specifying the radius of
      *      the normalization window.
-     * * 2: An {@link OperandType::FLOAT32} scalar, specifying the bias, must
-     *      not be zero.
-     * * 3: An {@link OperandType::FLOAT32} scalar, specifying the scale
-     *      factor, alpha.
-     * * 4: An {@link OperandType::FLOAT32} scalar, specifying the exponent,
-     *      beta.
+     * * 2: A scalar, specifying the bias, must not be zero.
+     *      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_FLOAT32}, the
+     *      alpha value must be of {@link OperandType::FLOAT32}.
+     * * 4: A scalar, specifying the exponent, beta.
+     *      For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta
+     *      value must be of {@link OperandType::FLOAT32}.
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 27.
      */
     LOCAL_RESPONSE_NORMALIZATION = 13,
 
@@ -763,45 +777,53 @@
      * * 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.
-     *
-     * Available since API level 27.
      */
     LOGISTIC = 14,
 
     /**
      * Projects an input to a bit vector via locality senstive hashing.
      *
+     * Supported input tensor {@link OperandType}:
+     * * {@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]: Number of hash functions.
-     *            Tensor[0].Dim[1]: Number of seeds per hash functions.
-     *            Tensor[0].Dim[1] <= 32 in sparse case.
+     *      Tensor[0].Dim[0]: Number of hash functions.
+     *      Tensor[0].Dim[1]: Number 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]
+     *      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(=1).
+     *        Sparse:
+     *          Value LSHProjectionType_SPARSE(=1).
      *          Computed bit vector is considered to be sparse.
      *          Each output element is an int32 made up of multiple bits
      *          computed from hash functions.
      *
-     *        Dense: Value LSHProjectionType_DENSE(=2).
+     *        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.
+     * * 0: If the projection type is Sparse:
+     *      Output.Dim == { Tensor[0].Dim[0] }
+     *      A tensor of int32 that represents hash signatures.
      *
-     * Available since API level 27.
+     *      If the projection type is Dense:
+     *      Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] }
+     *      A flattened tensor that represents projected bit vectors.
      */
     LSH_PROJECTION = 15,
 
@@ -901,71 +923,54 @@
      * Supported tensor {@link OperandType}:
      * * {@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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, input_size], where “batch_size” corresponds to the
-     *      batching dimension, and “input_size” is the size of the 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: The input-to-input weights (\f$W_{xi}\f$). Optional.
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, input_size], where “num_units” corresponds to the
-     *      number of cell units.
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, input_size].
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, input_size].
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, input_size].
+     *      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 {@link OperandType::TENSOR_FLOAT32}, 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.
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, output_size].
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, output_size].
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, output_size].
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units].
+     *      A 1-D tensor of shape [num_units].
      * * 10:The cell-to-forget weights (\f$W_{cf}\f$). Optional.
-     *      A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units].
+     *      A 1-D tensor of shape [num_units].
      * * 11:The cell-to-output weights (\f$W_{co}\f$). Optional.
-     *      A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units].
+     *      A 1-D tensor of shape [num_units].
      * * 12:The input gate bias (\f$b_i\f$). Optional.
-     *      A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units].
+     *      A 1-D tensor of shape [num_units].
      * * 13:The forget gate bias (\f$b_f\f$).
-     *      A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units].
+     *      A 1-D tensor of shape [num_units].
      * * 14:The cell bias (\f$b_c\f$).
-     *      A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units].
+     *      A 1-D tensor of shape [num_units].
      * * 15:The output gate bias (\f$b_o\f$).
-     *      A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units].
+     *      A 1-D tensor of shape [num_units].
      * * 16:The projection weights (\f$W_{proj}\f$). Optional.
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [output_size, num_units].
+     *      A 2-D tensor of shape [output_size, num_units].
      * * 17:The projection bias (\f$b_{proj}\f$). Optional.
-     *      A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [output_size].
+     *      A 1-D tensor of shape [output_size].
      * * 18:The output state (in) (\f$h_{t-1}\f$).
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, output_size].
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, num_units].
+     *      A 2-D tensor of shape [batch_size, num_units].
      * * 20:The activation function (\f$g\f$).
      *      A value indicating the activation function:
      *      <ul>
@@ -984,21 +989,15 @@
      *
      * Outputs:
      * * 0: The scratch buffer.
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, num_units * 3] with CIFG, or
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, output_size].
+     *      A 2-D tensor of shape [batch_size, output_size].
      * * 2: The cell state (out) (\f$C_t\f$).
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, num_units].
+     *      A 2-D tensor of shape [batch_size, num_units].
      * * 3: The output (\f$o_t\f$).
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, output_size]. This is effectively the same as the
-     *      current “output state (out)” value.
-     *
-     * Available since API level 27.
+     *      A 2-D tensor of shape [batch_size, output_size]. This is effectively
+     *      the same as the current “output state (out)” value.
      */
     LSTM = 16,
 
@@ -1019,7 +1018,8 @@
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
-     * Supported tensor rank: 4, with "NHWC" data layout.
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
      * Both explicit padding and implicit padding are supported.
      *
@@ -1067,8 +1067,8 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, out_height, out_width, depth].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     MAX_POOL_2D = 17,
 
@@ -1106,8 +1106,6 @@
      *      For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
      *      the following condition must be satisfied:
      *      output_scale > input1_scale * input2_scale.
-     *
-     * Available since API level 27.
      */
     MUL = 18,
 
@@ -1129,8 +1127,8 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RELU = 19,
 
@@ -1151,9 +1149,9 @@
      * * 0: A tensor, specifying the input.
      *
      * Outputs:
-     * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 27.
+     * * 0: The output tensor of the same shape as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RELU1 = 20,
 
@@ -1175,8 +1173,8 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RELU6 = 21,
 
@@ -1205,8 +1203,8 @@
      *
      * Outputs:
      * * 0: The output tensor, of shape specified by the input shape.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RESHAPE = 22,
 
@@ -1220,9 +1218,10 @@
      * Supported tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_FLOAT32}
      *
-     * Supported tensor rank: 4, with "NHWC" data layout.
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
-     * Inputs:
+     * Inputs (resizing by shape):
      * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
      *      the input.
      * * 1: An {@link OperandType::INT32} scalar, specifying the output
@@ -1233,8 +1232,6 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, new_height, new_width, depth].
-     *
-     * Available since API level 27.
      */
     RESIZE_BILINEAR = 23,
 
@@ -1257,25 +1254,23 @@
      * Supported tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_FLOAT32}
      *
+     * The input tensors must all be the same type.
+     *
      * Inputs:
      * * 0: input.
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32} of shape
-     *      [batch_size, input_size], where “batch_size” corresponds to the
-     *      batching dimension, and “input_size” is the size of the 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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, input_size], where “num_units” corresponds to the
-     *      number of units.
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, num_units], with columns corresponding to the weights
-     *      from each unit.
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units].
+     *      A 1-D tensor of shape [num_units].
      * * 4: hidden state (in).
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, num_units].
+     *      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
@@ -1283,15 +1278,11 @@
      *
      * Outputs:
      * * 0: hidden state (out).
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, num_units].
+     *      A 2-D tensor of shape [batch_size, num_units].
      *
      * * 1: output.
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, num_units]. This is effectively the same as the
-     *      current state value.
-     *
-     * Available since API level 27.
+     *      A 2-D tensor of shape [batch_size, num_units]. This is effectively
+     *      the same as the current state value.
      */
     RNN = 24,
 
@@ -1306,6 +1297,9 @@
      *         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_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
@@ -1314,15 +1308,15 @@
      *
      * Inputs:
      * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped.
-     * * 1: An {@link OperandType::FLOAT32} scalar, specifying the positive
-     *      scaling factor for the exponent, beta.
+     * * 1: A scalar, specifying the positive scaling factor for the exponent,
+     *      beta. If input0 is of {@link OperandType::TENSOR_FLOAT32} or
+     *      {@link OperandType::TENSOR_QUANT8_ASYMM}, the scalar must be of
+     *      {@link OperandType::FLOAT32}.
      *
      * 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.
-     *
-     * Available since API level 27.
      */
     SOFTMAX = 25,
 
@@ -1344,7 +1338,8 @@
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
-     * Supported tensor rank: 4, with "NHWC" data layout.
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
      * Inputs:
      * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
@@ -1356,8 +1351,8 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape [batches, height/block_size,
      *      width/block_size, depth_in*block_size*block_size].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     SPACE_TO_DEPTH = 26,
 
@@ -1403,25 +1398,23 @@
      * Supported tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_FLOAT32}
      *
+     * All input tensors must be the same type.
+     *
      * Inputs:
      * * 0: input.
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, input_size], where “batch_size” corresponds to the
-     *      batching dimension, and “input_size” is the size of the 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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, input_size], where “num_units” corresponds to the
-     *      number of units.
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [num_units, memory_size], where “memory_size” corresponds to the
-     *      fixed-size of the memory.
+     *      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 {@link OperandType::TENSOR_FLOAT32},
-     *      of shape [num_units].
+     *      An optional 1-D tensor of shape [num_units].
      * * 4: state (in).
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
-     *      [batch_size, (memory_size - 1) * num_units * rank].
+     *      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.
@@ -1431,13 +1424,11 @@
      *
      * Outputs:
      * * 0: state (out).
-     *      A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
+     *      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 {@link OperandType::TENSOR_FLOAT32}, of shape
+     *      A 2-D tensor of the same {@link OperandType} as the inputs, with shape
      *      [batch_size, num_units].
-     *
-     * Available since API level 27.
      */
     SVDF = 27,
 
@@ -1458,8 +1449,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 27.
      */
     TANH = 28,
 
diff --git a/neuralnetworks/1.0/types.t b/neuralnetworks/1.0/types.t
new file mode 100644
index 0000000..d7b26aa
--- /dev/null
+++ b/neuralnetworks/1.0/types.t
@@ -0,0 +1,431 @@
+%% template file for generating types.hal.
+%% see frameworks/ml/nn/tools/api/README.md.
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks@1.0;
+
+%insert Operand_1.0_Comment
+enum OperandType : int32_t {
+%insert Operand_1.0
+
+    /**
+     * DEPRECATED. Since HAL version 1.2, extensions are the preferred
+     * alternative to OEM operation and data types.
+     *
+     * OEM specific scalar value.
+     */
+    OEM                 = 10000,
+
+    /**
+     * DEPRECATED. Since HAL version 1.2, extensions are the preferred
+     * alternative to OEM operation and data types.
+     *
+     * A tensor of OEM specific values.
+     */
+    TENSOR_OEM_BYTE     = 10001,
+};
+
+%insert Operation_1.0_Comment
+enum OperationType : int32_t {
+%insert Operation_1.0
+
+    /**
+     * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to
+     * OEM operation and data types.
+     *
+     * This operation is OEM specific. It should only be used for OEM
+     * applications.
+     */
+    OEM_OPERATION = 10000,
+};
+
+/**
+ * Fused activation function types.
+ */
+enum FusedActivationFunc : int32_t {
+    NONE  = 0,
+    RELU  = 1,
+    RELU1 = 2,
+    RELU6 = 3,
+};
+
+/**
+ * How an operand is used.
+ */
+enum OperandLifeTime : int32_t {
+    /**
+     * 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 the model. It must not be an output
+     * operand of any operation.
+     *
+     * An operand can't be both input and output of a model.
+     */
+    MODEL_INPUT,
+
+    /**
+     * The operand is an output of the model. It must be an output
+     * operand of exactly one operation.
+     *
+     * An operand can't be both input and output of a model.
+     */
+    MODEL_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_REFERENCE,
+
+    /**
+     * The operand does not have a value. This is valid only for optional
+     * arguments of operations.
+     */
+    NO_VALUE,
+};
+
+/**
+ * Status of a device.
+ */
+enum DeviceStatus : int32_t {
+    AVAILABLE,
+    BUSY,
+    OFFLINE,
+    UNKNOWN,
+};
+
+/**
+ * Performance information for the reference workload.
+ *
+ * Used by a driver to report its performance characteristics.
+ */
+struct 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;
+};
+
+/**
+ * The capabilities of a driver.
+ */
+struct Capabilities {
+    /**
+     * Driver performance when operating on float32 data.
+     */
+    PerformanceInfo float32Performance;
+
+    /**
+     * Driver performance when operating on asymmetric 8-bit quantized data.
+     */
+    PerformanceInfo quantized8Performance;
+};
+
+/**
+ * Describes the location of a data object.
+ */
+struct DataLocation {
+    /**
+     * The index of the memory pool where this location is found.
+     */
+    uint32_t poolIndex;
+
+    /**
+     * Offset in bytes from the start of the pool.
+     */
+    uint32_t offset;
+
+    /**
+     * The length of the data in bytes.
+     */
+    uint32_t length;
+};
+
+/**
+ * Describes one operand of the model's graph.
+ */
+struct Operand {
+    /**
+     * Data type of the operand.
+     */
+    OperandType type;
+
+    /**
+     * Dimensions of the operand.
+     *
+     * For a scalar operand, dimensions.size() must be 0.
+     *
+     * For a tensor operand, dimensions.size() must be at least 1;
+     * however, any of the dimensions may be unspecified.
+     *
+     * 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.
+     *
+     * In the following situations, a tensor operand's dimensions must
+     * be fully specified:
+     *
+     *     . The operand has lifetime CONSTANT_COPY or
+     *       CONSTANT_REFERENCE.
+     *
+     *     . The operand has lifetime MODEL_INPUT or MODEL_OUTPUT. Fully
+     *       specified dimensions must either be present in the
+     *       Operand or they must be provided in the corresponding
+     *       RequestArgument.
+     *       EXCEPTION: If the input or output 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.
+     */
+    vec<uint32_t> dimensions;
+
+    /**
+     * The number of times this operand appears as an operation input.
+     *
+     * (For example, if this operand appears once in one operation's
+     * input list, and three times in another operation's input list,
+     * then numberOfConsumers = 4.)
+     */
+    uint32_t numberOfConsumers;
+
+    /**
+     * Quantized scale of the operand.
+     *
+     * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or
+     * TENSOR_INT32.
+     */
+    float scale;
+
+    /**
+     * Quantized zero-point offset of the operand.
+     *
+     * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM.
+     */
+    int32_t zeroPoint;
+
+    /**
+     * How the operand is used.
+     */
+    OperandLifeTime lifetime;
+
+    /**
+     * Where to find the data for this operand.
+     * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_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_REFERENCE:
+     * - location.poolIndex is set.
+     * - location.offset is the offset in bytes into the specified pool.
+     * - location.length is set.
+     */
+    DataLocation location;
+};
+
+/**
+ * Describes one operation of the model's graph.
+ */
+struct Operation {
+    /**
+     * The operation type.
+     */
+    OperationType type;
+
+    /**
+     * Describes the table that contains the indexes of the inputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> inputs;
+
+    /**
+     * Describes the table that contains the indexes of the outputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> outputs;
+};
+
+/**
+ * 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
+ * might not be known is the shape of the input tensors.
+ */
+struct Model {
+    /**
+     * All operands included in the model.
+     */
+    vec<Operand> operands;
+
+    /**
+     * All operations included in the model.
+     *
+     * The operations are sorted into execution order. Every operand
+     * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be
+     * written before it is read.
+     */
+    vec<Operation> operations;
+
+    /**
+     * Input indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> inputIndexes;
+
+    /**
+     * Output indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> outputIndexes;
+
+    /**
+     * 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.
+     */
+    vec<uint8_t> 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_REFERENCE.
+     */
+    vec<memory> pools;
+};
+
+/**
+ * Metadata information specifying the location of the input or output data and
+ * any updates to the input or output operand.
+ */
+struct 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.
+     */
+    bool 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.
+     */
+    vec<uint32_t> dimensions;
+};
+
+/**
+ * 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_REFERENCE.
+ */
+struct 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.inputIndexes.
+     *   E.g., input[i] corresponds to Model.inputIndexes[i].
+     */
+    vec<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.outputIndexes.
+     *   E.g., output[i] corresponds to Model.outputIndexes[i].
+     */
+    vec<RequestArgument> outputs;
+
+    /**
+     * A collection of shared memory pools containing operand data for both the
+     * inputs and the outputs to a model.
+     */
+    vec<memory> pools;
+};
+
+/**
+ * Return status of a function.
+ */
+enum ErrorStatus : int32_t {
+    NONE,
+    DEVICE_UNAVAILABLE,
+    GENERAL_FAILURE,
+    OUTPUT_INSUFFICIENT_SIZE,
+    INVALID_ARGUMENT,
+};
diff --git a/neuralnetworks/1.1/types.hal b/neuralnetworks/1.1/types.hal
index 73705bb..3d78fb6 100644
--- a/neuralnetworks/1.1/types.hal
+++ b/neuralnetworks/1.1/types.hal
@@ -26,7 +26,6 @@
  * The type of an operation in a model.
  */
 enum OperationType : @1.0::OperationType {
-
     /**
      * BatchToSpace for N-dimensional tensors.
      *
@@ -41,7 +40,8 @@
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
-     * Supported tensor rank: 4
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
      * Inputs:
      * * 0: An n-D tensor, specifying the tensor to be reshaped
@@ -51,8 +51,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     BATCH_TO_SPACE_ND = 29,
 
@@ -91,8 +91,6 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
      */
     DIV = 30,
 
@@ -126,8 +124,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be same as input0.
      */
     MEAN = 31,
 
@@ -138,7 +136,8 @@
      *
      * Supported tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (the pad value is undefined)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *   (the pad value is undefined)
      *
      * Supported tensor rank: up to 4
      *
@@ -160,11 +159,8 @@
      *      of the padding:
      *          output0.dimension[i] =
      *              padding[i, 0] + input0.dimension[i] + padding[i, 1]
-     *
-     *      NOTE: The pad value for {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}
-     *      is undefined.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     PAD = 32,
 
@@ -182,8 +178,10 @@
      * Supported tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *   (the pad value is undefined)
      *
-     * Supported tensor rank: 4
+     * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width,
+     * and Channels) data layout.
      *
      * Inputs:
      * * 0: An n-D tensor, specifying the input.
@@ -201,8 +199,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     SPACE_TO_BATCH_ND = 33,
 
@@ -232,8 +230,8 @@
      * * 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.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     SQUEEZE = 34,
 
@@ -278,8 +276,8 @@
      * 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.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     STRIDED_SLICE = 35,
 
@@ -318,8 +316,6 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
      */
     SUB = 36,
 
@@ -345,11 +341,10 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     TRANSPOSE = 37,
-
 };
 
 /**
diff --git a/neuralnetworks/1.1/types.t b/neuralnetworks/1.1/types.t
new file mode 100644
index 0000000..75ac2e7
--- /dev/null
+++ b/neuralnetworks/1.1/types.t
@@ -0,0 +1,158 @@
+%% template file for generating types.hal.
+%% see frameworks/ml/nn/tools/api/README.md.
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks@1.1;
+
+import @1.0::Operand;
+import @1.0::OperationType;
+import @1.0::PerformanceInfo;
+
+/**
+ * Operation types.
+ *
+ * The type of an operation in a model.
+ */
+enum OperationType : @1.0::OperationType {
+%insert Operation_1.1
+};
+
+/**
+ * The capabilities of a driver.
+ */
+struct Capabilities {
+    /**
+     * Driver performance when operating on float32 data.
+     */
+    PerformanceInfo float32Performance;
+
+    /**
+     * Driver performance when operating on asymmetric 8-bit quantized data.
+     */
+    PerformanceInfo quantized8Performance;
+
+    /**
+     * 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 relaxedFloat32toFloat16Performance;
+};
+
+/**
+ * Describes one operation of the model's graph.
+ */
+struct Operation {
+    /**
+     * The operation type.
+     */
+    OperationType type;
+
+    /**
+     * Describes the table that contains the indexes of the inputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> inputs;
+
+    /**
+     * Describes the table that contains the indexes of the outputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> outputs;
+};
+
+/**
+ * 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.
+ */
+struct Model {
+    /**
+     * All operands included in the model.
+     */
+    vec<Operand> operands;
+
+    /**
+     * All operations included in the model.
+     *
+     * The operations are sorted into execution order. Every operand
+     * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be
+     * written before it is read.
+     */
+    vec<Operation> operations;
+
+    /**
+     * Input indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> inputIndexes;
+
+    /**
+     * Output indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> outputIndexes;
+
+    /**
+     * 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.
+     */
+    vec<uint8_t> 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_REFERENCE.
+     */
+    vec<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.
+     */
+    bool relaxComputationFloat32toFloat16;
+};
+
+/**
+ * Execution preferences.
+ */
+enum ExecutionPreference : int32_t {
+    /**
+     * Prefer executing in a way that minimizes battery drain.
+     * This is desirable for compilations that will be executed often.
+     */
+    LOW_POWER = 0,
+    /**
+     * Prefer returning a single answer as fast as possible, even if this causes
+     * more power consumption.
+     */
+    FAST_SINGLE_ANSWER = 1,
+    /**
+     * Prefer maximizing the throughput of successive frames, for example when
+     * processing successive frames coming from the camera.
+     */
+    SUSTAINED_SPEED = 2,
+};
diff --git a/neuralnetworks/1.2/types.hal b/neuralnetworks/1.2/types.hal
index f368ce2..837ced5 100644
--- a/neuralnetworks/1.2/types.hal
+++ b/neuralnetworks/1.2/types.hal
@@ -43,8 +43,6 @@
      *
      * Values of this operand type are either true or false. A zero value
      * represents false; any other value represents true.
-     *
-     * Available since API level 29.
      */
     BOOL = 6,
     /**
@@ -55,14 +53,10 @@
      * realValue = integerValue * scale.
      *
      * scale is a 32 bit floating point with value greater than zero.
-     *
-     * Available since API level 29.
      */
     TENSOR_QUANT16_SYMM = 7,
     /**
      * A tensor of IEEE 754 16 bit floating point values.
-     *
-     * Available since API level 29.
      */
     TENSOR_FLOAT16 = 8,
     /**
@@ -70,14 +64,10 @@
      *
      * Values of this operand type are either true or false. A zero value
      * represents false; any other value represents true.
-     *
-     * Available since API level 29.
      */
     TENSOR_BOOL8 = 9,
     /**
      * An IEEE 754 16 bit floating point scalar value.
-     *
-     * Available since API level 29.
      */
     FLOAT16 = 10,
     /**
@@ -90,14 +80,13 @@
      * - 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.
-     *
-     * Available since API level 29.
      */
     TENSOR_QUANT8_SYMM_PER_CHANNEL = 11,
     /**
@@ -110,8 +99,6 @@
      *
      * The formula is:
      * real_value = (integer_value - zeroPoint) * scale.
-     *
-     * Available since API level 29.
      */
     TENSOR_QUANT16_ASYMM = 12,
     /**
@@ -122,20 +109,19 @@
      * realValue = integerValue * scale.
      *
      * scale is a 32 bit floating point with value greater than zero.
-     *
-     * Available since API level 29.
      */
     TENSOR_QUANT8_SYMM = 13,
+
     /*
-     * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to
-     * OEM operation and data types.
+     * DEPRECATED. Since HAL version 1.2, extensions are the preferred
+     * alternative to OEM operation and data types.
      *
      * OEM specific scalar value.
      * OEM                 = 10000,
      */
     /*
-     * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to
-     * OEM operation and data types.
+     * DEPRECATED. Since HAL version 1.2, extensions are the preferred
+     * alternative to OEM operation and data types.
      *
      * A tensor of OEM specific values.
      * TENSOR_OEM_BYTE     = 10001,
@@ -166,6 +152,7 @@
  * The type of an operation in a model.
  */
 enum OperationType : int32_t {
+
     /**
      * Adds two tensors, element-wise.
      *
@@ -187,12 +174,12 @@
      *     input2.dimension = {5, 4, 3, 1}
      *     output.dimension = {5, 4, 3, 2}
      *
-     * Since API level 29, generic zero-sized input tensor is supported. Zero
+     * 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 API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -202,14 +189,16 @@
      * * 0: A tensor.
      * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
      *      as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} 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.
      *
      * Outputs:
      * * 0: The sum, a tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
      */
     ADD = @1.1::OperationType:ADD,
 
@@ -227,7 +216,7 @@
      *         ) / sum(1)
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -235,13 +224,14 @@
      * 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 API level 29, zero batches is supported for this
-     *      tensor.
+     *      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
@@ -263,12 +253,12 @@
      *      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 API level 29.
+     *       Available since HAL version 1.2.
      *
      * Inputs (implicit padding):
      * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
-     *      the input. Since API level 29, zero batches is supported for this
-     *      tensor.
+     *      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)}.
@@ -285,13 +275,13 @@
      *      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 API level 29.
+     *      Available since HAL version 1.2.
      *
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, out_height, out_width, depth].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     AVERAGE_POOL_2D = @1.1::OperationType:AVERAGE_POOL_2D,
 
@@ -302,33 +292,34 @@
      * dimensions except the dimension along the concatenation axis.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (full support since API
-     *   level 29, see the input section)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *   (full support since HAL version 1.2, see the input section)
      *
      * Supported tensor rank: up to 4
      *
      * Inputs:
      * * 0 ~ n-1: The list of n input tensors, of shape
      *            [D0, D1, ..., Daxis(i), ..., Dm].
-     *            Before API level 29, all input tensors of
+     *            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.
-     *            Since API level 29, zero-sized tensors are supported.
+     *            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].
-     *
-     * Available since API level 27.
+     *      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.
      */
     CONCATENATION = @1.1::OperationType:CONCATENATION,
 
     /**
-     * Performs an 2-D convolution operation.
+     * 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
@@ -354,7 +345,7 @@
      * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
      * * * input.scale * filter.scale).
      *
-     * Available since API level 29:
+     * Available since HAL version 1.2:
      * * 16 bit floating point:
      * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
      *
@@ -368,27 +359,29 @@
      * 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 API level 29, zero batches is supported
-     *      for this tensor.
+     *      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 (extraParams.channelQuant.channelDim) must be set to 0.
+     *      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
+     *      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},
      *      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
+     *      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.
@@ -407,36 +400,37 @@
      *      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 API level 29.
+     *      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 API level 29.
+     *      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 API level 29.
+     *      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 API level 29, zero batches is supported
-     *      for this tensor.
+     *      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 (extraParams.channelQuant.channelDim) must be set to 0.
+     *      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
+     *      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},
      *      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
+     *      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
@@ -450,26 +444,23 @@
      *      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 API level 29.
+     *      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 API level 29.
+     *      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 API level 29.
+     *      Available since HAL version 1.2.
      *
      * Outputs:
      * * 0: The output 4-D tensor, of shape
-     *      [batches, out_height, out_width, depth_out]. Before API level 29,
-     *      for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the
-     *      following condition must be satisfied:
-     *      output_scale > input_scale * filter_scale
-     *
-     * Available since API level 27.
+     *      [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 = @1.1::OperationType:CONV_2D,
 
@@ -504,7 +495,7 @@
      * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
      * * * input.scale * filter.scale).
      *
-     * Available since API level 29:
+     * Available since HAL version 1.2:
      * * 16 bit floating point:
      * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
      *
@@ -518,6 +509,7 @@
      * 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.
      *
@@ -525,18 +517,19 @@
      * * 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 (extraParams.channelQuant.channelDim) must be set to 3.
+     *      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
+     *      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},
      *      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
+     *      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.
@@ -557,17 +550,17 @@
      *       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 API level 29.
+     *       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 API level 29.
+     *      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 API level 29.
+     *      Available since HAL version 1.2.
      *
      * Inputs (implicit padding):
      * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
@@ -575,14 +568,14 @@
      * * 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
+     *      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},
      *      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
+     *      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
@@ -598,27 +591,24 @@
      *      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 API level 29.
+     *      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 API level 29.
+     *      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 API level 29.
-
+     *      Available since HAL version 1.2.
      *
      * Outputs:
      * * 0: The output 4-D tensor, of shape
-     *      [batches, out_height, out_width, depth_out]. Before API level 29,
-     *      for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the
-     *      following condition must be satisfied:
+     *      [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
-     *
-     * Available since API level 27.
      */
     DEPTHWISE_CONV_2D = @1.1::OperationType:DEPTHWISE_CONV_2D,
 
@@ -638,7 +628,7 @@
      * be divisible by block_size * block_size
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -646,6 +636,7 @@
      * 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],
@@ -655,13 +646,13 @@
      *      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 API level 29.
+     *      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)].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     DEPTH_TO_SPACE = @1.1::OperationType:DEPTH_TO_SPACE,
 
@@ -674,22 +665,21 @@
      *
      * Supported input tensor {@link OperandType}:
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
-     * * {@link OperandType::TENSOR_QUANT8_SYMM} (since API level 29)
-     * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} (since API level 29)
+     * * {@link OperandType::TENSOR_QUANT8_SYMM} (since HAL version 1.2)
+     * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} (since HAL version 1.2)
      *
      * Supported output tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}.
      *
      * Supported tensor rank: up to 4
      *
      * Inputs:
-     * * 0: A tensor. Since API level 29, this tensor may be zero-sized.
+     * * 0: A tensor.
+     *      Since HAL version 1.2, this tensor may be zero-sized.
      *
      * Outputs:
      * * 0: A tensor with the same shape as input0.
-     *
-     * Available since API level 27.
      */
     DEQUANTIZE = @1.1::OperationType:DEQUANTIZE,
 
@@ -730,8 +720,8 @@
      * * 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.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input1.
      */
     EMBEDDING_LOOKUP = @1.1::OperationType:EMBEDDING_LOOKUP,
 
@@ -739,7 +729,7 @@
      * Computes element-wise floor() on the input tensor.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      *
      * Supported tensor rank: up to 4
@@ -750,8 +740,6 @@
      * Outputs:
      * * 0: The output tensor, of the same {@link OperandType} and dimensions as
      *      the input tensor.
-     *
-     * Available since API level 27.
      */
     FLOOR = @1.1::OperationType:FLOOR,
 
@@ -764,7 +752,7 @@
      *     outputs = activation(inputs * weights’ + bias)
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -777,8 +765,8 @@
      *      [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 API level 29, zero batch_size is
-     *      supported for this tensor.
+     *      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.
@@ -793,12 +781,9 @@
      *      invoke on the result.
      *
      * Outputs:
-     * * 0: The output tensor, of shape [batch_size, num_units]. Before API
-     *      level 29, For output tensor of {@link
-     *      OperandType::TENSOR_QUANT8_ASYMM}, the following condition must be
-     *      satisfied: output_scale > input_scale * filter_scale.
-     *
-     * Available since API level 27.
+     * * 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 = @1.1::OperationType:FULLY_CONNECTED,
 
@@ -849,13 +834,13 @@
      *
      * 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.
-     *
-     * Available since API level 27.
      */
     HASHTABLE_LOOKUP = @1.1::OperationType:HASHTABLE_LOOKUP,
 
@@ -872,12 +857,12 @@
      * 1-D slice along dimension dim.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since API level 29)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
      *
      * Supported tensor rank: up to 4
-     * Tensors with rank less than 4 are only supported since API level 29.
+     * 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.
@@ -885,14 +870,12 @@
      *      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 API level 29.
+     *      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.
-     *
-     * Available since API level 27.
      */
     L2_NORMALIZATION = @1.1::OperationType:L2_NORMALIZATION,
 
@@ -909,20 +892,21 @@
      *              sum(1))
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@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 API level 29, zero batches is supported for this
-     *      tensor.
+     *      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
@@ -944,12 +928,12 @@
      *      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 API level 29.
+     *       Available since HAL version 1.2.
      *
      * Inputs (implicit padding):
      * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
-     *      the input. Since API level 29, zero batches is supported for this
-     *      tensor.
+     *      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)}.
@@ -966,13 +950,11 @@
      *      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 API level 29.
+     *      Available since HAL version 1.2.
      *
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, out_height, out_width, depth].
-     *
-     * Available since API level 27.
      */
     L2_POOL_2D = @1.1::OperationType:L2_POOL_2D,
 
@@ -994,11 +976,11 @@
      * 1-D slice along specified dimension.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@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 API level 29.
+     * 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
@@ -1011,10 +993,10 @@
      *      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}.
+     *      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}.
@@ -1024,12 +1006,10 @@
      *      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 API level 29.
+     *      Available since HAL version 1.2.
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 27.
      */
     LOCAL_RESPONSE_NORMALIZATION = @1.1::OperationType:LOCAL_RESPONSE_NORMALIZATION,
 
@@ -1041,22 +1021,20 @@
      *     output = 1 / (1 + exp(-input))
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
      * Supported tensor rank: up to 4.
      *
      * Inputs:
-     * * 0: A tensor, specifying the input. Since API level 29, this tensor may
-     *      be zero-sized.
+     * * 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.
-     *
-     * Available since API level 27.
      */
     LOGISTIC = @1.1::OperationType:LOGISTIC,
 
@@ -1064,7 +1042,7 @@
      * Projects an input to a bit vector via locality senstive hashing.
      *
      * Supported input tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_INT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
@@ -1086,7 +1064,7 @@
      *      Tensor[1].Dim[0] == Tensor[2].Dim[0]
      * * 3: Type:
      *        Sparse:
-     *          Value LSHProjectionType_SPARSE(=3) (since API level 29).
+     *          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.
@@ -1107,14 +1085,12 @@
      * Outputs:
      * * 0: If the projection type is Sparse:
      *      Output.Dim == { Tensor[0].Dim[0] }
-     *      A tensor of int32 that represents hash signatures,
+     *      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.
-     *
-     * Available since API level 27.
-     * The offset value for sparse projections was added in API level 29.
+     * The offset value for sparse projections was added in HAL version 1.2.
      */
     LSH_PROJECTION = @1.1::OperationType:LSH_PROJECTION,
 
@@ -1170,7 +1146,7 @@
      *   matrix, each element of which is the product of the corresponding
      *   elements of the input matrices.
      *
-     * Since API level 29 LSTM supports layer normalization.
+     * 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
@@ -1197,7 +1173,7 @@
      * * 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.
-     * * (API level >= 29) The four layer normalization weights either all have
+     * * (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
@@ -1228,7 +1204,7 @@
      * Jimmy Ba et al. "Layer Normalization"
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      *
      * All input and output tensors must be of the same type.
@@ -1291,24 +1267,24 @@
      * * 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 API level 29 this scalar must be of type {@link
-     *      FLOAT32}. Since API level 29, if all the input
+     *      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
-     *      TENSOR_FLOAT16}, this scalar must be of type {@link
-     *      FLOAT16}.
+     *      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 API level 29 this scalar must be of type {@link
-     *      FLOAT32}. Since API level 29, if all the input
+     *      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
-     *      TENSOR_FLOAT16}, this scalar must be of type {@link
-     *      FLOAT16}.
-     * Since API level 29 there are additional inputs to this op:
+     *      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.
@@ -1333,8 +1309,6 @@
      * * 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.
-     *
-     * Available since API level 27.
      */
     LSTM = @1.1::OperationType:LSTM,
 
@@ -1352,7 +1326,7 @@
      *         )
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -1360,13 +1334,14 @@
      * 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 API level 29, zero batches is supported for this
-     *      tensor.
+     *      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
@@ -1388,12 +1363,12 @@
      *      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 API level 29.
+     *       Available since HAL version 1.2.
      *
      * Inputs (implicit padding):
      * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
-     *      the input. Since API level 29, zero batches is supported for this
-     *      tensor.
+     *      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)}.
@@ -1410,13 +1385,13 @@
      *      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 API level 29.
+     *      Available since HAL version 1.2.
      *
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, out_height, out_width, depth].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     MAX_POOL_2D = @1.1::OperationType:MAX_POOL_2D,
 
@@ -1435,15 +1410,15 @@
      * of the input operands. It starts with the trailing dimensions, and works
      * its way forward.
      *
-     * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
-     * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
-     *
-     * Since API level 29, generic zero-sized input tensor is supported. Zero
+     * 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}
+     *
      * Supported tensor rank: up to 4
      *
      * Inputs:
@@ -1459,8 +1434,6 @@
      *      For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
      *      the following condition must be satisfied:
      *      output_scale > input1_scale * input2_scale.
-     *
-     * Available since API level 27.
      */
     MUL = @1.1::OperationType:MUL,
 
@@ -1472,20 +1445,20 @@
      *     output = max(0, input)
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
      * Supported tensor rank: up to 4.
      *
      * Inputs:
-     * * 0: A tensor, specifying the input. Since API level 29, this tensor may
-     *      be zero-sized.
+     * * 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.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RELU = @1.1::OperationType:RELU,
 
@@ -1497,20 +1470,20 @@
      *     output = min(1.f, max(-1.f, input))
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
      * Supported tensor rank: up to 4.
      *
      * Inputs:
-     * * 0: A tensor, specifying the input. Since API level 29, this tensor may
-     *      be zero-sized.
+     * * 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.
-     *
-     * Available since API level 27.
+     * * 0: The output tensor of the same shape as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RELU1 = @1.1::OperationType:RELU1,
 
@@ -1522,20 +1495,20 @@
      *     output = min(6, max(0, input))
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
      * Supported tensor rank: up to 4.
      *
      * Inputs:
-     * * 0: A tensor, specifying the input. Since API level 29, this tensor may
-     *      be zero-sized.
+     * * 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.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RELU6 = @1.1::OperationType:RELU6,
 
@@ -1546,7 +1519,7 @@
      * tensor, but with a newly specified shape.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -1565,8 +1538,8 @@
      *
      * Outputs:
      * * 0: The output tensor, of shape specified by the input shape.
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RESHAPE = @1.1::OperationType:RESHAPE,
 
@@ -1578,30 +1551,31 @@
      * same as corner pixels of input.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since API level 29)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
      *
      * 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 API level 29, zero batches is supported for this
-     *      tensor.
+     *      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 API level 29.
+     *      Available since HAL version 1.2.
      *
-     * Inputs (resizing by scale, since API level 29):
+     * 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
@@ -1622,8 +1596,8 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, new_height, new_width, depth].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RESIZE_BILINEAR = @1.1::OperationType:RESIZE_BILINEAR,
 
@@ -1644,7 +1618,7 @@
      *   argument (if not “NONE”).
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      *
      * The input tensors must all be the same type.
@@ -1676,8 +1650,6 @@
      * * 1: output.
      *      A 2-D tensor of shape [batch_size, num_units]. This is effectively
      *      the same as the current state value.
-     *
-     * Available since API level 27.
      */
     RNN = @1.1::OperationType:RNN,
 
@@ -1696,34 +1668,32 @@
      * independently on each 1-D slice along specified dimension.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
      * Supported tensor rank: up to 4.
-     * Tensors with rank other than 2 or 4 are only supported since API level 29.
+     * 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
-     *      API level 29, this tensor may be zero-sized.
+     * * 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} or
      *      {@link OperandType::TENSOR_QUANT8_ASYMM}, 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}.
+     *      {@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 API level 29.
+     *      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.
-     *
-     * Available since API level 27.
      */
     SOFTMAX = @1.1::OperationType:SOFTMAX,
 
@@ -1742,7 +1712,7 @@
      * The input tensor's height and width must be divisible by block_size.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -1750,6 +1720,7 @@
      * 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],
@@ -1759,13 +1730,13 @@
      *      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 API level 29.
+     *      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].
-     *
-     * Available since API level 27.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     SPACE_TO_DEPTH = @1.1::OperationType:SPACE_TO_DEPTH,
 
@@ -1809,7 +1780,7 @@
      * the filters.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      *
      * All input tensors must be the same type.
@@ -1843,8 +1814,6 @@
      * * 1: output.
      *      A 2-D tensor of the same {@link OperandType} as the inputs, with shape
      *      [batch_size, num_units].
-     *
-     * Available since API level 27.
      */
     SVDF = @1.1::OperationType:SVDF,
 
@@ -1856,22 +1825,20 @@
      *     output = tanh(input)
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since API level 29)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
      *
      * Supported tensor rank: up to 4.
      *
      * Inputs:
-     * * 0: A tensor, specifying the input. Since API level 29, this tensor may
-     *      be zero-sized.
+     * * 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.
-     *
-     * Available since API level 27.
      */
     TANH = @1.1::OperationType:TANH,
 
@@ -1886,7 +1853,7 @@
      * This is the reverse of SpaceToBatch.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -1894,6 +1861,7 @@
      * 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
@@ -1906,8 +1874,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     BATCH_TO_SPACE_ND = @1.1::OperationType:BATCH_TO_SPACE_ND,
 
@@ -1931,12 +1899,12 @@
      *     input2.dimension = {5, 4, 3, 1}
      *     output.dimension = {5, 4, 3, 2}
      *
-     * Since API level 29, generic zero-sized input tensor is supported. Zero
+     * 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 API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      *
      * Supported tensor rank: up to 4
@@ -1951,8 +1919,6 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
      */
     DIV = @1.1::OperationType:DIV,
 
@@ -1965,7 +1931,7 @@
      * length 1.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -1987,21 +1953,21 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be same as input0.
      */
     MEAN = @1.1::OperationType:MEAN,
 
     /**
-     * Pads a tensor with zeros.
+     * Pads a tensor.
      *
      * This operation pads a tensor according to the specified paddings.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (full support since API
-     *   level 29, see the output section)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+     *   (full support since HAL version 1.2, see the output section)
      *
      * Supported tensor rank: up to 4
      *
@@ -2023,12 +1989,12 @@
      *      of the padding:
      *          output0.dimension[i] =
      *              padding[i, 0] + input0.dimension[i] + padding[i, 1]
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      *
-     *      NOTE: Before API level 29, the pad value for
-     *      {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} is undefined.
-     *      Since API level 29, the pad value is always the logical zero.
-     *
-     * Available since API level 28.
+     *      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 = @1.1::OperationType:PAD,
 
@@ -2044,14 +2010,16 @@
      * dimensions of the input are optionally zero padded according to paddings.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@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 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.
@@ -2068,12 +2036,16 @@
      *      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 API level 29.
+     *      Available since HAL version 1.2.
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      *
-     * Available since API level 28.
+     *      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 = @1.1::OperationType:SPACE_TO_BATCH_ND,
 
@@ -2086,7 +2058,7 @@
      * dimensions by specifying the axes (input1).
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -2104,8 +2076,8 @@
      * * 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.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     SQUEEZE = @1.1::OperationType:SQUEEZE,
 
@@ -2119,7 +2091,7 @@
      * reverse slice.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -2151,8 +2123,8 @@
      * 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.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     STRIDED_SLICE = @1.1::OperationType:STRIDED_SLICE,
 
@@ -2176,14 +2148,14 @@
      *     input2.dimension = {5, 4, 3, 1}
      *     output.dimension = {5, 4, 3, 2}
      *
-     * Since API level 29, generic zero-sized input tensor is supported. Zero
+     * 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 API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
-     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since API level 29)
+     * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
      *
      * Supported tensor rank: up to 4
      *
@@ -2197,8 +2169,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
      */
     SUB = @1.1::OperationType:SUB,
 
@@ -2212,7 +2184,7 @@
      * regular matrix transpose on 2-D input Tensors.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -2220,14 +2192,14 @@
      *
      * Inputs:
      * * 0: An n-D tensor, specifying the tensor to be transposed.
-     *      Since API level 29, this tensor may be zero-sized.
+     *      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.
-     *
-     * Available since API level 28.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     TRANSPOSE = @1.1::OperationType:TRANSPOSE,
 
@@ -2245,8 +2217,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 29.
      */
     ABS = 38,
 
@@ -2269,8 +2239,6 @@
      *
      * Outputs:
      * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor.
-     *
-     * Available since API level 29.
      */
     // There is no underscore in ARG_MAX to avoid name conflict with
     // the macro defined in libc/kernel/uapi/linux/limits.h.
@@ -2295,8 +2263,6 @@
      *
      * Outputs:
      * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor.
-     *
-     * Available since API level 29.
      */
     ARGMIN = 40,  // See ARGMAX for naming discussion.
 
@@ -2341,8 +2307,8 @@
      * * 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].
-     *
-     * Available since API level 29.
+     *      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,
 
@@ -2482,17 +2448,15 @@
      *       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
-     *       TENSOR_FLOAT16}, this scalar must be of type {@link
-     *       FLOAT16}.
+     *       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
-     *       TENSOR_FLOAT16}, this scalar must be of type {@link
-     *       FLOAT16}.
+     *       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.
@@ -2539,8 +2503,6 @@
      *      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]
-     *
-     * Available since API level 29.
      */
     BIDIRECTIONAL_SEQUENCE_LSTM = 42,
 
@@ -2658,8 +2620,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].
-     *
-     * Available since API level 29.
      */
     BIDIRECTIONAL_SEQUENCE_RNN = 43,
 
@@ -2737,8 +2697,6 @@
      * * 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.
-     *
-     * Available since API level 29.
      */
     BOX_WITH_NMS_LIMIT = 44,
 
@@ -2762,8 +2720,6 @@
      *
      * Outputs:
      * * 0: A tensor with the same shape as input0.
-     *
-     * Available since API level 29.
      */
     CAST = 45,
 
@@ -2800,8 +2756,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} and same shape as input0.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     CHANNEL_SHUFFLE = 46,
 
@@ -2856,14 +2812,14 @@
      * * 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}.
+     *       {@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}.
+     *       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
@@ -2882,8 +2838,6 @@
      *      output detection.
      * * 3: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape [batches],
      *      specifying the number of valid output detections for each batch.
-     *
-     * Available since API level 29.
      */
     DETECTION_POSTPROCESSING = 47,
 
@@ -2908,8 +2862,6 @@
      *
      * Outputs:
      * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
-     *
-     * Available since API level 29.
      */
     EQUAL = 48,
 
@@ -2927,8 +2879,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 29.
      */
     EXP = 49,
 
@@ -2956,8 +2906,8 @@
      * Outputs:
      * * 0: An (n + 1)-D tensor with the same {@link OperandType} and data as
      *      input0.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     EXPAND_DIMS = 50,
 
@@ -2994,8 +2944,8 @@
      *
      * Outputs:
      * * 0: An (n + k - 1)-D tensor with the same {@link OperandType} as input0.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     GATHER = 51,
 
@@ -3074,8 +3024,6 @@
      * * 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.
-     *
-     * Available since API level 29.
      */
     GENERATE_PROPOSALS = 52,
 
@@ -3100,8 +3048,6 @@
      *
      * Outputs:
      * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
-     *
-     * Available since API level 29.
      */
     GREATER = 53,
     /**
@@ -3125,8 +3071,6 @@
      *
      * Outputs:
      * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
-     *
-     * Available since API level 29.
      */
     GREATER_EQUAL = 54,
 
@@ -3191,7 +3135,8 @@
      *      [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 must be set to 0.
+     *      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
@@ -3229,7 +3174,8 @@
      *      [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 must be set to 0.
+     *      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
@@ -3258,8 +3204,8 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, out_height, out_width, depth_out].
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
      */
     GROUPED_CONV_2D = 55,
 
@@ -3300,12 +3246,14 @@
      * 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} 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].
-     *
-     * Available since API level 29.
+     *      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,
 
@@ -3339,26 +3287,24 @@
      * * 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}.
+     *      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}.
+     *      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}.
+     *      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.
-     *
-     * Available since API level 29.
      */
     INSTANCE_NORMALIZATION = 57,
 
@@ -3383,8 +3329,6 @@
      *
      * Outputs:
      * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
-     *
-     * Available since API level 29.
      */
     LESS = 58,
 
@@ -3409,8 +3353,6 @@
      *
      * Outputs:
      * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
-     *
-     * Available since API level 29.
      */
     LESS_EQUAL = 59,
 
@@ -3428,8 +3370,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 29.
      */
     LOG = 60,
 
@@ -3450,8 +3390,6 @@
      *
      * Outputs:
      * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
-     *
-     * Available since API level 29.
      */
     LOGICAL_AND = 61,
 
@@ -3468,8 +3406,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 29.
      */
     LOGICAL_NOT = 62,
 
@@ -3490,8 +3426,6 @@
      *
      * Outputs:
      * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
-     *
-     * Available since API level 29.
      */
     LOGICAL_OR = 63,
 
@@ -3523,8 +3457,6 @@
      * Outputs:
      * * 0: The output tensor of the same {@link OperandType} and shape as
      *      input0.
-     *
-     * Available since API level 29.
      */
     LOG_SOFTMAX = 64,
 
@@ -3543,11 +3475,13 @@
      * * 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.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
      */
     MAXIMUM = 65,
 
@@ -3566,11 +3500,13 @@
      * * 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.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
      */
     MINIMUM = 66,
 
@@ -3589,8 +3525,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 29.
      */
     NEG = 67,
 
@@ -3615,8 +3549,6 @@
      *
      * Outputs:
      * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
-     *
-     * Available since API level 29.
      */
     NOT_EQUAL = 68,
 
@@ -3657,8 +3589,8 @@
      *      of the padding:
      *          output0.dimension[i] =
      *              padding[i, 0] + input0.dimension[i] + padding[i, 1]
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     PAD_V2 = 69,
 
@@ -3689,8 +3621,6 @@
      *
      * Outputs:
      * * 0: An output tensor.
-     *
-     * Available since API level 29.
      */
     POW = 70,
 
@@ -3728,8 +3658,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be diffent from the input0 scale and zeroPoint.
      */
     PRELU = 71,
 
@@ -3752,8 +3682,6 @@
      * Outputs:
      * * 0: The output tensor of same shape as input0, but with
      *      {@link OperandType::TENSOR_QUANT8_ASYMM}.
-     *
-     * Available since API level 29.
      */
     QUANTIZE = 72,
 
@@ -3879,8 +3807,6 @@
      * Outputs:
      * * 0: A 2-D {@link OperandType::TENSOR_INT32} tensor with shape
      *      [batches, samples], containing the drawn samples.
-     *
-     * Available since API level 29.
      */
     RANDOM_MULTINOMIAL = 74,
 
@@ -3906,8 +3832,6 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 29.
      */
     REDUCE_ALL = 75,
 
@@ -3933,8 +3857,6 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 29.
      */
     REDUCE_ANY = 76,
 
@@ -3962,8 +3884,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     REDUCE_MAX = 77,
 
@@ -3991,8 +3913,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     REDUCE_MIN = 78,
 
@@ -4018,8 +3940,6 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 29.
      */
     REDUCE_PROD = 79,
 
@@ -4045,8 +3965,6 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0.
-     *
-     * Available since API level 29.
      */
     REDUCE_SUM = 80,
 
@@ -4064,7 +3982,7 @@
      * interpolation.
      *
      * Supported tensor {@link OperandType}:
-     * * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
+     * * {@link OperandType::TENSOR_FLOAT16}
      * * {@link OperandType::TENSOR_FLOAT32}
      * * {@link OperandType::TENSOR_QUANT8_ASYMM}
      *
@@ -4105,8 +4023,8 @@
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0. The output
      *      shape is [num_rois, out_height, out_width, depth].
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from the input0 scale and zeroPoint.
      */
     ROI_ALIGN = 81,
 
@@ -4156,8 +4074,8 @@
      * Outputs:
      * * 0: A tensor of the same {@link OperandType} as input0. The output
      *      shape is [num_rois, out_height, out_width, depth].
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     ROI_POOLING = 82,
 
@@ -4175,8 +4093,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 29.
      */
     RSQRT = 83,
 
@@ -4201,9 +4117,13 @@
      *      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} 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,
@@ -4222,8 +4142,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 29.
      */
     SIN = 85,
 
@@ -4235,7 +4153,6 @@
      * 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.
-     * Slice size in each dimension cannot be zero.
      *
      * A sum of begin offset and a size of a slice must not exceed size of a
      * corresponding dimension.
@@ -4257,8 +4174,8 @@
      *
      * Outputs:
      * * 0: An n-D tensor of the same type as the input containing the slice.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      its scale and zeroPoint has to be same as the input0 scale and zeroPoint.
      */
     SLICE = 86,
 
@@ -4282,8 +4199,8 @@
      *
      * Outputs:
      * * 0 ~ (num_splits - 1): Resulting subtensors.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     SPLIT = 87,
 
@@ -4301,8 +4218,6 @@
      *
      * Outputs:
      * * 0: The output tensor of same shape as input0.
-     *
-     * Available since API level 29.
      */
     SQRT = 88,
 
@@ -4330,8 +4245,8 @@
      *
      * Outputs:
      * * 0: A tiled tensor of the same {@link OperandType} and rank as `input`.
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     TILE = 89,
 
@@ -4357,10 +4272,10 @@
      * 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} 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.
-     *
-     * Available since API level 29.
      */
     TOPK_V2 = 90,
 
@@ -4374,7 +4289,7 @@
      * The output dimensions are functions of the filter dimensions, stride, and
      * padding.
      *
-     * Supported tensor {@link OperandCode} configurations:
+     * Supported tensor {@link OperandType} configurations:
      * * 16 bit floating point:
      * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
      *
@@ -4406,7 +4321,7 @@
      *      [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 (extraParams.channelQuant.channelDim) must be set to 0.
+     *      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
@@ -4443,7 +4358,7 @@
      *      [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 (extraParams.channelQuant.channelDim) must be set to 0.
+     *      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
@@ -4473,8 +4388,8 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, out_height, out_width, depth_out].
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint can be different from inputs' scale and zeroPoint.
      */
     TRANSPOSE_CONV_2D = 91,
 
@@ -4584,8 +4499,6 @@
      *      A 3-D tensor of shape:
      *        If time-major: [max_time, batch_size, output_size]
      *        If batch-major: [batch_size, max_time, output_size]
-     *
-     * Available since API level 29.
      */
     UNIDIRECTIONAL_SEQUENCE_LSTM = 92,
 
@@ -4641,8 +4554,6 @@
      *      it is set to 1, then the output has a shape [maxTime, batchSize,
      *      numUnits], otherwise the output has a shape [batchSize, maxTime,
      *      numUnits].
-     *
-     * Available since API level 29.
      */
     UNIDIRECTIONAL_SEQUENCE_RNN = 93,
 
@@ -4696,8 +4607,8 @@
      * Outputs:
      * * 0: The output 4-D tensor, of shape
      *      [batches, new_height, new_width, depth].
-     *
-     * Available since API level 29.
+     *      For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+     *      the scale and zeroPoint must be the same as input0.
      */
     RESIZE_NEAREST_NEIGHBOR = 94,
 
diff --git a/neuralnetworks/1.2/types.t b/neuralnetworks/1.2/types.t
new file mode 100644
index 0000000..d197f6b
--- /dev/null
+++ b/neuralnetworks/1.2/types.t
@@ -0,0 +1,725 @@
+%% template file for generating types.hal.
+%% see frameworks/ml/nn/tools/api/README.md.
+/*
+ * 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.
+ */
+
+package android.hardware.neuralnetworks@1.2;
+
+import @1.0::DataLocation;
+import @1.0::ErrorStatus;
+import @1.0::OperandLifeTime;
+import @1.0::OperandType;
+import @1.0::PerformanceInfo;
+import @1.1::OperationType;
+
+import android.hidl.safe_union@1.0::Monostate;
+
+enum Constant : uint32_t {
+    /**
+     * The byte size of the cache token.
+     */
+    BYTE_SIZE_OF_CACHE_TOKEN = 32,
+
+    /**
+     * The maximum number of files for each type of cache in compilation caching.
+     */
+    MAX_NUMBER_OF_CACHE_FILES = 32,
+};
+
+enum OperandType : @1.0::OperandType {
+%insert Operand_1.2
+%insert OEMDeprecationAndOperandTypeRangeMaxComment
+};
+
+/**
+ * The range of operand values in the OperandType enum.
+ */
+enum OperandTypeRange : uint32_t {
+    BASE_MIN        = 0,
+    FUNDAMENTAL_MIN = 0,
+%insert Operand_1.2_MAX
+    OEM_MIN         = 10000,
+    OEM_MAX         = 10001,
+    BASE_MAX        = 0xFFFF,
+};
+
+/**
+ * Operation types.
+ *
+ * The type of an operation in a model.
+ */
+enum OperationType : int32_t {
+
+%insert Operation_1.0
+
+%insert Operation_1.1
+
+%insert Operation_1.2
+
+    /**
+     * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to
+     * OEM operation and data types.
+     *
+     * This operation is OEM specific. It should only be used for OEM
+     * applications.
+     */
+    OEM_OPERATION = @1.1::OperationType:OEM_OPERATION,
+    /* ADDING A NEW FUNDAMENTAL OPERATION REQUIRES UPDATING THE VALUE OF
+     * OperationTypeRange::FUNDAMENTAL_MAX.
+     */
+    /* ADDING A NEW OEM OPERATION REQUIRES UPDATING THE VALUE OF
+     * OperationTypeRange::OEM_MAX.
+     */
+};
+
+/**
+ * The range of values in the OperationType enum.
+ */
+enum OperationTypeRange : uint32_t {
+    BASE_MIN        = 0,
+    FUNDAMENTAL_MIN = 0,
+%insert Operation_1.2_MAX
+    OEM_MIN         = 10000,
+    OEM_MAX         = 10000,
+    BASE_MAX        = 0xFFFF,
+};
+
+/**
+ * Device types.
+ *
+ * The type of NNAPI device.
+ */
+enum DeviceType : int32_t {
+    // Leaving 0 unused as it means unknown type in NDK NNAPI. There is no
+    // HAL equivalent of unknown type and a 1.2 HAL implementation must belong
+    // to one of the categories below.
+    /** 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,
+};
+
+/**
+ * The capabilities of a driver.
+ *
+ * Performance of an operation comes from the type of its first operand.
+ * This represents performance for non extension operand types.
+ */
+struct 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;
+
+    /**
+     * Driver performance when operating on a particular data type.
+     * In the case of float32 data, this is used when the calculations
+     * are not relaxed.
+     */
+    struct OperandPerformance {
+        OperandType type;
+        PerformanceInfo info;
+    };
+
+    /**
+     * Performance by operand type. Must be sorted by OperandType.
+     * If a particular OperandType is not present in operandPerformance,
+     * its performance is treated as { .execTime = FLT_MAX, .powerUsage = FLT_MAX }.
+     */
+    vec<OperandPerformance> operandPerformance;
+};
+
+/**
+ * Describes one operation of the model's graph.
+ */
+struct Operation {
+    /**
+     * The operation type.
+     *
+     * Besides the values listed in {@link OperationType}, any value above
+     * {@link OperationTypeRange::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.
+     */
+    vec<uint32_t> inputs;
+
+    /**
+     * Describes the table that contains the indexes of the outputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> outputs;
+};
+
+/**
+ * Parameters for TENSOR_QUANT8_SYMM_PER_CHANNEL operand.
+ */
+struct SymmPerChannelQuantParams {
+    /** Array of scaling values for each channel. Each value must be greater than zero. */
+    vec<float> scales;
+    /** Index of the channel dimension */
+    uint32_t channelDim;
+};
+
+/**
+ * Describes one operand of the model's graph.
+ */
+struct Operand {
+    /**
+     * The data type.
+     *
+     * Besides the values listed in {@link OperandType}, any value above
+     * {@link OperandTypeRange::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.
+     *
+     * In the following situations, a tensor operand's dimensions must
+     * be fully specified:
+     *
+     *     . The operand has lifetime CONSTANT_COPY or
+     *       CONSTANT_REFERENCE.
+     *
+     *     . The operand has lifetime MODEL_INPUT. 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.
+     */
+    vec<uint32_t> dimensions;
+
+    /**
+     * The number of times this operand appears as an operation input.
+     *
+     * (For example, if this operand appears once in one operation's
+     * input list, and three times in another operation's input list,
+     * then numberOfConsumers = 4.)
+     */
+    uint32_t numberOfConsumers;
+
+    /**
+     * Quantized scale of the operand.
+     *
+     * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or
+     * TENSOR_INT32.
+     */
+    float scale;
+
+    /**
+     * Quantized zero-point offset of the operand.
+     *
+     * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM.
+     */
+    int32_t zeroPoint;
+
+    /**
+     * How the operand is used.
+     */
+    OperandLifeTime lifetime;
+
+    /**
+     * Where to find the data for this operand.
+     * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_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_REFERENCE:
+     * - location.poolIndex is set.
+     * - location.offset is the offset in bytes into the specified pool.
+     * - location.length is set.
+     */
+    DataLocation location;
+
+    /**
+     * Additional parameters specific to a particular operand type.
+     */
+    safe_union ExtraParams {
+       /**
+        * No additional parameters.
+        */
+       Monostate none;
+
+       /**
+        * 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.
+        */
+       vec<uint8_t> extension;
+    } extraParams;
+};
+
+/**
+ * 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.
+ */
+struct Model {
+    /**
+     * All operands included in the model.
+     */
+    vec<Operand> operands;
+
+    /**
+     * All operations included in the model.
+     *
+     * The operations are sorted into execution order. Every operand
+     * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be
+     * written before it is read.
+     */
+    vec<Operation> operations;
+
+    /**
+     * Input indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> inputIndexes;
+
+    /**
+     * Output indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> outputIndexes;
+
+    /**
+     * 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.
+     */
+    vec<uint8_t> 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_REFERENCE.
+     */
+    vec<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.
+     */
+    bool relaxComputationFloat32toFloat16;
+
+    /**
+     * The mapping between extension names and prefixes of operand and
+     * operation type values.
+     *
+     * An operand or operation whose numeric type value is above
+     * {@link OperandTypeRange::BASE_MAX} or
+     * {@link OperationTypeRange::BASE_MAX} respectively should be interpreted
+     * as an extension operand. The low
+     * {@link Model::ExtensionTypeEncoding::LOW_BITS_TYPE} bits of the value
+     * correspond to the type ID within the extension and the high
+     * {@link Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX} bits encode
+     * the "prefix", which maps uniquely to the extension name.
+     *
+     * For example, if a model contains an operation whose value is
+     * 0xAAAABBBB and extensionNameToPrefix contains an entry with
+     * prefix=0xAAAA 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.
+     */
+    vec<ExtensionNameAndPrefix> extensionNameToPrefix;
+
+    /**
+     * A correspondence between an extension name and a prefix of operand and
+     * operation type values.
+     */
+    struct ExtensionNameAndPrefix {
+        /**
+         * The extension name.
+         *
+         * See {@link Extension::name} for the format specification.
+         */
+        string name;
+
+        /**
+         * The unique extension identifier within the model.
+         *
+         * See {@link Model::extensionNameToPrefix}.
+         */
+        uint16_t prefix;
+    };
+
+    /**
+     * Numeric values of extension operand and operation types have the
+     * following structure:
+     * - 16 high bits represent the "prefix", which corresponds uniquely to the
+     *   extension name.
+     * - 16 low bits represent the type ID within the extension.
+     */
+    enum ExtensionTypeEncoding : uint8_t {
+        HIGH_BITS_PREFIX = 16,
+        LOW_BITS_TYPE = 16,
+    };
+};
+
+/**
+ * Describes the shape information of an output operand after execution.
+ */
+struct OutputShape {
+    /**
+     * Dimensions of the operand.
+     */
+    vec<uint32_t> dimensions;
+
+    /**
+     * Whether the provided buffer size is sufficient for the output.
+     */
+    bool isSufficient;
+};
+
+/**
+ * Specifies whether or not to measure timing information during execution.
+ */
+enum MeasureTiming : int32_t {
+    NO  = 0,
+    YES = 1,
+};
+
+/**
+
+ * 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 microseconds.
+ * When a time is not available, it must be reported as UINT64_MAX.
+ */
+struct Timing {
+    /** Execution time on device (not driver, which runs on host processor). */
+    uint64_t timeOnDevice;
+    /** Execution time in driver (including time on device). */
+    uint64_t timeInDriver;
+};
+
+/**
+ * FmqRequestDatum is a single element of a serialized representation of an
+ * execution request (a {@link @1.0::Request} object and a {@link MeasureTiming}
+ * value) which is sent across FastMessageQueue.
+ *
+ * The serialized representation for a particular execution is referred to later
+ * in these descriptions as a 'packet'.
+ *
+ * FastMessageQueue can only pass HIDL-defined types that do not involve nested
+ * buffers, handles, or interfaces.
+ *
+ * The request is serialized as follows:
+ * 1) 'packetInformation'
+ * 2) For each input operand:
+ *    2.1) 'inputOperandInformation'
+ *    2.2) For each dimension element of the operand:
+ *         2.2.1) 'inputOperandDimensionValue'
+ * 3) For each output operand:
+ *    3.1) 'outputOperandInformation'
+ *    3.2) For each dimension element of the operand:
+ *         3.2.1) 'outputOperandDimensionValue'
+ * 4) For each pool:
+ *    4.1) 'poolIdentifier'
+ * 5) 'measureTiming'
+ */
+safe_union FmqRequestDatum {
+    /**
+     * Type to describe the high-level layout of the packet.
+     */
+    struct PacketInformation {
+        /**
+         * How many elements the packet contains, including the
+         * "packetInformation" datum.
+         */
+        uint32_t packetSize;
+
+        /**
+         * Number of input operands.
+         */
+        uint32_t numberOfInputOperands;
+
+        /**
+         * Number of output operands.
+         */
+        uint32_t numberOfOutputOperands;
+
+        /**
+         * Number of pool identifiers.
+         */
+        uint32_t numberOfPools;
+    };
+
+    /**
+     * Type representing the information for each operand.
+     */
+    struct OperandInformation {
+        /**
+         * 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, 'numberOfDimensions' is set to 0,  and the
+         * dimensions information is omitted from the serialization.
+         */
+        bool hasNoValue;
+
+        /**
+         * The location within one of the memory pools passed in the Request.
+         */
+        DataLocation location;
+
+        /**
+         * Number of subsequent elements that belong to the dimensions vector.
+         */
+        uint32_t numberOfDimensions;
+    };
+
+    /**
+     * packetInformation is the first element of the packet and describes the
+     * remainder of the packet.
+     */
+    PacketInformation packetInformation;
+
+    /**
+     * Information for each input operand.
+     */
+    OperandInformation inputOperandInformation;
+
+    /**
+     * Element of the dimensions vector.
+     */
+    uint32_t inputOperandDimensionValue;
+
+    /**
+     * Information for each output operand.
+     */
+    OperandInformation outputOperandInformation;
+
+    /**
+     * Element of the dimensions vector.
+     */
+    uint32_t outputOperandDimensionValue;
+
+    /**
+     * Unique identifier for a pool.
+     *
+     * A {@link @1.0::Request} passes across one or more pools of shared memory
+     * for the inputs and outputs of an execution. However, these memory pools
+     * are not able to be sent across FastMessageQueue directly. Instead, the
+     * producing side of the FMQ represents each different pool with a unique
+     * identifier, and sends this identifier across the FMQ. Whenever the
+     * consuming side of the FMQ needs the memory corresponding to this unique
+     * identifier, it can pass the identifier to
+     * {@link IBurstCallback::getMemories} to retreive the memory. Although this
+     * HIDL Binder call is expensive compared to communication across FMQ, it is
+     * only needed in the cases when the consumer does not recognize the unique
+     * identifier.
+     */
+    int32_t poolIdentifier;
+
+    /**
+     * Specifies whether or not to measure duration of the execution. The
+     * duration runs from the time the driver dequeues the request from a
+     * FastMessageQueue to the time the driver enqueues results to a
+     * FastMessageQueue.
+     */
+    MeasureTiming measureTiming;
+};
+
+/**
+ * FmqResultDatum is a single element of a serialized representation of the
+ * values returned from an execution ({@link @1.0::ErrorStatus},
+ * vec<{@link OutputShape}>, and {@link Timing}) which is returned via
+ * FastMessageQueue.
+ *
+ * The serialized representation for a particular execution is referred to later
+ * in these descriptions as a 'packet'.
+ *
+ * FastMessageQueue can only pass HIDL-defined types that do not involve nested
+ * buffers, handles, or interfaces.
+ *
+ * The execution return values ({@link @1.0::ErrorStatus} and
+ * vec<{@link OutputShape}>) are serialized as follows:
+ * 1) 'packetInformation'
+ * 2) For each returned operand:
+ *    2.1) 'operandInformation'
+ *    2.2) For each dimension element of the operand:
+ *         2.2.1) 'operandDimensionValue'
+ * 3) 'executionTiming'
+ */
+safe_union FmqResultDatum {
+    /**
+     * Type to describe the high-level layout of the packet.
+     */
+    struct PacketInformation {
+        /**
+         * How many elements the packet contains, including the
+         * "packetInformation" datum.
+         */
+        uint32_t packetSize;
+
+        /**
+         * Status of the execution.
+         */
+        ErrorStatus errorStatus;
+
+        /**
+         * Number of returned operands.
+         */
+        uint32_t numberOfOperands;
+    };
+
+    /**
+     * Type representing the information for each operand.
+     */
+    struct OperandInformation {
+        /**
+         * Indicates whether the operand's output buffer is large enough to
+         * store the operand's result data.
+         */
+        bool isSufficient;
+
+        /**
+         * Number of subsequent elements that belong to the dimensions vector.
+         */
+        uint32_t numberOfDimensions;
+    };
+
+    /**
+     * packetInformation is the first element of the packet and describes the
+     * remainder of the packet. It additionally includes the status of the
+     * execution.
+     */
+    PacketInformation packetInformation;
+
+    /**
+     * Information for each returned operand.
+     */
+    OperandInformation operandInformation;
+
+    /**
+     * Element of the dimensions vector.
+     */
+    uint32_t operandDimensionValue;
+
+    /**
+     * Duration of execution. Unless measurement was requested and execution
+     * succeeds, all times must be reported as UINT64_MAX. A driver may choose
+     * to report any time as UINT64_MAX, indicating that measurement is not
+     * available.
+     */
+    Timing executionTiming;
+};
+
+/**
+ * Information about an extension.
+ */
+struct 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 an extension operand type.
+     */
+    struct OperandTypeInformation {
+        /**
+         * The extension operand type.
+         */
+        uint16_t type;
+
+        /**
+         * Indicates whether the extension operand type represents a tensor or
+         * a scalar.
+         */
+        bool isTensor;
+
+        /**
+         * The byte size of the operand (if scalar) or of a single element (if
+         * tensor).
+         */
+        uint32_t byteSize;
+    };
+
+    /**
+     * Information about operand types defined by the extension.
+     */
+    vec<OperandTypeInformation> operandTypes;
+};
diff --git a/neuralnetworks/1.2/vts/functional/Android.bp b/neuralnetworks/1.2/vts/functional/Android.bp
index 3ba8879..fc727b7 100644
--- a/neuralnetworks/1.2/vts/functional/Android.bp
+++ b/neuralnetworks/1.2/vts/functional/Android.bp
@@ -14,12 +14,28 @@
 // limitations under the License.
 //
 
+cc_library_static {
+    name: "VtsHalNeuralNetworksV1_2Callbacks",
+    defaults: ["VtsHalTargetTestDefaults"],
+    export_include_dirs: ["include"],
+    srcs: [
+        "Callbacks.cpp",
+    ],
+    static_libs: [
+        "android.hardware.neuralnetworks@1.0",
+        "android.hardware.neuralnetworks@1.1",
+        "android.hardware.neuralnetworks@1.2",
+    ],
+    header_libs: [
+        "libbase_headers",
+    ]
+}
+
 cc_test {
     name: "VtsHalNeuralnetworksV1_2TargetTest",
     defaults: ["VtsHalTargetTestDefaults"],
     srcs: [
         "BasicTests.cpp",
-        "Callbacks.cpp",
         "CompilationCachingTests.cpp",
         "GeneratedTestHarness.cpp",
         "TestAssertions.cpp",
@@ -37,6 +53,7 @@
         "android.hardware.neuralnetworks@1.0",
         "android.hardware.neuralnetworks@1.1",
         "android.hardware.neuralnetworks@1.2",
+        "android.hardware.neuralnetworks@1.3",
         "android.hidl.allocator@1.0",
         "android.hidl.memory@1.0",
         "libgmock",
@@ -44,6 +61,7 @@
         "libneuralnetworks_generated_test_harness",
         "libneuralnetworks_utils",
         "VtsHalNeuralNetworksV1_0_utils",
+        "VtsHalNeuralNetworksV1_2Callbacks",
     ],
     whole_static_libs: [
         "neuralnetworks_generated_V1_0_example",
diff --git a/neuralnetworks/1.3/Android.bp b/neuralnetworks/1.3/Android.bp
new file mode 100644
index 0000000..0615ec6
--- /dev/null
+++ b/neuralnetworks/1.3/Android.bp
@@ -0,0 +1,21 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+    name: "android.hardware.neuralnetworks@1.3",
+    root: "android.hardware",
+    vndk: {
+        enabled: true,
+    },
+    srcs: [
+        "types.hal",
+        "IDevice.hal",
+    ],
+    interfaces: [
+        "android.hardware.neuralnetworks@1.0",
+        "android.hardware.neuralnetworks@1.1",
+        "android.hardware.neuralnetworks@1.2",
+        "android.hidl.base@1.0",
+        "android.hidl.safe_union@1.0",
+    ],
+    gen_java: false,
+}
diff --git a/neuralnetworks/1.3/IDevice.hal b/neuralnetworks/1.3/IDevice.hal
new file mode 100644
index 0000000..ee36fb4
--- /dev/null
+++ b/neuralnetworks/1.3/IDevice.hal
@@ -0,0 +1,171 @@
+/*
+ * 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.neuralnetworks@1.3;
+
+import @1.0::ErrorStatus;
+import @1.1::ExecutionPreference;
+import @1.2::Constant;
+import @1.2::DeviceType;
+import @1.2::Extension;
+import @1.2::IDevice;
+import @1.2::IPreparedModelCallback;
+
+/**
+ * This interface represents a device driver.
+ */
+interface IDevice extends @1.2::IDevice {
+    /**
+     * Gets the capabilities of a driver.
+     *
+     * @return status Error status of the call, must be:
+     *                - NONE if successful
+     *                - DEVICE_UNAVAILABLE if driver is offline or busy
+     *                - GENERAL_FAILURE if there is an unspecified error
+     * @return capabilities Capabilities of the driver.
+     */
+    getCapabilities_1_3() generates (ErrorStatus status, Capabilities capabilities);
+
+    /**
+     * Gets the supported operations in a model.
+     *
+     * getSupportedOperations indicates which operations of a model are fully
+     * supported by the vendor driver. If an operation may not be supported for
+     * any reason, getSupportedOperations must return false for that operation.
+     *
+     * @param model A model whose operations--and their corresponding operands--
+     *     are to be verified by the driver.
+     * @return status Error status of the call, must be:
+     *     - NONE if successful
+     *     - DEVICE_UNAVAILABLE if driver is offline or busy
+     *     - GENERAL_FAILURE if there is an unspecified error
+     *     - INVALID_ARGUMENT if provided model is invalid
+     * @return supportedOperations 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.
+     */
+    getSupportedOperations_1_3(Model model)
+        generates (ErrorStatus status, vec<bool> supportedOperations);
+
+    /**
+     * 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 handles provided to the driver: model cache and data
+     * cache. For more information on the two types of cache handles, 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 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 with ErrorStatus::NONE. If the
+     * asynchronous task fails to launch, prepareModel must immediately invoke
+     * the callback with ErrorStatus::GENERAL_FAILURE and nullptr for the
+     * IPreparedModel, then return 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.
+     *
+     * 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 modelCache A vector of handles with each entry holding exactly one
+     *     cache file descriptor 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 handles will be provided in
+     *     the same order when retrieving the preparedModel from cache files
+     *     with prepareModelFromCache.
+     * @param dataCache A vector of handles with each entry holding exactly one
+     *     cache file descriptor 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 handles will be provided in
+     *     the same order when retrieving the preparedModel from cache files
+     *     with prepareModelFromCache.
+     * @param token A caching token of length Constant::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.
+     * @return status Error status of launching a task which prepares the model
+     *     in the background; must be:
+     *     - NONE if preparation task is successfully launched
+     *     - 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
+     */
+    prepareModel_1_3(Model model, ExecutionPreference preference,
+                     vec<handle> modelCache, vec<handle> dataCache,
+                     uint8_t[Constant:BYTE_SIZE_OF_CACHE_TOKEN] token,
+                     IPreparedModelCallback callback)
+        generates (ErrorStatus status);
+};
diff --git a/neuralnetworks/1.3/types.hal b/neuralnetworks/1.3/types.hal
new file mode 100644
index 0000000..86ab287
--- /dev/null
+++ b/neuralnetworks/1.3/types.hal
@@ -0,0 +1,373 @@
+/*
+ * 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.neuralnetworks@1.3;
+
+import @1.0::DataLocation;
+import @1.0::OperandLifeTime;
+import @1.0::PerformanceInfo;
+import @1.2::OperandType;
+import @1.2::OperationType;
+import @1.2::SymmPerChannelQuantParams;
+
+import android.hidl.safe_union@1.0::Monostate;
+
+enum OperandType : @1.2::OperandType {
+    /**
+     * 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,
+
+    /*
+     * DEPRECATED. Since HAL version 1.2, extensions are the preferred
+     * alternative to OEM operation and data types.
+     *
+     * OEM specific scalar value.
+     * OEM                 = 10000,
+     */
+    /*
+     * DEPRECATED. Since HAL version 1.2, extensions are the preferred
+     * alternative to OEM operation and data types.
+     *
+     * A tensor of OEM specific values.
+     * TENSOR_OEM_BYTE     = 10001,
+     */
+    /* ADDING A NEW FUNDAMENTAL TYPE REQUIRES UPDATING THE VALUE OF
+     * OperandTypeRange::FUNDAMENTAL_MAX.
+     */
+    /* ADDING A NEW OEM TYPE REQUIRES UPDATING THE VALUE OF
+     * OperandTypeRange::OEM_MAX.
+     */
+};
+
+/**
+ * The range of operand values in the OperandType enum.
+ */
+enum OperandTypeRange : uint32_t {
+    BASE_MIN        = 0,
+    FUNDAMENTAL_MIN = 0,
+    FUNDAMENTAL_MAX = 14,
+    OEM_MIN         = 10000,
+    OEM_MAX         = 10001,
+    BASE_MAX        = 0xFFFF,
+};
+
+
+/**
+ * The capabilities of a driver.
+ *
+ * Performance of an operation comes from the type of its first operand.
+ * This represents performance for non extension operand types.
+ */
+struct 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;
+
+    /**
+     * Driver performance when operating on a particular data type.
+     * In the case of float32 data, this is used when the calculations
+     * are not relaxed.
+     */
+    struct OperandPerformance {
+        OperandType type;
+        PerformanceInfo info;
+    };
+
+    /**
+     * Performance by operand type. Must be sorted by OperandType.
+     * If a particular OperandType is not present in operandPerformance,
+     * its performance is treated as
+     * { .execTime = FLT_MAX, .powerUsage = FLT_MAX }.
+     */
+    vec<OperandPerformance> operandPerformance;
+};
+
+/**
+ * Describes one operand of the model's graph.
+ */
+struct Operand {
+    /**
+     * The data type.
+     *
+     * Besides the values listed in {@link OperandType}, any value above
+     * {@link OperandTypeRange::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.
+     *
+     * In the following situations, a tensor operand's dimensions must
+     * be fully specified:
+     *
+     *     . The operand has lifetime CONSTANT_COPY or
+     *       CONSTANT_REFERENCE.
+     *
+     *     . The operand has lifetime MODEL_INPUT. 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.
+     */
+    vec<uint32_t> dimensions;
+
+    /**
+     * The number of times this operand appears as an operation input.
+     *
+     * (For example, if this operand appears once in one operation's
+     * input list, and three times in another operation's input list,
+     * then numberOfConsumers = 4.)
+     */
+    uint32_t numberOfConsumers;
+
+    /**
+     * Quantized scale of the operand.
+     *
+     * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or
+     * TENSOR_INT32.
+     */
+    float scale;
+
+    /**
+     * Quantized zero-point offset of the operand.
+     *
+     * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM.
+     */
+    int32_t zeroPoint;
+
+    /**
+     * How the operand is used.
+     */
+    OperandLifeTime lifetime;
+
+    /**
+     * Where to find the data for this operand.
+     * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_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_REFERENCE:
+     * - location.poolIndex is set.
+     * - location.offset is the offset in bytes into the specified pool.
+     * - location.length is set.
+     */
+    DataLocation location;
+
+    /**
+     * Additional parameters specific to a particular operand type.
+     */
+    safe_union ExtraParams {
+       /**
+        * No additional parameters.
+        */
+       Monostate none;
+
+       /**
+        * 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.
+        */
+       vec<uint8_t> extension;
+    } extraParams;
+};
+
+/**
+ * Describes one operation of the model's graph.
+ */
+struct Operation {
+    /**
+     * The operation type.
+     */
+    OperationType type;
+
+    /**
+     * Describes the table that contains the indexes of the inputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> inputs;
+
+    /**
+     * Describes the table that contains the indexes of the outputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> outputs;
+};
+
+/**
+ * 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.
+ */
+struct Model {
+    /**
+     * All operands included in the model.
+     */
+    vec<Operand> operands;
+
+    /**
+     * All operations included in the model.
+     *
+     * The operations are sorted into execution order. Every operand
+     * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be
+     * written before it is read.
+     */
+    vec<Operation> operations;
+
+    /**
+     * Input indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> inputIndexes;
+
+    /**
+     * Output indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> outputIndexes;
+
+    /**
+     * 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.
+     */
+    vec<uint8_t> 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_REFERENCE.
+     */
+    vec<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.
+     */
+    bool relaxComputationFloat32toFloat16;
+
+    /**
+     * The mapping between extension names and prefixes of operand and
+     * operation type values.
+     *
+     * An operand or operation whose numeric type value is above
+     * {@link OperandTypeRange::BASE_MAX} or
+     * {@link OperationTypeRange::BASE_MAX} respectively should be interpreted
+     * as an extension operand. The low
+     * {@link Model::ExtensionTypeEncoding::LOW_BITS_TYPE} bits of the value
+     * correspond to the type ID within the extension and the high
+     * {@link Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX} bits encode
+     * the "prefix", which maps uniquely to the extension name.
+     *
+     * For example, if a model contains an operation whose value is
+     * 0xAAAABBBB and extensionNameToPrefix contains an entry with
+     * prefix=0xAAAA 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.
+     */
+    vec<ExtensionNameAndPrefix> extensionNameToPrefix;
+
+    /**
+     * A correspondence between an extension name and a prefix of operand and
+     * operation type values.
+     */
+    struct ExtensionNameAndPrefix {
+        /**
+         * The extension name.
+         *
+         * See {@link Extension::name} for the format specification.
+         */
+        string name;
+
+        /**
+         * The unique extension identifier within the model.
+         *
+         * See {@link Model::extensionNameToPrefix}.
+         */
+        uint16_t prefix;
+    };
+
+    /**
+     * Numeric values of extension operand and operation types have the
+     * following structure:
+     * - 16 high bits represent the "prefix", which corresponds uniquely to the
+     *   extension name.
+     * - 16 low bits represent the type ID within the extension.
+     */
+    enum ExtensionTypeEncoding : uint8_t {
+        HIGH_BITS_PREFIX = 16,
+        LOW_BITS_TYPE = 16,
+    };
+};
diff --git a/neuralnetworks/1.3/types.t b/neuralnetworks/1.3/types.t
new file mode 100644
index 0000000..d41cfd2
--- /dev/null
+++ b/neuralnetworks/1.3/types.t
@@ -0,0 +1,344 @@
+%% template file for generating types.hal.
+%% see frameworks/ml/nn/tools/api/README.md.
+/*
+ * 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.neuralnetworks@1.3;
+
+import @1.0::DataLocation;
+import @1.0::OperandLifeTime;
+import @1.0::PerformanceInfo;
+import @1.2::OperandType;
+import @1.2::OperationType;
+import @1.2::SymmPerChannelQuantParams;
+
+import android.hidl.safe_union@1.0::Monostate;
+
+enum OperandType : @1.2::OperandType {
+%insert Operand_1.3
+%insert OEMDeprecationAndOperandTypeRangeMaxComment
+};
+
+/**
+ * The range of operand values in the OperandType enum.
+ */
+enum OperandTypeRange : uint32_t {
+    BASE_MIN        = 0,
+    FUNDAMENTAL_MIN = 0,
+%insert Operand_1.3_MAX
+    OEM_MIN         = 10000,
+    OEM_MAX         = 10001,
+    BASE_MAX        = 0xFFFF,
+};
+
+
+/**
+ * The capabilities of a driver.
+ *
+ * Performance of an operation comes from the type of its first operand.
+ * This represents performance for non extension operand types.
+ */
+struct 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;
+
+    /**
+     * Driver performance when operating on a particular data type.
+     * In the case of float32 data, this is used when the calculations
+     * are not relaxed.
+     */
+    struct OperandPerformance {
+        OperandType type;
+        PerformanceInfo info;
+    };
+
+    /**
+     * Performance by operand type. Must be sorted by OperandType.
+     * If a particular OperandType is not present in operandPerformance,
+     * its performance is treated as
+     * { .execTime = FLT_MAX, .powerUsage = FLT_MAX }.
+     */
+    vec<OperandPerformance> operandPerformance;
+};
+
+/**
+ * Describes one operand of the model's graph.
+ */
+struct Operand {
+    /**
+     * The data type.
+     *
+     * Besides the values listed in {@link OperandType}, any value above
+     * {@link OperandTypeRange::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.
+     *
+     * In the following situations, a tensor operand's dimensions must
+     * be fully specified:
+     *
+     *     . The operand has lifetime CONSTANT_COPY or
+     *       CONSTANT_REFERENCE.
+     *
+     *     . The operand has lifetime MODEL_INPUT. 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.
+     */
+    vec<uint32_t> dimensions;
+
+    /**
+     * The number of times this operand appears as an operation input.
+     *
+     * (For example, if this operand appears once in one operation's
+     * input list, and three times in another operation's input list,
+     * then numberOfConsumers = 4.)
+     */
+    uint32_t numberOfConsumers;
+
+    /**
+     * Quantized scale of the operand.
+     *
+     * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or
+     * TENSOR_INT32.
+     */
+    float scale;
+
+    /**
+     * Quantized zero-point offset of the operand.
+     *
+     * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM.
+     */
+    int32_t zeroPoint;
+
+    /**
+     * How the operand is used.
+     */
+    OperandLifeTime lifetime;
+
+    /**
+     * Where to find the data for this operand.
+     * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_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_REFERENCE:
+     * - location.poolIndex is set.
+     * - location.offset is the offset in bytes into the specified pool.
+     * - location.length is set.
+     */
+    DataLocation location;
+
+    /**
+     * Additional parameters specific to a particular operand type.
+     */
+    safe_union ExtraParams {
+       /**
+        * No additional parameters.
+        */
+       Monostate none;
+
+       /**
+        * 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.
+        */
+       vec<uint8_t> extension;
+    } extraParams;
+};
+
+/**
+ * Describes one operation of the model's graph.
+ */
+struct Operation {
+    /**
+     * The operation type.
+     */
+    OperationType type;
+
+    /**
+     * Describes the table that contains the indexes of the inputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> inputs;
+
+    /**
+     * Describes the table that contains the indexes of the outputs of the
+     * operation. The offset is the index in the operandIndexes table.
+     */
+    vec<uint32_t> outputs;
+};
+
+/**
+ * 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.
+ */
+struct Model {
+    /**
+     * All operands included in the model.
+     */
+    vec<Operand> operands;
+
+    /**
+     * All operations included in the model.
+     *
+     * The operations are sorted into execution order. Every operand
+     * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be
+     * written before it is read.
+     */
+    vec<Operation> operations;
+
+    /**
+     * Input indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> inputIndexes;
+
+    /**
+     * Output indexes of the model. There must be at least one.
+     *
+     * Each value corresponds to the index of the operand in "operands".
+     */
+    vec<uint32_t> outputIndexes;
+
+    /**
+     * 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.
+     */
+    vec<uint8_t> 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_REFERENCE.
+     */
+    vec<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.
+     */
+    bool relaxComputationFloat32toFloat16;
+
+    /**
+     * The mapping between extension names and prefixes of operand and
+     * operation type values.
+     *
+     * An operand or operation whose numeric type value is above
+     * {@link OperandTypeRange::BASE_MAX} or
+     * {@link OperationTypeRange::BASE_MAX} respectively should be interpreted
+     * as an extension operand. The low
+     * {@link Model::ExtensionTypeEncoding::LOW_BITS_TYPE} bits of the value
+     * correspond to the type ID within the extension and the high
+     * {@link Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX} bits encode
+     * the "prefix", which maps uniquely to the extension name.
+     *
+     * For example, if a model contains an operation whose value is
+     * 0xAAAABBBB and extensionNameToPrefix contains an entry with
+     * prefix=0xAAAA 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.
+     */
+    vec<ExtensionNameAndPrefix> extensionNameToPrefix;
+
+    /**
+     * A correspondence between an extension name and a prefix of operand and
+     * operation type values.
+     */
+    struct ExtensionNameAndPrefix {
+        /**
+         * The extension name.
+         *
+         * See {@link Extension::name} for the format specification.
+         */
+        string name;
+
+        /**
+         * The unique extension identifier within the model.
+         *
+         * See {@link Model::extensionNameToPrefix}.
+         */
+        uint16_t prefix;
+    };
+
+    /**
+     * Numeric values of extension operand and operation types have the
+     * following structure:
+     * - 16 high bits represent the "prefix", which corresponds uniquely to the
+     *   extension name.
+     * - 16 low bits represent the type ID within the extension.
+     */
+    enum ExtensionTypeEncoding : uint8_t {
+        HIGH_BITS_PREFIX = 16,
+        LOW_BITS_TYPE = 16,
+    };
+};
diff --git a/neuralnetworks/1.3/vts/OWNERS b/neuralnetworks/1.3/vts/OWNERS
new file mode 100644
index 0000000..b5a8e1f
--- /dev/null
+++ b/neuralnetworks/1.3/vts/OWNERS
@@ -0,0 +1,16 @@
+# 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
+
+# VTS team
+yim@google.com
+yuexima@google.com
diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp
new file mode 100644
index 0000000..90ce852
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/Android.bp
@@ -0,0 +1,58 @@
+//
+// 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: "VtsHalNeuralNetworksV1_3TargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: [
+        "BasicTests.cpp",
+        "CompilationCachingTests.cpp",
+        "GeneratedTestHarness.cpp",
+        "TestAssertions.cpp",
+        "ValidateBurst.cpp",
+        "ValidateModel.cpp",
+        "ValidateRequest.cpp",
+        "VtsHalNeuralnetworks.cpp",
+    ],
+    shared_libs: [
+        "libfmq",
+        "libnativewindow",
+    ],
+    static_libs: [
+        "android.hardware.neuralnetworks@1.0",
+        "android.hardware.neuralnetworks@1.1",
+        "android.hardware.neuralnetworks@1.2",
+        "android.hardware.neuralnetworks@1.3",
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libgmock",
+        "libhidlmemory",
+        "libneuralnetworks_generated_test_harness",
+        "libneuralnetworks_utils",
+        "VtsHalNeuralNetworksV1_0_utils",
+        "VtsHalNeuralNetworksV1_2Callbacks",
+    ],
+    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: [
+        "libneuralnetworks_headers",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/1.3/vts/functional/BasicTests.cpp b/neuralnetworks/1.3/vts/functional/BasicTests.cpp
new file mode 100644
index 0000000..b64dc2f
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/BasicTests.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "neuralnetworks_hidl_hal_test"
+
+#include "VtsHalNeuralnetworks.h"
+
+namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+
+using V1_0::DeviceStatus;
+using V1_0::ErrorStatus;
+using V1_0::PerformanceInfo;
+using V1_2::Constant;
+using V1_2::DeviceType;
+using V1_2::Extension;
+
+// create device test
+TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
+
+// status test
+TEST_P(NeuralnetworksHidlTest, StatusTest) {
+    Return<DeviceStatus> status = kDevice->getStatus();
+    ASSERT_TRUE(status.isOk());
+    EXPECT_EQ(DeviceStatus::AVAILABLE, static_cast<DeviceStatus>(status));
+}
+
+// initialization
+TEST_P(NeuralnetworksHidlTest, GetCapabilitiesTest) {
+    using OperandPerformance = Capabilities::OperandPerformance;
+    Return<void> ret = kDevice->getCapabilities_1_3([](ErrorStatus status,
+                                                       const Capabilities& capabilities) {
+        EXPECT_EQ(ErrorStatus::NONE, status);
+
+        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(ret.isOk());
+}
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/CompilationCachingTests.cpp b/neuralnetworks/1.3/vts/functional/CompilationCachingTests.cpp
new file mode 100644
index 0000000..0ac4738
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/CompilationCachingTests.cpp
@@ -0,0 +1,1377 @@
+/*
+ * 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 "neuralnetworks_hidl_hal_test"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <gtest/gtest.h>
+#include <hidlmemory/mapping.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <random>
+#include <thread>
+
+#include "1.2/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 android::hardware::neuralnetworks::V1_3::vts::functional {
+
+using namespace test_helper;
+using V1_0::ErrorStatus;
+using V1_1::ExecutionPreference;
+using V1_2::Constant;
+using V1_2::IPreparedModel;
+using V1_2::OperationType;
+using V1_2::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 createCacheHandles(const std::vector<std::vector<std::string>>& fileGroups,
+                        const std::vector<AccessMode>& mode, hidl_vec<hidl_handle>* handles) {
+    handles->resize(fileGroups.size());
+    for (uint32_t i = 0; i < fileGroups.size(); i++) {
+        std::vector<int> fds;
+        for (const auto& file : fileGroups[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.push_back(fd);
+        }
+        native_handle_t* cacheNativeHandle = native_handle_create(fds.size(), 0);
+        ASSERT_NE(cacheNativeHandle, nullptr);
+        std::copy(fds.begin(), fds.end(), &cacheNativeHandle->data[0]);
+        (*handles)[i].setTo(cacheNativeHandle, /*shouldOwn=*/true);
+    }
+}
+
+void createCacheHandles(const std::vector<std::vector<std::string>>& fileGroups, AccessMode mode,
+                        hidl_vec<hidl_handle>* handles) {
+    createCacheHandles(fileGroups, std::vector<AccessMode>(fileGroups.size(), mode), handles);
+}
+
+// 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 {
+            .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(sp<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('/');
+
+        Return<void> ret = kDevice->getNumberOfCacheFilesNeeded(
+                [this](ErrorStatus status, uint32_t numModelCache, uint32_t numDataCache) {
+                    EXPECT_EQ(ErrorStatus::NONE, status);
+                    mNumModelCache = numModelCache;
+                    mNumDataCache = numDataCache;
+                });
+        EXPECT_TRUE(ret.isOk());
+        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)});
+        }
+        // Dummy handles, use AccessMode::WRITE_ONLY for createCacheHandles to create files.
+        hidl_vec<hidl_handle> modelHandle, dataHandle, tmpHandle;
+        createCacheHandles(mModelCache, AccessMode::WRITE_ONLY, &modelHandle);
+        createCacheHandles(mDataCache, AccessMode::WRITE_ONLY, &dataHandle);
+        createCacheHandles({{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) {
+        bool fullySupportsModel = false;
+        Return<void> supportedCall = kDevice->getSupportedOperations_1_3(
+                model,
+                [&fullySupportsModel, &model](ErrorStatus status, const hidl_vec<bool>& supported) {
+                    ASSERT_EQ(ErrorStatus::NONE, status);
+                    ASSERT_EQ(supported.size(), model.operations.size());
+                    fullySupportsModel = std::all_of(supported.begin(), supported.end(),
+                                                     [](bool valid) { return valid; });
+                });
+        EXPECT_TRUE(supportedCall.isOk());
+        return fullySupportsModel;
+    }
+
+    void saveModelToCache(const Model& model, const hidl_vec<hidl_handle>& modelCache,
+                          const hidl_vec<hidl_handle>& dataCache,
+                          sp<IPreparedModel>* preparedModel = nullptr) {
+        if (preparedModel != nullptr) *preparedModel = nullptr;
+
+        // Launch prepare model.
+        sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
+        hidl_array<uint8_t, sizeof(mToken)> cacheToken(mToken);
+        Return<ErrorStatus> prepareLaunchStatus =
+                kDevice->prepareModel_1_3(model, ExecutionPreference::FAST_SINGLE_ANSWER,
+                                          modelCache, dataCache, cacheToken, preparedModelCallback);
+        ASSERT_TRUE(prepareLaunchStatus.isOk());
+        ASSERT_EQ(static_cast<ErrorStatus>(prepareLaunchStatus), ErrorStatus::NONE);
+
+        // Retrieve prepared model.
+        preparedModelCallback->wait();
+        ASSERT_EQ(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+        if (preparedModel != nullptr) {
+            *preparedModel = IPreparedModel::castFrom(preparedModelCallback->getPreparedModel())
+                                     .withDefault(nullptr);
+        }
+    }
+
+    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 hidl_vec<hidl_handle>& modelCache,
+                               const hidl_vec<hidl_handle>& dataCache,
+                               sp<IPreparedModel>* preparedModel, ErrorStatus* status) {
+        // Launch prepare model from cache.
+        sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
+        hidl_array<uint8_t, sizeof(mToken)> cacheToken(mToken);
+        Return<ErrorStatus> prepareLaunchStatus = kDevice->prepareModelFromCache(
+                modelCache, dataCache, cacheToken, preparedModelCallback);
+        ASSERT_TRUE(prepareLaunchStatus.isOk());
+        if (static_cast<ErrorStatus>(prepareLaunchStatus) != ErrorStatus::NONE) {
+            *preparedModel = nullptr;
+            *status = static_cast<ErrorStatus>(prepareLaunchStatus);
+            return;
+        }
+
+        // Retrieve prepared model.
+        preparedModelCallback->wait();
+        *status = preparedModelCallback->getStatus();
+        *preparedModel = IPreparedModel::castFrom(preparedModelCallback->getPreparedModel())
+                                 .withDefault(nullptr);
+    }
+
+    // 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
+    // outer_size = mNum{Model|Data}Cache, inner_size = 1. The outer vector corresponds to handles
+    // and the inner vector is for fds held by each handle.
+    std::vector<std::vector<std::string>> mModelCache;
+    std::vector<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>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)] = {};
+    uint32_t mNumModelCache;
+    uint32_t mNumDataCache;
+    uint32_t mIsCachingSupported;
+
+    const sp<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;
+    sp<IPreparedModel> preparedModel = nullptr;
+
+    // Save the compilation to cache.
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(model, modelCache, dataCache);
+    }
+
+    // Retrieve preparedModel from cache.
+    {
+        preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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(preparedModel, testModel,
+                          /*testDynamicOutputShape=*/false);
+}
+
+TEST_P(CompilationCachingTest, CacheSavingAndRetrievalNonZeroOffset) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+    sp<IPreparedModel> preparedModel = nullptr;
+
+    // Save the compilation to cache.
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        uint8_t dummyBytes[] = {0, 0};
+        // Write a dummy 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].getNativeHandle()->data[0], &dummyBytes,
+                            sizeof(dummyBytes)),
+                      sizeof(dummyBytes));
+        }
+        for (uint32_t i = 0; i < dataCache.size(); i++) {
+            ASSERT_EQ(
+                    write(dataCache[i].getNativeHandle()->data[0], &dummyBytes, sizeof(dummyBytes)),
+                    sizeof(dummyBytes));
+        }
+        saveModelToCache(model, modelCache, dataCache);
+    }
+
+    // Retrieve preparedModel from cache.
+    {
+        preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        uint8_t dummyByte = 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].getNativeHandle()->data[0], &dummyByte, 1), 0);
+        }
+        for (uint32_t i = 0; i < dataCache.size(); i++) {
+            ASSERT_GE(read(dataCache[i].getNativeHandle()->data[0], &dummyByte, 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(preparedModel, testModel,
+                          /*testDynamicOutputShape=*/false);
+}
+
+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.
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        // Pass an additional cache file for model cache.
+        mModelCache.push_back({mTmpCache});
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache.pop_back();
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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) {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        // Pop out the last cache file.
+        auto tmp = mModelCache.back();
+        mModelCache.pop_back();
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache.push_back(tmp);
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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.
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        // Pass an additional cache file for data cache.
+        mDataCache.push_back({mTmpCache});
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache.pop_back();
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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) {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        // Pop out the last cache file.
+        auto tmp = mDataCache.back();
+        mDataCache.pop_back();
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache.push_back(tmp);
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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.
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(model, modelCache, dataCache);
+    }
+
+    // Test with number of model cache files greater than mNumModelCache.
+    {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        mModelCache.push_back({mTmpCache});
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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) {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        auto tmp = mModelCache.back();
+        mModelCache.pop_back();
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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.
+    {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        mDataCache.push_back({mTmpCache});
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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) {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        auto tmp = mDataCache.back();
+        mDataCache.pop_back();
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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, SaveToCacheInvalidNumFd) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+
+    // Go through each handle in model cache, test with NumFd greater than 1.
+    for (uint32_t i = 0; i < mNumModelCache; i++) {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        // Pass an invalid number of fds for handle i.
+        mModelCache[i].push_back(mTmpCache);
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache[i].pop_back();
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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 model cache, test with NumFd equal to 0.
+    for (uint32_t i = 0; i < mNumModelCache; i++) {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        // Pass an invalid number of fds for handle i.
+        auto tmp = mModelCache[i].back();
+        mModelCache[i].pop_back();
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache[i].push_back(tmp);
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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 NumFd greater than 1.
+    for (uint32_t i = 0; i < mNumDataCache; i++) {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        // Pass an invalid number of fds for handle i.
+        mDataCache[i].push_back(mTmpCache);
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache[i].pop_back();
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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 NumFd equal to 0.
+    for (uint32_t i = 0; i < mNumDataCache; i++) {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        // Pass an invalid number of fds for handle i.
+        auto tmp = mDataCache[i].back();
+        mDataCache[i].pop_back();
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache[i].push_back(tmp);
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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, PrepareModelFromCacheInvalidNumFd) {
+    // Create test HIDL model and compile.
+    const TestModel& testModel = createTestModel();
+    const Model model = createModel(testModel);
+    if (checkEarlyTermination(model)) return;
+
+    // Save the compilation to cache.
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(model, modelCache, dataCache);
+    }
+
+    // Go through each handle in model cache, test with NumFd greater than 1.
+    for (uint32_t i = 0; i < mNumModelCache; i++) {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        mModelCache[i].push_back(mTmpCache);
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache[i].pop_back();
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::GENERAL_FAILURE) {
+            ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Go through each handle in model cache, test with NumFd equal to 0.
+    for (uint32_t i = 0; i < mNumModelCache; i++) {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        auto tmp = mModelCache[i].back();
+        mModelCache[i].pop_back();
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mModelCache[i].push_back(tmp);
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::GENERAL_FAILURE) {
+            ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Go through each handle in data cache, test with NumFd greater than 1.
+    for (uint32_t i = 0; i < mNumDataCache; i++) {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        mDataCache[i].push_back(mTmpCache);
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache[i].pop_back();
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        if (status != ErrorStatus::GENERAL_FAILURE) {
+            ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+        }
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+
+    // Go through each handle in data cache, test with NumFd equal to 0.
+    for (uint32_t i = 0; i < mNumDataCache; i++) {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        auto tmp = mDataCache[i].back();
+        mDataCache[i].pop_back();
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        mDataCache[i].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++) {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        modelCacheMode[i] = AccessMode::READ_ONLY;
+        createCacheHandles(mModelCache, modelCacheMode, &modelCache);
+        createCacheHandles(mDataCache, dataCacheMode, &dataCache);
+        modelCacheMode[i] = AccessMode::READ_WRITE;
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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++) {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        dataCacheMode[i] = AccessMode::READ_ONLY;
+        createCacheHandles(mModelCache, modelCacheMode, &modelCache);
+        createCacheHandles(mDataCache, dataCacheMode, &dataCache);
+        dataCacheMode[i] = AccessMode::READ_WRITE;
+        sp<IPreparedModel> preparedModel = nullptr;
+        saveModelToCache(model, modelCache, dataCache, &preparedModel);
+        ASSERT_NE(preparedModel, nullptr);
+        // Execute and verify results.
+        EvaluatePreparedModel(preparedModel, testModel,
+                              /*testDynamicOutputShape=*/false);
+        // 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.
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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++) {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        modelCacheMode[i] = AccessMode::WRITE_ONLY;
+        createCacheHandles(mModelCache, modelCacheMode, &modelCache);
+        createCacheHandles(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++) {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        dataCacheMode[i] = AccessMode::WRITE_ONLY;
+        createCacheHandles(mModelCache, modelCacheMode, &modelCache);
+        createCacheHandles(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 file groups.
+// The outer vector corresponds to handles and the inner vector is for fds held by each handle.
+// The outer vector sizes must match and the inner vectors must have size = 1.
+static void copyCacheFiles(const std::vector<std::vector<std::string>>& from,
+                           const std::vector<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++) {
+        ASSERT_EQ(from[i].size(), 1u);
+        ASSERT_EQ(to[i].size(), 1u);
+        int fromFd = open(from[i][0].c_str(), O_RDONLY);
+        int toFd = open(to[i][0].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[0].append("_mul");
+    }
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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.
+        {
+            hidl_vec<hidl_handle> modelCache, dataCache;
+            createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheHandles(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.
+        {
+            sp<IPreparedModel> preparedModel = nullptr;
+            ErrorStatus status;
+            hidl_vec<hidl_handle> modelCache, dataCache;
+            createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheHandles(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(preparedModel, testModelAdd,
+                                      /*testDynamicOutputShape=*/false);
+            }
+        }
+    }
+}
+
+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[0].append("_mul");
+    }
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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.
+        {
+            hidl_vec<hidl_handle> modelCache, dataCache;
+            createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+            saveModelToCache(modelAdd, modelCache, dataCache);
+        }
+
+        // Retrieve preparedModel from cache.
+        {
+            sp<IPreparedModel> preparedModel = nullptr;
+            ErrorStatus status;
+            hidl_vec<hidl_handle> modelCache, dataCache;
+            createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheHandles(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(preparedModel, testModelAdd,
+                                      /*testDynamicOutputShape=*/false);
+            }
+        }
+    }
+}
+
+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[0].append("_mul");
+    }
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        saveModelToCache(modelMul, modelCache, dataCache);
+    }
+
+    // Use a different token for modelAdd.
+    mToken[0]++;
+
+    // Save the modelAdd compilation to cache.
+    {
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(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.
+    {
+        sp<IPreparedModel> preparedModel = nullptr;
+        ErrorStatus status;
+        hidl_vec<hidl_handle> modelCache, dataCache;
+        createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+        createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+        prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+        ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+        ASSERT_EQ(preparedModel, nullptr);
+    }
+}
+
+static const auto kNamedDeviceChoices = testing::ValuesIn(getNamedDevices());
+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);
+}
+
+INSTANTIATE_TEST_CASE_P(TestCompilationCaching, CompilationCachingTest,
+                        testing::Combine(kNamedDeviceChoices, 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.
+        {
+            hidl_vec<hidl_handle> modelCache, dataCache;
+            createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+            saveModelToCache(model, modelCache, dataCache);
+        }
+
+        bool skip = false;
+        modifier(&skip);
+        if (skip) return;
+
+        // Retrieve preparedModel from cache.
+        {
+            sp<IPreparedModel> preparedModel = nullptr;
+            ErrorStatus status;
+            hidl_vec<hidl_handle> modelCache, dataCache;
+            createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+            createCacheHandles(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][0], 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][0], 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][0], 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][0], 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>(Constant::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));
+}
+
+INSTANTIATE_TEST_CASE_P(TestCompilationCaching, CompilationCachingSecurityTest,
+                        testing::Combine(kNamedDeviceChoices, kOperandTypeChoices,
+                                         testing::Range(0U, 10U)),
+                        printCompilationCachingSecurityTest);
+
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp
new file mode 100644
index 0000000..16a7d70
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp
@@ -0,0 +1,418 @@
+/*
+ * 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 "GeneratedTestHarness.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/neuralnetworks/1.0/IDevice.h>
+#include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h>
+#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
+#include <android/hardware/neuralnetworks/1.0/IPreparedModelCallback.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/IDevice.h>
+#include <android/hardware/neuralnetworks/1.2/IDevice.h>
+#include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h>
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <android/hardware/neuralnetworks/1.2/IPreparedModelCallback.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+#include <android/hardware/neuralnetworks/1.3/IDevice.h>
+#include <android/hardware/neuralnetworks/1.3/types.h>
+#include <android/hidl/allocator/1.0/IAllocator.h>
+#include <android/hidl/memory/1.0/IMemory.h>
+#include <hidlmemory/mapping.h>
+
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <iostream>
+#include <numeric>
+
+#include "1.0/Utils.h"
+#include "1.2/Callbacks.h"
+#include "ExecutionBurstController.h"
+#include "MemoryUtils.h"
+#include "TestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+
+using namespace test_helper;
+using hidl::memory::V1_0::IMemory;
+using V1_0::DataLocation;
+using V1_0::ErrorStatus;
+using V1_0::OperandLifeTime;
+using V1_0::Request;
+using V1_1::ExecutionPreference;
+using V1_2::Constant;
+using V1_2::IPreparedModel;
+using V1_2::MeasureTiming;
+using V1_2::OperationType;
+using V1_2::OutputShape;
+using V1_2::SymmPerChannelQuantParams;
+using V1_2::Timing;
+using V1_2::implementation::ExecutionCallback;
+using V1_2::implementation::PreparedModelCallback;
+using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+
+enum class OutputType { FULLY_SPECIFIED, UNSPECIFIED, INSUFFICIENT };
+
+Model createModel(const TestModel& testModel) {
+    // Model operands.
+    hidl_vec<Operand> operands(testModel.operands.size());
+    size_t constCopySize = 0, constRefSize = 0;
+    for (uint32_t i = 0; i < testModel.operands.size(); i++) {
+        const auto& op = testModel.operands[i];
+
+        DataLocation loc = {};
+        if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) {
+            loc = {.poolIndex = 0,
+                   .offset = static_cast<uint32_t>(constCopySize),
+                   .length = static_cast<uint32_t>(op.data.size())};
+            constCopySize += op.data.alignedSize();
+        } else if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) {
+            loc = {.poolIndex = 0,
+                   .offset = static_cast<uint32_t>(constRefSize),
+                   .length = static_cast<uint32_t>(op.data.size())};
+            constRefSize += op.data.alignedSize();
+        }
+
+        Operand::ExtraParams extraParams;
+        if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
+            extraParams.channelQuant(SymmPerChannelQuantParams{
+                    .scales = op.channelQuant.scales, .channelDim = op.channelQuant.channelDim});
+        }
+
+        operands[i] = {.type = static_cast<OperandType>(op.type),
+                       .dimensions = op.dimensions,
+                       .numberOfConsumers = op.numberOfConsumers,
+                       .scale = op.scale,
+                       .zeroPoint = op.zeroPoint,
+                       .lifetime = static_cast<OperandLifeTime>(op.lifetime),
+                       .location = loc,
+                       .extraParams = std::move(extraParams)};
+    }
+
+    // Model operations.
+    hidl_vec<Operation> operations(testModel.operations.size());
+    std::transform(testModel.operations.begin(), testModel.operations.end(), operations.begin(),
+                   [](const TestOperation& op) -> Operation {
+                       return {.type = static_cast<OperationType>(op.type),
+                               .inputs = op.inputs,
+                               .outputs = op.outputs};
+                   });
+
+    // Constant copies.
+    hidl_vec<uint8_t> operandValues(constCopySize);
+    for (uint32_t i = 0; i < testModel.operands.size(); i++) {
+        const auto& op = testModel.operands[i];
+        if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) {
+            const uint8_t* begin = op.data.get<uint8_t>();
+            const uint8_t* end = begin + op.data.size();
+            std::copy(begin, end, operandValues.data() + operands[i].location.offset);
+        }
+    }
+
+    // Shared memory.
+    hidl_vec<hidl_memory> pools = {};
+    if (constRefSize > 0) {
+        hidl_vec_push_back(&pools, nn::allocateSharedMemory(constRefSize));
+        CHECK_NE(pools[0].size(), 0u);
+
+        // load data
+        sp<IMemory> mappedMemory = mapMemory(pools[0]);
+        CHECK(mappedMemory.get() != nullptr);
+        uint8_t* mappedPtr =
+                reinterpret_cast<uint8_t*>(static_cast<void*>(mappedMemory->getPointer()));
+        CHECK(mappedPtr != nullptr);
+
+        for (uint32_t i = 0; i < testModel.operands.size(); i++) {
+            const auto& op = testModel.operands[i];
+            if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) {
+                const uint8_t* begin = op.data.get<uint8_t>();
+                const uint8_t* end = begin + op.data.size();
+                std::copy(begin, end, mappedPtr + operands[i].location.offset);
+            }
+        }
+    }
+
+    return {.operands = std::move(operands),
+            .operations = std::move(operations),
+            .inputIndexes = testModel.inputIndexes,
+            .outputIndexes = testModel.outputIndexes,
+            .operandValues = std::move(operandValues),
+            .pools = std::move(pools),
+            .relaxComputationFloat32toFloat16 = testModel.isRelaxed};
+}
+
+static bool isOutputSizeGreaterThanOne(const TestModel& testModel, uint32_t index) {
+    const auto byteSize = testModel.operands[testModel.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->outputIndexes) {
+        auto& dims = model->operands[i].dimensions;
+        std::fill(dims.begin(), dims.end(), 0);
+    }
+}
+
+static Return<ErrorStatus> ExecutePreparedModel(const sp<IPreparedModel>& preparedModel,
+                                                const Request& request, MeasureTiming measure,
+                                                sp<ExecutionCallback>& callback) {
+    return preparedModel->execute_1_2(request, measure, callback);
+}
+static Return<ErrorStatus> ExecutePreparedModel(const sp<IPreparedModel>& preparedModel,
+                                                const Request& request, MeasureTiming measure,
+                                                hidl_vec<OutputShape>* outputShapes,
+                                                Timing* timing) {
+    ErrorStatus result;
+    Return<void> ret = preparedModel->executeSynchronously(
+            request, measure,
+            [&result, outputShapes, timing](ErrorStatus error, const hidl_vec<OutputShape>& shapes,
+                                            const Timing& time) {
+                result = error;
+                *outputShapes = shapes;
+                *timing = time;
+            });
+    if (!ret.isOk()) {
+        return ErrorStatus::GENERAL_FAILURE;
+    }
+    return result;
+}
+static std::shared_ptr<::android::nn::ExecutionBurstController> CreateBurst(
+        const sp<IPreparedModel>& preparedModel) {
+    return android::nn::ExecutionBurstController::create(preparedModel, /*blocking=*/true);
+}
+enum class Executor { ASYNC, SYNC, BURST };
+
+void EvaluatePreparedModel(const sp<IPreparedModel>& preparedModel, const TestModel& testModel,
+                           Executor executor, MeasureTiming measure, OutputType outputType) {
+    // If output0 does not have size larger than one byte, we can not test with insufficient buffer.
+    if (outputType == OutputType::INSUFFICIENT && !isOutputSizeGreaterThanOne(testModel, 0)) {
+        return;
+    }
+
+    Request request = createRequest(testModel);
+    if (outputType == OutputType::INSUFFICIENT) {
+        makeOutputInsufficientSize(/*outputIndex=*/0, &request);
+    }
+
+    ErrorStatus executionStatus;
+    hidl_vec<OutputShape> outputShapes;
+    Timing timing;
+    switch (executor) {
+        case Executor::ASYNC: {
+            SCOPED_TRACE("asynchronous");
+
+            // launch execution
+            sp<ExecutionCallback> executionCallback = new ExecutionCallback();
+            Return<ErrorStatus> executionLaunchStatus =
+                    ExecutePreparedModel(preparedModel, request, measure, executionCallback);
+            ASSERT_TRUE(executionLaunchStatus.isOk());
+            EXPECT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(executionLaunchStatus));
+
+            // retrieve execution status
+            executionCallback->wait();
+            executionStatus = executionCallback->getStatus();
+            outputShapes = executionCallback->getOutputShapes();
+            timing = executionCallback->getTiming();
+
+            break;
+        }
+        case Executor::SYNC: {
+            SCOPED_TRACE("synchronous");
+
+            // execute
+            Return<ErrorStatus> executionReturnStatus =
+                    ExecutePreparedModel(preparedModel, request, measure, &outputShapes, &timing);
+            ASSERT_TRUE(executionReturnStatus.isOk());
+            executionStatus = static_cast<ErrorStatus>(executionReturnStatus);
+
+            break;
+        }
+        case Executor::BURST: {
+            SCOPED_TRACE("burst");
+
+            // create burst
+            const std::shared_ptr<::android::nn::ExecutionBurstController> controller =
+                    CreateBurst(preparedModel);
+            ASSERT_NE(nullptr, controller.get());
+
+            // create memory keys
+            std::vector<intptr_t> keys(request.pools.size());
+            for (size_t i = 0; i < keys.size(); ++i) {
+                keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]);
+            }
+
+            // execute burst
+            std::tie(executionStatus, outputShapes, timing) =
+                    controller->compute(request, measure, keys);
+
+            break;
+        }
+    }
+
+    if (outputType != OutputType::FULLY_SPECIFIED &&
+        executionStatus == ErrorStatus::GENERAL_FAILURE) {
+        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 (measure == MeasureTiming::NO) {
+        EXPECT_EQ(UINT64_MAX, timing.timeOnDevice);
+        EXPECT_EQ(UINT64_MAX, timing.timeInDriver);
+    } else {
+        if (timing.timeOnDevice != UINT64_MAX && timing.timeInDriver != UINT64_MAX) {
+            EXPECT_LE(timing.timeOnDevice, timing.timeInDriver);
+        }
+    }
+
+    switch (outputType) {
+        case OutputType::FULLY_SPECIFIED:
+            // 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.outputIndexes.size());
+            break;
+        case OutputType::UNSPECIFIED:
+            // 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.outputIndexes.size());
+            break;
+        case OutputType::INSUFFICIENT:
+            ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus);
+            ASSERT_EQ(outputShapes.size(), testModel.outputIndexes.size());
+            ASSERT_FALSE(outputShapes[0].isSufficient);
+            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.operands[testModel.outputIndexes[i]].dimensions;
+        const std::vector<uint32_t> actual = outputShapes[i].dimensions;
+        EXPECT_EQ(expect, actual);
+    }
+
+    // Retrieve execution results.
+    const std::vector<TestBuffer> outputs = getOutputBuffers(request);
+
+    // We want "close-enough" results.
+    checkResults(testModel, outputs);
+}
+
+void EvaluatePreparedModel(const sp<IPreparedModel>& preparedModel, const TestModel& testModel,
+                           bool testDynamicOutputShape) {
+    if (testDynamicOutputShape) {
+        EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::NO,
+                              OutputType::UNSPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::NO,
+                              OutputType::UNSPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::NO,
+                              OutputType::UNSPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::YES,
+                              OutputType::UNSPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::YES,
+                              OutputType::UNSPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::YES,
+                              OutputType::UNSPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::NO,
+                              OutputType::INSUFFICIENT);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::NO,
+                              OutputType::INSUFFICIENT);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::NO,
+                              OutputType::INSUFFICIENT);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::YES,
+                              OutputType::INSUFFICIENT);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::YES,
+                              OutputType::INSUFFICIENT);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::YES,
+                              OutputType::INSUFFICIENT);
+    } else {
+        EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::NO,
+                              OutputType::FULLY_SPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::NO,
+                              OutputType::FULLY_SPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::NO,
+                              OutputType::FULLY_SPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::YES,
+                              OutputType::FULLY_SPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::YES,
+                              OutputType::FULLY_SPECIFIED);
+        EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::YES,
+                              OutputType::FULLY_SPECIFIED);
+    }
+}
+
+void Execute(const sp<IDevice>& device, const TestModel& testModel, bool testDynamicOutputShape) {
+    Model model = createModel(testModel);
+    if (testDynamicOutputShape) {
+        makeOutputDimensionsUnspecified(&model);
+    }
+
+    sp<IPreparedModel> preparedModel;
+    createPreparedModel(device, model, &preparedModel);
+    if (preparedModel == nullptr) return;
+
+    EvaluatePreparedModel(preparedModel, testModel, testDynamicOutputShape);
+}
+
+void GeneratedTestBase::SetUp() {
+    testing::TestWithParam<GeneratedTestParam>::SetUp();
+    ASSERT_NE(kDevice, nullptr);
+}
+
+std::vector<NamedModel> getNamedModels(const FilterFn& 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 {};
+
+TEST_P(GeneratedTest, Test) {
+    Execute(kDevice, kTestModel, /*testDynamicOutputShape=*/false);
+}
+
+TEST_P(DynamicOutputShapeTest, Test) {
+    Execute(kDevice, kTestModel, /*testDynamicOutputShape=*/true);
+}
+
+INSTANTIATE_GENERATED_TEST(GeneratedTest,
+                           [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest,
+                           [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.h b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.h
new file mode 100644
index 0000000..b9277cf
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.h
@@ -0,0 +1,66 @@
+/*
+ * 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_NEURALNETWORKS_V1_3_GENERATED_TEST_HARNESS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_V1_3_GENERATED_TEST_HARNESS_H
+
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <android/hardware/neuralnetworks/1.3/IDevice.h>
+#include <android/hardware/neuralnetworks/1.3/types.h>
+#include <functional>
+#include <vector>
+#include "1.0/Utils.h"
+#include "TestHarness.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace android::hardware::neuralnetworks::V1_3::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 sp<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);
+
+std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info);
+
+#define INSTANTIATE_GENERATED_TEST(TestSuite, filter)                                     \
+    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 sp<IDevice>& device, const Model& model,
+                  sp<V1_2::IPreparedModel>* preparedModel);
+
+void EvaluatePreparedModel(const sp<V1_2::IPreparedModel>& preparedModel,
+                           const test_helper::TestModel& testModel, bool testDynamicOutputShape);
+
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_V1_3_GENERATED_TEST_HARNESS_H
diff --git a/neuralnetworks/1.3/vts/functional/TestAssertions.cpp b/neuralnetworks/1.3/vts/functional/TestAssertions.cpp
new file mode 100644
index 0000000..7361078
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/TestAssertions.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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 <android/hardware/neuralnetworks/1.3/types.h>
+#include "TestHarness.h"
+
+namespace android::hardware::neuralnetworks::V1_3 {
+
+// 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)
+
+using V1_2::OperationType;
+
+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 android::hardware::neuralnetworks::V1_3
diff --git a/neuralnetworks/1.3/vts/functional/ValidateBurst.cpp b/neuralnetworks/1.3/vts/functional/ValidateBurst.cpp
new file mode 100644
index 0000000..95f9f42
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/ValidateBurst.cpp
@@ -0,0 +1,407 @@
+/*
+ * 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 "neuralnetworks_hidl_hal_test"
+
+#include "VtsHalNeuralnetworks.h"
+
+#include "1.2/Callbacks.h"
+#include "ExecutionBurstController.h"
+#include "ExecutionBurstServer.h"
+#include "GeneratedTestHarness.h"
+#include "TestHarness.h"
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <cstring>
+
+namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+
+using nn::ExecutionBurstController;
+using nn::RequestChannelSender;
+using nn::ResultChannelReceiver;
+using V1_0::ErrorStatus;
+using V1_0::Request;
+using V1_2::FmqRequestDatum;
+using V1_2::FmqResultDatum;
+using V1_2::IBurstCallback;
+using V1_2::IBurstContext;
+using V1_2::IPreparedModel;
+using V1_2::MeasureTiming;
+using V1_2::Timing;
+using ExecutionBurstCallback = ExecutionBurstController::ExecutionBurstCallback;
+
+// This constant value represents the length of an FMQ that is large enough to
+// return a result from a burst execution for all of the generated test cases.
+constexpr size_t kExecutionBurstChannelLength = 1024;
+
+// This constant value represents a length of an FMQ that is not large enough
+// to return a result from a burst execution for some of the generated test
+// cases.
+constexpr size_t kExecutionBurstChannelSmallLength = 8;
+
+///////////////////////// UTILITY FUNCTIONS /////////////////////////
+
+static bool badTiming(Timing timing) {
+    return timing.timeOnDevice == UINT64_MAX && timing.timeInDriver == UINT64_MAX;
+}
+
+static void createBurst(const sp<IPreparedModel>& preparedModel, const sp<IBurstCallback>& callback,
+                        std::unique_ptr<RequestChannelSender>* sender,
+                        std::unique_ptr<ResultChannelReceiver>* receiver,
+                        sp<IBurstContext>* context,
+                        size_t resultChannelLength = kExecutionBurstChannelLength) {
+    ASSERT_NE(nullptr, preparedModel.get());
+    ASSERT_NE(nullptr, sender);
+    ASSERT_NE(nullptr, receiver);
+    ASSERT_NE(nullptr, context);
+
+    // create FMQ objects
+    auto [fmqRequestChannel, fmqRequestDescriptor] =
+            RequestChannelSender::create(kExecutionBurstChannelLength, /*blocking=*/true);
+    auto [fmqResultChannel, fmqResultDescriptor] =
+            ResultChannelReceiver::create(resultChannelLength, /*blocking=*/true);
+    ASSERT_NE(nullptr, fmqRequestChannel.get());
+    ASSERT_NE(nullptr, fmqResultChannel.get());
+    ASSERT_NE(nullptr, fmqRequestDescriptor);
+    ASSERT_NE(nullptr, fmqResultDescriptor);
+
+    // configure burst
+    ErrorStatus errorStatus;
+    sp<IBurstContext> burstContext;
+    const Return<void> ret = preparedModel->configureExecutionBurst(
+            callback, *fmqRequestDescriptor, *fmqResultDescriptor,
+            [&errorStatus, &burstContext](ErrorStatus status, const sp<IBurstContext>& context) {
+                errorStatus = status;
+                burstContext = context;
+            });
+    ASSERT_TRUE(ret.isOk());
+    ASSERT_EQ(ErrorStatus::NONE, errorStatus);
+    ASSERT_NE(nullptr, burstContext.get());
+
+    // return values
+    *sender = std::move(fmqRequestChannel);
+    *receiver = std::move(fmqResultChannel);
+    *context = burstContext;
+}
+
+static void createBurstWithResultChannelLength(
+        const sp<IPreparedModel>& preparedModel, size_t resultChannelLength,
+        std::shared_ptr<ExecutionBurstController>* controller) {
+    ASSERT_NE(nullptr, preparedModel.get());
+    ASSERT_NE(nullptr, controller);
+
+    // create FMQ objects
+    std::unique_ptr<RequestChannelSender> sender;
+    std::unique_ptr<ResultChannelReceiver> receiver;
+    sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback();
+    sp<IBurstContext> context;
+    ASSERT_NO_FATAL_FAILURE(createBurst(preparedModel, callback, &sender, &receiver, &context,
+                                        resultChannelLength));
+    ASSERT_NE(nullptr, sender.get());
+    ASSERT_NE(nullptr, receiver.get());
+    ASSERT_NE(nullptr, context.get());
+
+    // return values
+    *controller = std::make_shared<ExecutionBurstController>(std::move(sender), std::move(receiver),
+                                                             context, callback);
+}
+
+// Primary validation function. This function will take a valid serialized
+// request, apply a mutation to it to invalidate the serialized request, then
+// pass it to interface calls that use the serialized request. Note that the
+// serialized request here is passed by value, and any mutation to the
+// serialized request does not leave this function.
+static void validate(RequestChannelSender* sender, ResultChannelReceiver* receiver,
+                     const std::string& message, std::vector<FmqRequestDatum> serialized,
+                     const std::function<void(std::vector<FmqRequestDatum>*)>& mutation) {
+    mutation(&serialized);
+
+    // skip if packet is too large to send
+    if (serialized.size() > kExecutionBurstChannelLength) {
+        return;
+    }
+
+    SCOPED_TRACE(message);
+
+    // send invalid packet
+    ASSERT_TRUE(sender->sendPacket(serialized));
+
+    // receive error
+    auto results = receiver->getBlocking();
+    ASSERT_TRUE(results.has_value());
+    const auto [status, outputShapes, timing] = std::move(*results);
+    EXPECT_NE(ErrorStatus::NONE, status);
+    EXPECT_EQ(0u, outputShapes.size());
+    EXPECT_TRUE(badTiming(timing));
+}
+
+// For validation, valid packet entries are mutated to invalid packet entries,
+// or invalid packet entries are inserted into valid packets. This function
+// creates pre-set invalid packet entries for convenience.
+static std::vector<FmqRequestDatum> createBadRequestPacketEntries() {
+    const FmqRequestDatum::PacketInformation packetInformation = {
+            /*.packetSize=*/10, /*.numberOfInputOperands=*/10, /*.numberOfOutputOperands=*/10,
+            /*.numberOfPools=*/10};
+    const FmqRequestDatum::OperandInformation operandInformation = {
+            /*.hasNoValue=*/false, /*.location=*/{}, /*.numberOfDimensions=*/10};
+    const int32_t invalidPoolIdentifier = std::numeric_limits<int32_t>::max();
+    std::vector<FmqRequestDatum> bad(7);
+    bad[0].packetInformation(packetInformation);
+    bad[1].inputOperandInformation(operandInformation);
+    bad[2].inputOperandDimensionValue(0);
+    bad[3].outputOperandInformation(operandInformation);
+    bad[4].outputOperandDimensionValue(0);
+    bad[5].poolIdentifier(invalidPoolIdentifier);
+    bad[6].measureTiming(MeasureTiming::YES);
+    return bad;
+}
+
+// For validation, valid packet entries are mutated to invalid packet entries,
+// or invalid packet entries are inserted into valid packets. This function
+// retrieves pre-set invalid packet entries for convenience. This function
+// caches these data so they can be reused on subsequent validation checks.
+static const std::vector<FmqRequestDatum>& getBadRequestPacketEntries() {
+    static const std::vector<FmqRequestDatum> bad = createBadRequestPacketEntries();
+    return bad;
+}
+
+///////////////////////// REMOVE DATUM ////////////////////////////////////
+
+static void removeDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver,
+                            const std::vector<FmqRequestDatum>& serialized) {
+    for (size_t index = 0; index < serialized.size(); ++index) {
+        const std::string message = "removeDatum: removed datum at index " + std::to_string(index);
+        validate(sender, receiver, message, serialized,
+                 [index](std::vector<FmqRequestDatum>* serialized) {
+                     serialized->erase(serialized->begin() + index);
+                 });
+    }
+}
+
+///////////////////////// ADD DATUM ////////////////////////////////////
+
+static void addDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver,
+                         const std::vector<FmqRequestDatum>& serialized) {
+    const std::vector<FmqRequestDatum>& extra = getBadRequestPacketEntries();
+    for (size_t index = 0; index <= serialized.size(); ++index) {
+        for (size_t type = 0; type < extra.size(); ++type) {
+            const std::string message = "addDatum: added datum type " + std::to_string(type) +
+                                        " at index " + std::to_string(index);
+            validate(sender, receiver, message, serialized,
+                     [index, type, &extra](std::vector<FmqRequestDatum>* serialized) {
+                         serialized->insert(serialized->begin() + index, extra[type]);
+                     });
+        }
+    }
+}
+
+///////////////////////// MUTATE DATUM ////////////////////////////////////
+
+static bool interestingCase(const FmqRequestDatum& lhs, const FmqRequestDatum& rhs) {
+    using Discriminator = FmqRequestDatum::hidl_discriminator;
+
+    const bool differentValues = (lhs != rhs);
+    const bool sameDiscriminator = (lhs.getDiscriminator() == rhs.getDiscriminator());
+    const auto discriminator = rhs.getDiscriminator();
+    const bool isDimensionValue = (discriminator == Discriminator::inputOperandDimensionValue ||
+                                   discriminator == Discriminator::outputOperandDimensionValue);
+
+    return differentValues && !(sameDiscriminator && isDimensionValue);
+}
+
+static void mutateDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver,
+                            const std::vector<FmqRequestDatum>& serialized) {
+    const std::vector<FmqRequestDatum>& change = getBadRequestPacketEntries();
+    for (size_t index = 0; index < serialized.size(); ++index) {
+        for (size_t type = 0; type < change.size(); ++type) {
+            if (interestingCase(serialized[index], change[type])) {
+                const std::string message = "mutateDatum: changed datum at index " +
+                                            std::to_string(index) + " to datum type " +
+                                            std::to_string(type);
+                validate(sender, receiver, message, serialized,
+                         [index, type, &change](std::vector<FmqRequestDatum>* serialized) {
+                             (*serialized)[index] = change[type];
+                         });
+            }
+        }
+    }
+}
+
+///////////////////////// BURST VALIATION TESTS ////////////////////////////////////
+
+static void validateBurstSerialization(const sp<IPreparedModel>& preparedModel,
+                                       const Request& request) {
+    // create burst
+    std::unique_ptr<RequestChannelSender> sender;
+    std::unique_ptr<ResultChannelReceiver> receiver;
+    sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback();
+    sp<IBurstContext> context;
+    ASSERT_NO_FATAL_FAILURE(createBurst(preparedModel, callback, &sender, &receiver, &context));
+    ASSERT_NE(nullptr, sender.get());
+    ASSERT_NE(nullptr, receiver.get());
+    ASSERT_NE(nullptr, context.get());
+
+    // load memory into callback slots
+    std::vector<intptr_t> keys;
+    keys.reserve(request.pools.size());
+    std::transform(request.pools.begin(), request.pools.end(), std::back_inserter(keys),
+                   [](const auto& pool) { return reinterpret_cast<intptr_t>(&pool); });
+    const std::vector<int32_t> slots = callback->getSlots(request.pools, keys);
+
+    // ensure slot std::numeric_limits<int32_t>::max() doesn't exist (for
+    // subsequent slot validation testing)
+    ASSERT_TRUE(std::all_of(slots.begin(), slots.end(), [](int32_t slot) {
+        return slot != std::numeric_limits<int32_t>::max();
+    }));
+
+    // serialize the request
+    const auto serialized = android::nn::serialize(request, MeasureTiming::YES, slots);
+
+    // validations
+    removeDatumTest(sender.get(), receiver.get(), serialized);
+    addDatumTest(sender.get(), receiver.get(), serialized);
+    mutateDatumTest(sender.get(), receiver.get(), serialized);
+}
+
+// This test validates that when the Result message size exceeds length of the
+// result FMQ, the service instance gracefully fails and returns an error.
+static void validateBurstFmqLength(const sp<IPreparedModel>& preparedModel,
+                                   const Request& request) {
+    // create regular burst
+    std::shared_ptr<ExecutionBurstController> controllerRegular;
+    ASSERT_NO_FATAL_FAILURE(createBurstWithResultChannelLength(
+            preparedModel, kExecutionBurstChannelLength, &controllerRegular));
+    ASSERT_NE(nullptr, controllerRegular.get());
+
+    // create burst with small output channel
+    std::shared_ptr<ExecutionBurstController> controllerSmall;
+    ASSERT_NO_FATAL_FAILURE(createBurstWithResultChannelLength(
+            preparedModel, kExecutionBurstChannelSmallLength, &controllerSmall));
+    ASSERT_NE(nullptr, controllerSmall.get());
+
+    // load memory into callback slots
+    std::vector<intptr_t> keys(request.pools.size());
+    for (size_t i = 0; i < keys.size(); ++i) {
+        keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]);
+    }
+
+    // collect serialized result by running regular burst
+    const auto [statusRegular, outputShapesRegular, timingRegular] =
+            controllerRegular->compute(request, MeasureTiming::NO, keys);
+
+    // skip test if regular burst output isn't useful for testing a failure
+    // caused by having too small of a length for the result FMQ
+    const std::vector<FmqResultDatum> serialized =
+            android::nn::serialize(statusRegular, outputShapesRegular, timingRegular);
+    if (statusRegular != ErrorStatus::NONE ||
+        serialized.size() <= kExecutionBurstChannelSmallLength) {
+        return;
+    }
+
+    // by this point, execution should fail because the result channel isn't
+    // large enough to return the serialized result
+    const auto [statusSmall, outputShapesSmall, timingSmall] =
+            controllerSmall->compute(request, MeasureTiming::NO, keys);
+    EXPECT_NE(ErrorStatus::NONE, statusSmall);
+    EXPECT_EQ(0u, outputShapesSmall.size());
+    EXPECT_TRUE(badTiming(timingSmall));
+}
+
+static bool isSanitized(const FmqResultDatum& datum) {
+    using Discriminator = FmqResultDatum::hidl_discriminator;
+
+    // check to ensure the padding values in the returned
+    // FmqResultDatum::OperandInformation are initialized to 0
+    if (datum.getDiscriminator() == Discriminator::operandInformation) {
+        static_assert(
+                offsetof(FmqResultDatum::OperandInformation, isSufficient) == 0,
+                "unexpected value for offset of FmqResultDatum::OperandInformation::isSufficient");
+        static_assert(
+                sizeof(FmqResultDatum::OperandInformation::isSufficient) == 1,
+                "unexpected value for size of FmqResultDatum::OperandInformation::isSufficient");
+        static_assert(offsetof(FmqResultDatum::OperandInformation, numberOfDimensions) == 4,
+                      "unexpected value for offset of "
+                      "FmqResultDatum::OperandInformation::numberOfDimensions");
+        static_assert(sizeof(FmqResultDatum::OperandInformation::numberOfDimensions) == 4,
+                      "unexpected value for size of "
+                      "FmqResultDatum::OperandInformation::numberOfDimensions");
+        static_assert(sizeof(FmqResultDatum::OperandInformation) == 8,
+                      "unexpected value for size of "
+                      "FmqResultDatum::OperandInformation");
+
+        constexpr size_t paddingOffset =
+                offsetof(FmqResultDatum::OperandInformation, isSufficient) +
+                sizeof(FmqResultDatum::OperandInformation::isSufficient);
+        constexpr size_t paddingSize =
+                offsetof(FmqResultDatum::OperandInformation, numberOfDimensions) - paddingOffset;
+
+        FmqResultDatum::OperandInformation initialized{};
+        std::memset(&initialized, 0, sizeof(initialized));
+
+        const char* initializedPaddingStart =
+                reinterpret_cast<const char*>(&initialized) + paddingOffset;
+        const char* datumPaddingStart =
+                reinterpret_cast<const char*>(&datum.operandInformation()) + paddingOffset;
+
+        return std::memcmp(datumPaddingStart, initializedPaddingStart, paddingSize) == 0;
+    }
+
+    // there are no other padding initialization checks required, so return true
+    // for any sum-type that isn't FmqResultDatum::OperandInformation
+    return true;
+}
+
+static void validateBurstSanitized(const sp<IPreparedModel>& preparedModel,
+                                   const Request& request) {
+    // create burst
+    std::unique_ptr<RequestChannelSender> sender;
+    std::unique_ptr<ResultChannelReceiver> receiver;
+    sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback();
+    sp<IBurstContext> context;
+    ASSERT_NO_FATAL_FAILURE(createBurst(preparedModel, callback, &sender, &receiver, &context));
+    ASSERT_NE(nullptr, sender.get());
+    ASSERT_NE(nullptr, receiver.get());
+    ASSERT_NE(nullptr, context.get());
+
+    // load memory into callback slots
+    std::vector<intptr_t> keys;
+    keys.reserve(request.pools.size());
+    std::transform(request.pools.begin(), request.pools.end(), std::back_inserter(keys),
+                   [](const auto& pool) { return reinterpret_cast<intptr_t>(&pool); });
+    const std::vector<int32_t> slots = callback->getSlots(request.pools, keys);
+
+    // send valid request
+    ASSERT_TRUE(sender->send(request, MeasureTiming::YES, slots));
+
+    // receive valid result
+    auto serialized = receiver->getPacketBlocking();
+    ASSERT_TRUE(serialized.has_value());
+
+    // sanitize result
+    ASSERT_TRUE(std::all_of(serialized->begin(), serialized->end(), isSanitized))
+            << "The result serialized data is not properly sanitized";
+}
+
+///////////////////////////// ENTRY POINT //////////////////////////////////
+
+void validateBurst(const sp<IPreparedModel>& preparedModel, const Request& request) {
+    ASSERT_NO_FATAL_FAILURE(validateBurstSerialization(preparedModel, request));
+    ASSERT_NO_FATAL_FAILURE(validateBurstFmqLength(preparedModel, request));
+    ASSERT_NO_FATAL_FAILURE(validateBurstSanitized(preparedModel, request));
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
new file mode 100644
index 0000000..44b32a9
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
@@ -0,0 +1,718 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "neuralnetworks_hidl_hal_test"
+
+#include "1.0/Utils.h"
+#include "1.2/Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+
+using V1_0::ErrorStatus;
+using V1_0::OperandLifeTime;
+using V1_1::ExecutionPreference;
+using V1_2::IPreparedModel;
+using V1_2::OperationType;
+using V1_2::OperationTypeRange;
+using V1_2::SymmPerChannelQuantParams;
+using V1_2::implementation::PreparedModelCallback;
+using HidlToken =
+        hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+
+///////////////////////// UTILITY FUNCTIONS /////////////////////////
+
+static void validateGetSupportedOperations(const sp<IDevice>& device, const std::string& message,
+                                           const Model& model) {
+    SCOPED_TRACE(message + " [getSupportedOperations_1_3]");
+
+    Return<void> ret = device->getSupportedOperations_1_3(
+            model, [&](ErrorStatus status, const hidl_vec<bool>&) {
+                EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, status);
+            });
+    EXPECT_TRUE(ret.isOk());
+}
+
+static void validatePrepareModel(const sp<IDevice>& device, const std::string& message,
+                                 const Model& model, ExecutionPreference preference) {
+    SCOPED_TRACE(message + " [prepareModel_1_3]");
+
+    sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
+    Return<ErrorStatus> prepareLaunchStatus =
+            device->prepareModel_1_3(model, preference, hidl_vec<hidl_handle>(),
+                                     hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+    ASSERT_TRUE(prepareLaunchStatus.isOk());
+    ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, static_cast<ErrorStatus>(prepareLaunchStatus));
+
+    preparedModelCallback->wait();
+    ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+    ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, prepareReturnStatus);
+    sp<IPreparedModel> preparedModel = getPreparedModel_1_2(preparedModelCallback);
+    ASSERT_EQ(nullptr, preparedModel.get());
+}
+
+static bool validExecutionPreference(ExecutionPreference preference) {
+    return preference == ExecutionPreference::LOW_POWER ||
+           preference == ExecutionPreference::FAST_SINGLE_ANSWER ||
+           preference == ExecutionPreference::SUSTAINED_SPEED;
+}
+
+// Primary validation function. This function will take a valid model, apply a
+// mutation to it to invalidate the model, then pass it to interface calls that
+// use the model. Note that the model here is passed by value, and any mutation
+// to the model does not leave this function.
+static void validate(const sp<IDevice>& device, const std::string& message, Model model,
+                     const std::function<void(Model*)>& mutation,
+                     ExecutionPreference preference = ExecutionPreference::FAST_SINGLE_ANSWER) {
+    mutation(&model);
+    if (validExecutionPreference(preference)) {
+        validateGetSupportedOperations(device, message, model);
+    }
+    validatePrepareModel(device, message, model, preference);
+}
+
+static uint32_t addOperand(Model* model) {
+    return hidl_vec_push_back(&model->operands,
+                              {
+                                      .type = OperandType::INT32,
+                                      .dimensions = {},
+                                      .numberOfConsumers = 0,
+                                      .scale = 0.0f,
+                                      .zeroPoint = 0,
+                                      .lifetime = OperandLifeTime::MODEL_INPUT,
+                                      .location = {.poolIndex = 0, .offset = 0, .length = 0},
+                              });
+}
+
+static uint32_t addOperand(Model* model, OperandLifeTime lifetime) {
+    uint32_t index = addOperand(model);
+    model->operands[index].numberOfConsumers = 1;
+    model->operands[index].lifetime = lifetime;
+    return index;
+}
+
+///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
+
+static const uint32_t invalidOperandTypes[] = {
+        static_cast<uint32_t>(OperandTypeRange::FUNDAMENTAL_MIN) - 1,
+        static_cast<uint32_t>(OperandTypeRange::FUNDAMENTAL_MAX) + 1,
+        static_cast<uint32_t>(OperandTypeRange::OEM_MIN) - 1,
+        static_cast<uint32_t>(OperandTypeRange::OEM_MAX) + 1,
+};
+
+static void mutateOperandTypeTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        for (uint32_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) {
+                model->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 sp<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const uint32_t invalidRank = getInvalidRank(model.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) {
+            model->operands[operand].dimensions = std::vector<uint32_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:
+            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 sp<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const float invalidScale = getInvalidScale(model.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) {
+            model->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:
+            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 sp<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::vector<int32_t> invalidZeroPoints =
+                getInvalidZeroPoints(model.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) {
+                model->operands[operand].zeroPoint = invalidZeroPoint;
+            });
+        }
+    }
+}
+
+///////////////////////// VALIDATE EXTRA ??? /////////////////////////
+
+// TODO: Operand::lifetime
+// 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 = hidl_vec<uint32_t>();
+            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 : hidl_vec<uint32_t>({1});
+            newOperand.scale = 0.0f;
+            newOperand.zeroPoint = 0;
+            break;
+        case OperandType::TENSOR_INT32:
+            newOperand.dimensions =
+                    operand->dimensions.size() > 0 ? operand->dimensions : hidl_vec<uint32_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 : hidl_vec<uint32_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 : hidl_vec<uint32_t>({1});
+            newOperand.scale = 0.0f;
+            newOperand.zeroPoint = 0;
+
+            SymmPerChannelQuantParams channelQuant;
+            channelQuant.channelDim = 0;
+            channelQuant.scales = hidl_vec<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.channelQuant(std::move(channelQuant));
+        } break;
+        case OperandType::OEM:
+        case OperandType::TENSOR_OEM_BYTE:
+        default:
+            break;
+    }
+    *operand = newOperand;
+}
+
+static bool mutateOperationOperandTypeSkip(size_t operand, OperandType type, const Model& model) {
+    // Do not test OEM types
+    if (type == model.operands[operand].type || type == OperandType::OEM ||
+        type == OperandType::TENSOR_OEM_BYTE) {
+        return true;
+    }
+    for (const Operation& operation : model.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_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
+        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) {
+                    return true;
+                }
+            } break;
+            case OperationType::QUANTIZE:
+            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_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;
+            default:
+                break;
+        }
+    }
+    return false;
+}
+
+static void mutateOperationOperandTypeTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        for (OperandType invalidOperandType : hidl_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) {
+                mutateOperand(&model->operands[operand], invalidOperandType);
+            });
+        }
+    }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION TYPE /////////////////////////
+
+static const uint32_t invalidOperationTypes[] = {
+        static_cast<uint32_t>(OperationTypeRange::FUNDAMENTAL_MAX) + 1,
+        static_cast<uint32_t>(OperationTypeRange::OEM_MIN) - 1,
+        static_cast<uint32_t>(OperationTypeRange::OEM_MAX) + 1,
+};
+
+static void mutateOperationTypeTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (uint32_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) {
+                model->operations[operation].type =
+                        static_cast<OperationType>(invalidOperationType);
+            });
+        }
+    }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION INPUT OPERAND INDEX /////////////////////////
+
+static void mutateOperationInputOperandIndexTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        const uint32_t invalidOperand = model.operands.size();
+        for (size_t input = 0; input < model.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) {
+                model->operations[operation].inputs[input] = invalidOperand;
+            });
+        }
+    }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION OUTPUT OPERAND INDEX /////////////////////////
+
+static void mutateOperationOutputOperandIndexTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        const uint32_t invalidOperand = model.operands.size();
+        for (size_t output = 0; output < model.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) {
+                model->operations[operation].outputs[output] = invalidOperand;
+            });
+        }
+    }
+}
+
+///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
+
+static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
+    if (vec) {
+        // remove elements matching "value"
+        auto last = std::remove(vec->begin(), vec->end(), value);
+        vec->resize(std::distance(vec->begin(), last));
+
+        // 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) {
+    hidl_vec_removeAt(&model->operands, index);
+    for (Operation& operation : model->operations) {
+        removeValueAndDecrementGreaterValues(&operation.inputs, index);
+        removeValueAndDecrementGreaterValues(&operation.outputs, index);
+    }
+    removeValueAndDecrementGreaterValues(&model->inputIndexes, index);
+    removeValueAndDecrementGreaterValues(&model->outputIndexes, index);
+}
+
+static bool removeOperandSkip(size_t operand, const Model& model) {
+    for (const Operation& operation : model.operations) {
+        // Skip removeOperandTest for the following operations.
+        // - SPLIT's outputs are not checked during prepareModel.
+        if (operation.type == OperationType::SPLIT) {
+            for (const size_t outOprand : operation.outputs) {
+                if (operand == outOprand) {
+                    return true;
+                }
+            }
+        }
+        // BIDIRECTIONAL_SEQUENCE_LSTM and BIDIRECTIONAL_SEQUENCE_RNN can have either one or two
+        // outputs depending on their mergeOutputs parameter.
+        if (operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_LSTM ||
+            operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_RNN) {
+            for (const size_t outOprand : operation.outputs) {
+                if (operand == outOprand) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+static void removeOperandTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        if (removeOperandSkip(operand, model)) {
+            continue;
+        }
+        const std::string message = "removeOperandTest: operand " + std::to_string(operand);
+        validate(device, message, model,
+                 [operand](Model* model) { removeOperand(model, operand); });
+    }
+}
+
+///////////////////////// REMOVE OPERATION /////////////////////////
+
+static void removeOperation(Model* model, uint32_t index) {
+    for (uint32_t operand : model->operations[index].inputs) {
+        model->operands[operand].numberOfConsumers--;
+    }
+    hidl_vec_removeAt(&model->operations, index);
+}
+
+static void removeOperationTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        const std::string message = "removeOperationTest: operation " + std::to_string(operation);
+        validate(device, message, model,
+                 [operation](Model* model) { 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.
+    // - 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::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 sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (size_t input = 0; input < model.operations[operation].inputs.size(); ++input) {
+            const Operation& op = model.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) {
+                uint32_t operand = model->operations[operation].inputs[input];
+                model->operands[operand].numberOfConsumers--;
+                hidl_vec_removeAt(&model->operations[operation].inputs, input);
+            });
+        }
+    }
+}
+
+///////////////////////// REMOVE OPERATION OUTPUT /////////////////////////
+
+static void removeOperationOutputTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (size_t output = 0; output < model.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) {
+                hidl_vec_removeAt(&model->operations[operation].outputs, 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)) {
+        return true;
+    }
+    return false;
+}
+
+static void addOperationInputTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        if (addOperationInputSkip(model.operations[operation])) {
+            continue;
+        }
+        const std::string message = "addOperationInputTest: operation " + std::to_string(operation);
+        validate(device, message, model, [operation](Model* model) {
+            uint32_t index = addOperand(model, OperandLifeTime::MODEL_INPUT);
+            hidl_vec_push_back(&model->operations[operation].inputs, index);
+            hidl_vec_push_back(&model->inputIndexes, index);
+        });
+    }
+}
+
+///////////////////////// ADD OPERATION OUTPUT /////////////////////////
+
+static void addOperationOutputTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        const std::string message =
+                "addOperationOutputTest: operation " + std::to_string(operation);
+        validate(device, message, model, [operation](Model* model) {
+            uint32_t index = addOperand(model, OperandLifeTime::MODEL_OUTPUT);
+            hidl_vec_push_back(&model->operations[operation].outputs, index);
+            hidl_vec_push_back(&model->outputIndexes, 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 sp<IDevice>& device, const Model& model) {
+    for (int32_t preference : invalidExecutionPreferences) {
+        const std::string message =
+                "mutateExecutionPreferenceTest: preference " + std::to_string(preference);
+        validate(
+                device, message, model, [](Model*) {},
+                static_cast<ExecutionPreference>(preference));
+    }
+}
+
+////////////////////////// ENTRY POINT //////////////////////////////
+
+void validateModel(const sp<IDevice>& device, const Model& model) {
+    mutateOperandTypeTest(device, model);
+    mutateOperandRankTest(device, model);
+    mutateOperandScaleTest(device, model);
+    mutateOperandZeroPointTest(device, model);
+    mutateOperationOperandTypeTest(device, model);
+    mutateOperationTypeTest(device, model);
+    mutateOperationInputOperandIndexTest(device, model);
+    mutateOperationOutputOperandIndexTest(device, model);
+    removeOperandTest(device, model);
+    removeOperationTest(device, model);
+    removeOperationInputTest(device, model);
+    removeOperationOutputTest(device, model);
+    addOperationInputTest(device, model);
+    addOperationOutputTest(device, model);
+    mutateExecutionPreferenceTest(device, model);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/ValidateRequest.cpp b/neuralnetworks/1.3/vts/functional/ValidateRequest.cpp
new file mode 100644
index 0000000..6122123
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/ValidateRequest.cpp
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "neuralnetworks_hidl_hal_test"
+
+#include "1.0/Utils.h"
+#include "1.2/Callbacks.h"
+#include "ExecutionBurstController.h"
+#include "GeneratedTestHarness.h"
+#include "TestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+
+using V1_0::ErrorStatus;
+using V1_0::Request;
+using V1_2::IPreparedModel;
+using V1_2::MeasureTiming;
+using V1_2::OutputShape;
+using V1_2::Timing;
+using V1_2::implementation::ExecutionCallback;
+
+///////////////////////// UTILITY FUNCTIONS /////////////////////////
+
+static bool badTiming(Timing timing) {
+    return timing.timeOnDevice == UINT64_MAX && timing.timeInDriver == UINT64_MAX;
+}
+
+// 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. Note that the request here is passed by value, and any
+// mutation to the request does not leave this function.
+static void validate(const sp<IPreparedModel>& preparedModel, const std::string& message,
+                     Request request, const std::function<void(Request*)>& mutation) {
+    mutation(&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;
+    };
+    MeasureTiming measure = (hash & 1) ? MeasureTiming::YES : MeasureTiming::NO;
+
+    // asynchronous
+    {
+        SCOPED_TRACE(message + " [execute_1_2]");
+
+        sp<ExecutionCallback> executionCallback = new ExecutionCallback();
+        Return<ErrorStatus> executeLaunchStatus =
+                preparedModel->execute_1_2(request, measure, executionCallback);
+        ASSERT_TRUE(executeLaunchStatus.isOk());
+        ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, static_cast<ErrorStatus>(executeLaunchStatus));
+
+        executionCallback->wait();
+        ErrorStatus executionReturnStatus = executionCallback->getStatus();
+        const auto& outputShapes = executionCallback->getOutputShapes();
+        Timing timing = executionCallback->getTiming();
+        ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionReturnStatus);
+        ASSERT_EQ(outputShapes.size(), 0);
+        ASSERT_TRUE(badTiming(timing));
+    }
+
+    // synchronous
+    {
+        SCOPED_TRACE(message + " [executeSynchronously]");
+
+        Return<void> executeStatus = preparedModel->executeSynchronously(
+                request, measure,
+                [](ErrorStatus error, const hidl_vec<OutputShape>& outputShapes,
+                   const Timing& timing) {
+                    ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, error);
+                    EXPECT_EQ(outputShapes.size(), 0);
+                    EXPECT_TRUE(badTiming(timing));
+                });
+        ASSERT_TRUE(executeStatus.isOk());
+    }
+
+    // burst
+    {
+        SCOPED_TRACE(message + " [burst]");
+
+        // create burst
+        std::shared_ptr<::android::nn::ExecutionBurstController> burst =
+                android::nn::ExecutionBurstController::create(preparedModel, /*blocking=*/true);
+        ASSERT_NE(nullptr, burst.get());
+
+        // create memory keys
+        std::vector<intptr_t> keys(request.pools.size());
+        for (size_t i = 0; i < keys.size(); ++i) {
+            keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]);
+        }
+
+        // execute and verify
+        ErrorStatus error;
+        std::vector<OutputShape> outputShapes;
+        Timing timing;
+        std::tie(error, outputShapes, timing) = burst->compute(request, measure, keys);
+        EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, error);
+        EXPECT_EQ(outputShapes.size(), 0);
+        EXPECT_TRUE(badTiming(timing));
+
+        // additional burst testing
+        if (request.pools.size() > 0) {
+            // valid free
+            burst->freeMemory(keys.front());
+
+            // negative test: invalid free of unknown (blank) memory
+            burst->freeMemory(intptr_t{});
+
+            // negative test: double free of memory
+            burst->freeMemory(keys.front());
+        }
+    }
+}
+
+///////////////////////// REMOVE INPUT ////////////////////////////////////
+
+static void removeInputTest(const sp<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) { hidl_vec_removeAt(&request->inputs, input); });
+    }
+}
+
+///////////////////////// REMOVE OUTPUT ////////////////////////////////////
+
+static void removeOutputTest(const sp<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) { hidl_vec_removeAt(&request->outputs, output); });
+    }
+}
+
+///////////////////////////// ENTRY POINT //////////////////////////////////
+
+void validateRequest(const sp<IPreparedModel>& preparedModel, const Request& request) {
+    removeInputTest(preparedModel, request);
+    removeOutputTest(preparedModel, request);
+}
+
+void validateRequestFailure(const sp<IPreparedModel>& preparedModel, const Request& request) {
+    SCOPED_TRACE("Expecting request to fail [executeSynchronously]");
+    Return<void> executeStatus = preparedModel->executeSynchronously(
+            request, MeasureTiming::NO,
+            [](ErrorStatus error, const hidl_vec<OutputShape>& outputShapes, const Timing& timing) {
+                ASSERT_NE(ErrorStatus::NONE, error);
+                EXPECT_EQ(outputShapes.size(), 0);
+                EXPECT_TRUE(badTiming(timing));
+            });
+    ASSERT_TRUE(executeStatus.isOk());
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.cpp b/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.cpp
new file mode 100644
index 0000000..4f0e150
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "neuralnetworks_hidl_hal_test"
+
+#include "VtsHalNeuralnetworks.h"
+#include <android-base/logging.h>
+#include <hidl/ServiceManagement.h>
+#include <string>
+#include <utility>
+#include "1.0/Callbacks.h"
+#include "1.0/Utils.h"
+#include "GeneratedTestHarness.h"
+#include "TestHarness.h"
+
+namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+
+using HidlToken =
+        hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+using V1_0::ErrorStatus;
+using V1_0::Request;
+using V1_1::ExecutionPreference;
+using V1_2::IPreparedModel;
+using V1_2::implementation::PreparedModelCallback;
+
+// internal helper function
+void createPreparedModel(const sp<IDevice>& device, const Model& model,
+                         sp<IPreparedModel>* preparedModel) {
+    ASSERT_NE(nullptr, preparedModel);
+    *preparedModel = nullptr;
+
+    // see if service can handle model
+    bool fullySupportsModel = false;
+    const Return<void> supportedCall = device->getSupportedOperations_1_3(
+            model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) {
+                ASSERT_EQ(ErrorStatus::NONE, status);
+                ASSERT_NE(0ul, supported.size());
+                fullySupportsModel = std::all_of(supported.begin(), supported.end(),
+                                                 [](bool valid) { return valid; });
+            });
+    ASSERT_TRUE(supportedCall.isOk());
+
+    // launch prepare model
+    const sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
+    const Return<ErrorStatus> prepareLaunchStatus = device->prepareModel_1_3(
+            model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec<hidl_handle>(),
+            hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+    ASSERT_TRUE(prepareLaunchStatus.isOk());
+    ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus));
+
+    // retrieve prepared model
+    preparedModelCallback->wait();
+    const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+    *preparedModel = getPreparedModel_1_2(preparedModelCallback);
+
+    // The getSupportedOperations_1_3 call returns a list of operations that are
+    // guaranteed not to fail if prepareModel_1_3 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());
+        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 NeuralnetworksHidlTest::SetUp() {
+    testing::TestWithParam<NeuralnetworksHidlTestParam>::SetUp();
+    ASSERT_NE(kDevice, nullptr);
+}
+
+static NamedDevice makeNamedDevice(const std::string& name) {
+    return {name, IDevice::getService(name)};
+}
+
+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 = hardware::getAllHalInstanceNames(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 printNeuralnetworksHidlTest(
+        const testing::TestParamInfo<NeuralnetworksHidlTestParam>& info) {
+    return gtestCompliantName(getName(info.param));
+}
+
+INSTANTIATE_DEVICE_TEST(NeuralnetworksHidlTest);
+
+// Forward declaration from ValidateModel.cpp
+void validateModel(const sp<IDevice>& device, const Model& model);
+// Forward declaration from ValidateRequest.cpp
+void validateRequest(const sp<IPreparedModel>& preparedModel, const V1_0::Request& request);
+// Forward declaration from ValidateRequest.cpp
+void validateRequestFailure(const sp<IPreparedModel>& preparedModel, const V1_0::Request& request);
+// Forward declaration from ValidateBurst.cpp
+void validateBurst(const sp<IPreparedModel>& preparedModel, const V1_0::Request& request);
+
+void validateEverything(const sp<IDevice>& device, const Model& model, const Request& request) {
+    validateModel(device, model);
+
+    // Create IPreparedModel.
+    sp<IPreparedModel> preparedModel;
+    createPreparedModel(device, model, &preparedModel);
+    if (preparedModel == nullptr) return;
+
+    validateRequest(preparedModel, request);
+    validateBurst(preparedModel, request);
+}
+
+void validateFailure(const sp<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.
+    sp<IPreparedModel> preparedModel;
+    createPreparedModel(device, model, &preparedModel);
+    if (preparedModel == nullptr) return;
+
+    validateRequestFailure(preparedModel, request);
+}
+
+TEST_P(ValidationTest, Test) {
+    const Model model = createModel(kTestModel);
+    const Request request = createRequest(kTestModel);
+    if (kTestModel.expectFailure) {
+        validateFailure(kDevice, model, request);
+    } else {
+        validateEverything(kDevice, model, request);
+    }
+}
+
+INSTANTIATE_GENERATED_TEST(ValidationTest, [](const test_helper::TestModel&) { return true; });
+
+sp<IPreparedModel> getPreparedModel_1_2(const sp<PreparedModelCallback>& callback) {
+    sp<V1_0::IPreparedModel> preparedModelV1_0 = callback->getPreparedModel();
+    return IPreparedModel::castFrom(preparedModelV1_0).withDefault(nullptr);
+}
+
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.h b/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.h
new file mode 100644
index 0000000..fc654ce
--- /dev/null
+++ b/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.h
@@ -0,0 +1,58 @@
+/*
+ * 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_NEURALNETWORKS_V1_3_VTS_HAL_NEURALNETWORKS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_V1_3_VTS_HAL_NEURALNETWORKS_H
+
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <android/hardware/neuralnetworks/1.3/IDevice.h>
+#include <android/hardware/neuralnetworks/1.3/types.h>
+#include <gtest/gtest.h>
+#include "1.0/Utils.h"
+#include "1.2/Callbacks.h"
+
+namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+
+using NamedDevice = Named<sp<IDevice>>;
+using NeuralnetworksHidlTestParam = NamedDevice;
+
+class NeuralnetworksHidlTest : public testing::TestWithParam<NeuralnetworksHidlTestParam> {
+  protected:
+    void SetUp() override;
+    const sp<IDevice> kDevice = getData(GetParam());
+};
+
+const std::vector<NamedDevice>& getNamedDevices();
+
+std::string printNeuralnetworksHidlTest(
+        const testing::TestParamInfo<NeuralnetworksHidlTestParam>& info);
+
+#define INSTANTIATE_DEVICE_TEST(TestSuite)                                                 \
+    INSTANTIATE_TEST_SUITE_P(PerInstance, TestSuite, testing::ValuesIn(getNamedDevices()), \
+                             printNeuralnetworksHidlTest)
+
+// Create an IPreparedModel object. If the model cannot be prepared,
+// "preparedModel" will be nullptr instead.
+void createPreparedModel(const sp<IDevice>& device, const Model& model,
+                         sp<V1_2::IPreparedModel>* preparedModel);
+
+// Utility function to get PreparedModel from callback and downcast to V1_2.
+sp<V1_2::IPreparedModel> getPreparedModel_1_2(
+        const sp<V1_2::implementation::PreparedModelCallback>& callback);
+
+}  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_V1_3_VTS_HAL_NEURALNETWORKS_H
diff --git a/power/stats/1.0/default/PowerStats.cpp b/power/stats/1.0/default/PowerStats.cpp
index 78766f2..68275ce 100644
--- a/power/stats/1.0/default/PowerStats.cpp
+++ b/power/stats/1.0/default/PowerStats.cpp
@@ -87,7 +87,7 @@
     std::string railFileName;
     std::string spsFileName;
     uint32_t index = 0;
-    uint32_t samplingRate;
+    unsigned long samplingRate;
     for (const auto& path : mPm.devicePaths) {
         railFileName = path + "/enabled_rails";
         spsFileName = path + "/sampling_rate";
@@ -109,10 +109,11 @@
         while (std::getline(railNames, line)) {
             std::vector<std::string> words = android::base::Split(line, ":");
             if (words.size() == 2) {
-                mPm.railsInfo.emplace(words[0], RailData{.devicePath = path,
-                                                         .index = index,
-                                                         .subsysName = words[1],
-                                                         .samplingRate = samplingRate});
+                mPm.railsInfo.emplace(
+                        words[0], RailData{.devicePath = path,
+                                           .index = index,
+                                           .subsysName = words[1],
+                                           .samplingRate = static_cast<uint32_t>(samplingRate)});
                 index++;
             } else {
                 ALOGW("Unexpected format in file: %s", railFileName.c_str());
diff --git a/radio/1.2/types.hal b/radio/1.2/types.hal
index dffebd3..f10d753 100644
--- a/radio/1.2/types.hal
+++ b/radio/1.2/types.hal
@@ -161,7 +161,8 @@
     ScanType type;
 
     /**
-     * Time interval in seconds between periodic scans, only valid when type = PERIODIC
+     * Time interval in seconds between the completion of one scan and the start of a subsequent scan.
+     * This field is only valid when 'type' is 'PERIODIC'.
      * Range: ScanIntervalRange:MIN to ScanIntervalRange:MAX
      */
     int32_t interval;
diff --git a/sensors/1.0/default/android.hardware.sensors@1.0-service.rc b/sensors/1.0/default/android.hardware.sensors@1.0-service.rc
index 4faa562..b41730b 100644
--- a/sensors/1.0/default/android.hardware.sensors@1.0-service.rc
+++ b/sensors/1.0/default/android.hardware.sensors@1.0-service.rc
@@ -2,6 +2,6 @@
     interface android.hardware.sensors@1.0::ISensors default
     class hal
     user system
-    group system wakelock
+    group system wakelock uhid
     capabilities BLOCK_SUSPEND
     rlimit rtprio 10 10
diff --git a/sensors/2.0/multihal/Android.bp b/sensors/2.0/multihal/Android.bp
index 216cc20..710835f 100644
--- a/sensors/2.0/multihal/Android.bp
+++ b/sensors/2.0/multihal/Android.bp
@@ -24,7 +24,6 @@
         "libcutils",
         "libfmq",
         "libhidlbase",
-        "libhidltransport",
         "liblog",
         "libpower",
         "libutils",
diff --git a/sensors/2.0/multihal/HalProxy.cpp b/sensors/2.0/multihal/HalProxy.cpp
index f051a11..1fbc787 100644
--- a/sensors/2.0/multihal/HalProxy.cpp
+++ b/sensors/2.0/multihal/HalProxy.cpp
@@ -49,8 +49,15 @@
 }
 
 HalProxy::~HalProxy() {
-    // TODO: Join any running threads and clean up FMQs and any other allocated
-    // state.
+    {
+        std::lock_guard<std::mutex> lockGuard(mEventQueueWriteMutex);
+        mPendingWritesRun = false;
+        mEventQueueWriteCV.notify_one();
+    }
+    if (mPendingWritesThread.joinable()) {
+        mPendingWritesThread.join();
+    }
+    // TODO: Cleanup wakeup thread once it is implemented
 }
 
 Return<void> HalProxy::getSensorsList(getSensorsList_cb _hidl_cb) {
@@ -120,7 +127,8 @@
         result = Result::BAD_VALUE;
     }
 
-    // TODO: start threads to read wake locks and process events from sub HALs.
+    mPendingWritesThread = std::thread(startPendingWritesThread, this);
+    // TODO: start threads to read wake locks.
 
     for (size_t i = 0; i < mSubHalList.size(); i++) {
         auto subHal = mSubHalList[i];
@@ -162,25 +170,34 @@
     return result;
 }
 
-Return<void> HalProxy::registerDirectChannel(const SharedMemInfo& /* mem */,
+Return<void> HalProxy::registerDirectChannel(const SharedMemInfo& mem,
                                              registerDirectChannel_cb _hidl_cb) {
-    // TODO: During init, discover the first sub-HAL in the config that has sensors with direct
-    // channel support, if any, and proxy the API call there.
-    _hidl_cb(Result::INVALID_OPERATION, -1 /* channelHandle */);
+    if (mDirectChannelSubHal == nullptr) {
+        _hidl_cb(Result::INVALID_OPERATION, -1 /* channelHandle */);
+    } else {
+        mDirectChannelSubHal->registerDirectChannel(mem, _hidl_cb);
+    }
     return Return<void>();
 }
 
-Return<Result> HalProxy::unregisterDirectChannel(int32_t /* channelHandle */) {
-    // TODO: During init, discover the first sub-HAL in the config that has sensors with direct
-    // channel support, if any, and proxy the API call there.
-    return Result::INVALID_OPERATION;
+Return<Result> HalProxy::unregisterDirectChannel(int32_t channelHandle) {
+    Result result;
+    if (mDirectChannelSubHal == nullptr) {
+        result = Result::INVALID_OPERATION;
+    } else {
+        result = mDirectChannelSubHal->unregisterDirectChannel(channelHandle);
+    }
+    return result;
 }
 
-Return<void> HalProxy::configDirectReport(int32_t /* sensorHandle */, int32_t /* channelHandle */,
-                                          RateLevel /* rate */, configDirectReport_cb _hidl_cb) {
-    // TODO: During init, discover the first sub-HAL in the config that has sensors with direct
-    // channel support, if any, and proxy the API call there.
-    _hidl_cb(Result::INVALID_OPERATION, 0 /* reportToken */);
+Return<void> HalProxy::configDirectReport(int32_t sensorHandle, int32_t channelHandle,
+                                          RateLevel rate, configDirectReport_cb _hidl_cb) {
+    if (mDirectChannelSubHal == nullptr) {
+        _hidl_cb(Result::INVALID_OPERATION, -1 /* reportToken */);
+    } else {
+        mDirectChannelSubHal->configDirectReport(clearSubHalIndex(sensorHandle), channelHandle,
+                                                 rate, _hidl_cb);
+    }
     return Return<void>();
 }
 
@@ -268,21 +285,66 @@
     initializeSensorList();
 }
 
+void HalProxy::startPendingWritesThread(HalProxy* halProxy) {
+    halProxy->handlePendingWrites();
+}
+
+void HalProxy::handlePendingWrites() {
+    // TODO: Find a way to optimize locking strategy maybe using two mutexes instead of one.
+    std::unique_lock<std::mutex> lock(mEventQueueWriteMutex);
+    while (mPendingWritesRun) {
+        mEventQueueWriteCV.wait(
+                lock, [&] { return !mPendingWriteEventsQueue.empty() || !mPendingWritesRun; });
+        if (!mPendingWriteEventsQueue.empty() && mPendingWritesRun) {
+            std::vector<Event>& pendingWriteEvents = mPendingWriteEventsQueue.front();
+            size_t eventQueueSize = mEventQueue->getQuantumCount();
+            size_t numToWrite = std::min(pendingWriteEvents.size(), eventQueueSize);
+            lock.unlock();
+            // TODO: Find a way to interrup writeBlocking if the thread should exit
+            // so we don't have to wait for timeout on framework restarts.
+            if (!mEventQueue->writeBlocking(
+                        pendingWriteEvents.data(), numToWrite,
+                        static_cast<uint32_t>(EventQueueFlagBits::EVENTS_READ),
+                        static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS),
+                        kWakelockTimeoutNs, mEventQueueFlag)) {
+                ALOGE("Dropping %zu events after blockingWrite failed.", numToWrite);
+            } else {
+                mEventQueueFlag->wake(static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS));
+            }
+            lock.lock();
+            if (pendingWriteEvents.size() > eventQueueSize) {
+                // TODO: Check if this erase operation is too inefficient. It will copy all the
+                // events ahead of it down to fill gap off array at front after the erase.
+                pendingWriteEvents.erase(pendingWriteEvents.begin(),
+                                         pendingWriteEvents.begin() + eventQueueSize);
+            } else {
+                mPendingWriteEventsQueue.pop();
+            }
+        }
+    }
+}
+
 void HalProxy::postEventsToMessageQueue(const std::vector<Event>& events) {
-    std::lock_guard<std::mutex> lock(mEventQueueMutex);
-    size_t numToWrite = std::min(events.size(), mEventQueue->availableToWrite());
-    if (numToWrite > 0) {
-        if (mEventQueue->write(events.data(), numToWrite)) {
-            // TODO: While loop if mEventQueue->avaiableToWrite > 0 to possibly fit in more writes
-            // immediately
-            mEventQueueFlag->wake(static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS));
-        } else {
-            numToWrite = 0;
+    size_t numToWrite = 0;
+    std::lock_guard<std::mutex> lock(mEventQueueWriteMutex);
+    if (mPendingWriteEventsQueue.empty()) {
+        numToWrite = std::min(events.size(), mEventQueue->availableToWrite());
+        if (numToWrite > 0) {
+            if (mEventQueue->write(events.data(), numToWrite)) {
+                // TODO: While loop if mEventQueue->avaiableToWrite > 0 to possibly fit in more
+                // writes immediately
+                mEventQueueFlag->wake(static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS));
+            } else {
+                numToWrite = 0;
+            }
         }
     }
     if (numToWrite < events.size()) {
-        // TODO: Post from events[numToWrite -> end] to background events queue
-        // Signal background thread
+        // TODO: Bound the mPendingWriteEventsQueue so that we do not trigger OOMs if framework
+        // stalls
+        mPendingWriteEventsQueue.push(
+                std::vector<Event>(events.begin() + numToWrite, events.end()));
+        mEventQueueWriteCV.notify_one();
     }
 }
 
diff --git a/sensors/2.0/multihal/include/HalProxy.h b/sensors/2.0/multihal/include/HalProxy.h
index bdcc1ff..ae4b2c5 100644
--- a/sensors/2.0/multihal/include/HalProxy.h
+++ b/sensors/2.0/multihal/include/HalProxy.h
@@ -24,7 +24,12 @@
 #include <hidl/MQDescriptor.h>
 #include <hidl/Status.h>
 
+#include <atomic>
+#include <condition_variable>
 #include <map>
+#include <mutex>
+#include <queue>
+#include <thread>
 
 namespace android {
 namespace hardware {
@@ -159,6 +164,7 @@
      */
     std::vector<ISensorsSubHal*> mSubHalList;
 
+    //! The list of subhal callbacks for each subhal where the indices correlate with mSubHalList
     std::vector<const sp<IHalProxyCallback>> mSubHalCallbacks;
 
     /**
@@ -179,6 +185,9 @@
     //! The mutex for the event queue.
     std::mutex mEventQueueMutex;
 
+    //! The timeout for each pending write on background thread for events.
+    static const int64_t kWakelockTimeoutNs = 5 * INT64_C(1000000000) /* 5 seconds */;
+
     //! The scoped wakelock ref count.
     size_t mWakelockRefCount = 0;
 
@@ -188,6 +197,21 @@
     //! The bit mask used to get the subhal index from a sensor handle.
     static constexpr uint32_t kSensorHandleSubHalIndexMask = 0xFF000000;
 
+    //! The events that were not able to be written to fmq right away
+    std::queue<std::vector<Event>> mPendingWriteEventsQueue;
+
+    //! The mutex protecting writing to the fmq and the pending events queue
+    std::mutex mEventQueueWriteMutex;
+
+    //! The condition variable waiting on pending write events to stack up
+    std::condition_variable mEventQueueWriteCV;
+
+    //! The thread object ptr that handles pending writes
+    std::thread mPendingWritesThread;
+
+    //! The bool indicating whether to end the pending writes background thread or not
+    bool mPendingWritesRun = true;
+
     /**
      * Initialize the list of SubHal objects in mSubHalList by reading from dynamic libraries
      * listed in a config file.
@@ -211,6 +235,16 @@
     void initializeSubHalCallbacksAndSensorList();
 
     /**
+     * Starts the thread that handles pending writes to event fmq.
+     *
+     * @param halProxy The HalProxy object pointer.
+     */
+    static void startPendingWritesThread(HalProxy* halProxy);
+
+    //! Handles the pending writes on events to eventqueue.
+    void handlePendingWrites();
+
+    /**
      * Clear direct channel flags if the HalProxy has already chosen a subhal as its direct channel
      * subhal. Set the directChannelSubHal pointer to the subHal passed in if this is the first
      * direct channel enabled sensor seen.
diff --git a/sensors/2.0/multihal/tests/Android.bp b/sensors/2.0/multihal/tests/Android.bp
index ab260a4..aa44687 100644
--- a/sensors/2.0/multihal/tests/Android.bp
+++ b/sensors/2.0/multihal/tests/Android.bp
@@ -28,7 +28,6 @@
         "libcutils",
         "libfmq",
         "libhidlbase",
-        "libhidltransport",
         "liblog",
         "libpower",
         "libutils",
@@ -83,7 +82,6 @@
         "libcutils",
         "libfmq",
         "libhidlbase",
-        "libhidltransport",
         "liblog",
         "libpower",
         "libutils",
diff --git a/sensors/2.0/multihal/tests/HalProxy_test.cpp b/sensors/2.0/multihal/tests/HalProxy_test.cpp
index 4b1a15e..61fb14c 100644
--- a/sensors/2.0/multihal/tests/HalProxy_test.cpp
+++ b/sensors/2.0/multihal/tests/HalProxy_test.cpp
@@ -22,11 +22,10 @@
 #include "ScopedWakelock.h"
 #include "SensorsSubHal.h"
 
+#include <chrono>
+#include <thread>
 #include <vector>
 
-#undef LOG_TAG
-#define LOG_TAG "HalProxy_test"
-
 namespace {
 
 using ::android::hardware::hidl_vec;
@@ -98,7 +97,7 @@
  * Construct and return a HIDL Event type thats sensorHandle refers to a proximity sensor
  *    which is a wakeup type sensor.
  *
- * @ return A proximity event.
+ * @return A proximity event.
  */
 Event makeProximityEvent();
 
@@ -106,10 +105,30 @@
  * Construct and return a HIDL Event type thats sensorHandle refers to a proximity sensor
  *    which is a wakeup type sensor.
  *
- * @ return A proximity event.
+ * @return A proximity event.
  */
 Event makeAccelerometerEvent();
 
+/**
+ * Make a certain number of proximity type events with the sensorHandle field set to
+ * the proper number for AllSensorsSubHal subhal type.
+ *
+ * @param numEvents The number of events to make.
+ *
+ * @return The created list of events.
+ */
+std::vector<Event> makeMultipleProximityEvents(size_t numEvents);
+
+/**
+ * Make a certain number of accelerometer type events with the sensorHandle field set to
+ * the proper number for AllSensorsSubHal subhal type.
+ *
+ * @param numEvents The number of events to make.
+ *
+ * @return The created list of events.
+ */
+std::vector<Event> makeMultipleAccelerometerEvents(size_t numEvents);
+
 // Tests follow
 TEST(HalProxyTest, GetSensorsListOneSubHalTest) {
     AllSensorsSubHal subHal;
@@ -232,10 +251,7 @@
     ::android::sp<ISensorsCallback> callback = new SensorsCallback();
     proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
 
-    std::vector<Event> events;
-    for (size_t i = 0; i < kNumEvents; i++) {
-        events.push_back(makeAccelerometerEvent());
-    }
+    std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
     subHal.postEvents(events, false /* wakeup */);
 
     EXPECT_EQ(eventQueue->availableToRead(), kNumEvents);
@@ -272,15 +288,114 @@
     ::android::sp<ISensorsCallback> callback = new SensorsCallback();
     proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
 
-    std::vector<Event> events;
-    for (size_t i = 0; i < kNumEvents; i++) {
-        events.push_back(makeProximityEvent());
-    }
+    std::vector<Event> events = makeMultipleProximityEvents(kNumEvents);
     subHal.postEvents(events, true /* wakeup */);
 
     EXPECT_EQ(eventQueue->availableToRead(), kNumEvents);
 }
 
+TEST(HalProxyTest, PostEventsMultipleSubhals) {
+    constexpr size_t kQueueSize = 5;
+    constexpr size_t kNumEvents = 2;
+    AllSensorsSubHal subHal1, subHal2;
+    std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2};
+    HalProxy proxy(subHals);
+    std::unique_ptr<EventMessageQueue> eventQueue =
+            std::make_unique<EventMessageQueue>(kQueueSize, true);
+    std::unique_ptr<WakeupMessageQueue> wakeLockQueue =
+            std::make_unique<WakeupMessageQueue>(kQueueSize, true);
+    ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+    proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
+
+    std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
+    subHal1.postEvents(events, false /* wakeup */);
+
+    EXPECT_EQ(eventQueue->availableToRead(), kNumEvents);
+
+    subHal2.postEvents(events, false /* wakeup */);
+
+    EXPECT_EQ(eventQueue->availableToRead(), kNumEvents * 2);
+}
+
+TEST(HalProxyTest, PostEventsDelayedWrite) {
+    constexpr size_t kQueueSize = 5;
+    constexpr size_t kNumEvents = 6;
+    AllSensorsSubHal subHal1, subHal2;
+    std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2};
+    HalProxy proxy(subHals);
+    std::unique_ptr<EventMessageQueue> eventQueue =
+            std::make_unique<EventMessageQueue>(kQueueSize, true);
+    std::unique_ptr<WakeupMessageQueue> wakeLockQueue =
+            std::make_unique<WakeupMessageQueue>(kQueueSize, true);
+    ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+    proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
+
+    std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
+    subHal1.postEvents(events, false /* wakeup */);
+
+    EXPECT_EQ(eventQueue->availableToRead(), kQueueSize);
+
+    Event eventOut;
+    // writeblock 1 event out of queue, timeout for half a second
+    EXPECT_TRUE(eventQueue->readBlocking(&eventOut, 1, 500000000));
+
+    // Sleep for a half second so that blocking write has time complete in background thread
+    std::this_thread::sleep_for(std::chrono::milliseconds(500));
+
+    // proxy background thread should have wrote last event when it saw space
+    EXPECT_EQ(eventQueue->availableToRead(), kQueueSize);
+}
+
+TEST(HalProxyTest, PostEventsMultipleSubhalsThreaded) {
+    constexpr size_t kQueueSize = 5;
+    constexpr size_t kNumEvents = 2;
+    AllSensorsSubHal subHal1, subHal2;
+    std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2};
+    HalProxy proxy(subHals);
+    std::unique_ptr<EventMessageQueue> eventQueue =
+            std::make_unique<EventMessageQueue>(kQueueSize, true);
+    std::unique_ptr<WakeupMessageQueue> wakeLockQueue =
+            std::make_unique<WakeupMessageQueue>(kQueueSize, true);
+    ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+    proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
+
+    std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
+
+    std::thread t1(&AllSensorsSubHal::postEvents, &subHal1, events, false);
+    std::thread t2(&AllSensorsSubHal::postEvents, &subHal2, events, false);
+
+    t1.join();
+    t2.join();
+
+    EXPECT_EQ(eventQueue->availableToRead(), kNumEvents * 2);
+}
+
+TEST(HalProxyTest, DestructingWithEventsPendingOnBackgroundThreadTest) {
+    constexpr size_t kQueueSize = 5;
+    constexpr size_t kNumEvents = 6;
+    AllSensorsSubHal subHal;
+    std::vector<ISensorsSubHal*> subHals{&subHal};
+
+    std::unique_ptr<EventMessageQueue> eventQueue =
+            std::make_unique<EventMessageQueue>(kQueueSize, true);
+    std::unique_ptr<WakeupMessageQueue> wakeLockQueue =
+            std::make_unique<WakeupMessageQueue>(kQueueSize, true);
+    ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+    HalProxy proxy(subHals);
+    proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
+
+    std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
+    subHal.postEvents(events, false /* wakeup */);
+
+    // Sleep for a half second so that background thread has time to attempt it's blocking write
+    std::this_thread::sleep_for(std::chrono::milliseconds(500));
+
+    // Should see a 5 second wait for blocking write timeout here
+
+    // Should be one events left on pending writes queue here and proxy will destruct
+    // If this TEST completes then it was a success, if it hangs we will see a crash
+}
+
 // Helper implementations follow
 void testSensorsListFromProxyAndSubHal(const std::vector<SensorInfo>& proxySensorsList,
                                        const std::vector<SensorInfo>& subHalSensorsList) {
@@ -332,4 +447,20 @@
     return event;
 }
 
+std::vector<Event> makeMultipleProximityEvents(size_t numEvents) {
+    std::vector<Event> events;
+    for (size_t i = 0; i < numEvents; i++) {
+        events.push_back(makeProximityEvent());
+    }
+    return events;
+}
+
+std::vector<Event> makeMultipleAccelerometerEvents(size_t numEvents) {
+    std::vector<Event> events;
+    for (size_t i = 0; i < numEvents; i++) {
+        events.push_back(makeAccelerometerEvent());
+    }
+    return events;
+}
+
 }  // namespace
diff --git a/tv/tuner/1.0/IDemux.hal b/tv/tuner/1.0/IDemux.hal
index e03095b..7fd7e26 100644
--- a/tv/tuner/1.0/IDemux.hal
+++ b/tv/tuner/1.0/IDemux.hal
@@ -1,3 +1,19 @@
+/*
+ * 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.tuner@1.0;
 
 import IDemuxCallback;
@@ -46,9 +62,9 @@
      *
      * It is used by the client to get the descriptor of the filter's Fast
      * Message Queue. The data in FMQ is filtered out from MPEG transport
-     * stream. The data is origanized to data blocks which may have
+     * stream. The data is organized to data blocks which may have
      * different length. The length's information of one or multiple data blocks
-     * is sent to client throught DemuxFilterEvent.
+     * is sent to client through DemuxFilterEvent.
      *
      * @param filterId the ID of the filter.
      * @return result Result status of the operation.
@@ -81,7 +97,7 @@
     /**
      * Start the filter.
      *
-     * It is used by the client to ask the filter to start filterring data.
+     * It is used by the client to ask the filter to start filtering data.
      *
      * @param filterId the ID of the filter.
      * @return result Result status of the operation.
@@ -202,7 +218,7 @@
      *
      * It is used by the client to get the descriptor of the output's Fast
      * Message Queue. The data in FMQ is muxed packets output from selected
-     * filters. The packet's format is specifed by DemuxDataFormat in
+     * filters. The packet's format is specified by DemuxDataFormat in
      * DemuxOutputSettings.
      *
      * @return result Result status of the operation.
@@ -236,7 +252,7 @@
      *         INVALID_STATE if failed for wrong state.
      *         UNKNOWN_ERROR if failed for other reasons.
      */
-    attachOutputTsFilter(DemuxFilterId filterId) generates (Result result);
+    attachOutputFilter(DemuxFilterId filterId) generates (Result result);
 
     /**
      * Detach one filter from the demux's output.
@@ -250,7 +266,7 @@
      *         INVALID_STATE if failed for wrong state.
      *         UNKNOWN_ERROR if failed for other reasons.
      */
-    detachOutputTsFilter(DemuxFilterId filterId) generates (Result result);
+    detachOutputFilter(DemuxFilterId filterId) generates (Result result);
 
     /**
      * Start to take data to the demux's output.
diff --git a/tv/tuner/1.0/IDemuxCallback.hal b/tv/tuner/1.0/IDemuxCallback.hal
index 55e8420..7bce9ef 100644
--- a/tv/tuner/1.0/IDemuxCallback.hal
+++ b/tv/tuner/1.0/IDemuxCallback.hal
@@ -1,3 +1,19 @@
+/*
+ * 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.tuner@1.0;
 
 interface IDemuxCallback {
diff --git a/tv/tuner/1.0/IDescrambler.hal b/tv/tuner/1.0/IDescrambler.hal
index d078657..61ff1df 100644
--- a/tv/tuner/1.0/IDescrambler.hal
+++ b/tv/tuner/1.0/IDescrambler.hal
@@ -1,3 +1,19 @@
+/*
+ * 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.tuner@1.0;
 /**
  * Descrambler is used to descramble input data.
diff --git a/tv/tuner/1.0/IFrontend.hal b/tv/tuner/1.0/IFrontend.hal
index 8788643..83e390d 100644
--- a/tv/tuner/1.0/IFrontend.hal
+++ b/tv/tuner/1.0/IFrontend.hal
@@ -145,10 +145,10 @@
      *         cable frontend.
      *         UNKNOWN_ERROR if failed for other reasons.
      */
-    setLnb(ILnb lnb) generates (Result result);
+    setLnb(LnbId lnbId) generates (Result result);
 
     /**
-     * Enble or Disable Low Noise Amplifier (LNA).
+     * Enable or Disable Low Noise Amplifier (LNA).
      *
      * @param bEnable true if activate LNA module; false if deactivate LNA
      *
@@ -158,22 +158,4 @@
      *         UNKNOWN_ERROR if failed for other reasons.
      */
     setLna(bool bEnable) generates (Result result);
-
-    /**
-     *  Sends DiSEqC (Digital Satellite Equipment Control) message.
-     *
-     * Client sends DiSeqc message to DiSEqc compatible device through the
-     * frontend. The response message from the device comes back to the client
-     * through frontend's callback onDiseqcMessage.
-     *
-     * @param diseqcMessage a byte array of data for DiSEqC message which is
-     *        specified by EUTELSAT Bus Functional Specification Version 4.2.
-     *
-     * @return result Result status of the operation.
-     *         SUCCESS if successful,
-     *         INVALID_STATE if the frontend can't send DiSEqc Message, such as
-     *         cable frontend.
-     *         UNKNOWN_ERROR if failed for other reasons.
-     */
-    sendDiseqcMessage(vec<uint8_t> diseqcMessage) generates (Result result);
 };
diff --git a/tv/tuner/1.0/ILnb.hal b/tv/tuner/1.0/ILnb.hal
index 49fc3b4..6b7119e 100644
--- a/tv/tuner/1.0/ILnb.hal
+++ b/tv/tuner/1.0/ILnb.hal
@@ -55,6 +55,24 @@
     setSatellitePosition(FrontendLnbPosition position) generates (Result result);
 
     /**
+     *  Sends DiSEqC (Digital Satellite Equipment Control) message.
+     *
+     * Client sends DiSeqc message to DiSEqc to LNB. The response message from
+     * the device comes back to the client through frontend's callback
+     * onDiseqcMessage.
+     *
+     * @param diseqcMessage a byte array of data for DiSEqC message which is
+     *        specified by EUTELSAT Bus Functional Specification Version 4.2.
+     *
+     * @return result Result status of the operation.
+     *         SUCCESS if successful,
+     *         INVALID_STATE if the frontend can't send DiSEqc Message, such as
+     *         cable frontend.
+     *         UNKNOWN_ERROR if failed for other reasons.
+     */
+    sendDiseqcMessage(vec<uint8_t> diseqcMessage) generates (Result result);
+
+    /**
      * Releases the LNB instance
      *
      * Associated resources are released.  close may be called more than once.
diff --git a/tv/tuner/1.0/ITuner.hal b/tv/tuner/1.0/ITuner.hal
index f1a8617..1cf0e38 100644
--- a/tv/tuner/1.0/ITuner.hal
+++ b/tv/tuner/1.0/ITuner.hal
@@ -23,7 +23,7 @@
 
 /**
  * Top level interface to manage Frontend, Demux and Decrambler hardware
- * resouces which are needed for Android TV.
+ * resources which are needed for Android TV.
  */
 interface ITuner {
     /**
@@ -68,6 +68,16 @@
          generates (Result result, DemuxId demuxId, IDemux demux);
 
     /**
+     * Retrieve the Demux's Capabilities.
+     *
+     * @return result Result status of the operation.
+     *         SUCCESS if successful,
+     *         UNKNOWN_ERROR if the inquiry failed for other reasons.
+     * @return caps the Demux's Capabilities.
+     */
+    getDemuxCaps() generates (Result result, DemuxCapabilities caps);
+
+    /**
      * Create a new instance of Descrambler.
      *
      * It is used by the client to create a Descrambler instance.
@@ -81,14 +91,13 @@
          generates (Result result, IDescrambler descrambler);
 
     /**
-     * Create a new instance of Descrambler.
+     * Retrieve the frontend's information.
      *
-     * It is used by the client to create a Descrambler instance.
-     *
+     * @param frontendId the id of the frontend to be inquiried.
      * @return result Result status of the operation.
      *         SUCCESS if successful,
-     *         UNKNOWN_ERROR if creation failed for other reasons.
-     * @return descrambler the newly created descrambler interface.
+     *         UNKNOWN_ERROR if the inquiry failed for other reasons.
+     * @return info the frontend's information.
      */
     getFrontendInfo(FrontendId frontendId)
         generates (Result result, FrontendInfo info);
@@ -119,6 +128,5 @@
      */
     openLnbById(LnbId lnbId)
         generates (Result result, ILnb lnb);
-
 };
 
diff --git a/tv/tuner/1.0/default/Demux.cpp b/tv/tuner/1.0/default/Demux.cpp
index 889e42e..d65df59 100644
--- a/tv/tuner/1.0/default/Demux.cpp
+++ b/tv/tuner/1.0/default/Demux.cpp
@@ -67,8 +67,9 @@
         0x73, 0x63, 0x65, 0x6e, 0x65,
 };
 
-Demux::Demux(uint32_t demuxId) {
+Demux::Demux(uint32_t demuxId, sp<Tuner> tuner) {
     mDemuxId = demuxId;
+    mTunerService = tuner;
 }
 
 Demux::~Demux() {}
@@ -76,9 +77,20 @@
 Return<Result> Demux::setFrontendDataSource(uint32_t frontendId) {
     ALOGV("%s", __FUNCTION__);
 
-    mSourceFrontendId = frontendId;
+    if (mTunerService == nullptr) {
+        return Result::NOT_INITIALIZED;
+    }
 
-    return Result::SUCCESS;
+    mFrontend = mTunerService->getFrontendById(frontendId);
+
+    if (mFrontend == nullptr) {
+        return Result::INVALID_STATE;
+    }
+
+    mFrontendSourceFile = mFrontend->getSourceFile();
+
+    mTunerService->setFrontendAsDemuxSource(frontendId, mDemuxId);
+    return startBroadcastInputLoop();
 }
 
 Return<void> Demux::addFilter(DemuxFilterType type, uint32_t bufferSize,
@@ -100,6 +112,8 @@
         mFilterEventFlags.resize(filterId + 1);
         mFilterThreadRunning.resize(filterId + 1);
         mFilterThreads.resize(filterId + 1);
+        mFilterPids.resize(filterId + 1);
+        mFilterOutputs.resize(filterId + 1);
     }
 
     mUsedFilterIds.insert(filterId);
@@ -142,10 +156,34 @@
     return Void();
 }
 
-Return<Result> Demux::configureFilter(uint32_t /* filterId */,
-                                      const DemuxFilterSettings& /* settings */) {
+Return<Result> Demux::configureFilter(uint32_t filterId, const DemuxFilterSettings& settings) {
     ALOGV("%s", __FUNCTION__);
 
+    switch (mFilterEvents[filterId].filterType) {
+        case DemuxFilterType::SECTION:
+            mFilterPids[filterId] = settings.section().tpid;
+            break;
+        case DemuxFilterType::PES:
+            mFilterPids[filterId] = settings.pesData().tpid;
+            break;
+        case DemuxFilterType::TS:
+            mFilterPids[filterId] = settings.ts().tpid;
+            break;
+        case DemuxFilterType::AUDIO:
+            mFilterPids[filterId] = settings.audio().tpid;
+            break;
+        case DemuxFilterType::VIDEO:
+            mFilterPids[filterId] = settings.video().tpid;
+            break;
+        case DemuxFilterType::RECORD:
+            mFilterPids[filterId] = settings.record().tpid;
+            break;
+        case DemuxFilterType::PCR:
+            mFilterPids[filterId] = settings.pcr().tpid;
+            break;
+        default:
+            return Result::UNKNOWN_ERROR;
+    }
     return Result::SUCCESS;
 }
 
@@ -158,36 +196,18 @@
         return Result::INVALID_ARGUMENT;
     }
 
-    switch (mFilterEvents[filterId].filterType) {
-        case DemuxFilterType::SECTION:
-            result = startFilterLoop(filterId);
-            break;
-        case DemuxFilterType::PES:
-            result = startPesFilterHandler(filterId);
-            break;
-        case DemuxFilterType::TS:
-            result = startTsFilterHandler();
-            return Result::SUCCESS;
-        case DemuxFilterType::AUDIO:
-        case DemuxFilterType::VIDEO:
-            result = startMediaFilterHandler(filterId);
-            break;
-        case DemuxFilterType::RECORD:
-            result = startRecordFilterHandler(filterId);
-            break;
-        case DemuxFilterType::PCR:
-            result = startPcrFilterHandler();
-            return Result::SUCCESS;
-        default:
-            return Result::UNKNOWN_ERROR;
-    }
+    result = startFilterLoop(filterId);
 
     return result;
 }
 
-Return<Result> Demux::stopFilter(uint32_t /* filterId */) {
+Return<Result> Demux::stopFilter(uint32_t filterId) {
     ALOGV("%s", __FUNCTION__);
 
+    mFilterThreadRunning[filterId] = false;
+
+    std::lock_guard<std::mutex> lock(mFilterThreadLock);
+
     return Result::SUCCESS;
 }
 
@@ -238,6 +258,8 @@
     mFilterMQs.clear();
     mFilterEvents.clear();
     mFilterEventFlags.clear();
+    mFilterOutputs.clear();
+    mFilterPids.clear();
     mLastUsedFilterId = -1;
 
     return Result::SUCCESS;
@@ -277,19 +299,21 @@
     return Void();
 }
 
-Return<Result> Demux::configureOutput(const DemuxOutputSettings& /* settings */) {
+Return<Result> Demux::configureOutput(const DemuxOutputSettings& settings) {
+    ALOGV("%s", __FUNCTION__);
+
+    mOutputConfigured = true;
+    mOutputSettings = settings;
+    return Result::SUCCESS;
+}
+
+Return<Result> Demux::attachOutputFilter(uint32_t /*filterId*/) {
     ALOGV("%s", __FUNCTION__);
 
     return Result::SUCCESS;
 }
 
-Return<Result> Demux::attachOutputTsFilter(uint32_t /*filterId*/) {
-    ALOGV("%s", __FUNCTION__);
-
-    return Result::SUCCESS;
-}
-
-Return<Result> Demux::detachOutputTsFilter(uint32_t /* filterId */) {
+Return<Result> Demux::detachOutputFilter(uint32_t /* filterId */) {
     ALOGV("%s", __FUNCTION__);
 
     return Result::SUCCESS;
@@ -353,15 +377,26 @@
     return Void();
 }
 
-Return<Result> Demux::configureInput(const DemuxInputSettings& /* settings */) {
+Return<Result> Demux::configureInput(const DemuxInputSettings& settings) {
     ALOGV("%s", __FUNCTION__);
 
+    mInputConfigured = true;
+    mInputSettings = settings;
+
     return Result::SUCCESS;
 }
 
 Return<Result> Demux::startInput() {
     ALOGV("%s", __FUNCTION__);
 
+    if (!mInputCallback) {
+        return Result::NOT_INITIALIZED;
+    }
+
+    if (!mInputConfigured) {
+        return Result::INVALID_STATE;
+    }
+
     pthread_create(&mInputThread, NULL, __threadLoopInput, this);
     pthread_setname_np(mInputThread, "demux_input_waiting_loop");
 
@@ -373,6 +408,10 @@
 Return<Result> Demux::stopInput() {
     ALOGV("%s", __FUNCTION__);
 
+    mInputThreadRunning = false;
+
+    std::lock_guard<std::mutex> lock(mInputThreadLock);
+
     return Result::SUCCESS;
 }
 
@@ -403,36 +442,52 @@
     return Result::SUCCESS;
 }
 
-Result Demux::startSectionFilterHandler(uint32_t filterId, vector<uint8_t> data) {
-    if (!writeSectionsAndCreateEvent(filterId, data)) {
+Result Demux::startSectionFilterHandler(uint32_t filterId) {
+    if (mFilterOutputs[filterId].empty()) {
+        return Result::SUCCESS;
+    }
+    if (!writeSectionsAndCreateEvent(filterId, mFilterOutputs[filterId])) {
         ALOGD("[Demux] filter %d fails to write into FMQ. Ending thread", filterId);
         return Result::UNKNOWN_ERROR;
     }
 
+    mFilterOutputs[filterId].clear();
+
     return Result::SUCCESS;
 }
 
 Result Demux::startPesFilterHandler(uint32_t filterId) {
-    // TODO generate multiple events in one event callback
+    std::lock_guard<std::mutex> lock(mFilterEventLock);
     DemuxFilterPesEvent pesEvent;
-    pesEvent = {
-            // temp dump meta data
-            .streamId = 0,
-            .dataLength = 530,
-    };
-    mFilterEvents[filterId].events.resize(1);
-    mFilterEvents[filterId].events[0].pes(pesEvent);
-    /*pthread_create(&mThreadId, NULL, __threadLoop, this);
-    pthread_setname_np(mThreadId, "demux_section_filter_waiting_loop");*/
-    if (!writeDataToFilterMQ(fakeDataInputBuffer, filterId)) {
-        return Result::INVALID_STATE;
+    if (mFilterOutputs[filterId].empty()) {
+        return Result::SUCCESS;
     }
 
-    if (mDemuxCallbacks[filterId] == nullptr) {
-        return Result::NOT_INITIALIZED;
+    for (int i = 0; i < mFilterOutputs[filterId].size(); i += 188) {
+        uint8_t pusi = mFilterOutputs[filterId][i + 1] & 0x40;
+        uint8_t adaptFieldControl = (mFilterOutputs[filterId][i + 3] & 0x30) >> 4;
+        ALOGD("[Demux] pusi %d, adaptFieldControl %d", pusi, adaptFieldControl);
+        if (pusi && (adaptFieldControl == 0x01)) {
+            vector<uint8_t>::const_iterator first = mFilterOutputs[filterId].begin() + i + 4;
+            vector<uint8_t>::const_iterator last = mFilterOutputs[filterId].begin() + i + 187;
+            vector<uint8_t> filterOutData(first, last);
+            if (!writeDataToFilterMQ(filterOutData, filterId)) {
+                mFilterOutputs[filterId].clear();
+                return Result::INVALID_STATE;
+            }
+            pesEvent = {
+                    // temp dump meta data
+                    .streamId = filterOutData[3],
+                    .dataLength = static_cast<uint16_t>(filterOutData.size()),
+            };
+            int size = mFilterEvents[filterId].events.size();
+            mFilterEvents[filterId].events.resize(size + 1);
+            mFilterEvents[filterId].events[size].pes(pesEvent);
+        }
     }
 
-    mDemuxCallbacks[filterId]->onFilterEvent(mFilterEvents[filterId]);
+    mFilterOutputs[filterId].clear();
+
     return Result::SUCCESS;
 }
 
@@ -451,6 +506,8 @@
     };
     mFilterEvents[filterId].events.resize(1);
     mFilterEvents[filterId].events[0].media() = mediaEvent;
+
+    mFilterOutputs[filterId].clear();
     // TODO handle write FQM for media stream
     return Result::SUCCESS;
 }
@@ -465,6 +522,8 @@
     recordEvent.indexMask.tsIndexMask() = 0x01;
     mFilterEvents[filterId].events.resize(1);
     mFilterEvents[filterId].events[0].ts() = recordEvent;
+
+    mFilterOutputs[filterId].clear();
     return Result::SUCCESS;
 }
 
@@ -499,18 +558,18 @@
 bool Demux::writeSectionsAndCreateEvent(uint32_t filterId, vector<uint8_t> data) {
     // TODO check how many sections has been read
     std::lock_guard<std::mutex> lock(mFilterEventLock);
-    int size = mFilterEvents[filterId].events.size();
-    mFilterEvents[filterId].events.resize(size + 1);
     if (!writeDataToFilterMQ(data, filterId)) {
         return false;
     }
+    int size = mFilterEvents[filterId].events.size();
+    mFilterEvents[filterId].events.resize(size + 1);
     DemuxFilterSectionEvent secEvent;
     secEvent = {
             // temp dump meta data
             .tableId = 0,
             .version = 1,
             .sectionNum = 1,
-            .dataLength = 530,
+            .dataLength = static_cast<uint16_t>(data.size()),
     };
     mFilterEvents[filterId].events[size].section(secEvent);
     return true;
@@ -524,21 +583,44 @@
     return false;
 }
 
-bool Demux::filterAndOutputData() {
-    ALOGD("[Demux] start to dispatch data to filters");
+bool Demux::readInputFMQ() {
     // Read input data from the input FMQ
     int size = mInputMQ->availableToRead();
+    int inputPacketSize = mInputSettings.packetSize;
     vector<uint8_t> dataOutputBuffer;
-    dataOutputBuffer.resize(size);
-    mInputMQ->read(dataOutputBuffer.data(), size);
+    dataOutputBuffer.resize(inputPacketSize);
 
-    Result result;
-    // Filter the data and feed the output to each filter
+    // Dispatch the packet to the PID matching filter output buffer
+    for (int i = 0; i < size / inputPacketSize; i++) {
+        if (!mInputMQ->read(dataOutputBuffer.data(), inputPacketSize)) {
+            return false;
+        }
+        startTsFilter(dataOutputBuffer);
+    }
+
+    return true;
+}
+
+void Demux::startTsFilter(vector<uint8_t> data) {
     set<uint32_t>::iterator it;
     for (it = mUsedFilterIds.begin(); it != mUsedFilterIds.end(); it++) {
+        uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff));
+        ALOGW("start ts filter pid: %d", pid);
+        if (pid == mFilterPids[*it]) {
+            mFilterOutputs[*it].insert(mFilterOutputs[*it].end(), data.begin(), data.end());
+        }
+    }
+}
+
+bool Demux::startFilterDispatcher() {
+    Result result;
+    set<uint32_t>::iterator it;
+
+    // Handle the output data per filter type
+    for (it = mUsedFilterIds.begin(); it != mUsedFilterIds.end(); it++) {
         switch (mFilterEvents[*it].filterType) {
             case DemuxFilterType::SECTION:
-                result = startSectionFilterHandler(*it, dataOutputBuffer);
+                result = startSectionFilterHandler(*it);
                 break;
             case DemuxFilterType::PES:
                 result = startPesFilterHandler(*it);
@@ -578,6 +660,7 @@
 
 void Demux::filterThreadLoop(uint32_t filterId) {
     ALOGD("[Demux] filter %d threadLoop start.", filterId);
+    std::lock_guard<std::mutex> lock(mFilterThreadLock);
     mFilterThreadRunning[filterId] = true;
 
     // For the first time of filter output, implementation needs to send the filter
@@ -640,6 +723,7 @@
 
 void Demux::inputThreadLoop() {
     ALOGD("[Demux] input threadLoop start.");
+    std::lock_guard<std::mutex> lock(mInputThreadLock);
     mInputThreadRunning = true;
 
     while (mInputThreadRunning) {
@@ -651,18 +735,112 @@
             ALOGD("[Demux] wait for data ready on the input FMQ");
             continue;
         }
-        // Our current implementation filter the data and write it into the filter FMQ immedaitely
+        // Our current implementation filter the data and write it into the filter FMQ immediately
         // after the DATA_READY from the VTS/framework
-        if (!filterAndOutputData()) {
+        if (!readInputFMQ() || !startFilterDispatcher()) {
             ALOGD("[Demux] input data failed to be filtered. Ending thread");
             break;
         }
+
+        maySendInputStatusCallback();
     }
 
     mInputThreadRunning = false;
     ALOGD("[Demux] input thread ended.");
 }
 
+void Demux::maySendInputStatusCallback() {
+    std::lock_guard<std::mutex> lock(mInputStatusLock);
+    int availableToRead = mInputMQ->availableToRead();
+    int availableToWrite = mInputMQ->availableToWrite();
+
+    DemuxInputStatus newStatus =
+            checkStatusChange(availableToWrite, availableToRead, mInputSettings.highThreshold,
+                              mInputSettings.lowThreshold);
+    if (mIntputStatus != newStatus) {
+        mInputCallback->onInputStatus(newStatus);
+        mIntputStatus = newStatus;
+    }
+}
+
+DemuxInputStatus Demux::checkStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
+                                          uint32_t highThreshold, uint32_t lowThreshold) {
+    if (availableToWrite == 0) {
+        return DemuxInputStatus::SPACE_FULL;
+    } else if (availableToRead > highThreshold) {
+        return DemuxInputStatus::SPACE_ALMOST_FULL;
+    } else if (availableToRead < lowThreshold) {
+        return DemuxInputStatus::SPACE_ALMOST_EMPTY;
+    } else if (availableToRead == 0) {
+        return DemuxInputStatus::SPACE_EMPTY;
+    }
+    return mIntputStatus;
+}
+
+Result Demux::startBroadcastInputLoop() {
+    pthread_create(&mBroadcastInputThread, NULL, __threadLoopBroadcast, this);
+    pthread_setname_np(mBroadcastInputThread, "broadcast_input_thread");
+
+    return Result::SUCCESS;
+}
+
+void* Demux::__threadLoopBroadcast(void* user) {
+    Demux* const self = static_cast<Demux*>(user);
+    self->broadcastInputThreadLoop();
+    return 0;
+}
+
+void Demux::broadcastInputThreadLoop() {
+    std::lock_guard<std::mutex> lock(mBroadcastInputThreadLock);
+    mBroadcastInputThreadRunning = true;
+    mKeepFetchingDataFromFrontend = true;
+
+    // open the stream and get its length
+    std::ifstream inputData(mFrontendSourceFile, std::ifstream::binary);
+    // TODO take the packet size from the frontend setting
+    int packetSize = 188;
+    int writePacketAmount = 6;
+    char* buffer = new char[packetSize];
+    ALOGW("[Demux] broadcast input thread loop start %s", mFrontendSourceFile.c_str());
+    if (!inputData.is_open()) {
+        mBroadcastInputThreadRunning = false;
+        ALOGW("[Demux] Error %s", strerror(errno));
+    }
+
+    while (mBroadcastInputThreadRunning) {
+        // move the stream pointer for packet size * 6 every read until the end
+        while (mKeepFetchingDataFromFrontend) {
+            for (int i = 0; i < writePacketAmount; i++) {
+                inputData.read(buffer, packetSize);
+                if (!inputData) {
+                    mBroadcastInputThreadRunning = false;
+                    break;
+                }
+                // filter and dispatch filter output
+                vector<uint8_t> byteBuffer;
+                byteBuffer.resize(sizeof(buffer));
+                for (int index = 0; index < byteBuffer.size(); index++) {
+                    byteBuffer[index] = static_cast<uint8_t>(buffer[index]);
+                }
+                startTsFilter(byteBuffer);
+                inputData.seekg(packetSize, inputData.cur);
+            }
+            startFilterDispatcher();
+            sleep(1);
+        }
+    }
+
+    ALOGW("[Demux] Broadcast Input thread end.");
+    delete[] buffer;
+    inputData.close();
+}
+
+void Demux::stopBroadcastInput() {
+    mKeepFetchingDataFromFrontend = false;
+    mBroadcastInputThreadRunning = false;
+    std::lock_guard<std::mutex> lock(mBroadcastInputThreadLock);
+}
+
 }  // namespace implementation
 }  // namespace V1_0
 }  // namespace tuner
diff --git a/tv/tuner/1.0/default/Demux.h b/tv/tuner/1.0/default/Demux.h
index 2fdde8d..e4a4e2b 100644
--- a/tv/tuner/1.0/default/Demux.h
+++ b/tv/tuner/1.0/default/Demux.h
@@ -20,6 +20,8 @@
 #include <android/hardware/tv/tuner/1.0/IDemux.h>
 #include <fmq/MessageQueue.h>
 #include <set>
+#include "Frontend.h"
+#include "Tuner.h"
 
 using namespace std;
 
@@ -40,9 +42,12 @@
 
 using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
 
+class Tuner;
+class Frontend;
+
 class Demux : public IDemux {
   public:
-    Demux(uint32_t demuxId);
+    Demux(uint32_t demuxId, sp<Tuner> tuner);
 
     ~Demux();
 
@@ -91,9 +96,9 @@
 
     virtual Return<Result> configureOutput(const DemuxOutputSettings& settings) override;
 
-    virtual Return<Result> attachOutputTsFilter(uint32_t filterId) override;
+    virtual Return<Result> attachOutputFilter(uint32_t filterId) override;
 
-    virtual Return<Result> detachOutputTsFilter(uint32_t filterId) override;
+    virtual Return<Result> detachOutputFilter(uint32_t filterId) override;
 
     virtual Return<Result> startOutput() override;
 
@@ -103,7 +108,17 @@
 
     virtual Return<Result> removeOutput() override;
 
+    // Functions interacts with Tuner Service
+    void stopBroadcastInput();
+
   private:
+    // Tuner service
+    sp<Tuner> mTunerService;
+
+    // Frontend source
+    sp<Frontend> mFrontend;
+    string mFrontendSourceFile;
+
     // A struct that passes the arguments to a newly created filter thread
     struct ThreadArgs {
         Demux* user;
@@ -115,13 +130,14 @@
      * They are also responsible to write the filtered output into the filter FMQ
      * and update the filterEvent bound with the same filterId.
      */
-    Result startSectionFilterHandler(uint32_t filterId, vector<uint8_t> data);
+    Result startSectionFilterHandler(uint32_t filterId);
     Result startPesFilterHandler(uint32_t filterId);
     Result startTsFilterHandler();
     Result startMediaFilterHandler(uint32_t filterId);
     Result startRecordFilterHandler(uint32_t filterId);
     Result startPcrFilterHandler();
     Result startFilterLoop(uint32_t filterId);
+    Result startBroadcastInputLoop();
 
     /**
      * To create a FilterMQ with the the next available Filter ID.
@@ -136,18 +152,24 @@
     bool writeDataToFilterMQ(const std::vector<uint8_t>& data, uint32_t filterId);
     bool readDataFromMQ();
     bool writeSectionsAndCreateEvent(uint32_t filterId, vector<uint8_t> data);
+    void maySendInputStatusCallback();
+    DemuxInputStatus checkStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
+                                       uint32_t highThreshold, uint32_t lowThreshold);
     /**
      * A dispatcher to read and dispatch input data to all the started filters.
      * Each filter handler handles the data filtering/output writing/filterEvent updating.
      */
-    bool filterAndOutputData();
+    bool readInputFMQ();
+    void startTsFilter(vector<uint8_t> data);
+    bool startFilterDispatcher();
     static void* __threadLoopFilter(void* data);
     static void* __threadLoopInput(void* user);
+    static void* __threadLoopBroadcast(void* user);
     void filterThreadLoop(uint32_t filterId);
     void inputThreadLoop();
+    void broadcastInputThreadLoop();
 
     uint32_t mDemuxId;
-    uint32_t mSourceFrontendId;
     /**
      * Record the last used filter id. Initial value is -1.
      * Filter Id starts with 0.
@@ -169,6 +191,8 @@
      * A list of created FilterMQ ptrs.
      * The array number is the filter ID.
      */
+    vector<uint16_t> mFilterPids;
+    vector<vector<uint8_t>> mFilterOutputs;
     vector<unique_ptr<FilterMQ>> mFilterMQs;
     vector<EventFlag*> mFilterEventFlags;
     vector<DemuxFilterEvent> mFilterEvents;
@@ -182,15 +206,26 @@
     vector<sp<IDemuxCallback>> mDemuxCallbacks;
     sp<IDemuxCallback> mInputCallback;
     sp<IDemuxCallback> mOutputCallback;
+    bool mInputConfigured = false;
+    bool mOutputConfigured = false;
+    DemuxInputSettings mInputSettings;
+    DemuxOutputSettings mOutputSettings;
+
     // Thread handlers
     pthread_t mInputThread;
     pthread_t mOutputThread;
+    pthread_t mBroadcastInputThread;
     vector<pthread_t> mFilterThreads;
+
+    // FMQ status local records
+    DemuxInputStatus mIntputStatus;
     /**
      * If a specific filter's writing loop is still running
      */
     vector<bool> mFilterThreadRunning;
     bool mInputThreadRunning;
+    bool mBroadcastInputThreadRunning;
+    bool mKeepFetchingDataFromFrontend;
     /**
      * Lock to protect writes to the FMQs
      */
@@ -198,8 +233,16 @@
     /**
      * Lock to protect writes to the filter event
      */
+    // TODO make each filter separate event lock
     std::mutex mFilterEventLock;
     /**
+     * Lock to protect writes to the input status
+     */
+    std::mutex mInputStatusLock;
+    std::mutex mBroadcastInputThreadLock;
+    std::mutex mFilterThreadLock;
+    std::mutex mInputThreadLock;
+    /**
      * How many times a filter should write
      * TODO make this dynamic/random/can take as a parameter
      */
diff --git a/tv/tuner/1.0/default/Frontend.cpp b/tv/tuner/1.0/default/Frontend.cpp
index 0609d05..1e07edd 100644
--- a/tv/tuner/1.0/default/Frontend.cpp
+++ b/tv/tuner/1.0/default/Frontend.cpp
@@ -27,14 +27,10 @@
 namespace V1_0 {
 namespace implementation {
 
-Frontend::Frontend() {
-    // Init callback to nullptr
-    mCallback = nullptr;
-}
-
-Frontend::Frontend(FrontendType type, FrontendId id) {
+Frontend::Frontend(FrontendType type, FrontendId id, sp<Tuner> tuner) {
     mType = type;
     mId = id;
+    mTunerService = tuner;
     // Init callback to nullptr
     mCallback = nullptr;
 }
@@ -67,13 +63,18 @@
         return Result::INVALID_STATE;
     }
 
-    mCallback->onEvent(FrontendEventType::NO_SIGNAL);
+    // TODO dynamically allocate file to the source file
+    mSourceStreamFile = FRONTEND_STREAM_FILE;
+
+    mCallback->onEvent(FrontendEventType::LOCKED);
     return Result::SUCCESS;
 }
 
 Return<Result> Frontend::stopTune() {
     ALOGV("%s", __FUNCTION__);
 
+    mTunerService->frontendStopTune(mId);
+
     return Result::SUCCESS;
 }
 
@@ -105,13 +106,7 @@
     return Result::SUCCESS;
 }
 
-Return<Result> Frontend::setLnb(const sp<ILnb>& /* lnb */) {
-    ALOGV("%s", __FUNCTION__);
-
-    return Result::SUCCESS;
-}
-
-Return<Result> Frontend::sendDiseqcMessage(const hidl_vec<uint8_t>& /* diseqcMessage */) {
+Return<Result> Frontend::setLnb(uint32_t /* lnb */) {
     ALOGV("%s", __FUNCTION__);
 
     return Result::SUCCESS;
@@ -125,6 +120,10 @@
     return mId;
 }
 
+string Frontend::getSourceFile() {
+    return mSourceStreamFile;
+}
+
 }  // namespace implementation
 }  // namespace V1_0
 }  // namespace tuner
diff --git a/tv/tuner/1.0/default/Frontend.h b/tv/tuner/1.0/default/Frontend.h
index fc586b5..07fa7b9 100644
--- a/tv/tuner/1.0/default/Frontend.h
+++ b/tv/tuner/1.0/default/Frontend.h
@@ -18,7 +18,9 @@
 #define ANDROID_HARDWARE_TV_TUNER_V1_0_FRONTEND_H_
 
 #include <android/hardware/tv/tuner/1.0/IFrontend.h>
-#include <android/hardware/tv/tuner/1.0/ITuner.h>
+#include <fstream>
+#include <iostream>
+#include "Tuner.h"
 
 using namespace std;
 
@@ -35,11 +37,11 @@
 using ::android::hardware::tv::tuner::V1_0::IFrontendCallback;
 using ::android::hardware::tv::tuner::V1_0::Result;
 
+class Tuner;
+
 class Frontend : public IFrontend {
   public:
-    Frontend();
-
-    Frontend(FrontendType type, FrontendId id);
+    Frontend(FrontendType type, FrontendId id, sp<Tuner> tuner);
 
     virtual Return<Result> close() override;
 
@@ -56,21 +58,26 @@
     virtual Return<void> getStatus(const hidl_vec<FrontendStatusType>& statusTypes,
                                    getStatus_cb _hidl_cb) override;
 
-    virtual Return<Result> sendDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage) override;
-
     virtual Return<Result> setLna(bool bEnable) override;
 
-    virtual Return<Result> setLnb(const sp<ILnb>& lnb) override;
+    virtual Return<Result> setLnb(uint32_t lnb) override;
 
     FrontendType getFrontendType();
 
     FrontendId getFrontendId();
 
+    string getSourceFile();
+
   private:
     virtual ~Frontend();
     sp<IFrontendCallback> mCallback;
+    sp<Tuner> mTunerService;
     FrontendType mType = FrontendType::UNDEFINED;
     FrontendId mId = 0;
+
+    const string FRONTEND_STREAM_FILE = "/vendor/etc/test1.ts";
+    string mSourceStreamFile;
+    std::ifstream mFrontendData;
 };
 
 }  // namespace implementation
diff --git a/tv/tuner/1.0/default/Lnb.cpp b/tv/tuner/1.0/default/Lnb.cpp
index b81bb15..1446f7f 100644
--- a/tv/tuner/1.0/default/Lnb.cpp
+++ b/tv/tuner/1.0/default/Lnb.cpp
@@ -48,6 +48,12 @@
     return Result::SUCCESS;
 }
 
+Return<Result> Lnb::sendDiseqcMessage(const hidl_vec<uint8_t>& /* diseqcMessage */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return Result::SUCCESS;
+}
+
 Return<Result> Lnb::close() {
     ALOGV("%s", __FUNCTION__);
 
diff --git a/tv/tuner/1.0/default/Lnb.h b/tv/tuner/1.0/default/Lnb.h
index df7e0fe..4c251f7 100644
--- a/tv/tuner/1.0/default/Lnb.h
+++ b/tv/tuner/1.0/default/Lnb.h
@@ -38,12 +38,14 @@
   public:
     Lnb();
 
-    virtual Return<Result> setVoltage(FrontendLnbVoltage voltage);
+    virtual Return<Result> setVoltage(FrontendLnbVoltage voltage) override;
 
     virtual Return<Result> setTone(FrontendLnbTone tone) override;
 
     virtual Return<Result> setSatellitePosition(FrontendLnbPosition position) override;
 
+    virtual Return<Result> sendDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage) override;
+
     virtual Return<Result> close() override;
 
   private:
diff --git a/tv/tuner/1.0/default/Tuner.cpp b/tv/tuner/1.0/default/Tuner.cpp
index 00831ae..f86b28d 100644
--- a/tv/tuner/1.0/default/Tuner.cpp
+++ b/tv/tuner/1.0/default/Tuner.cpp
@@ -38,14 +38,14 @@
     // Array index matches their FrontendId in the default impl
     mFrontendSize = 8;
     mFrontends.resize(mFrontendSize);
-    mFrontends[0] = new Frontend();
-    mFrontends[1] = new Frontend(FrontendType::ATSC, 1);
-    mFrontends[2] = new Frontend(FrontendType::DVBC, 2);
-    mFrontends[3] = new Frontend(FrontendType::DVBS, 3);
-    mFrontends[4] = new Frontend(FrontendType::DVBT, 4);
-    mFrontends[5] = new Frontend(FrontendType::ISDBT, 5);
-    mFrontends[6] = new Frontend(FrontendType::ANALOG, 6);
-    mFrontends[7] = new Frontend(FrontendType::ATSC, 7);
+    mFrontends[0] = new Frontend(FrontendType::DVBT, 0, this);
+    mFrontends[1] = new Frontend(FrontendType::ATSC, 1, this);
+    mFrontends[2] = new Frontend(FrontendType::DVBC, 2, this);
+    mFrontends[3] = new Frontend(FrontendType::DVBS, 3, this);
+    mFrontends[4] = new Frontend(FrontendType::DVBT, 4, this);
+    mFrontends[5] = new Frontend(FrontendType::ISDBT, 5, this);
+    mFrontends[6] = new Frontend(FrontendType::ANALOG, 6, this);
+    mFrontends[7] = new Frontend(FrontendType::ATSC, 7, this);
 }
 
 Tuner::~Tuner() {}
@@ -81,12 +81,22 @@
 
     DemuxId demuxId = mLastUsedId + 1;
     mLastUsedId += 1;
-    sp<IDemux> demux = new Demux(demuxId);
+    sp<Demux> demux = new Demux(demuxId, this);
+    mDemuxes[demuxId] = demux;
 
     _hidl_cb(Result::SUCCESS, demuxId, demux);
     return Void();
 }
 
+Return<void> Tuner::getDemuxCaps(getDemuxCaps_cb _hidl_cb) {
+    ALOGV("%s", __FUNCTION__);
+
+    DemuxCapabilities caps;
+
+    _hidl_cb(Result::SUCCESS, caps);
+    return Void();
+}
+
 Return<void> Tuner::openDescrambler(openDescrambler_cb _hidl_cb) {
     ALOGV("%s", __FUNCTION__);
 
@@ -123,6 +133,25 @@
     return Void();
 }
 
+sp<Frontend> Tuner::getFrontendById(uint32_t frontendId) {
+    ALOGV("%s", __FUNCTION__);
+
+    return mFrontends[frontendId];
+}
+
+void Tuner::setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId) {
+    mFrontendToDemux[frontendId] = demuxId;
+}
+
+void Tuner::frontendStopTune(uint32_t frontendId) {
+    map<uint32_t, uint32_t>::iterator it = mFrontendToDemux.find(frontendId);
+    uint32_t demuxId;
+    if (it != mFrontendToDemux.end()) {
+        demuxId = it->second;
+        mDemuxes[demuxId]->stopBroadcastInput();
+    }
+}
+
 }  // namespace implementation
 }  // namespace V1_0
 }  // namespace tuner
diff --git a/tv/tuner/1.0/default/Tuner.h b/tv/tuner/1.0/default/Tuner.h
index 62227ee..96da257 100644
--- a/tv/tuner/1.0/default/Tuner.h
+++ b/tv/tuner/1.0/default/Tuner.h
@@ -18,6 +18,8 @@
 #define ANDROID_HARDWARE_TV_TUNER_V1_0_TUNER_H_
 
 #include <android/hardware/tv/tuner/1.0/ITuner.h>
+#include <map>
+#include "Demux.h"
 #include "Frontend.h"
 
 using namespace std;
@@ -29,6 +31,9 @@
 namespace V1_0 {
 namespace implementation {
 
+class Frontend;
+class Demux;
+
 class Tuner : public ITuner {
   public:
     Tuner();
@@ -39,6 +44,8 @@
 
     virtual Return<void> openDemux(openDemux_cb _hidl_cb) override;
 
+    virtual Return<void> getDemuxCaps(getDemuxCaps_cb _hidl_cb) override;
+
     virtual Return<void> openDescrambler(openDescrambler_cb _hidl_cb) override;
 
     virtual Return<void> getFrontendInfo(FrontendId frontendId,
@@ -48,10 +55,18 @@
 
     virtual Return<void> openLnbById(LnbId lnbId, openLnbById_cb _hidl_cb) override;
 
+    sp<Frontend> getFrontendById(uint32_t frontendId);
+
+    void setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId);
+
+    void frontendStopTune(uint32_t frontendId);
+
   private:
     virtual ~Tuner();
     // Static mFrontends array to maintain local frontends information
     vector<sp<Frontend>> mFrontends;
+    std::map<uint32_t, uint32_t> mFrontendToDemux;
+    std::map<uint32_t, sp<Demux>> mDemuxes;
     // To maintain how many Frontends we have
     int mFrontendSize;
     // The last used demux id. Initial value is -1.
diff --git a/tv/tuner/1.0/types.hal b/tv/tuner/1.0/types.hal
index d37f63a..890c1ed 100644
--- a/tv/tuner/1.0/types.hal
+++ b/tv/tuner/1.0/types.hal
@@ -43,7 +43,7 @@
     ANALOG,
     /* Advanced Television Systems Committee (ATSC) Standard A/72. */
     ATSC,
-    /* Advanced Television Systems Committee (ATSC 3.0) Standard A/330. */
+    /* Advanced Television Systems Committee (ATSC 3.0) Standard A/300. */
     ATSC3,
     /**
      * Digital Video Broadcasting - Cable
@@ -58,16 +58,16 @@
     DVBS,
     /**
      * Digital Video Broadcasting - Terrestrial
-     * DVB Terresttrial Frontend Standard ETSI EN 300 468 V1.15.1 and
+     * DVB Terrestrial Frontend Standard ETSI EN 300 468 V1.15.1 and
      * ETSI EN 302 755 V1.4.1.
      */
     DVBT,
     /* Integrated Services Digital Broadcasting-Satellite (ISDB-S)
-     * ARIB SDT-B20 is technical document of ISDB-S.
+     * ARIB STD-B20 is technical document of ISDB-S.
      */
     ISDBS,
     /* Integrated Services Digital Broadcasting-Satellite (ISDB-S)
-     * ARIB TR-B15 is technical document of ISDB-S3.
+     * ARIB STD-B44 is technical document of ISDB-S3.
      */
     ISDBS3,
     /* Integrated Services Digital Broadcasting-Terrestrial (ISDB-T or SBTVD)
@@ -164,8 +164,10 @@
 @export
 enum FrontendAtscModulation : uint32_t {
     UNDEFINED = 0,
-    MOD_8VSB = 1 << 0,
-    MOD_16VSB = 1 << 1,
+    /** hardware is able to detect and set modulation automatically */
+    AUTO      = 1 << 0,
+    MOD_8VSB  = 1 << 2,
+    MOD_16VSB = 1 << 3,
 };
 
 /**
@@ -191,12 +193,14 @@
 @export
 enum FrontendAtsc3Modulation : uint32_t {
     UNDEFINED = 0,
-    MOD_QPSK = 1 << 0,
-    MOD_16QAM = 1 << 1,
-    MOD_64QAM = 1 << 2,
-    MOD_256QAM = 1 << 3,
-    MOD_1024QAM = 1 << 4,
-    MOD_4096QAM = 1 << 5,
+    /** hardware is able to detect and set modulation automatically */
+    AUTO        = 1 << 0,
+    MOD_QPSK    = 1 << 1,
+    MOD_16QAM   = 1 << 2,
+    MOD_64QAM   = 1 << 3,
+    MOD_256QAM  = 1 << 4,
+    MOD_1024QAM = 1 << 5,
+    MOD_4096QAM = 1 << 6,
 };
 
 /**
@@ -205,9 +209,11 @@
 @export
 enum FrontendAtsc3Bandwidth : uint32_t {
     UNDEFINED = 0,
-    BANDWIDTH_8MHZ = 1 << 0,
-    BANDWIDTH_7MHZ = 1 << 1,
-    BANDWIDTH_6MHZ = 1 << 2,
+    /** hardware is able to detect and set bandwidth automatically */
+    AUTO = 1 << 0,
+    BANDWIDTH_6MHZ = 1 << 1,
+    BANDWIDTH_7MHZ = 1 << 2,
+    BANDWIDTH_8MHZ = 1 << 3,
 };
 
 /**
@@ -215,9 +221,11 @@
  */
 @export
 enum FrontendAtsc3TimeInterleaveMode : uint32_t {
-    UNDEFINED,
-    CTI,
-    HTI,
+    UNDEFINED = 0,
+    /** hardware is able to detect and set TimeInterleaveMode automatically */
+    AUTO = 1 << 0,
+    CTI  = 1 << 1,
+    HTI  = 1 << 2,
 };
 
 /**
@@ -247,13 +255,39 @@
  */
 @export
 enum FrontendAtsc3Fec : uint32_t {
-    UNDEFINED,
-    BCH_LDPC_16K,
-    BCH_LDPC_64K,
-    CRC_LDPC_16K,
-    CRC_LDPC_64K,
-    LDPC_16K,
-    LDPC_64K,
+    UNDEFINED = 0,
+    /** hardware is able to detect and set FEC automatically */
+    AUTO     = 1 << 0,
+    BCH_LDPC_16K = 1 << 1,
+    BCH_LDPC_64K = 1 << 2,
+    CRC_LDPC_16K = 1 << 3,
+    CRC_LDPC_64K = 1 << 4,
+    LDPC_16K     = 1 << 5,
+    LDPC_64K     = 1 << 6,
+};
+
+/**
+ *  Demodulator Output Format for an ATSC3 Frontend.
+ */
+@export
+enum FrontendAtsc3DemodOutputFormat : uint8_t {
+    /** Dummy. Scan uses this. */
+    UNDEFINED = 0,
+    /** ALP format. Typically used in US region. */
+    ATSC3_LINKLAYER_PACKET = 1 << 0,
+    /** BaseBand packet format. Typically used in Korea region. */
+    BASEBAND_PACKET        = 1 << 1,
+};
+
+/**
+ *  PLP basis Signal Settings for an ATSC3 Frontend.
+ */
+struct FrontendAtsc3PlpSettings {
+    uint8_t plpId;
+    FrontendAtsc3Modulation modulation;
+    FrontendAtsc3TimeInterleaveMode interleaveMode;
+    FrontendAtsc3CodeRate codeRate;
+    FrontendAtsc3Fec fec;
 };
 
 /**
@@ -262,21 +296,28 @@
 struct FrontendAtsc3Settings {
     /** Signal frequency in Hertz */
     uint32_t frequency;
+    /** Bandwidth of tuning band. */
     FrontendAtsc3Bandwidth bandwidth;
-    FrontendAtsc3TimeInterleaveMode interleaveMode;
-    FrontendAtsc3CodeRate codeRate;
-    FrontendAtsc3Fec fec;
-    vec<uint8_t> plpIdList;
+    FrontendAtsc3DemodOutputFormat demodOutputFormat;
+    vec<FrontendAtsc3PlpSettings> plpSettings;
 };
 
 /**
  *  Capabilities for ATSC3 Frontend.
  */
 struct FrontendAtsc3Capabilities {
-    /** Modulation capability */
-    bitfield<FrontendAtsc3Modulation> modulationCap;
     /** Bandwidth capability */
     bitfield<FrontendAtsc3Bandwidth> bandwidthCap;
+    /** Modulation capability */
+    bitfield<FrontendAtsc3Modulation> modulationCap;
+    /** TimeInterleaveMode capability */
+    bitfield<FrontendAtsc3TimeInterleaveMode> timeInterleaveModeCap;
+    /** CodeRate capability */
+    bitfield<FrontendAtsc3CodeRate> codeRateCap;
+    /** FEC capability */
+    bitfield<FrontendAtsc3Fec> fecCap;
+    /** Demodulator Output Format capability */
+    bitfield<FrontendAtsc3DemodOutputFormat> demodOutputFormatCap;
 };
 
 /**
@@ -614,7 +655,7 @@
 };
 
 /**
- *  Modulaltion Type for ISDBS.
+ *  Modulation Type for ISDBS.
  */
 @export
 enum FrontendIsdbsModulation : uint32_t {
@@ -647,7 +688,7 @@
 @export
 enum FrontendIsdbsStreamIdType : uint32_t {
     STREAM_ID,
-    RELATIVE_STREAM_ID,
+    RELATIVE_STREAM_NUMBER,
 };
 
 /**
@@ -845,6 +886,7 @@
     M_EIA_J = 1 << 13,
     I_NICAM = 1 << 14,
     L_NICAM = 1 << 15,
+    L_PRIME = 1 << 16,
 };
 
 /**
@@ -907,17 +949,27 @@
     PLP_IDS,
     /** Locked group Ids for DVBT2 frontend. */
     GROUP_IDS,
-    /** Locked the number of the Plps. */
-   INPUT_STREAM_IDS,
-    /** Locked signal stardard.  */
+    /** Stream Ids. */
+    INPUT_STREAM_IDS,
+    /** Locked signal standard.  */
     STANDARD,
+    /** PLP status in a tuned frequency band for ATSC3 frontend. */
+    ATSC3_PLP_INFO,
+};
+
+/**
+ *  ATSC3.0 PLP information for scan
+ */
+struct FrontendScanAtsc3PlpInfo {
+    uint8_t plpId;
+    bool bLlsFlag;
 };
 
 /**
  *  Scan Message for Frontend.
  */
 safe_union FrontendScanMessage {
-    bool islocked;
+    bool isLocked;
     bool isEnd;
     /** scan progress percent (0..100) */
     uint8_t progressPercent;
@@ -927,11 +979,13 @@
     uint32_t symbolRate;
     vec<uint8_t> plpIds;
     vec<uint8_t> groupIds;
-    vec<uint8_t> inputStreamIds;
+    vec<uint16_t> inputStreamIds;
     safe_union standard {
         FrontendDvbsStandard sStd;
         FrontendDvbtStandard tStd;
     } std;
+    /** A list of PLP status in a tuned frequency band for ATSC3 frontend. */
+    vec<FrontendScanAtsc3PlpInfo> atsc3PlpInfos;
 };
 
 /**
@@ -940,17 +994,17 @@
 @export
 enum FrontendEventType : uint32_t {
     /**
-     * If frontend locked the signal which is specified by tune method, HAL sent
+     * If frontend locked the signal which is specified by tune method, HAL sends
      * Locked event.
      */
     LOCKED,
     /**
      * If frontend can't locked the signal which is specified by tune method,
-     * HAL sent NO_SIGNAL event.
+     * HAL sends NO_SIGNAL event.
      */
     NO_SIGNAL,
     /**
-     * If frontend detect that the locked signal get lost, HAL sent LOST_LOCK
+     * If frontend detect that the locked signal get lost, HAL sends LOST_LOCK
      * event.
      */
     LOST_LOCK,
@@ -977,15 +1031,15 @@
  */
 @export
 enum FrontendStatusType : uint32_t {
-    /** Lock status for RF or Demod. */
-    LOCK,
+    /** Lock status for Demod. */
+    DEMOD_LOCK,
     /** Signal to Noise Ratio. */
     SNR,
     /** Bit Error Ratio. */
     BER,
     /** Packages Error Ratio. */
     PER,
-    /** Bit Error Ratio befor FEC. */
+    /** Bit Error Ratio before FEC. */
     PRE_BER,
     /*
      * Signal Quality (0..100). Good data over total data in percent can be
@@ -993,7 +1047,7 @@
      */
     SIGNAL_QUALITY,
     /** Signal Strength. */
-    SIGGAL_STRENGTH,
+    SIGNAL_STRENGTH,
     /** Symbol Rate. */
     SYMBOL_RATE,
     /** Forward Error Correction Type. */
@@ -1008,21 +1062,62 @@
     PLP_ID,
     /** Status for Emergency Warning Broadcasting System. */
     EWBS,
+    /** Automatic Gain Control. */
+    AGC,
+    /** Low Noise Amplifier. */
+    LNA,
+    /** Lock status for stream. */
+    STREAM_LOCK,
+    /** Error status by layer. */
+    LAYER_ERROR,
+    /** CN value by VBER. */
+    VBER_CN,
+    /** CN value by LBER. */
+    LBER_CN,
+    /** CN value by XER. */
+    XER_CN,
+    /** Moduration Error Ratio. */
+    MER,
+    /** Difference between tuning frequency and actual locked frequency. */
+    FREQ_OFFSET,
+    /* Hierarchy for DVBT. */
+    HIERARCHY,
+    /** Lock status for RF. */
+    RF_LOCK,
+    /** PLP information in a frequency band for ATSC3.0 frontend. */
+    ATSC3_PLP_INFO,
 };
 
 /**
+ * Status for each tuning PLPs
+ */
+struct FrontendStatusAtsc3PlpInfo {
+    /** PLP Id value. */
+    uint8_t plpId;
+    /** Demod Lock/Unlock status of this particular PLP. */
+    bool isLocked;
+    /** Uncorrectable Error Counts (UEC) of this particular PLP since last tune operation. */
+    uint32_t uec;
+};
+
+
+/**
  * Modulation Type for Frontend's status.
  */
 safe_union FrontendModulationStatus {
+    FrontendDvbcModulation dvbc;
     FrontendDvbsModulation dvbs;
-    FrontendAtsc3Modulation atsc3;
+    FrontendIsdbsModulation isdbs;
+    FrontendIsdbs3Modulation isdbs3;
+    FrontendIsdbtModulation isdbt;
 };
 
 /**
  *  The status for Frontend.
  */
 safe_union FrontendStatus {
-    bool isLocked;
+    /** Lock status for Demod in True/False. */
+    bool isDemodLocked;
     /** SNR value measured by 0.001 dB. */
     int32_t snr;
     /** The number of error bit per 1 billion bits. */
@@ -1043,6 +1138,25 @@
     FrontendLnbVoltage lnbVoltage;
     uint8_t plpId;
     bool isEWBS;
+    /** AGC value is normalized from 0 to 255. */
+    uint8_t agc;
+    bool isLnaOn;
+    bool isStreamLock;
+    vec<bool> isLayerError;
+    /** CN value by VBER measured by 0.001 dB */
+    int32_t vberCn;
+    /** CN value by LBER measured by 0.001 dB */
+    int32_t lberCn;
+    /** CN value by XER measured by 0.001 dB */
+    int32_t xerCn;
+    /** MER value measured by 0.001 dB */
+    int32_t mer;
+    /** Frequency difference in Hertz. */
+    int32_t freqOffset;
+    FrontendDvbtHierarchy hierarchy;
+    bool isRfLocked;
+    /** A list of PLP status for tuned PLPs for ATSC3 frontend. */
+    vec<FrontendStatusAtsc3PlpInfo> plpInfo;
 };
 
 /**
@@ -1121,7 +1235,6 @@
     POSITION_B,
 };
 
-
 /* Demux ID is used to associate with a hardware demux resource. */
 typedef uint32_t DemuxId;
 
@@ -1150,7 +1263,7 @@
      */
     AUDIO,
     /**
-     * A filter to filter Vidoe Metadata out from input stream.
+     * A filter to filter Video Metadata out from input stream.
      */
     VIDEO,
     /**
@@ -1475,6 +1588,8 @@
     PES,
     /* Data is Elementary Stream. */
     ES,
+    /* Data is TLV (type-length-value) Stream for JP SHV */
+    SHV_TLV,
 };
 
 /**
@@ -1534,6 +1649,10 @@
     SPACE_FULL         = 1 << 3,
 };
 
+/**
+ *  The Settings for the demux's input.
+ */
+@export
 struct DemuxInputSettings {
     /**
      * Register for interested status events so that the HAL can send these
@@ -1559,3 +1678,30 @@
      */
     uint8_t packetSize;
 };
+
+/**
+ *  Capabilities for Demux.
+ */
+@export
+struct DemuxCapabilities {
+    /* The number of Demux to be supported. */
+    uint32_t numDemux;
+    /* The number of Input to be supported. */
+    uint32_t numInput;
+    /* The number of Output to be supported. */
+    uint32_t numOutput;
+    /* The number of TS Filter to be supported. */
+    uint32_t numTsFilter;
+    /* The number of Section Filter to be supported. */
+    uint32_t numSectionFilter;
+    /* The number of Audio Filter to be supported. */
+    uint32_t numAudioFilter;
+    /* The number of Video Filter to be supported. */
+    uint32_t numVideoFilter;
+    /* The number of PES Filter to be supported. */
+    uint32_t numPesFilter;
+    /* The number of PCR Filter to be supported. */
+    uint32_t numPcrFilter;
+    /* The maximum number of bytes is supported in the mask of Section Filter. */
+    uint32_t numBytesInSectionFilter;
+};
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
index 7256cc4..7936185 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
@@ -39,6 +39,7 @@
 #include <map>
 
 #define WAIT_TIMEOUT 3000000000
+#define WAIT_TIMEOUT_data_ready 3000000000 * 4
 
 using android::Condition;
 using android::IMemory;
@@ -58,8 +59,10 @@
 using android::hardware::Void;
 using android::hardware::tv::tuner::V1_0::DemuxDataFormat;
 using android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
+using android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings;
 using android::hardware::tv::tuner::V1_0::DemuxFilterPesEvent;
 using android::hardware::tv::tuner::V1_0::DemuxFilterSectionEvent;
+using android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings;
 using android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
 using android::hardware::tv::tuner::V1_0::DemuxFilterStatus;
 using android::hardware::tv::tuner::V1_0::DemuxFilterType;
@@ -130,9 +133,6 @@
 
 const uint16_t FMQ_SIZE_4K = 0x1000;
 const uint32_t FMQ_SIZE_1M = 0x100000;
-// Equal to SECTION_WRITE_COUNT on the HAL impl side
-// The HAL impl will repeatedly write to the FMQ the count times
-const uint16_t SECTION_READ_COUNT = 10;
 
 struct FilterConf {
     DemuxFilterType type;
@@ -214,11 +214,15 @@
 class DemuxCallback : public IDemuxCallback {
   public:
     virtual Return<void> onFilterEvent(const DemuxFilterEvent& filterEvent) override {
-        ALOGW("[VTS] FILTER EVENT %d", filterEvent.filterId);
         android::Mutex::Autolock autoLock(mMsgLock);
-        mFilterEventReceived = true;
+        // Temprarily we treat the first coming back filter data on the matching pid a success
+        // once all of the MQ are cleared, means we got all the expected output
         mFilterIdToEvent[filterEvent.filterId] = filterEvent;
-        startFilterEventThread(filterEvent);
+        readFilterEventData(filterEvent.filterId);
+        mPidFilterOutputCount++;
+        // mFilterIdToMQ.erase(filterEvent.filterId);
+
+        // startFilterEventThread(filterEvent);
         mMsgCondition.signal();
         return Void();
     }
@@ -232,13 +236,16 @@
 
     virtual Return<void> onInputStatus(DemuxInputStatus status) override {
         // android::Mutex::Autolock autoLock(mMsgLock);
+        ALOGW("[vts] input status %d", status);
         switch (status) {
             case DemuxInputStatus::SPACE_EMPTY:
             case DemuxInputStatus::SPACE_ALMOST_EMPTY:
+                ALOGW("[vts] keep inputing %d", status);
                 mKeepWritingInputFMQ = true;
                 break;
             case DemuxInputStatus::SPACE_ALMOST_FULL:
             case DemuxInputStatus::SPACE_FULL:
+                ALOGW("[vts] stop inputing %d", status);
                 mKeepWritingInputFMQ = false;
                 break;
         }
@@ -246,78 +253,64 @@
     }
 
     void testOnFilterEvent(uint32_t filterId);
-    void testOnSectionFilterEvent(sp<IDemux>& demux, uint32_t filterId, MQDesc& filterMQDescriptor,
-                                  MQDesc& inputMQDescriptor);
     void testFilterDataOutput();
-    // Legacy
-    bool readAndCompareSectionEventData(uint32_t filterId);
+    void stopInputThread();
 
     void startPlaybackInputThread(InputConf inputConf, MQDesc& inputMQDescriptor);
     void startFilterEventThread(DemuxFilterEvent event);
     static void* __threadLoopInput(void* threadArgs);
     static void* __threadLoopFilter(void* threadArgs);
-    void inputThreadLoop(InputConf inputConf, bool* keepWritingInputFMQ, MQDesc& inputMQDescriptor);
+    void inputThreadLoop(InputConf* inputConf, bool* keepWritingInputFMQ);
     void filterThreadLoop(DemuxFilterEvent& event);
 
     void updateFilterMQ(uint32_t filterId, MQDesc& filterMQDescriptor);
     void updateGoldenOutputMap(uint32_t filterId, string goldenOutputFile);
+    bool readFilterEventData(uint32_t filterId);
 
   private:
     struct InputThreadArgs {
         DemuxCallback* user;
-        InputConf inputConf;
+        InputConf* inputConf;
         bool* keepWritingInputFMQ;
-        MQDesc& inputMQDesc;
     };
     struct FilterThreadArgs {
         DemuxCallback* user;
-        DemuxFilterEvent& event;
+        DemuxFilterEvent event;
     };
     uint16_t mDataLength = 0;
     std::vector<uint8_t> mDataOutputBuffer;
 
     bool mFilterEventReceived;
     std::map<uint32_t, string> mFilterIdToGoldenOutput;
-    std::map<uint32_t, DemuxFilterEvent> mFilterIdToEvent;
 
     std::map<uint32_t, std::unique_ptr<FilterMQ>> mFilterIdToMQ;
     std::unique_ptr<FilterMQ> mInputMQ;
     std::map<uint32_t, EventFlag*> mFilterIdToMQEventFlag;
+    std::map<uint32_t, DemuxFilterEvent> mFilterIdToEvent;
     EventFlag* mInputMQEventFlag;
 
     android::Mutex mMsgLock;
     android::Mutex mFilterOutputLock;
+    android::Mutex mInputThreadLock;
     android::Condition mMsgCondition;
     android::Condition mFilterOutputCondition;
 
-    bool mKeepWritingInputFMQ;
+    bool mKeepWritingInputFMQ = true;
     bool mInputThreadRunning;
     pthread_t mInputThread;
     pthread_t mFilterThread;
+
+    int mPidFilterOutputCount = 0;
 };
 
-// Legacy
-void DemuxCallback::testOnFilterEvent(uint32_t filterId) {
-    android::Mutex::Autolock autoLock(mMsgLock);
-    while (!mFilterEventReceived) {
-        if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) {
-            EXPECT_TRUE(false) << "filter event not received within timeout";
-            return;
-        }
-    }
-    // Reset the filter event recieved flag
-    mFilterEventReceived = false;
-    // Check if filter id match
-    EXPECT_TRUE(filterId == mFilterIdToEvent[filterId].filterId) << "filter id match";
-}
-
 void DemuxCallback::startPlaybackInputThread(InputConf inputConf, MQDesc& inputMQDescriptor) {
+    mInputMQ = std::make_unique<FilterMQ>(inputMQDescriptor, true /* resetPointers */);
+    EXPECT_TRUE(mInputMQ);
     struct InputThreadArgs* threadArgs =
             (struct InputThreadArgs*)malloc(sizeof(struct InputThreadArgs));
     threadArgs->user = this;
-    threadArgs->inputConf = inputConf;
+    threadArgs->inputConf = &inputConf;
     threadArgs->keepWritingInputFMQ = &mKeepWritingInputFMQ;
-    threadArgs->inputMQDesc = inputMQDescriptor;
 
     pthread_create(&mInputThread, NULL, __threadLoopInput, (void*)threadArgs);
     pthread_setname_np(mInputThread, "test_playback_input_loop");
@@ -334,72 +327,22 @@
 }
 
 void DemuxCallback::testFilterDataOutput() {
-    android::Mutex::Autolock autoLock(mFilterOutputLock);
-    while (!mFilterIdToMQ.empty()) {
-        if (-ETIMEDOUT == mFilterOutputCondition.waitRelative(mFilterOutputLock, WAIT_TIMEOUT)) {
-            EXPECT_TRUE(false) << "filter output does not match golden output within timeout";
+    android::Mutex::Autolock autoLock(mMsgLock);
+    while (mPidFilterOutputCount < 1) {
+        if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) {
+            EXPECT_TRUE(false) << "filter output matching pid does not output within timeout";
             return;
         }
     }
+    mPidFilterOutputCount = 0;
+    ALOGW("[vts] pass and stop");
 }
 
-// Legacy
-void DemuxCallback::testOnSectionFilterEvent(sp<IDemux>& demux, uint32_t filterId,
-                                             MQDesc& filterMQDescriptor,
-                                             MQDesc& inputMQDescriptor) {
-    Result status;
-    // Create MQ to read the output into the local buffer
-    mFilterIdToMQ[filterId] =
-            std::make_unique<FilterMQ>(filterMQDescriptor, true /* resetPointers */);
-    EXPECT_TRUE(mFilterIdToMQ[filterId]);
-    // Get the MQ to write the input to the HAL
-    mInputMQ = std::make_unique<FilterMQ>(inputMQDescriptor, true /* resetPointers */);
-    EXPECT_TRUE(mInputMQ);
-    // Create the EventFlag that is used to signal the HAL impl that data have been
-    // read the Filter FMQ
-    EXPECT_TRUE(EventFlag::createEventFlag(mFilterIdToMQ[filterId]->getEventFlagWord(),
-                                           &mFilterIdToMQEventFlag[filterId]) == android::OK);
-    // Create the EventFlag that is used to signal the HAL impl that data have been
-    // written into the Input FMQ
-    EXPECT_TRUE(EventFlag::createEventFlag(mInputMQ->getEventFlagWord(), &mInputMQEventFlag) ==
-                android::OK);
-    // Start filter
-    status = demux->startFilter(filterId);
-    status = demux->startInput();
+void DemuxCallback::stopInputThread() {
+    mInputThreadRunning = false;
+    mKeepWritingInputFMQ = false;
 
-    EXPECT_EQ(status, Result::SUCCESS);
-    // Test start filter and receive callback event
-    for (int i = 0; i < SECTION_READ_COUNT; i++) {
-        // Write input FMQ and notify the Tuner Implementation
-        EXPECT_TRUE(mInputMQ->write(goldenDataOutputBuffer.data(), goldenDataOutputBuffer.size()));
-        mInputMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
-        testOnFilterEvent(filterId);
-        // checksum of mDataOutputBuffer and Input golden input
-        if (readAndCompareSectionEventData(filterId) && i < SECTION_READ_COUNT - 1) {
-            mFilterIdToMQEventFlag[filterId]->wake(
-                    static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED));
-        }
-    }
-}
-
-// Legacy
-bool DemuxCallback::readAndCompareSectionEventData(uint32_t filterId) {
-    bool result = false;
-    DemuxFilterEvent filterEvent = mFilterIdToEvent[filterId];
-    for (int i = 0; i < filterEvent.events.size(); i++) {
-        DemuxFilterSectionEvent event = filterEvent.events[i].section();
-        mDataLength = event.dataLength;
-        EXPECT_TRUE(mDataLength == goldenDataOutputBuffer.size()) << "buffer size does not match";
-
-        mDataOutputBuffer.resize(mDataLength);
-        result = mFilterIdToMQ[filterId]->read(mDataOutputBuffer.data(), mDataLength);
-        EXPECT_TRUE(result) << "can't read from Filter MQ";
-
-        for (int i = 0; i < mDataLength; i++) {
-            EXPECT_TRUE(goldenDataOutputBuffer[i] == mDataOutputBuffer[i]) << "data does not match";
-        }
-    }
-    return result;
+    android::Mutex::Autolock autoLock(mInputThreadLock);
 }
 
 void DemuxCallback::updateFilterMQ(uint32_t filterId, MQDesc& filterMQDescriptor) {
@@ -418,58 +361,60 @@
     DemuxCallback* const self =
             static_cast<DemuxCallback*>(((struct InputThreadArgs*)threadArgs)->user);
     self->inputThreadLoop(((struct InputThreadArgs*)threadArgs)->inputConf,
-                          ((struct InputThreadArgs*)threadArgs)->keepWritingInputFMQ,
-                          ((struct InputThreadArgs*)threadArgs)->inputMQDesc);
+                          ((struct InputThreadArgs*)threadArgs)->keepWritingInputFMQ);
     return 0;
 }
 
-void DemuxCallback::inputThreadLoop(InputConf inputConf, bool* keepWritingInputFMQ,
-                                    MQDesc& inputMQDescriptor) {
+void DemuxCallback::inputThreadLoop(InputConf* inputConf, bool* keepWritingInputFMQ) {
+    android::Mutex::Autolock autoLock(mInputThreadLock);
     mInputThreadRunning = true;
 
-    std::unique_ptr inputMQ =
-            std::make_unique<FilterMQ>(inputMQDescriptor, true /* resetPointers */);
-    EXPECT_TRUE(inputMQ);
-
     // Create the EventFlag that is used to signal the HAL impl that data have been
     // written into the Input FMQ
     EventFlag* inputMQEventFlag;
-    EXPECT_TRUE(EventFlag::createEventFlag(inputMQ->getEventFlagWord(), &inputMQEventFlag) ==
+    EXPECT_TRUE(EventFlag::createEventFlag(mInputMQ->getEventFlagWord(), &inputMQEventFlag) ==
                 android::OK);
 
     // open the stream and get its length
-    std::ifstream inputData(inputConf.inputDataFile /*"ts/test1.ts"*/, std::ifstream::binary);
-    int writeSize = inputConf.setting.packetSize * 6;
+    std::ifstream inputData(inputConf->inputDataFile, std::ifstream::binary);
+    int writeSize = inputConf->setting.packetSize * 6;
     char* buffer = new char[writeSize];
-    if (!inputData) {
-        // log
+    ALOGW("[vts] input thread loop start %s", inputConf->inputDataFile.c_str());
+    if (!inputData.is_open()) {
         mInputThreadRunning = false;
+        ALOGW("[vts] Error %s", strerror(errno));
     }
 
     while (mInputThreadRunning) {
-        // move the stream pointer for packet size * 2k? every read until end
+        // move the stream pointer for packet size * 6 every read until the end
         while (*keepWritingInputFMQ) {
             inputData.read(buffer, writeSize);
             if (!inputData) {
                 int leftSize = inputData.gcount();
+                if (leftSize == 0) {
+                    mInputThreadRunning = false;
+                    break;
+                }
                 inputData.clear();
                 inputData.read(buffer, leftSize);
                 // Write the left over of the input data and quit the thread
                 if (leftSize > 0) {
-                    EXPECT_TRUE(inputMQ->write((unsigned char*)&buffer[0],
-                                               leftSize / inputConf.setting.packetSize));
+                    EXPECT_TRUE(mInputMQ->write((unsigned char*)&buffer[0], leftSize));
                     inputMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
                 }
                 mInputThreadRunning = false;
                 break;
             }
             // Write input FMQ and notify the Tuner Implementation
-            EXPECT_TRUE(inputMQ->write((unsigned char*)&buffer[0], 6));
+            EXPECT_TRUE(mInputMQ->write((unsigned char*)&buffer[0], writeSize));
             inputMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
             inputData.seekg(writeSize, inputData.cur);
+            sleep(1);
         }
     }
 
+    ALOGW("[vts] Input thread end.");
+
     delete[] buffer;
     inputData.close();
 }
@@ -481,9 +426,9 @@
     return 0;
 }
 
-void DemuxCallback::filterThreadLoop(DemuxFilterEvent& /*event*/) {
+void DemuxCallback::filterThreadLoop(DemuxFilterEvent& /* event */) {
     android::Mutex::Autolock autoLock(mFilterOutputLock);
-    // Read from MQ[event.filterId] per event and filter type
+    // Read from mFilterIdToMQ[event.filterId] per event and filter type
 
     // Assemble to filterOutput[filterId]
 
@@ -494,6 +439,30 @@
     // end thread
 }
 
+bool DemuxCallback::readFilterEventData(uint32_t filterId) {
+    bool result = false;
+    DemuxFilterEvent filterEvent = mFilterIdToEvent[filterId];
+    ALOGW("[vts] reading from filter FMQ %d", filterId);
+    // todo separate filter handlers
+    for (int i = 0; i < filterEvent.events.size(); i++) {
+        DemuxFilterPesEvent event = filterEvent.events[i].pes();
+        mDataLength = event.dataLength;
+        // EXPECT_TRUE(mDataLength == goldenDataOutputBuffer.size()) << "buffer size does not
+        // match";
+
+        mDataOutputBuffer.resize(mDataLength);
+        result = mFilterIdToMQ[filterId]->read(mDataOutputBuffer.data(), mDataLength);
+        EXPECT_TRUE(result) << "can't read from Filter MQ";
+
+        /*for (int i = 0; i < mDataLength; i++) {
+            EXPECT_TRUE(goldenDataOutputBuffer[i] == mDataOutputBuffer[i]) << "data does not match";
+        }*/
+    }
+    mFilterIdToMQEventFlag[filterId]->wake(
+            static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED));
+    return result;
+}
+
 // Test environment for Tuner HIDL HAL.
 class TunerHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase {
   public:
@@ -528,6 +497,7 @@
     sp<DemuxCallback> mDemuxCallback;
     MQDesc mFilterMQDescriptor;
     MQDesc mInputMQDescriptor;
+    vector<uint32_t> mUsedFilterIds;
 
     uint32_t mDemuxId;
     uint32_t mFilterId;
@@ -540,7 +510,8 @@
     ::testing::AssertionResult stopTuneFrontend(int32_t frontendId);
     ::testing::AssertionResult closeFrontend(int32_t frontendId);
     ::testing::AssertionResult createDemux();
-    ::testing::AssertionResult createDemuxWithFrontend(int32_t frontendId);
+    ::testing::AssertionResult createDemuxWithFrontend(int32_t frontendId,
+                                                       FrontendSettings settings);
     ::testing::AssertionResult getInputMQDescriptor();
     ::testing::AssertionResult addInputToDemux(DemuxInputSettings setting);
     ::testing::AssertionResult addFilterToDemux(DemuxFilterType type, DemuxFilterSettings setting);
@@ -552,10 +523,8 @@
     ::testing::AssertionResult playbackDataFlowTest(vector<FilterConf> filterConf,
                                                     InputConf inputConf,
                                                     vector<string> goldenOutputFiles);
-
-    // Legacy
-    ::testing::AssertionResult addSectionFilterToDemux();
-    ::testing::AssertionResult readSectionFilterDataOutput();
+    ::testing::AssertionResult broadcastDataFlowTest(vector<FilterConf> filterConf,
+                                                     vector<string> goldenOutputFiles);
 };
 
 ::testing::AssertionResult TunerHidlTest::createFrontend(int32_t frontendId) {
@@ -586,7 +555,7 @@
             .frequency = 0,
             .modulation = FrontendAtscModulation::UNDEFINED,
     };
-    frontendSettings.atsc() = frontendAtscSettings;
+    frontendSettings.atsc(frontendAtscSettings);
     mFrontendCallback->testOnEvent(mFrontend, frontendSettings);
 
     FrontendDvbtSettings frontendDvbtSettings{
@@ -630,7 +599,8 @@
     return ::testing::AssertionResult(status == Result::SUCCESS);
 }
 
-::testing::AssertionResult TunerHidlTest::createDemuxWithFrontend(int32_t frontendId) {
+::testing::AssertionResult TunerHidlTest::createDemuxWithFrontend(int32_t frontendId,
+                                                                  FrontendSettings settings) {
     Result status;
 
     if (!mDemux && createDemux() == ::testing::AssertionFailure()) {
@@ -641,6 +611,8 @@
         return ::testing::AssertionFailure();
     }
 
+    mFrontendCallback->testOnEvent(mFrontend, settings);
+
     status = mDemux->setFrontendDataSource(frontendId);
 
     return ::testing::AssertionResult(status == Result::SUCCESS);
@@ -705,7 +677,7 @@
         mDemuxCallback = new DemuxCallback();
     }
 
-    // Add section filter to the local demux
+    // Add playback input to the local demux
     status = mDemux->addInput(FMQ_SIZE_1M, mDemuxCallback);
 
     if (status != Result::SUCCESS) {
@@ -732,29 +704,6 @@
     return ::testing::AssertionResult(status == Result::SUCCESS);
 }
 
-// Legacy
-::testing::AssertionResult TunerHidlTest::addSectionFilterToDemux() {
-    Result status;
-
-    if (!mDemux && createDemux() == ::testing::AssertionFailure()) {
-        return ::testing::AssertionFailure();
-    }
-
-    // Create demux callback
-    if (!mDemuxCallback) {
-        mDemuxCallback = new DemuxCallback();
-    }
-
-    // Add section filter to the local demux
-    mDemux->addFilter(DemuxFilterType::SECTION, FMQ_SIZE_4K, mDemuxCallback,
-                      [&](Result result, uint32_t filterId) {
-                          mFilterId = filterId;
-                          status = result;
-                      });
-
-    return ::testing::AssertionResult(status == Result::SUCCESS);
-}
-
 ::testing::AssertionResult TunerHidlTest::addFilterToDemux(DemuxFilterType type,
                                                            DemuxFilterSettings setting) {
     Result status;
@@ -800,36 +749,10 @@
     return ::testing::AssertionResult(status == Result::SUCCESS);
 }
 
-// Legacy
-::testing::AssertionResult TunerHidlTest::readSectionFilterDataOutput() {
-    // Filter Configuration Module
-    DemuxInputSettings setting{
-            .statusMask = 0xf,
-            .lowThreshold = 0x1000,
-            .highThreshold = 0x100000,
-            .dataFormat = DemuxDataFormat::TS,
-            .packetSize = 188,
-    };
-    if (addSectionFilterToDemux() == ::testing::AssertionFailure() ||
-        getFilterMQDescriptor(mFilterId) == ::testing::AssertionFailure() ||
-        addInputToDemux(setting) == ::testing::AssertionFailure() ||
-        getInputMQDescriptor() == ::testing::AssertionFailure()) {
-        return ::testing::AssertionFailure();
-    }
-
-    // Data Verify Module
-    // Test start filter and read the output data
-    mDemuxCallback->testOnSectionFilterEvent(mDemux, mFilterId, mFilterMQDescriptor,
-                                             mInputMQDescriptor);
-
-    // Clean Up Module
-    return closeDemux();  //::testing::AssertionSuccess();
-}
-
-::testing::AssertionResult TunerHidlTest::playbackDataFlowTest(vector<FilterConf> filterConf,
-                                                               InputConf inputConf,
-                                                               vector<string> goldenOutputFiles) {
+::testing::AssertionResult TunerHidlTest::playbackDataFlowTest(
+        vector<FilterConf> filterConf, InputConf inputConf, vector<string> /*goldenOutputFiles*/) {
     Result status;
+    int filterIdsSize;
     // Filter Configuration Module
     for (int i = 0; i < filterConf.size(); i++) {
         if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) ==
@@ -838,8 +761,11 @@
             getFilterMQDescriptor(mFilterId) == ::testing::AssertionFailure()) {
             return ::testing::AssertionFailure();
         }
+        filterIdsSize = mUsedFilterIds.size();
+        mUsedFilterIds.resize(filterIdsSize + 1);
+        mUsedFilterIds[filterIdsSize] = mFilterId;
         mDemuxCallback->updateFilterMQ(mFilterId, mFilterMQDescriptor);
-        mDemuxCallback->updateGoldenOutputMap(mFilterId, goldenOutputFiles[i]);
+        // mDemuxCallback->updateGoldenOutputMap(mFilterId, goldenOutputFiles[i]);
         status = mDemux->startFilter(mFilterId);
         if (status != Result::SUCCESS) {
             return ::testing::AssertionFailure();
@@ -860,8 +786,76 @@
 
     // Data Verify Module
     mDemuxCallback->testFilterDataOutput();
+    mDemuxCallback->stopInputThread();
 
     // Clean Up Module
+    for (int i = 0; i <= filterIdsSize; i++) {
+        if (mDemux->stopFilter(mUsedFilterIds[i]) != Result::SUCCESS) {
+            return ::testing::AssertionFailure();
+        }
+    }
+    if (mDemux->stopInput() != Result::SUCCESS) {
+        return ::testing::AssertionFailure();
+    }
+    return closeDemux();
+}
+
+::testing::AssertionResult TunerHidlTest::broadcastDataFlowTest(
+        vector<FilterConf> filterConf, vector<string> /*goldenOutputFiles*/) {
+    Result status;
+    hidl_vec<FrontendId> feIds;
+
+    mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& frontendIds) {
+        status = result;
+        feIds = frontendIds;
+    });
+
+    if (feIds.size() == 0) {
+        ALOGW("[   WARN   ] Frontend isn't available");
+        return ::testing::AssertionFailure();
+    }
+
+    FrontendDvbtSettings dvbt{
+            .frequency = 1000,
+    };
+    FrontendSettings settings;
+    settings.dvbt(dvbt);
+
+    if (createDemuxWithFrontend(feIds[0], settings) != ::testing::AssertionSuccess()) {
+        return ::testing::AssertionFailure();
+    }
+
+    int filterIdsSize;
+    // Filter Configuration Module
+    for (int i = 0; i < filterConf.size(); i++) {
+        if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) ==
+                    ::testing::AssertionFailure() ||
+            // TODO use a map to save the FMQs/EvenFlags and pass to callback
+            getFilterMQDescriptor(mFilterId) == ::testing::AssertionFailure()) {
+            return ::testing::AssertionFailure();
+        }
+        filterIdsSize = mUsedFilterIds.size();
+        mUsedFilterIds.resize(filterIdsSize + 1);
+        mUsedFilterIds[filterIdsSize] = mFilterId;
+        mDemuxCallback->updateFilterMQ(mFilterId, mFilterMQDescriptor);
+        status = mDemux->startFilter(mFilterId);
+        if (status != Result::SUCCESS) {
+            return ::testing::AssertionFailure();
+        }
+    }
+
+    // Data Verify Module
+    mDemuxCallback->testFilterDataOutput();
+
+    // Clean Up Module
+    for (int i = 0; i <= filterIdsSize; i++) {
+        if (mDemux->stopFilter(mUsedFilterIds[i]) != Result::SUCCESS) {
+            return ::testing::AssertionFailure();
+        }
+    }
+    if (mFrontend->stopTune() != Result::SUCCESS) {
+        return ::testing::AssertionFailure();
+    }
     return closeDemux();
 }
 
@@ -948,7 +942,7 @@
     }
 }
 
-TEST_F(TunerHidlTest, CreateDemuxWithFrontend) {
+/*TEST_F(TunerHidlTest, CreateDemuxWithFrontend) {
     Result status;
     hidl_vec<FrontendId> feIds;
 
@@ -963,10 +957,17 @@
         return;
     }
 
+    FrontendDvbtSettings dvbt{
+        .frequency = 1000,
+    };
+    FrontendSettings settings;
+    settings.dvbt(dvbt);
+
     for (size_t i = 0; i < feIds.size(); i++) {
-        ASSERT_TRUE(createDemuxWithFrontend(feIds[i]));
+        ASSERT_TRUE(createDemuxWithFrontend(feIds[i], settings));
+        mFrontend->stopTune();
     }
-}
+}*/
 
 TEST_F(TunerHidlTest, CreateDemux) {
     description("Create Demux");
@@ -991,9 +992,63 @@
 /*
  * DATA FLOW TESTS
  */
-TEST_F(TunerHidlTest, ReadSectionFilterOutput) {
-    description("Read data output from FMQ of a Section Filter");
-    ASSERT_TRUE(readSectionFilterDataOutput());
+TEST_F(TunerHidlTest, PlaybackDataFlowWithPesFilterTest) {
+    description("Feed ts data from playback and configure pes filter to get output");
+
+    // todo modulize the filter conf parser
+    vector<FilterConf> filterConf;
+    filterConf.resize(1);
+
+    DemuxFilterSettings filterSetting;
+    DemuxFilterPesDataSettings pesFilterSetting{
+            .tpid = 18,
+    };
+    filterSetting.pesData(pesFilterSetting);
+    FilterConf pesFilterConf{
+            .type = DemuxFilterType::PES,
+            .setting = filterSetting,
+    };
+    filterConf[0] = pesFilterConf;
+
+    DemuxInputSettings inputSetting{
+            .statusMask = 0xf,
+            .lowThreshold = 0x1000,
+            .highThreshold = 0x07fff,
+            .dataFormat = DemuxDataFormat::TS,
+            .packetSize = 188,
+    };
+
+    InputConf inputConf{
+            .inputDataFile = "/vendor/etc/test1.ts",
+            .setting = inputSetting,
+    };
+
+    vector<string> goldenOutputFiles;
+
+    ASSERT_TRUE(playbackDataFlowTest(filterConf, inputConf, goldenOutputFiles));
+}
+
+TEST_F(TunerHidlTest, BroadcastDataFlowWithPesFilterTest) {
+    description("Feed ts data from frontend and test with PES filter");
+
+    // todo modulize the filter conf parser
+    vector<FilterConf> filterConf;
+    filterConf.resize(1);
+
+    DemuxFilterSettings filterSetting;
+    DemuxFilterPesDataSettings pesFilterSetting{
+            .tpid = 18,
+    };
+    filterSetting.pesData(pesFilterSetting);
+    FilterConf pesFilterConf{
+            .type = DemuxFilterType::PES,
+            .setting = filterSetting,
+    };
+    filterConf[0] = pesFilterConf;
+
+    vector<string> goldenOutputFiles;
+
+    ASSERT_TRUE(broadcastDataFlowTest(filterConf, goldenOutputFiles));
 }
 
 }  // namespace
diff --git a/tv/tuner/README.md b/tv/tuner/README.md
new file mode 100644
index 0000000..a833c87
--- /dev/null
+++ b/tv/tuner/README.md
@@ -0,0 +1,12 @@
+# Tuner HALs
+
+## Overview
+
+TV specific tuners.
+
+Sett 1.0/ITuner.hal for an overview.
+
+*** note
+**Warning:** The HALs are not (yet) frozen, as the HAL definition is
+expected to evolve between Android releases.
+***
diff --git a/vibrator/1.4/Android.bp b/vibrator/1.4/Android.bp
new file mode 100644
index 0000000..cf31fcd
--- /dev/null
+++ b/vibrator/1.4/Android.bp
@@ -0,0 +1,22 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+    name: "android.hardware.vibrator@1.4",
+    root: "android.hardware",
+    vndk: {
+        enabled: true,
+    },
+    srcs: [
+        "types.hal",
+        "IVibrator.hal",
+        "IVibratorCallback.hal",
+    ],
+    interfaces: [
+        "android.hardware.vibrator@1.0",
+        "android.hardware.vibrator@1.1",
+        "android.hardware.vibrator@1.2",
+        "android.hardware.vibrator@1.3",
+        "android.hidl.base@1.0",
+    ],
+    gen_java: true,
+}
diff --git a/vibrator/1.4/IVibrator.hal b/vibrator/1.4/IVibrator.hal
new file mode 100644
index 0000000..913abe3
--- /dev/null
+++ b/vibrator/1.4/IVibrator.hal
@@ -0,0 +1,57 @@
+/*
+ * 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.vibrator@1.4;
+
+import @1.0::EffectStrength;
+import @1.3::Effect;
+import @1.0::Status;
+import @1.3::IVibrator;
+import IVibratorCallback;
+
+interface IVibrator extends @1.3::IVibrator {
+    /**
+     * Determine capabilities of the vibrator HAL.
+     */
+    getCapabilities() generates (bitfield<Capabilities> capabilities);
+
+    /**
+     * Turn on vibrator
+     *
+     * This function must only be called after the previous timeout has expired or
+     * was canceled (through off()).
+     * @param timeoutMs number of milliseconds to vibrate.
+     * @param callback A callback used to inform Frameworks of state change, if supported.
+     * @return vibratorOnRet whether vibrator command was successful or not.
+     */
+    on_1_4(uint32_t timeoutMs, IVibratorCallback callback) generates (Status vibratorOnRet);
+
+    /**
+     * Fire off a predefined haptic event.
+     *
+     * @param effect The type of haptic event to trigger.
+     * @param strength The intensity of haptic event to trigger.
+     * @param callback A callback used to inform Frameworks of state change, if supported.
+     * @return status Whether the effect was successfully performed or not. Must
+     *     return Status::UNSUPPORTED_OPERATION if the effect is not supported.
+     * @return lengthMs The length of time the event is expected to take in
+     *     milliseconds. This doesn't need to be perfectly accurate, but should be a reasonable
+     *     approximation. Should be a positive, non-zero value if the returned status is Status::OK,
+     *     and set to 0 otherwise.
+     */
+    perform_1_4(Effect effect, EffectStrength strength, IVibratorCallback callback)
+        generates (Status status, uint32_t lengthMs);
+};
diff --git a/vibrator/1.4/IVibratorCallback.hal b/vibrator/1.4/IVibratorCallback.hal
new file mode 100644
index 0000000..76281bc
--- /dev/null
+++ b/vibrator/1.4/IVibratorCallback.hal
@@ -0,0 +1,21 @@
+/*
+ * 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.vibrator@1.4;
+
+interface IVibratorCallback {
+    oneway onComplete();
+};
diff --git a/vibrator/1.4/types.hal b/vibrator/1.4/types.hal
new file mode 100644
index 0000000..acc49b1
--- /dev/null
+++ b/vibrator/1.4/types.hal
@@ -0,0 +1,22 @@
+/*
+ * 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.vibrator@1.4;
+
+enum Capabilities : uint32_t {
+    ON_COMPLETION_CALLBACK = 1 << 0,
+    PERFORM_COMPLETION_CALLBACK = 1 << 1,
+};
diff --git a/vibrator/1.4/vts/functional/Android.bp b/vibrator/1.4/vts/functional/Android.bp
new file mode 100644
index 0000000..4cdf3b6
--- /dev/null
+++ b/vibrator/1.4/vts/functional/Android.bp
@@ -0,0 +1,30 @@
+//
+// 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: "VtsHalVibratorV1_4TargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: ["VtsHalVibratorV1_4TargetTest.cpp"],
+    static_libs: [
+        "android.hardware.vibrator@1.0",
+        "android.hardware.vibrator@1.1",
+        "android.hardware.vibrator@1.2",
+        "android.hardware.vibrator@1.3",
+        "android.hardware.vibrator@1.4",
+    ],
+    test_suites: ["general-tests"],
+}
+
diff --git a/vibrator/1.4/vts/functional/VtsHalVibratorV1_4TargetTest.cpp b/vibrator/1.4/vts/functional/VtsHalVibratorV1_4TargetTest.cpp
new file mode 100644
index 0000000..b51cc96
--- /dev/null
+++ b/vibrator/1.4/vts/functional/VtsHalVibratorV1_4TargetTest.cpp
@@ -0,0 +1,170 @@
+/*
+ * 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 "vibrator_hidl_hal_test"
+
+#include <android-base/logging.h>
+#include <android/hardware/vibrator/1.0/types.h>
+#include <android/hardware/vibrator/1.4/IVibrator.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+#include <unistd.h>
+
+#include <future>
+
+using ::android::sp;
+using ::android::hardware::hidl_bitfield;
+using ::android::hardware::hidl_enum_range;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::vibrator::V1_0::EffectStrength;
+using ::android::hardware::vibrator::V1_0::Status;
+using ::android::hardware::vibrator::V1_3::Effect;
+using ::android::hardware::vibrator::V1_4::Capabilities;
+using ::android::hardware::vibrator::V1_4::IVibrator;
+using ::android::hardware::vibrator::V1_4::IVibratorCallback;
+
+#define EXPECT_OK(ret) ASSERT_TRUE((ret).isOk())
+
+class CompletionCallback : public IVibratorCallback {
+  public:
+    CompletionCallback(std::function<void()> callback) : mCallback(callback) {}
+    Return<void> onComplete() override {
+        mCallback();
+        return Void();
+    }
+
+  private:
+    std::function<void()> mCallback;
+};
+
+class VibratorHidlTest_1_4 : public testing::TestWithParam<std::string> {
+  public:
+    virtual void SetUp() override {
+        vibrator = IVibrator::getService(GetParam());
+        ASSERT_NE(vibrator, nullptr);
+        capabilities = vibrator->getCapabilities();
+    }
+
+    virtual void TearDown() override {}
+
+    sp<IVibrator> vibrator;
+    hidl_bitfield<Capabilities> capabilities;
+};
+
+TEST_P(VibratorHidlTest_1_4, OnWithCallback) {
+    if (capabilities & Capabilities::ON_COMPLETION_CALLBACK) {
+        std::promise<void> completionPromise;
+        std::future<void> completionFuture{completionPromise.get_future()};
+        sp<CompletionCallback> callback =
+                new CompletionCallback([&completionPromise] { completionPromise.set_value(); });
+        uint32_t duration = 250;
+        std::chrono::milliseconds timeout{duration * 2};
+        EXPECT_EQ(Status::OK, vibrator->on_1_4(duration, callback));
+        EXPECT_EQ(completionFuture.wait_for(timeout), std::future_status::ready);
+        vibrator->off();
+    }
+}
+
+static void validatePerformEffectUnsupportedOperation(Status status, uint32_t lengthMs) {
+    ASSERT_EQ(Status::UNSUPPORTED_OPERATION, status);
+    ASSERT_EQ(static_cast<uint32_t>(0), lengthMs)
+            << "Effects that return UNSUPPORTED_OPERATION must have a duration of zero";
+}
+
+static void validatePerformEffect(Status status, uint32_t lengthMs) {
+    ASSERT_TRUE(status == Status::OK || status == Status::UNSUPPORTED_OPERATION);
+    if (status == Status::OK) {
+        ASSERT_LT(static_cast<uint32_t>(0), lengthMs)
+                << "Effects that return OK must return a positive duration";
+    } else {
+        validatePerformEffectUnsupportedOperation(status, lengthMs);
+    }
+}
+
+/*
+ * Test to make sure effects within the valid range return are either supported and return OK with
+ * a valid duration, or are unsupported and return UNSUPPORTED_OPERATION with a duration of 0.
+ */
+TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4) {
+    Status performStatus;
+    uint32_t performLength;
+    auto validateWrapper = [&](Status status, uint32_t lengthMs) {
+        performStatus = status;
+        performLength = lengthMs;
+        validatePerformEffect(status, lengthMs);
+    };
+    for (const auto& effect : hidl_enum_range<Effect>()) {
+        for (const auto& strength : hidl_enum_range<EffectStrength>()) {
+            std::promise<void> completionPromise;
+            std::future<void> completionFuture{completionPromise.get_future()};
+            sp<CompletionCallback> callback =
+                    new CompletionCallback([&completionPromise] { completionPromise.set_value(); });
+            EXPECT_OK(vibrator->perform_1_4(effect, strength, callback, validateWrapper));
+            if (performStatus == Status::OK &&
+                (capabilities & Capabilities::PERFORM_COMPLETION_CALLBACK)) {
+                std::chrono::milliseconds timeout{performLength * 2};
+                EXPECT_EQ(completionFuture.wait_for(timeout), std::future_status::ready);
+            }
+        }
+    }
+}
+
+/*
+ * Test to make sure effect values above the valid range are rejected.
+ */
+TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4_BadEffects_AboveValidRange) {
+    Effect effect = *std::prev(hidl_enum_range<Effect>().end());
+    Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) + 1);
+    EXPECT_OK(vibrator->perform_1_4(badEffect, EffectStrength::LIGHT, nullptr,
+                                    validatePerformEffectUnsupportedOperation));
+}
+
+/*
+ * Test to make sure effect values below the valid range are rejected.
+ */
+TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4_BadEffects_BelowValidRange) {
+    Effect effect = *hidl_enum_range<Effect>().begin();
+    Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) - 1);
+    EXPECT_OK(vibrator->perform_1_4(badEffect, EffectStrength::LIGHT, nullptr,
+                                    validatePerformEffectUnsupportedOperation));
+}
+
+/*
+ * Test to make sure strength values above the valid range are rejected.
+ */
+TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4_BadStrength_AboveValidRange) {
+    EffectStrength strength = *std::prev(hidl_enum_range<EffectStrength>().end());
+    EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) + 1);
+    EXPECT_OK(vibrator->perform_1_4(Effect::THUD, badStrength, nullptr,
+                                    validatePerformEffectUnsupportedOperation));
+}
+
+/*
+ * Test to make sure strength values below the valid range are rejected.
+ */
+TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4_BadStrength_BelowValidRange) {
+    EffectStrength strength = *hidl_enum_range<EffectStrength>().begin();
+    EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) - 1);
+    EXPECT_OK(vibrator->perform_1_4(Effect::THUD, badStrength, nullptr,
+                                    validatePerformEffectUnsupportedOperation));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        PerInstance, VibratorHidlTest_1_4,
+        testing::ValuesIn(android::hardware::getAllHalInstanceNames(IVibrator::descriptor)),
+        android::hardware::PrintInstanceNameToString);
diff --git a/wifi/1.0/vts/functional/Android.bp b/wifi/1.0/vts/functional/Android.bp
index 397ad17..6fa6e7e 100644
--- a/wifi/1.0/vts/functional/Android.bp
+++ b/wifi/1.0/vts/functional/Android.bp
@@ -28,7 +28,9 @@
     shared_libs: [
         "libnativehelper",
     ],
-    static_libs: ["android.hardware.wifi@1.0"],
+    static_libs: [
+        "android.hardware.wifi@1.0",
+    ],
 }
 
 cc_test {
@@ -36,7 +38,6 @@
     defaults: ["VtsHalTargetTestDefaults"],
     srcs: [
         "VtsHalWifiV1_0TargetTest.cpp",
-        "wifi_ap_iface_hidl_test.cpp",
         "wifi_chip_hidl_test.cpp",
         "wifi_p2p_iface_hidl_test.cpp",
         "wifi_rtt_controller_hidl_test.cpp",
@@ -52,11 +53,14 @@
     test_suites: ["general-tests"],
 }
 
+// These tests are split out so that they can be conditioned on presence of the
+// "android.hardware.wifi.aware" feature.
 cc_test {
     name: "VtsHalWifiNanV1_0TargetTest",
     defaults: ["VtsHalTargetTestDefaults"],
     srcs: [
         "VtsHalWifiV1_0TargetTest.cpp",
+        "wifi_chip_hidl_nan_test.cpp",
         "wifi_nan_iface_hidl_test.cpp",
     ],
     static_libs: [
@@ -65,3 +69,20 @@
     ],
     test_suites: ["general-tests"],
 }
+
+// These tests are split out so that they can be conditioned on presence of
+// the hostapd HAL, which indicates SoftAP support.
+cc_test {
+    name: "VtsHalWifiApV1_0TargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: [
+        "VtsHalWifiV1_0TargetTest.cpp",
+        "wifi_ap_iface_hidl_test.cpp",
+        "wifi_chip_hidl_ap_test.cpp",
+    ],
+    static_libs: [
+        "VtsHalWifiV1_0TargetTestUtil",
+        "android.hardware.wifi@1.0",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/wifi/1.0/vts/functional/VtsHalWifiV1_0TargetTest.cpp b/wifi/1.0/vts/functional/VtsHalWifiV1_0TargetTest.cpp
index e7b8593..9d25014 100644
--- a/wifi/1.0/vts/functional/VtsHalWifiV1_0TargetTest.cpp
+++ b/wifi/1.0/vts/functional/VtsHalWifiV1_0TargetTest.cpp
@@ -41,10 +41,7 @@
     ::testing::AddGlobalTestEnvironment(gEnv);
     ::testing::InitGoogleTest(&argc, argv);
     gEnv->init(&argc, argv);
-    int status = gEnv->initFromOptions(argc, argv);
-    if (status == 0) {
-        status = RUN_ALL_TESTS();
-        LOG(INFO) << "Test result = " << status;
-    }
+    int status = RUN_ALL_TESTS();
+    LOG(INFO) << "Test result = " << status;
     return status;
 }
diff --git a/wifi/1.0/vts/functional/wifi_ap_iface_hidl_test.cpp b/wifi/1.0/vts/functional/wifi_ap_iface_hidl_test.cpp
index e5762f2..c55221d 100644
--- a/wifi/1.0/vts/functional/wifi_ap_iface_hidl_test.cpp
+++ b/wifi/1.0/vts/functional/wifi_ap_iface_hidl_test.cpp
@@ -29,21 +29,17 @@
 using ::android::hardware::wifi::V1_0::WifiStatusCode;
 using ::android::sp;
 
-extern WifiHidlEnvironment* gEnv;
-
 /**
  * Fixture to use for all AP Iface HIDL interface tests.
  */
 class WifiApIfaceHidlTest : public ::testing::VtsHalHidlTargetTestBase {
    public:
     virtual void SetUp() override {
-        if (!gEnv->isSoftApOn) return;
         wifi_ap_iface_ = getWifiApIface();
         ASSERT_NE(nullptr, wifi_ap_iface_.get());
     }
 
     virtual void TearDown() override {
-        if (!gEnv->isSoftApOn) return;
         stopWifi();
     }
 
@@ -57,7 +53,6 @@
  * successfully created.
  */
 TEST(WifiApIfaceHidlTestNoFixture, Create) {
-    if (!gEnv->isSoftApOn) return;
     EXPECT_NE(nullptr, getWifiApIface().get());
     stopWifi();
 }
@@ -67,7 +62,6 @@
  * Ensures that the correct interface type is returned for AP interface.
  */
 TEST_F(WifiApIfaceHidlTest, GetType) {
-    if (!gEnv->isSoftApOn) return;
     const auto& status_and_type = HIDL_INVOKE(wifi_ap_iface_, getType);
     EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_type.first.code);
     EXPECT_EQ(IfaceType::AP, status_and_type.second);
@@ -79,7 +73,6 @@
  * status code.
  */
 TEST_F(WifiApIfaceHidlTest, SetCountryCode) {
-    if (!gEnv->isSoftApOn) return;
     const android::hardware::hidl_array<int8_t, 2> kCountryCode{
         std::array<int8_t, 2>{{0x55, 0x53}}};
     EXPECT_EQ(WifiStatusCode::SUCCESS,
@@ -91,7 +84,6 @@
  * Ensures that we can retrieve valid frequencies for 2.4 GHz band.
  */
 TEST_F(WifiApIfaceHidlTest, GetValidFrequenciesForBand) {
-    if (!gEnv->isSoftApOn) return;
     const auto& status_and_freqs = HIDL_INVOKE(
         wifi_ap_iface_, getValidFrequenciesForBand, WifiBand::BAND_24GHZ);
     EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_freqs.first.code);
diff --git a/wifi/1.0/vts/functional/wifi_chip_hidl_ap_test.cpp b/wifi/1.0/vts/functional/wifi_chip_hidl_ap_test.cpp
new file mode 100644
index 0000000..232ffdd
--- /dev/null
+++ b/wifi/1.0/vts/functional/wifi_chip_hidl_ap_test.cpp
@@ -0,0 +1,168 @@
+/*
+ * 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 <android-base/logging.h>
+
+#include <android/hardware/wifi/1.0/IWifiChip.h>
+
+#include <VtsHalHidlTargetTestBase.h>
+
+#include "wifi_hidl_call_util.h"
+#include "wifi_hidl_test_utils.h"
+
+using ::android::sp;
+using ::android::hardware::wifi::V1_0::ChipModeId;
+using ::android::hardware::wifi::V1_0::IfaceType;
+using ::android::hardware::wifi::V1_0::IWifiApIface;
+using ::android::hardware::wifi::V1_0::IWifiChip;
+using ::android::hardware::wifi::V1_0::IWifiIface;
+using ::android::hardware::wifi::V1_0::WifiStatus;
+using ::android::hardware::wifi::V1_0::WifiStatusCode;
+
+/**
+ * Fixture for IWifiChip tests that are conditioned on SoftAP support.
+ */
+class WifiChipHidlApTest : public ::testing::VtsHalHidlTargetTestBase {
+   public:
+    virtual void SetUp() override {
+        wifi_chip_ = getWifiChip();
+        ASSERT_NE(nullptr, wifi_chip_.get());
+    }
+
+    virtual void TearDown() override { stopWifi(); }
+
+   protected:
+    // Helper function to configure the Chip in one of the supported modes.
+    // Most of the non-mode-configuration-related methods require chip
+    // to be first configured.
+    ChipModeId configureChipForIfaceType(IfaceType type, bool expectSuccess) {
+        ChipModeId mode_id;
+        EXPECT_EQ(expectSuccess,
+                  configureChipToSupportIfaceType(wifi_chip_, type, &mode_id));
+        return mode_id;
+    }
+
+    std::string getIfaceName(const sp<IWifiIface>& iface) {
+        const auto& status_and_name = HIDL_INVOKE(iface, getName);
+        EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_name.first.code);
+        return status_and_name.second;
+    }
+
+    WifiStatusCode createApIface(sp<IWifiApIface>* ap_iface) {
+        const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createApIface);
+        *ap_iface = status_and_iface.second;
+        return status_and_iface.first.code;
+    }
+
+    WifiStatusCode removeApIface(const std::string& name) {
+        return HIDL_INVOKE(wifi_chip_, removeApIface, name).code;
+    }
+
+    sp<IWifiChip> wifi_chip_;
+};
+
+/*
+ * CreateApIface
+ * Configures the chip in AP mode and ensures that at least 1 iface creation
+ * succeeds.
+ */
+TEST_F(WifiChipHidlApTest, CreateApIface) {
+    configureChipForIfaceType(IfaceType::AP, true);
+
+    sp<IWifiApIface> iface;
+    EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&iface));
+    EXPECT_NE(nullptr, iface.get());
+}
+
+/*
+ * GetApIfaceNames
+ * Configures the chip in AP mode and ensures that the iface list is empty
+ * before creating the iface. Then, create the iface and ensure that
+ * iface name is returned via the list.
+ */
+TEST_F(WifiChipHidlApTest, GetApIfaceNames) {
+    configureChipForIfaceType(IfaceType::AP, true);
+
+    const auto& status_and_iface_names1 =
+        HIDL_INVOKE(wifi_chip_, getApIfaceNames);
+    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names1.first.code);
+    EXPECT_EQ(0u, status_and_iface_names1.second.size());
+
+    sp<IWifiApIface> iface;
+    EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&iface));
+    EXPECT_NE(nullptr, iface.get());
+
+    std::string iface_name = getIfaceName(iface);
+    const auto& status_and_iface_names2 =
+        HIDL_INVOKE(wifi_chip_, getApIfaceNames);
+    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names2.first.code);
+    EXPECT_EQ(1u, status_and_iface_names2.second.size());
+    EXPECT_EQ(iface_name, status_and_iface_names2.second[0]);
+
+    EXPECT_EQ(WifiStatusCode::SUCCESS, removeApIface(iface_name));
+    const auto& status_and_iface_names3 =
+        HIDL_INVOKE(wifi_chip_, getApIfaceNames);
+    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names3.first.code);
+    EXPECT_EQ(0u, status_and_iface_names3.second.size());
+}
+
+/*
+ * GetApIface
+ * Configures the chip in AP mode and create an iface. Then, retrieve
+ * the iface object using the correct name and ensure any other name
+ * doesn't retrieve an iface object.
+ */
+TEST_F(WifiChipHidlApTest, GetApIface) {
+    configureChipForIfaceType(IfaceType::AP, true);
+
+    sp<IWifiApIface> ap_iface;
+    EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&ap_iface));
+    EXPECT_NE(nullptr, ap_iface.get());
+
+    std::string iface_name = getIfaceName(ap_iface);
+    const auto& status_and_iface1 =
+        HIDL_INVOKE(wifi_chip_, getApIface, iface_name);
+    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface1.first.code);
+    EXPECT_NE(nullptr, status_and_iface1.second.get());
+
+    std::string invalid_name = iface_name + "0";
+    const auto& status_and_iface2 =
+        HIDL_INVOKE(wifi_chip_, getApIface, invalid_name);
+    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, status_and_iface2.first.code);
+    EXPECT_EQ(nullptr, status_and_iface2.second.get());
+}
+
+/*
+ * RemoveApIface
+ * Configures the chip in AP mode and create an iface. Then, remove
+ * the iface object using the correct name and ensure any other name
+ * doesn't remove the iface.
+ */
+TEST_F(WifiChipHidlApTest, RemoveApIface) {
+    configureChipForIfaceType(IfaceType::AP, true);
+
+    sp<IWifiApIface> ap_iface;
+    EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&ap_iface));
+    EXPECT_NE(nullptr, ap_iface.get());
+
+    std::string iface_name = getIfaceName(ap_iface);
+    std::string invalid_name = iface_name + "0";
+    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeApIface(invalid_name));
+    EXPECT_EQ(WifiStatusCode::SUCCESS, removeApIface(iface_name));
+
+    // No such iface exists now. So, this should return failure.
+    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeApIface(iface_name));
+}
diff --git a/wifi/1.0/vts/functional/wifi_chip_hidl_nan_test.cpp b/wifi/1.0/vts/functional/wifi_chip_hidl_nan_test.cpp
new file mode 100644
index 0000000..595f23a
--- /dev/null
+++ b/wifi/1.0/vts/functional/wifi_chip_hidl_nan_test.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 <android-base/logging.h>
+
+#include <android/hardware/wifi/1.0/IWifiChip.h>
+
+#include <VtsHalHidlTargetTestBase.h>
+
+#include "wifi_hidl_call_util.h"
+#include "wifi_hidl_test_utils.h"
+
+using ::android::sp;
+using ::android::hardware::wifi::V1_0::ChipModeId;
+using ::android::hardware::wifi::V1_0::IfaceType;
+using ::android::hardware::wifi::V1_0::IWifiChip;
+using ::android::hardware::wifi::V1_0::IWifiIface;
+using ::android::hardware::wifi::V1_0::IWifiNanIface;
+using ::android::hardware::wifi::V1_0::WifiStatus;
+using ::android::hardware::wifi::V1_0::WifiStatusCode;
+
+/**
+ * Fixture for IWifiChip tests that are conditioned on NAN support.
+ */
+class WifiChipHidlNanTest : public ::testing::VtsHalHidlTargetTestBase {
+   public:
+    virtual void SetUp() override {
+        wifi_chip_ = getWifiChip();
+        ASSERT_NE(nullptr, wifi_chip_.get());
+    }
+
+    virtual void TearDown() override { stopWifi(); }
+
+   protected:
+    // Helper function to configure the Chip in one of the supported modes.
+    // Most of the non-mode-configuration-related methods require chip
+    // to be first configured.
+    ChipModeId configureChipForIfaceType(IfaceType type, bool expectSuccess) {
+        ChipModeId mode_id;
+        EXPECT_EQ(expectSuccess,
+                  configureChipToSupportIfaceType(wifi_chip_, type, &mode_id));
+        return mode_id;
+    }
+
+    std::string getIfaceName(const sp<IWifiIface>& iface) {
+        const auto& status_and_name = HIDL_INVOKE(iface, getName);
+        EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_name.first.code);
+        return status_and_name.second;
+    }
+
+    WifiStatusCode createNanIface(sp<IWifiNanIface>* nan_iface) {
+        const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createNanIface);
+        *nan_iface = status_and_iface.second;
+        return status_and_iface.first.code;
+    }
+
+    WifiStatusCode removeNanIface(const std::string& name) {
+        return HIDL_INVOKE(wifi_chip_, removeNanIface, name).code;
+    }
+
+    sp<IWifiChip> wifi_chip_;
+};
+
+/*
+ * CreateNanIface
+ * Configures the chip in NAN mode and ensures that at least 1 iface creation
+ * succeeds.
+ */
+TEST_F(WifiChipHidlNanTest, CreateNanIface) {
+    configureChipForIfaceType(IfaceType::NAN, true);
+
+    sp<IWifiNanIface> iface;
+    ASSERT_EQ(WifiStatusCode::SUCCESS, createNanIface(&iface));
+    EXPECT_NE(nullptr, iface.get());
+}
+
+/*
+ * GetNanIfaceNames
+ * Configures the chip in NAN mode and ensures that the iface list is empty
+ * before creating the iface. Then, create the iface and ensure that
+ * iface name is returned via the list.
+ */
+TEST_F(WifiChipHidlNanTest, GetNanIfaceNames) {
+    configureChipForIfaceType(IfaceType::NAN, true);
+
+    const auto& status_and_iface_names1 =
+        HIDL_INVOKE(wifi_chip_, getNanIfaceNames);
+    ASSERT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names1.first.code);
+    EXPECT_EQ(0u, status_and_iface_names1.second.size());
+
+    sp<IWifiNanIface> iface;
+    EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&iface));
+    EXPECT_NE(nullptr, iface.get());
+
+    std::string iface_name = getIfaceName(iface);
+    const auto& status_and_iface_names2 =
+        HIDL_INVOKE(wifi_chip_, getNanIfaceNames);
+    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names2.first.code);
+    EXPECT_EQ(1u, status_and_iface_names2.second.size());
+    EXPECT_EQ(iface_name, status_and_iface_names2.second[0]);
+
+    EXPECT_EQ(WifiStatusCode::SUCCESS, removeNanIface(iface_name));
+    const auto& status_and_iface_names3 =
+        HIDL_INVOKE(wifi_chip_, getNanIfaceNames);
+    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names3.first.code);
+    EXPECT_EQ(0u, status_and_iface_names3.second.size());
+}
+
+/*
+ * GetNanIface
+ * Configures the chip in NAN mode and create an iface. Then, retrieve
+ * the iface object using the correct name and ensure any other name
+ * doesn't retrieve an iface object.
+ */
+TEST_F(WifiChipHidlNanTest, GetNanIface) {
+    configureChipForIfaceType(IfaceType::NAN, true);
+
+    sp<IWifiNanIface> nan_iface;
+    EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&nan_iface));
+    EXPECT_NE(nullptr, nan_iface.get());
+
+    std::string iface_name = getIfaceName(nan_iface);
+    const auto& status_and_iface1 =
+        HIDL_INVOKE(wifi_chip_, getNanIface, iface_name);
+    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface1.first.code);
+    EXPECT_NE(nullptr, status_and_iface1.second.get());
+
+    std::string invalid_name = iface_name + "0";
+    const auto& status_and_iface2 =
+        HIDL_INVOKE(wifi_chip_, getNanIface, invalid_name);
+    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, status_and_iface2.first.code);
+    EXPECT_EQ(nullptr, status_and_iface2.second.get());
+}
+
+/*
+ * RemoveNanIface
+ * Configures the chip in NAN mode and create an iface. Then, remove
+ * the iface object using the correct name and ensure any other name
+ * doesn't remove the iface.
+ */
+TEST_F(WifiChipHidlNanTest, RemoveNanIface) {
+    configureChipForIfaceType(IfaceType::NAN, true);
+
+    sp<IWifiNanIface> nan_iface;
+    EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&nan_iface));
+    EXPECT_NE(nullptr, nan_iface.get());
+
+    std::string iface_name = getIfaceName(nan_iface);
+    std::string invalid_name = iface_name + "0";
+    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeNanIface(invalid_name));
+
+    EXPECT_EQ(WifiStatusCode::SUCCESS, removeNanIface(iface_name));
+
+    // No such iface exists now. So, this should return failure.
+    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeNanIface(iface_name));
+}
diff --git a/wifi/1.0/vts/functional/wifi_chip_hidl_test.cpp b/wifi/1.0/vts/functional/wifi_chip_hidl_test.cpp
index 1b7e821..2601b78 100644
--- a/wifi/1.0/vts/functional/wifi_chip_hidl_test.cpp
+++ b/wifi/1.0/vts/functional/wifi_chip_hidl_test.cpp
@@ -36,9 +36,7 @@
 using ::android::hardware::wifi::V1_0::WifiStatus;
 using ::android::hardware::wifi::V1_0::WifiStatusCode;
 using ::android::hardware::wifi::V1_0::IWifiChip;
-using ::android::hardware::wifi::V1_0::IWifiApIface;
 using ::android::hardware::wifi::V1_0::IWifiIface;
-using ::android::hardware::wifi::V1_0::IWifiNanIface;
 using ::android::hardware::wifi::V1_0::IWifiP2pIface;
 using ::android::hardware::wifi::V1_0::IWifiRttController;
 using ::android::hardware::wifi::V1_0::IWifiStaIface;
@@ -64,7 +62,10 @@
 }  // namespace
 
 /**
- * Fixture to use for all Wifi chip HIDL interface tests.
+ * Fixture for IWifiChip tests.
+ *
+ * Tests that require SoftAP or NAN support should go into WifiChipHidlApTest or
+ * WifiChipHidlNanTest respectively.
  */
 class WifiChipHidlTest : public ::testing::VtsHalHidlTargetTestBase {
    public:
@@ -114,26 +115,6 @@
         return status_and_name.second;
     }
 
-    WifiStatusCode createApIface(sp<IWifiApIface>* ap_iface) {
-        const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createApIface);
-        *ap_iface = status_and_iface.second;
-        return status_and_iface.first.code;
-    }
-
-    WifiStatusCode removeApIface(const std::string& name) {
-        return HIDL_INVOKE(wifi_chip_, removeApIface, name).code;
-    }
-
-    WifiStatusCode createNanIface(sp<IWifiNanIface>* nan_iface) {
-        const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createNanIface);
-        *nan_iface = status_and_iface.second;
-        return status_and_iface.first.code;
-    }
-
-    WifiStatusCode removeNanIface(const std::string& name) {
-        return HIDL_INVOKE(wifi_chip_, removeNanIface, name).code;
-    }
-
     WifiStatusCode createP2pIface(sp<IWifiP2pIface>* p2p_iface) {
         const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createP2pIface);
         *p2p_iface = status_and_iface.second;
@@ -360,201 +341,6 @@
 }
 
 /*
- * CreateApIface
- * Configures the chip in AP mode and ensures that at least 1 iface creation
- * succeeds.
- */
-TEST_F(WifiChipHidlTest, CreateApIface) {
-    if (!gEnv->isSoftApOn) return;
-    configureChipForIfaceType(IfaceType::AP, true);
-
-    sp<IWifiApIface> iface;
-    EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&iface));
-    EXPECT_NE(nullptr, iface.get());
-}
-
-/*
- * GetApIfaceNames
- * Configures the chip in AP mode and ensures that the iface list is empty
- * before creating the iface. Then, create the iface and ensure that
- * iface name is returned via the list.
- */
-TEST_F(WifiChipHidlTest, GetApIfaceNames) {
-    if (!gEnv->isSoftApOn) return;
-    configureChipForIfaceType(IfaceType::AP, true);
-
-    const auto& status_and_iface_names1 =
-        HIDL_INVOKE(wifi_chip_, getApIfaceNames);
-    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names1.first.code);
-    EXPECT_EQ(0u, status_and_iface_names1.second.size());
-
-    sp<IWifiApIface> iface;
-    EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&iface));
-    EXPECT_NE(nullptr, iface.get());
-
-    std::string iface_name = getIfaceName(iface);
-    const auto& status_and_iface_names2 =
-        HIDL_INVOKE(wifi_chip_, getApIfaceNames);
-    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names2.first.code);
-    EXPECT_EQ(1u, status_and_iface_names2.second.size());
-    EXPECT_EQ(iface_name, status_and_iface_names2.second[0]);
-
-    EXPECT_EQ(WifiStatusCode::SUCCESS, removeApIface(iface_name));
-    const auto& status_and_iface_names3 =
-        HIDL_INVOKE(wifi_chip_, getApIfaceNames);
-    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names3.first.code);
-    EXPECT_EQ(0u, status_and_iface_names3.second.size());
-}
-
-/*
- * GetApIface
- * Configures the chip in AP mode and create an iface. Then, retrieve
- * the iface object using the correct name and ensure any other name
- * doesn't retrieve an iface object.
- */
-TEST_F(WifiChipHidlTest, GetApIface) {
-    if (!gEnv->isSoftApOn) return;
-    configureChipForIfaceType(IfaceType::AP, true);
-
-    sp<IWifiApIface> ap_iface;
-    EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&ap_iface));
-    EXPECT_NE(nullptr, ap_iface.get());
-
-    std::string iface_name = getIfaceName(ap_iface);
-    const auto& status_and_iface1 =
-        HIDL_INVOKE(wifi_chip_, getApIface, iface_name);
-    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface1.first.code);
-    EXPECT_NE(nullptr, status_and_iface1.second.get());
-
-    std::string invalid_name = iface_name + "0";
-    const auto& status_and_iface2 =
-        HIDL_INVOKE(wifi_chip_, getApIface, invalid_name);
-    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, status_and_iface2.first.code);
-    EXPECT_EQ(nullptr, status_and_iface2.second.get());
-}
-
-/*
- * RemoveApIface
- * Configures the chip in AP mode and create an iface. Then, remove
- * the iface object using the correct name and ensure any other name
- * doesn't remove the iface.
- */
-TEST_F(WifiChipHidlTest, RemoveApIface) {
-    if (!gEnv->isSoftApOn) return;
-    configureChipForIfaceType(IfaceType::AP, true);
-
-    sp<IWifiApIface> ap_iface;
-    EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&ap_iface));
-    EXPECT_NE(nullptr, ap_iface.get());
-
-    std::string iface_name = getIfaceName(ap_iface);
-    std::string invalid_name = iface_name + "0";
-    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeApIface(invalid_name));
-    EXPECT_EQ(WifiStatusCode::SUCCESS, removeApIface(iface_name));
-
-    // No such iface exists now. So, this should return failure.
-    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeApIface(iface_name));
-}
-
-/*
- * CreateNanIface
- * Configures the chip in NAN mode and ensures that at least 1 iface creation
- * succeeds.
- */
-TEST_F(WifiChipHidlTest, CreateNanIface) {
-    if (!gEnv->isNanOn) return;
-    configureChipForIfaceType(IfaceType::NAN, gEnv->isNanOn);
-
-    sp<IWifiNanIface> iface;
-    ASSERT_EQ(WifiStatusCode::SUCCESS, createNanIface(&iface));
-    EXPECT_NE(nullptr, iface.get());
-}
-
-/*
- * GetNanIfaceNames
- * Configures the chip in NAN mode and ensures that the iface list is empty
- * before creating the iface. Then, create the iface and ensure that
- * iface name is returned via the list.
- */
-TEST_F(WifiChipHidlTest, GetNanIfaceNames) {
-    if (!gEnv->isNanOn) return;
-    configureChipForIfaceType(IfaceType::NAN, gEnv->isNanOn);
-
-    const auto& status_and_iface_names1 =
-        HIDL_INVOKE(wifi_chip_, getNanIfaceNames);
-    ASSERT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names1.first.code);
-    EXPECT_EQ(0u, status_and_iface_names1.second.size());
-
-    sp<IWifiNanIface> iface;
-    EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&iface));
-    EXPECT_NE(nullptr, iface.get());
-
-    std::string iface_name = getIfaceName(iface);
-    const auto& status_and_iface_names2 =
-        HIDL_INVOKE(wifi_chip_, getNanIfaceNames);
-    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names2.first.code);
-    EXPECT_EQ(1u, status_and_iface_names2.second.size());
-    EXPECT_EQ(iface_name, status_and_iface_names2.second[0]);
-
-    EXPECT_EQ(WifiStatusCode::SUCCESS, removeNanIface(iface_name));
-    const auto& status_and_iface_names3 =
-        HIDL_INVOKE(wifi_chip_, getNanIfaceNames);
-    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names3.first.code);
-    EXPECT_EQ(0u, status_and_iface_names3.second.size());
-}
-
-/*
- * GetNanIface
- * Configures the chip in NAN mode and create an iface. Then, retrieve
- * the iface object using the correct name and ensure any other name
- * doesn't retrieve an iface object.
- */
-TEST_F(WifiChipHidlTest, GetNanIface) {
-    if (!gEnv->isNanOn) return;
-    configureChipForIfaceType(IfaceType::NAN, gEnv->isNanOn);
-
-    sp<IWifiNanIface> nan_iface;
-    EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&nan_iface));
-    EXPECT_NE(nullptr, nan_iface.get());
-
-    std::string iface_name = getIfaceName(nan_iface);
-    const auto& status_and_iface1 =
-        HIDL_INVOKE(wifi_chip_, getNanIface, iface_name);
-    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface1.first.code);
-    EXPECT_NE(nullptr, status_and_iface1.second.get());
-
-    std::string invalid_name = iface_name + "0";
-    const auto& status_and_iface2 =
-        HIDL_INVOKE(wifi_chip_, getNanIface, invalid_name);
-    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, status_and_iface2.first.code);
-    EXPECT_EQ(nullptr, status_and_iface2.second.get());
-}
-
-/*
- * RemoveNanIface
- * Configures the chip in NAN mode and create an iface. Then, remove
- * the iface object using the correct name and ensure any other name
- * doesn't remove the iface.
- */
-TEST_F(WifiChipHidlTest, RemoveNanIface) {
-    if (!gEnv->isNanOn) return;
-    configureChipForIfaceType(IfaceType::NAN, gEnv->isNanOn);
-
-    sp<IWifiNanIface> nan_iface;
-    EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&nan_iface));
-    EXPECT_NE(nullptr, nan_iface.get());
-
-    std::string iface_name = getIfaceName(nan_iface);
-    std::string invalid_name = iface_name + "0";
-    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeNanIface(invalid_name));
-
-    EXPECT_EQ(WifiStatusCode::SUCCESS, removeNanIface(iface_name));
-
-    // No such iface exists now. So, this should return failure.
-    EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeNanIface(iface_name));
-}
-
-/*
  * CreateP2pIface
  * Configures the chip in P2P mode and ensures that at least 1 iface creation
  * succeeds.
diff --git a/wifi/1.0/vts/functional/wifi_hidl_test_utils.h b/wifi/1.0/vts/functional/wifi_hidl_test_utils.h
index d430ce0..7dacaf1 100644
--- a/wifi/1.0/vts/functional/wifi_hidl_test_utils.h
+++ b/wifi/1.0/vts/functional/wifi_hidl_test_utils.h
@@ -54,48 +54,4 @@
         stopWifi();
         sleep(5);
     }
-
-   public:
-    // Whether NaN feature is supported on the device.
-    bool isNanOn = false;
-    // Whether SoftAp feature is supported on the device.
-    bool isSoftApOn = false;
-
-    void usage(char* me, char* arg) {
-        fprintf(stderr,
-                "unrecognized option: %s\n\n"
-                "usage: %s <gtest options> <test options>\n\n"
-                "test options are:\n\n"
-                "-N, --nan_on: Whether NAN feature is supported\n"
-                "-S, --softap_on: Whether SOFTAP feature is supported\n",
-                arg, me);
-    }
-
-    int initFromOptions(int argc, char** argv) {
-        static struct option options[] = {{"nan_on", no_argument, 0, 'N'},
-                                          {"softap_on", no_argument, 0, 'S'},
-                                          {0, 0, 0, 0}};
-
-        int c;
-        while ((c = getopt_long(argc, argv, "NS", options, NULL)) >= 0) {
-            switch (c) {
-                case 'N':
-                    isNanOn = true;
-                    break;
-                case 'S':
-                    isSoftApOn = true;
-                    break;
-                default:
-                    usage(argv[0], argv[optind]);
-                    return 2;
-            }
-        }
-
-        if (optind < argc) {
-            usage(argv[0], argv[optind]);
-            return 2;
-        }
-
-        return 0;
-    }
 };
diff --git a/wifi/1.1/vts/functional/VtsHalWifiV1_1TargetTest.cpp b/wifi/1.1/vts/functional/VtsHalWifiV1_1TargetTest.cpp
index a0f97f8..673fed3 100644
--- a/wifi/1.1/vts/functional/VtsHalWifiV1_1TargetTest.cpp
+++ b/wifi/1.1/vts/functional/VtsHalWifiV1_1TargetTest.cpp
@@ -41,10 +41,7 @@
     ::testing::AddGlobalTestEnvironment(gEnv);
     ::testing::InitGoogleTest(&argc, argv);
     gEnv->init(&argc, argv);
-    int status = gEnv->initFromOptions(argc, argv);
-    if (status == 0) {
-        int status = RUN_ALL_TESTS();
-        LOG(INFO) << "Test result = " << status;
-    }
+    int status = RUN_ALL_TESTS();
+    LOG(INFO) << "Test result = " << status;
     return status;
 }
diff --git a/wifi/1.3/vts/functional/wifi_sta_iface_hidl_test.cpp b/wifi/1.3/vts/functional/wifi_sta_iface_hidl_test.cpp
index 71e90ac..d382f30 100644
--- a/wifi/1.3/vts/functional/wifi_sta_iface_hidl_test.cpp
+++ b/wifi/1.3/vts/functional/wifi_sta_iface_hidl_test.cpp
@@ -27,6 +27,8 @@
 #include "wifi_hidl_test_utils.h"
 
 using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::wifi::V1_0::WifiStatus;
 using ::android::hardware::wifi::V1_0::WifiStatusCode;
 using ::android::hardware::wifi::V1_3::IWifiStaIface;
 
@@ -59,14 +61,11 @@
  * and return a success status code.
  */
 TEST_F(WifiStaIfaceHidlTest, GetFactoryMacAddress) {
-    const auto& status_and_mac =
+    std::pair<WifiStatus, hidl_array<uint8_t, 6> > status_and_mac =
         HIDL_INVOKE(wifi_sta_iface_, getFactoryMacAddress);
     EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_mac.first.code);
-    const int num_elements = sizeof(status_and_mac.second) / sizeof(uint8_t);
-    EXPECT_EQ(6, num_elements);
-    for (int i = 0; i < num_elements; i++) {
-        EXPECT_NE(0, status_and_mac.second[i]);
-    }
+    hidl_array<uint8_t, 6> all_zero{};
+    EXPECT_NE(all_zero, status_and_mac.second);
 }
 
 /*
diff --git a/wifi/1.4/Android.bp b/wifi/1.4/Android.bp
index a6ac020..aba8b44 100644
--- a/wifi/1.4/Android.bp
+++ b/wifi/1.4/Android.bp
@@ -8,6 +8,7 @@
     },
     srcs: [
         "IWifi.hal",
+        "IWifiApIface.hal",
     ],
     interfaces: [
         "android.hardware.wifi@1.0",
diff --git a/wifi/1.4/IWifiApIface.hal b/wifi/1.4/IWifiApIface.hal
new file mode 100644
index 0000000..af88afb
--- /dev/null
+++ b/wifi/1.4/IWifiApIface.hal
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package android.hardware.wifi@1.4;
+
+import @1.0::IWifiApIface;
+import @1.0::MacAddress;
+import @1.0::WifiStatus;
+
+/**
+ * Represents a network interface in AP mode.
+ *
+ * This can be obtained through @1.0::IWifiChip.getApIface() and casting
+ * IWifiApIface up to 1.4.
+ */
+interface IWifiApIface extends @1.0::IWifiApIface {
+    /**
+     * Changes the MAC address of the interface to the given MAC address.
+     *
+     * @param mac MAC address to change to.
+     * @return status WifiStatus of the operation.
+     *         Possible status codes:
+     *         |WifiStatusCode.SUCCESS|,
+     *         |WifiStatusCode.ERROR_WIFI_IFACE_INVALID|,
+     *         |WifiStatusCode.ERROR_UNKNOWN|
+     */
+    setMacAddress(MacAddress mac) generates (WifiStatus status);
+
+    /**
+     * Gets the factory MAC address of the interface.
+     *
+     * @return status WifiStatus of the operation
+     *         Possible status codes:
+     *         |WifiStatusCode.SUCCESS|,
+     *         |WifiStatusCode.ERROR_WIFI_IFACE_INVALID|,
+     *         |WifiStatusCode.ERROR_UNKNOWN|
+     * @return mac factory MAC address of the interface
+     */
+     getFactoryMacAddress() generates (WifiStatus status, MacAddress mac);
+};
diff --git a/wifi/1.4/default/tests/wifi_chip_unit_tests.cpp b/wifi/1.4/default/tests/wifi_chip_unit_tests.cpp
index 2ad093a..b0357ba 100644
--- a/wifi/1.4/default/tests/wifi_chip_unit_tests.cpp
+++ b/wifi/1.4/default/tests/wifi_chip_unit_tests.cpp
@@ -173,8 +173,9 @@
     std::string createIface(const IfaceType& type) {
         std::string iface_name;
         if (type == IfaceType::AP) {
-            chip_->createApIface([&iface_name](const WifiStatus& status,
-                                               const sp<IWifiApIface>& iface) {
+            chip_->createApIface([&iface_name](
+                                     const WifiStatus& status,
+                                     const sp<V1_0::IWifiApIface>& iface) {
                 if (WifiStatusCode::SUCCESS == status.code) {
                     ASSERT_NE(iface.get(), nullptr);
                     iface->getName([&iface_name](const WifiStatus& status,
diff --git a/wifi/1.4/default/wifi_ap_iface.cpp b/wifi/1.4/default/wifi_ap_iface.cpp
index 13ce2dd..b860910 100644
--- a/wifi/1.4/default/wifi_ap_iface.cpp
+++ b/wifi/1.4/default/wifi_ap_iface.cpp
@@ -85,6 +85,20 @@
                            hidl_status_cb, band);
 }
 
+Return<void> WifiApIface::setMacAddress(const hidl_array<uint8_t, 6>& mac,
+                                        setMacAddress_cb hidl_status_cb) {
+    return validateAndCall(this, WifiStatusCode::ERROR_WIFI_IFACE_INVALID,
+                           &WifiApIface::setMacAddressInternal, hidl_status_cb,
+                           mac);
+}
+
+Return<void> WifiApIface::getFactoryMacAddress(
+    getFactoryMacAddress_cb hidl_status_cb) {
+    return validateAndCall(this, WifiStatusCode::ERROR_WIFI_IFACE_INVALID,
+                           &WifiApIface::getFactoryMacAddressInternal,
+                           hidl_status_cb);
+}
+
 std::pair<WifiStatus, std::string> WifiApIface::getNameInternal() {
     return {createWifiStatus(WifiStatusCode::SUCCESS), ifname_};
 }
@@ -111,6 +125,26 @@
             ifname_, hidl_struct_util::convertHidlWifiBandToLegacy(band));
     return {createWifiStatusFromLegacyError(legacy_status), valid_frequencies};
 }
+
+WifiStatus WifiApIface::setMacAddressInternal(
+    const std::array<uint8_t, 6>& mac) {
+    bool status = iface_util_.lock()->setMacAddress(ifname_, mac);
+    if (!status) {
+        return createWifiStatus(WifiStatusCode::ERROR_UNKNOWN);
+    }
+    return createWifiStatus(WifiStatusCode::SUCCESS);
+}
+
+std::pair<WifiStatus, std::array<uint8_t, 6>>
+WifiApIface::getFactoryMacAddressInternal() {
+    std::array<uint8_t, 6> mac =
+        iface_util_.lock()->getFactoryMacAddress(ifname_);
+    if (mac[0] == 0 && mac[1] == 0 && mac[2] == 0 && mac[3] == 0 &&
+        mac[4] == 0 && mac[5] == 0) {
+        return {createWifiStatus(WifiStatusCode::ERROR_UNKNOWN), mac};
+    }
+    return {createWifiStatus(WifiStatusCode::SUCCESS), mac};
+}
 }  // namespace implementation
 }  // namespace V1_4
 }  // namespace wifi
diff --git a/wifi/1.4/default/wifi_ap_iface.h b/wifi/1.4/default/wifi_ap_iface.h
index 179acac..cb3ed3d 100644
--- a/wifi/1.4/default/wifi_ap_iface.h
+++ b/wifi/1.4/default/wifi_ap_iface.h
@@ -18,7 +18,7 @@
 #define WIFI_AP_IFACE_H_
 
 #include <android-base/macros.h>
-#include <android/hardware/wifi/1.0/IWifiApIface.h>
+#include <android/hardware/wifi/1.4/IWifiApIface.h>
 
 #include "wifi_feature_flags.h"
 #include "wifi_iface_util.h"
@@ -34,7 +34,7 @@
 /**
  * HIDL interface object used to control a AP Iface instance.
  */
-class WifiApIface : public V1_0::IWifiApIface {
+class WifiApIface : public V1_4::IWifiApIface {
    public:
     WifiApIface(
         const std::string& ifname,
@@ -53,6 +53,10 @@
                                 setCountryCode_cb hidl_status_cb) override;
     Return<void> getValidFrequenciesForBand(
         WifiBand band, getValidFrequenciesForBand_cb hidl_status_cb) override;
+    Return<void> setMacAddress(const hidl_array<uint8_t, 6>& mac,
+                               setMacAddress_cb hidl_status_cb) override;
+    Return<void> getFactoryMacAddress(
+        getFactoryMacAddress_cb hidl_status_cb) override;
 
    private:
     // Corresponding worker functions for the HIDL methods.
@@ -61,6 +65,9 @@
     WifiStatus setCountryCodeInternal(const std::array<int8_t, 2>& code);
     std::pair<WifiStatus, std::vector<WifiChannelInMhz>>
     getValidFrequenciesForBandInternal(WifiBand band);
+    WifiStatus setMacAddressInternal(const std::array<uint8_t, 6>& mac);
+    std::pair<WifiStatus, std::array<uint8_t, 6>>
+    getFactoryMacAddressInternal();
 
     std::string ifname_;
     std::weak_ptr<legacy_hal::WifiLegacyHal> legacy_hal_;
diff --git a/wifi/1.4/vts/OWNERS b/wifi/1.4/vts/OWNERS
new file mode 100644
index 0000000..8bfb148
--- /dev/null
+++ b/wifi/1.4/vts/OWNERS
@@ -0,0 +1,2 @@
+rpius@google.com
+etancohen@google.com
diff --git a/wifi/1.4/vts/functional/Android.bp b/wifi/1.4/vts/functional/Android.bp
new file mode 100644
index 0000000..42c60f2
--- /dev/null
+++ b/wifi/1.4/vts/functional/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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.
+//
+
+// SoftAP-specific tests, similar to VtsHalWifiApV1_0TargetTest.
+cc_test {
+    name: "VtsHalWifiApV1_4TargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: [
+        "VtsHalWifiV1_4TargetTest.cpp",
+        "wifi_ap_iface_hidl_test.cpp",
+    ],
+    static_libs: [
+        "VtsHalWifiV1_0TargetTestUtil",
+        "android.hardware.wifi@1.0",
+        "android.hardware.wifi@1.1",
+        "android.hardware.wifi@1.2",
+        "android.hardware.wifi@1.3",
+        "android.hardware.wifi@1.4",
+    ],
+}
diff --git a/wifi/1.4/vts/functional/VtsHalWifiV1_4TargetTest.cpp b/wifi/1.4/vts/functional/VtsHalWifiV1_4TargetTest.cpp
new file mode 100644
index 0000000..deac0fa
--- /dev/null
+++ b/wifi/1.4/vts/functional/VtsHalWifiV1_4TargetTest.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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 <android-base/logging.h>
+#include <android/hardware/wifi/1.4/IWifi.h>
+
+#include "wifi_hidl_test_utils.h"
+
+using ::android::hardware::wifi::V1_4::IWifi;
+
+// Test environment for Wifi HIDL HAL.
+class WifiHidlEnvironment_1_4 : public WifiHidlEnvironment {
+   public:
+    // get the test environment singleton
+    static WifiHidlEnvironment_1_4* Instance() {
+        static WifiHidlEnvironment_1_4* instance = new WifiHidlEnvironment_1_4;
+        return instance;
+    }
+
+    virtual void registerTestServices() override {
+        registerTestService<android::hardware::wifi::V1_4::IWifi>();
+    }
+
+   private:
+    WifiHidlEnvironment_1_4() {}
+};
+
+WifiHidlEnvironment_1_4* gEnv = WifiHidlEnvironment_1_4::Instance();
+
+int main(int argc, char** argv) {
+    ::testing::AddGlobalTestEnvironment(gEnv);
+    ::testing::InitGoogleTest(&argc, argv);
+    gEnv->init(&argc, argv);
+    int status = RUN_ALL_TESTS();
+    LOG(INFO) << "Test result = " << status;
+    return status;
+}
diff --git a/wifi/1.4/vts/functional/wifi_ap_iface_hidl_test.cpp b/wifi/1.4/vts/functional/wifi_ap_iface_hidl_test.cpp
new file mode 100644
index 0000000..68e9bbb
--- /dev/null
+++ b/wifi/1.4/vts/functional/wifi_ap_iface_hidl_test.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Staache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/hardware/wifi/1.4/IWifiApIface.h>
+
+#include <VtsHalHidlTargetTestBase.h>
+
+#include "wifi_hidl_call_util.h"
+#include "wifi_hidl_test_utils.h"
+
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::wifi::V1_0::WifiStatus;
+using ::android::hardware::wifi::V1_0::WifiStatusCode;
+using ::android::hardware::wifi::V1_4::IWifiApIface;
+
+extern WifiHidlEnvironment* gEnv;
+
+/**
+ * Fixture to use for all STA Iface HIDL interface tests.
+ */
+class WifiApIfaceHidlTest : public ::testing::VtsHalHidlTargetTestBase {
+   public:
+    virtual void SetUp() override {
+        wifi_ap_iface_ = IWifiApIface::castFrom(getWifiApIface());
+        ASSERT_NE(nullptr, wifi_ap_iface_.get());
+    }
+
+    virtual void TearDown() override {
+        stopWifi();
+    }
+
+   protected:
+    sp<IWifiApIface> wifi_ap_iface_;
+};
+
+/*
+ * SetMacAddress:
+ * Ensures that calls to set MAC address will return a success status
+ * code.
+ */
+TEST_F(WifiApIfaceHidlTest, SetMacAddress) {
+    const hidl_array<uint8_t, 6> kMac{{0x12, 0x22, 0x33, 0x52, 0x10, 0x41}};
+    EXPECT_EQ(WifiStatusCode::SUCCESS,
+              HIDL_INVOKE(wifi_ap_iface_, setMacAddress, kMac).code);
+}
+
+/*
+ * GetFactoryMacAddress:
+ * Ensures that calls to get factory MAC address will retrieve a non-zero MAC
+ * and return a success status code.
+ */
+TEST_F(WifiApIfaceHidlTest, GetFactoryMacAddress) {
+    std::pair<WifiStatus, hidl_array<uint8_t, 6> > status_and_mac =
+        HIDL_INVOKE(wifi_ap_iface_, getFactoryMacAddress);
+    EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_mac.first.code);
+    hidl_array<uint8_t, 6> all_zero{};
+    EXPECT_NE(all_zero, status_and_mac.second);
+}
diff --git a/wifi/supplicant/1.3/Android.bp b/wifi/supplicant/1.3/Android.bp
index 6633d9d..3f20531 100644
--- a/wifi/supplicant/1.3/Android.bp
+++ b/wifi/supplicant/1.3/Android.bp
@@ -9,6 +9,8 @@
     srcs: [
         "types.hal",
         "ISupplicant.hal",
+        "ISupplicantStaIface.hal",
+        "ISupplicantStaIfaceCallback.hal",
         "ISupplicantStaNetwork.hal",
     ],
     interfaces: [
diff --git a/wifi/supplicant/1.3/ISupplicantStaIface.hal b/wifi/supplicant/1.3/ISupplicantStaIface.hal
new file mode 100644
index 0000000..62b4033
--- /dev/null
+++ b/wifi/supplicant/1.3/ISupplicantStaIface.hal
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+package android.hardware.wifi.supplicant@1.3;
+
+import @1.0::SupplicantStatus;
+import @1.2::ISupplicantStaIface;
+import @1.3::ISupplicantStaIfaceCallback;
+
+/**
+ * Interface exposed by the supplicant for each station mode network
+ * interface (e.g wlan0) it controls.
+ */
+interface ISupplicantStaIface extends @1.2::ISupplicantStaIface {
+    /**
+     * Register for callbacks from this interface.
+     *
+     * These callbacks are invoked for events that are specific to this interface.
+     * Registration of multiple callback objects is supported. These objects must
+     * be automatically deleted when the corresponding client process is dead or
+     * if this interface is removed.
+     *
+     * @param callback An instance of the |ISupplicantStaIfaceCallback| HIDL
+     *        interface object.
+     * @return status Status of the operation.
+     *         Possible status codes:
+     *         |SupplicantStatusCode.SUCCESS|,
+     *         |SupplicantStatusCode.FAILURE_UNKNOWN|,
+     *         |SupplicantStatusCode.FAILURE_IFACE_INVALID|
+     */
+    registerCallback_1_3(ISupplicantStaIfaceCallback callback)
+        generates (SupplicantStatus status);
+};
diff --git a/wifi/supplicant/1.3/ISupplicantStaIfaceCallback.hal b/wifi/supplicant/1.3/ISupplicantStaIfaceCallback.hal
new file mode 100644
index 0000000..107e0fc
--- /dev/null
+++ b/wifi/supplicant/1.3/ISupplicantStaIfaceCallback.hal
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+package android.hardware.wifi.supplicant@1.3;
+
+import @1.2::ISupplicantStaIfaceCallback;
+
+/**
+ * Callback Interface exposed by the supplicant service
+ * for each station mode interface (ISupplicantStaIface).
+ *
+ * Clients need to host an instance of this HIDL interface object and
+ * pass a reference of the object to the supplicant via the
+ * corresponding |ISupplicantStaIface.registerCallback_1_3| method.
+ */
+interface ISupplicantStaIfaceCallback extends @1.2::ISupplicantStaIfaceCallback {
+    /**
+     * Indicates PMK cache added event.
+     *
+     * @param expirationTimeInSec expiration time in seconds
+     * @param serializedEntry is serialized PMK cache entry, the content is
+     *              opaque for the framework and depends on the native implementation.
+     */
+    oneway onPmkCacheAdded(int64_t expirationTimeInSec, vec<uint8_t> serializedEntry);
+};
diff --git a/wifi/supplicant/1.3/ISupplicantStaNetwork.hal b/wifi/supplicant/1.3/ISupplicantStaNetwork.hal
index eb9de9a..5e265c6 100644
--- a/wifi/supplicant/1.3/ISupplicantStaNetwork.hal
+++ b/wifi/supplicant/1.3/ISupplicantStaNetwork.hal
@@ -47,4 +47,19 @@
      * @return ocspType ocsp type.
      */
     getOcsp() generates (SupplicantStatus status, OcspType ocspType);
+
+    /**
+     * Add a PMK into supplicant PMK cache.
+     *
+     * @param serializedEntry is serialized PMK cache entry, the content is
+     *              opaque for the framework and depends on the native implementation.
+     * @return status Status of the operation
+     *         Possible status codes:
+     *         |SupplicantStatusCode.SUCCESS|,
+     *         |SupplicantStatusCode.FAILURE_ARGS_INVALID|,
+     *         |SupplicantStatusCode.FAILURE_UNKNOWN|,
+     *         |SupplicantStatusCode.FAILURE_NETWORK_INVALID|
+     */
+    setPmkCache(vec<uint8_t> serializedEntry)
+        generates (SupplicantStatus status);
 };
diff --git a/wifi/supplicant/1.3/vts/functional/Android.bp b/wifi/supplicant/1.3/vts/functional/Android.bp
index 67c7348..abb8600 100644
--- a/wifi/supplicant/1.3/vts/functional/Android.bp
+++ b/wifi/supplicant/1.3/vts/functional/Android.bp
@@ -42,6 +42,7 @@
     defaults: ["VtsHalTargetTestDefaults"],
     srcs: [
         "VtsHalWifiSupplicantV1_3TargetTest.cpp",
+        "supplicant_sta_iface_hidl_test.cpp",
         "supplicant_sta_network_hidl_test.cpp",
     ],
     static_libs: [
diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.cpp b/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.cpp
index 86959eb..308808d 100644
--- a/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.cpp
+++ b/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.cpp
@@ -21,8 +21,13 @@
 #include "supplicant_hidl_test_utils_1_3.h"
 
 using ::android::sp;
+using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaIface;
 using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaNetwork;
 
+sp<ISupplicantStaIface> getSupplicantStaIface_1_3() {
+    return ISupplicantStaIface::castFrom(getSupplicantStaIface());
+}
+
 sp<ISupplicantStaNetwork> createSupplicantStaNetwork_1_3() {
     return ISupplicantStaNetwork::castFrom(createSupplicantStaNetwork());
 }
diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.h b/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.h
index 8e64162..39dbb8f 100644
--- a/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.h
+++ b/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.h
@@ -17,8 +17,11 @@
 #ifndef SUPPLICANT_HIDL_TEST_UTILS_1_3_H
 #define SUPPLICANT_HIDL_TEST_UTILS_1_3_H
 
+#include <android/hardware/wifi/supplicant/1.3/ISupplicantStaIface.h>
 #include <android/hardware/wifi/supplicant/1.3/ISupplicantStaNetwork.h>
 
+android::sp<android::hardware::wifi::supplicant::V1_3::ISupplicantStaIface>
+getSupplicantStaIface_1_3();
 android::sp<android::hardware::wifi::supplicant::V1_3::ISupplicantStaNetwork>
 createSupplicantStaNetwork_1_3();
 
diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp b/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp
new file mode 100644
index 0000000..9b68a47
--- /dev/null
+++ b/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp
@@ -0,0 +1,181 @@
+/*
+ * 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 <VtsHalHidlTargetTestBase.h>
+#include <android/hardware/wifi/supplicant/1.2/types.h>
+#include <android/hardware/wifi/supplicant/1.3/ISupplicantStaIface.h>
+#include <android/hardware/wifi/supplicant/1.3/ISupplicantStaIfaceCallback.h>
+#include <android/hardware/wifi/supplicant/1.3/ISupplicantStaNetwork.h>
+#include <android/hardware/wifi/supplicant/1.3/types.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+#include "supplicant_hidl_test_utils.h"
+#include "supplicant_hidl_test_utils_1_3.h"
+
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::wifi::supplicant::V1_0::SupplicantStatus;
+using ::android::hardware::wifi::supplicant::V1_0::SupplicantStatusCode;
+using ::android::hardware::wifi::supplicant::V1_2::DppAkm;
+using ::android::hardware::wifi::supplicant::V1_2::DppFailureCode;
+using ::android::hardware::wifi::supplicant::V1_2::DppProgressCode;
+using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaIface;
+using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaIfaceCallback;
+using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaNetwork;
+
+class SupplicantStaIfaceHidlTest : public ::testing::VtsHalHidlTargetTestBase {
+   public:
+    virtual void SetUp() override {
+        startSupplicantAndWaitForHidlService();
+        EXPECT_TRUE(turnOnExcessiveLogging());
+        sta_iface_ = getSupplicantStaIface_1_3();
+        ASSERT_NE(sta_iface_.get(), nullptr);
+    }
+
+    virtual void TearDown() override { stopSupplicant(); }
+
+    int64_t pmkCacheExpirationTimeInSec;
+    std::vector<uint8_t> serializedPmkCacheEntry;
+
+   protected:
+    // ISupplicantStaIface object used for all tests in this fixture.
+    sp<ISupplicantStaIface> sta_iface_;
+};
+
+class IfaceCallback : public ISupplicantStaIfaceCallback {
+    Return<void> onNetworkAdded(uint32_t /* id */) override { return Void(); }
+    Return<void> onNetworkRemoved(uint32_t /* id */) override { return Void(); }
+    Return<void> onStateChanged(
+        ISupplicantStaIfaceCallback::State /* newState */,
+        const hidl_array<uint8_t, 6>& /*bssid */, uint32_t /* id */,
+        const hidl_vec<uint8_t>& /* ssid */) override {
+        return Void();
+    }
+    Return<void> onAnqpQueryDone(
+        const hidl_array<uint8_t, 6>& /* bssid */,
+        const ISupplicantStaIfaceCallback::AnqpData& /* data */,
+        const ISupplicantStaIfaceCallback::Hs20AnqpData& /* hs20Data */)
+        override {
+        return Void();
+    }
+    virtual Return<void> onHs20IconQueryDone(
+        const hidl_array<uint8_t, 6>& /* bssid */,
+        const hidl_string& /* fileName */,
+        const hidl_vec<uint8_t>& /* data */) override {
+        return Void();
+    }
+    virtual Return<void> onHs20SubscriptionRemediation(
+        const hidl_array<uint8_t, 6>& /* bssid */,
+        ISupplicantStaIfaceCallback::OsuMethod /* osuMethod */,
+        const hidl_string& /* url*/) override {
+        return Void();
+    }
+    Return<void> onHs20DeauthImminentNotice(
+        const hidl_array<uint8_t, 6>& /* bssid */, uint32_t /* reasonCode */,
+        uint32_t /* reAuthDelayInSec */,
+        const hidl_string& /* url */) override {
+        return Void();
+    }
+    Return<void> onDisconnected(const hidl_array<uint8_t, 6>& /* bssid */,
+                                bool /* locallyGenerated */,
+                                ISupplicantStaIfaceCallback::ReasonCode
+                                /* reasonCode */) override {
+        return Void();
+    }
+    Return<void> onAssociationRejected(
+        const hidl_array<uint8_t, 6>& /* bssid */,
+        ISupplicantStaIfaceCallback::StatusCode /* statusCode */,
+        bool /*timedOut */) override {
+        return Void();
+    }
+    Return<void> onAuthenticationTimeout(
+        const hidl_array<uint8_t, 6>& /* bssid */) override {
+        return Void();
+    }
+    Return<void> onBssidChanged(
+        ISupplicantStaIfaceCallback::BssidChangeReason /* reason */,
+        const hidl_array<uint8_t, 6>& /* bssid */) override {
+        return Void();
+    }
+    Return<void> onEapFailure() override { return Void(); }
+    Return<void> onEapFailure_1_1(
+        ISupplicantStaIfaceCallback::EapErrorCode /* eapErrorCode */) override {
+        return Void();
+    }
+    Return<void> onWpsEventSuccess() override { return Void(); }
+    Return<void> onWpsEventFail(
+        const hidl_array<uint8_t, 6>& /* bssid */,
+        ISupplicantStaIfaceCallback::WpsConfigError /* configError */,
+        ISupplicantStaIfaceCallback::WpsErrorIndication /* errorInd */)
+        override {
+        return Void();
+    }
+    Return<void> onWpsEventPbcOverlap() override { return Void(); }
+    Return<void> onExtRadioWorkStart(uint32_t /* id */) override {
+        return Void();
+    }
+    Return<void> onExtRadioWorkTimeout(uint32_t /* id*/) override {
+        return Void();
+    }
+    Return<void> onDppSuccessConfigReceived(
+        const hidl_vec<uint8_t>& /* ssid */, const hidl_string& /* password */,
+        const hidl_array<uint8_t, 32>& /* psk */,
+        DppAkm /* securityAkm */) override {
+        return Void();
+    }
+    Return<void> onDppSuccessConfigSent() override { return Void(); }
+    Return<void> onDppProgress(DppProgressCode /* code */) override {
+        return Void();
+    }
+    Return<void> onDppFailure(DppFailureCode /* code */) override {
+        return Void();
+    }
+    Return<void> onPmkCacheAdded(
+        int64_t /* expirationTimeInSec */,
+        const hidl_vec<uint8_t>& /* serializedEntry */) override {
+        return Void();
+    }
+};
+
+class IfacePmkCacheCallback : public IfaceCallback {
+    SupplicantStaIfaceHidlTest& parent_;
+    Return<void> onPmkCacheAdded(
+        int64_t expirationTimeInSec,
+        const hidl_vec<uint8_t>& serializedEntry) override {
+        parent_.pmkCacheExpirationTimeInSec = expirationTimeInSec;
+        parent_.serializedPmkCacheEntry = serializedEntry;
+        return Void();
+    }
+
+   public:
+    IfacePmkCacheCallback(SupplicantStaIfaceHidlTest& parent)
+        : parent_(parent) {}
+};
+
+/*
+ * RegisterCallback_1_3
+ */
+TEST_F(SupplicantStaIfaceHidlTest, RegisterCallback_1_3) {
+    sta_iface_->registerCallback_1_3(
+        new IfaceCallback(), [](const SupplicantStatus& status) {
+            EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code);
+        });
+}
diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp b/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp
index e5be0cc..07bc9d8 100644
--- a/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp
+++ b/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp
@@ -71,3 +71,16 @@
             EXPECT_EQ(testOcspType, ocspType);
         });
 }
+
+/*
+ * SetPmkCacheEntry
+ */
+TEST_F(SupplicantStaNetworkHidlTest, SetPmkCache) {
+    uint8_t bytes[128] = {0};
+    std::vector<uint8_t> serializedEntry(bytes, bytes + sizeof(bytes));
+
+    sta_network_->setPmkCache(
+        serializedEntry, [](const SupplicantStatus &status) {
+            EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code);
+        });
+}