Fix out of Bounds Read in convertSubgraphFromHAL in ShimConverter.cpp in libneuralnetworks_shim_static am: 6e1bbe89e0 am: 7f7ef8e234 am: a421402f58 am: a2178d4ed9 am: 2ec50f02e4 am: 4c14694ba6

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/NeuralNetworks/+/23914069

Change-Id: I736c7f2892e79bab1e67b7d0e65b65f5047b88d0
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/Android.bp b/Android.bp
index 96188d6..0720455 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,38 +36,7 @@
 }
 
 cc_defaults {
-    name: "neuralnetworks_float16",
-    // Note: the newlines in the "cflags" sections are intentional to ensure
-    // bpfmt -w -s does not change the order of the compiler flags.
-    arch: {
-        x86: {
-            cflags: [
-                "-D_Float16=__fp16",
-
-                "-Xclang",
-                "-fnative-half-type",
-
-                "-Xclang",
-                "-fallow-half-arguments-and-returns",
-            ],
-        },
-        x86_64: {
-            cflags: [
-                "-D_Float16=__fp16",
-
-                "-Xclang",
-                "-fnative-half-type",
-
-                "-Xclang",
-                "-fallow-half-arguments-and-returns",
-            ],
-        },
-    },
-}
-
-cc_defaults {
     name: "neuralnetworks_defaults",
-    defaults: ["neuralnetworks_float16"],
     cflags: [
         "-O3",
         "-Wall",
diff --git a/NNAPI_OWNERS b/NNAPI_OWNERS
index 7e6ea31..465bb08 100644
--- a/NNAPI_OWNERS
+++ b/NNAPI_OWNERS
@@ -1,8 +1,6 @@
 [email protected]
 [email protected]
[email protected]
 [email protected]
[email protected]
 [email protected]
 [email protected]
 [email protected]
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 63d3aed..14b9083 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -29,6 +29,23 @@
         }
       ]
     },
+    // TODO(b/244359503): Re-enable once the conversion layer is fixed.
+    // {
+    //   "name": "NeuralNetworksTest_v2_static",
+    //   "options": [
+    //     {
+    //       // Restrict NeuralNetworksTest_v2_static to run only a single
+    //       // pass consisting of:
+    //       // * useCpuOnly = 0
+    //       // * computeMode = ComputeMode::ASYNC
+    //       //
+    //       // The value here is a bitmask indicating only "pass 2"
+    //       // should be run (4 = 2^2). The bit conversions can be
+    //       // found in packages/modules/NeuralNetworks/runtime/test/TestMain.cpp.
+    //       "native-test-flag": "4"
+    //     }
+    //   ]
+    // },
     {
       "name": "CtsNNAPITestCases"
     }
diff --git a/apex/manifest.json b/apex/manifest.json
index f390aaf..5975f26 100644
--- a/apex/manifest.json
+++ b/apex/manifest.json
@@ -1,4 +1,7 @@
 {
     "name": "com.android.neuralnetworks",
-    "version": 330400000
+
+    // Placeholder module version to be replaced during build.
+    // Do not change!
+    "version": 0
 }
diff --git a/common/Android.bp b/common/Android.bp
index 159ceee..108cbe1 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -228,6 +228,7 @@
     defaults: [
         "libneuralnetworks_common_defaults",
     ],
+    min_sdk_version: "30",
 }
 
 cc_library_static {
@@ -362,7 +363,6 @@
 cc_defaults {
     name: "NeuralNetworksTest_common",
     defaults: [
-        "neuralnetworks_float16",
         "neuralnetworks_use_latest_utils_hal_aidl",
     ],
     host_supported: true,
@@ -416,6 +416,9 @@
         "philox_random_headers",
         "tensorflow_headers",
     ],
+    test_suites: [
+        "general-tests",
+    ],
 }
 
 cc_test {
diff --git a/common/cpu_operations/LSTM.cpp b/common/cpu_operations/LSTM.cpp
index 8c1a4c2..66b6bee 100644
--- a/common/cpu_operations/LSTM.cpp
+++ b/common/cpu_operations/LSTM.cpp
@@ -18,11 +18,6 @@
 
 #include "LSTM.h"
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-parameter"
-#include <tensorflow/lite/kernels/internal/reference/portable_tensor_utils.h>
-#pragma clang diagnostic pop
-
 #include <tensorflow/lite/kernels/internal/tensor_utils.h>
 
 #include <vector>
diff --git a/common/random/Android.bp b/common/random/Android.bp
index df2b472..a05b833 100644
--- a/common/random/Android.bp
+++ b/common/random/Android.bp
@@ -31,6 +31,7 @@
         "com.android.neuralnetworks",
         "test_com.android.neuralnetworks",
     ],
+    min_sdk_version: "30",
     sdk_version: "current",
 }
 
@@ -38,6 +39,7 @@
     name: "philox_random",
     host_supported: true,
     vendor_available: true,
+    min_sdk_version: "30",
     apex_available: [
         "//apex_available:platform",
         "com.android.neuralnetworks",
diff --git a/common/random/random.cc b/common/random/random.cc
index 9d4b33f..168396e 100644
--- a/common/random/random.cc
+++ b/common/random/random.cc
@@ -15,8 +15,8 @@
 
 #include "random.h"
 
-#include <tensorflow/core/platform/mutex.h>
 #include <tensorflow/core/platform/types.h>
+
 #include <random>
 
 namespace tensorflow {
diff --git a/common/types/include/nnapi/Result.h b/common/types/include/nnapi/Result.h
index 698ab70..2d27106 100644
--- a/common/types/include/nnapi/Result.h
+++ b/common/types/include/nnapi/Result.h
@@ -135,8 +135,8 @@
  * following functions for the type:
  * * `::android::nn::nnTryHasValue` returns `true` if the `expr` holds a successful value, false if
  *    the `expr` value holds an error
- * * `::android::nn::nnTryGetError` returns the successful value of `expr` or crashes
- * * `::android::nn::nnTryGetValue` returns the error value of `expr` or crashes
+ * * `::android::nn::nnTryGetError` returns the error value of `expr` or crashes
+ * * `::android::nn::nnTryGetValue` returns the successful value of `expr` or crashes
  *
  * Usage at call site:
  *     const auto [a, b, c] = NN_TRY(failableFunction(args));
diff --git a/common/types/include/nnapi/TypeUtils.h b/common/types/include/nnapi/TypeUtils.h
index 87e7c3f..b6964fc 100644
--- a/common/types/include/nnapi/TypeUtils.h
+++ b/common/types/include/nnapi/TypeUtils.h
@@ -289,6 +289,14 @@
         return result;
     }
 
+    // This is needed because conversion to Result<int> is ambiguous
+    // due to the above bool() operator overload
+    operator Result<int>() {  // NOLINT(google-explicit-constructor)
+        auto result = base::unexpected(std::move(mBuffer)->str());
+        mBuffer.reset();
+        return result;
+    }
+
    private:
     std::optional<std::ostringstream> mBuffer = std::ostringstream{};
 };
diff --git a/driver/sample/Android.bp b/driver/sample/Android.bp
index 286cdcd..85a29c3 100644
--- a/driver/sample/Android.bp
+++ b/driver/sample/Android.bp
@@ -112,3 +112,39 @@
         "libneuralnetworks_cl",
     ],
 }
+
+cc_fuzz {
+    name: "android.hardware.neuralnetworks-service.example_fuzzer",
+    host_supported: true,
+    defaults: [
+        "neuralnetworks_defaults",
+        "neuralnetworks_use_latest_utils_hal_aidl",
+        "service_fuzzer_defaults",
+    ],
+    header_libs: [
+        "libneuralnetworks_headers",
+    ],
+    shared_libs: [
+        "liblog",
+        "libtextclassifier_hash",
+    ],
+    static_libs: [
+        "libaidlcommonsupport",
+        "libneuralnetworks_common",
+        "neuralnetworks_canonical_sample_driver",
+        "neuralnetworks_utils_hal_adapter_aidl",
+    ],
+    target: {
+        android: {
+            shared_libs: [
+                "libnativewindow",
+            ],
+        },
+    },
+    srcs: ["Fuzzer.cpp"],
+    fuzz_config: {
+        cc: [
+            "[email protected]",
+        ],
+    },
+}
diff --git a/driver/sample/Fuzzer.cpp b/driver/sample/Fuzzer.cpp
new file mode 100644
index 0000000..1979410
--- /dev/null
+++ b/driver/sample/Fuzzer.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Fuzzer"
+
+#include <aidl/android/hardware/neuralnetworks/BnDevice.h>
+#include <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <nnapi/hal/aidl/Adapter.h>
+
+#include <memory>
+#include <string>
+
+#include "CanonicalDevice.h"
+
+namespace aidl::android::hardware::neuralnetworks::fuzzer {
+namespace {
+
+std::shared_ptr<BnDevice> makeDevice() {
+    const std::string name = "nnapi-sample";
+    auto device = std::make_shared<::android::nn::sample::Device>(name);
+    return adapter::adapt(std::move(device));
+}
+
+void limitLoggingToCrashes() {
+    [[maybe_unused]] static const auto oldSeverity = ::android::base::SetMinimumLogSeverity(
+            ::android::base::LogSeverity::FATAL_WITHOUT_ABORT);
+}
+
+}  // namespace
+}  // namespace aidl::android::hardware::neuralnetworks::fuzzer
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // Limit NNAPI fuzz test logging to crashes (which is what the test cares about) to reduce the
+    // noise and potentially speed up testing.
+    aidl::android::hardware::neuralnetworks::fuzzer::limitLoggingToCrashes();
+
+    // Initialize the Device under test when LLVMFuzzerTestOneInput is first called, and reuse it in
+    // later calls.
+    static const auto device = aidl::android::hardware::neuralnetworks::fuzzer::makeDevice();
+
+    android::fuzzService(device->asBinder().get(), FuzzedDataProvider(data, size));
+    return 0;
+}
diff --git a/driver/sample_aidl/SampleDriverAidlLimited.cpp b/driver/sample_aidl/SampleDriverAidlLimited.cpp
index 08264d8..4b92f77 100644
--- a/driver/sample_aidl/SampleDriverAidlLimited.cpp
+++ b/driver/sample_aidl/SampleDriverAidlLimited.cpp
@@ -59,6 +59,10 @@
     CHECK_EQ(devices.size(), aidlDevices.size());
     for (size_t i = 0; i < aidlDevices.size(); ++i) {
         const std::string name = devices[i]->getName();
+        if (name != "nnapi-sample_quant") {
+            continue;
+        }
+
         const std::string fqName = std::string(AidlIDevice::descriptor) + "/" + name;
         const binder_status_t status =
                 AServiceManager_addService(aidlDevices[i]->asBinder().get(), fqName.c_str());
diff --git a/driver/sample_aidl/config/android.hardware.neuralnetworks-service-sample-limited.xml b/driver/sample_aidl/config/android.hardware.neuralnetworks-service-sample-limited.xml
index 2f74f2d..f57117e 100644
--- a/driver/sample_aidl/config/android.hardware.neuralnetworks-service-sample-limited.xml
+++ b/driver/sample_aidl/config/android.hardware.neuralnetworks-service-sample-limited.xml
@@ -2,9 +2,6 @@
     <hal format="aidl">
         <name>android.hardware.neuralnetworks</name>
         <version>4</version>
-        <fqname>IDevice/nnapi-sample_float_fast</fqname>
-        <fqname>IDevice/nnapi-sample_float_slow</fqname>
-        <fqname>IDevice/nnapi-sample_minimal</fqname>
         <fqname>IDevice/nnapi-sample_quant</fqname>
     </hal>
 </manifest>
diff --git a/driver/sample_shim/Android.bp b/driver/sample_shim/Android.bp
index 249ead9..b0659ec 100644
--- a/driver/sample_shim/Android.bp
+++ b/driver/sample_shim/Android.bp
@@ -49,6 +49,9 @@
         android_arm: {
             srcs: ["android_arm/neuralnetworks_sample_sl_driver_prebuilt.so"],
         },
+        android_riscv64: {
+            srcs: ["android_riscv64/neuralnetworks_sample_sl_driver_prebuilt.so"],
+        },
     },
     apex_available: ["//apex_available:platform"],
 }
diff --git a/driver/sample_shim/android_riscv64/neuralnetworks_sample_sl_driver_prebuilt.so b/driver/sample_shim/android_riscv64/neuralnetworks_sample_sl_driver_prebuilt.so
new file mode 100755
index 0000000..63e5a11
--- /dev/null
+++ b/driver/sample_shim/android_riscv64/neuralnetworks_sample_sl_driver_prebuilt.so
Binary files differ
diff --git a/driver/sample_shim/generate_prebuilts.sh b/driver/sample_shim/generate_prebuilts.sh
index 812a5a9..5b4b89f 100755
--- a/driver/sample_shim/generate_prebuilts.sh
+++ b/driver/sample_shim/generate_prebuilts.sh
@@ -13,7 +13,7 @@
 cd $ANDROID_BUILD_TOP
 
 source build/envsetup.sh
-ARCHS="x86,arm,arm64,x86_64"
+ARCHS="x86,arm,arm64,x86_64,riscv64"
 SAMPLE_SL_DRIVER="neuralnetworks_sample_sl_driver"
 
 for arch in ${ARCHS//,/ }
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 7bc3b1b..21ec0b9 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -54,6 +54,7 @@
     host_supported: true,
     vendor_available: true,
     export_include_dirs: ["include"],
+    min_sdk_version: "30",
     apex_available: [
         "com.android.neuralnetworks",
         "test_com.android.neuralnetworks", // Due to the dependency from libneuralnetworks_common
@@ -105,7 +106,6 @@
             version_script: "libneuralnetworks.map.txt",
             generated_sources: ["statslog_neuralnetworks.cpp"],
             generated_headers: ["statslog_neuralnetworks.h"],
-            export_generated_headers: ["statslog_neuralnetworks.h"],
             srcs: [
                 "TelemetryStatsd.cpp",
             ],
@@ -170,6 +170,35 @@
     ],
 }
 
+cc_defaults {
+    name: "libneuralnetworks_v2_defaults",
+    defaults: ["libneuralnetworks_defaults"],
+    srcs: [
+        "FlatbufferModelBuilder.cpp",
+        "NeuralNetworksV2.cpp",
+        "operation_converters/AddOperationConverter.cpp",
+        "operation_converters/ArithmeticOperationConverter.cpp",
+        "operation_converters/Conv2DOperationConverter.cpp",
+        "operation_converters/DepthwiseConv2DOperationConverter.cpp",
+        "operation_converters/LogisticOperationConverter.cpp",
+        "operation_converters/OperationConverterResolver.cpp",
+        "operation_converters/SubGraphContext.cpp",
+    ],
+
+    exclude_srcs: [
+        "NeuralNetworks.cpp",
+    ],
+
+    static_libs: [
+        "libtflite_static",
+    ],
+
+    include_dirs: [
+        "external/flatbuffers/include",
+        "external/tensorflow",
+    ],
+}
+
 cc_library_shared {
     name: "libneuralnetworks",
     llndk: {
@@ -180,11 +209,11 @@
         "libneuralnetworks_defaults",
         "neuralnetworks_defaults",
     ],
+    min_sdk_version: "30",
     apex_available: [
         "com.android.neuralnetworks",
         "test_com.android.neuralnetworks",
     ],
-
     stubs: {
         versions: [
             "30",
@@ -225,6 +254,24 @@
 }
 
 cc_library_static {
+    name: "libneuralnetworks_v2_static_experimental",
+    defaults: [
+        "libneuralnetworks_v2_defaults",
+        "neuralnetworks_defaults",
+    ],
+    exclude_static_libs: [
+        "libneuralnetworks_common",
+        "neuralnetworks_types",
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "libneuralnetworks_common_experimental",
+        "neuralnetworks_types_experimental",
+    ],
+    cflags: ["-DNN_EXPERIMENTAL_FEATURE"],
+}
+
+cc_library_static {
     name: "libneuralnetworks_cl",
     defaults: [
         "neuralnetworks_cl_defaults",
@@ -285,6 +332,9 @@
     symbol_file: "libneuralnetworks.map.txt",
     // Android O-MR1
     first_version: "27",
+    export_header_libs: [
+        "libneuralnetworks_ndk_headers",
+    ],
 }
 
 genrule {
diff --git a/runtime/FlatbufferModelBuilder.cpp b/runtime/FlatbufferModelBuilder.cpp
new file mode 100644
index 0000000..be3faaa
--- /dev/null
+++ b/runtime/FlatbufferModelBuilder.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FlatbufferModelBuilder"
+
+#include "FlatbufferModelBuilder.h"
+
+#include <LegacyUtils.h>
+
+#include "FlatbufferModelBuilderUtils.h"
+#include "operation_converters/OperationConverterResolver.h"
+
+namespace android {
+namespace nn {
+
+void FlatbufferModelBuilder::verifyModel(const tflite::Model* model) {
+    flatbuffers::Verifier verifier(mBuilder.GetBufferPointer(), mBuilder.GetSize());
+    CHECK(model != nullptr);
+    CHECK(model->Verify(verifier));
+}
+
+void FlatbufferModelBuilder::initializeBufferVector() {
+    mBufferVector.clear();
+
+    std::vector<uint8_t> emptyData;
+    auto emptyBuffer = tflite::CreateBufferDirect(mBuilder, &emptyData);
+    mBufferVector.push_back(emptyBuffer);
+}
+
+void FlatbufferModelBuilder::initializeOpCodeIndexForOperationType() {
+    mOpCodeIndexForOperationType.clear();
+    mOpCodeIndexForOperationType.resize(kNumberOfOperationTypes, -1);
+}
+
+std::vector<MetadataFlatbuffer> FlatbufferModelBuilder::createMetadataVector() {
+    std::vector<MetadataFlatbuffer> metadataVector;
+    for (uint32_t i = 0; i < mBufferVector.size(); i++) {
+        auto metadata = tflite::CreateMetadataDirect(mBuilder, std::to_string(i).c_str() /* name */,
+                                                     i /* buffer */);
+        metadataVector.push_back(metadata);
+    }
+    return metadataVector;
+}
+
+Result<const tflite::Model*> FlatbufferModelBuilder::createTfliteModel() {
+    mModel = makeModel();
+
+    // Initialize and clear data structures
+    initializeBufferVector();
+    mOpCodesVector.clear();
+    initializeOpCodeIndexForOperationType();
+
+    // Generate subgraphs
+    auto subgraphsVector = NN_TRY(createSubGraphs());
+
+    auto metadataVector = createMetadataVector();
+
+    ModelFlatbuffer flatbufferModel = tflite::CreateModelDirect(
+            mBuilder, 3 /* version*/, &mOpCodesVector /* operator_codes */,
+            &subgraphsVector /* subgraphs */, nullptr /* description */,
+            &mBufferVector /* buffers */, nullptr /* metadata_buffer */,
+            &metadataVector /* metadata */);
+    mBuilder.Finish(flatbufferModel);
+
+    const tflite::Model* tfliteModel = tflite::GetModel(mBuilder.GetBufferPointer());
+    verifyModel(tfliteModel);
+    return tfliteModel;
+}
+
+Result<SubGraphFlatbuffer> FlatbufferModelBuilder::createSubGraphFlatbuffer(
+        const Model::Subgraph& subgraph) {
+    // TFLite does not support unspecified ranks in Operands
+    NN_TRY(checkAllTensorOperandsHaveSpecifiedRank(subgraph.operands));
+    // TFLite does not support dynamic shapes for subgrah output Operands
+    NN_TRY(checkNoSubgraphOutputOperandsHaveDynamicShape(subgraph.operands));
+
+    SubGraphContext context(&mModel, &subgraph, &mBuilder, &mOpCodesVector,
+                            &mOpCodeIndexForOperationType, &mBufferVector);
+    for (const Operation& operation : subgraph.operations) {
+        const IOperationConverter* converter =
+                OperationConverterResolver::get()->findOperationConverter(operation.type);
+        NN_RET_CHECK(converter != nullptr)
+                << "IOperationConverter not implemented for OperationType: " << operation.type;
+
+        NN_TRY(converter->convert(operation, &context));
+    }
+
+    for (uint32_t idx : subgraph.inputIndexes) {
+        context.addSubGraphInput(idx);
+    }
+    for (uint32_t idx : subgraph.outputIndexes) {
+        context.addSubGraphOutput(idx);
+    }
+
+    return context.finish();
+}
+
+Result<std::vector<SubGraphFlatbuffer>> FlatbufferModelBuilder::createSubGraphs() {
+    // We do not support control flow yet
+    NN_RET_CHECK(mModel.referenced.empty()) << "Control flow for multiple subgraphs not supported";
+
+    std::vector<SubGraphFlatbuffer> subGraphVector;
+
+    auto mainSubGraph = NN_TRY(createSubGraphFlatbuffer(mModel.main));
+    subGraphVector.push_back(mainSubGraph);
+
+    return subGraphVector;
+}
+
+}  // namespace nn
+}  // namespace android
diff --git a/runtime/FlatbufferModelBuilder.h b/runtime/FlatbufferModelBuilder.h
new file mode 100644
index 0000000..2356cc6
--- /dev/null
+++ b/runtime/FlatbufferModelBuilder.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_FLATBUFFER_MODEL_BUILDER_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_FLATBUFFER_MODEL_BUILDER_H
+
+#include <tensorflow/lite/schema/schema_generated.h>
+
+#include <utility>
+#include <vector>
+
+#include "FlatbufferModelBuilderUtils.h"
+#include "ModelBuilder.h"
+#include "NeuralNetworks.h"
+
+namespace android {
+namespace nn {
+
+class FlatbufferModelBuilder : public ModelBuilder {
+   public:
+    // Return generated TFLite Model if successful
+    Result<const tflite::Model*> createTfliteModel();
+
+   private:
+    void verifyModel(const tflite::Model* model);
+
+    // Clears mBufferVector and initializes the first Buffer to be an empty Buffer
+    // for Tensors that do not have a buffer.
+    void initializeBufferVector();
+    // Clears mOpCodeIndexForOperationType and initializes elements to be -1
+    void initializeOpCodeIndexForOperationType();
+
+    // Helper functions to convert Subgraphs
+    Result<SubGraphFlatbuffer> createSubGraphFlatbuffer(const Model::Subgraph& subgraph);
+    Result<std::vector<SubGraphFlatbuffer>> createSubGraphs();
+
+    // Generates metadata for each Buffer
+    // Must be called after mBufferVector is filled.
+    std::vector<MetadataFlatbuffer> createMetadataVector();
+
+    flatbuffers::FlatBufferBuilder mBuilder;
+    Model mModel;
+
+    std::vector<OperatorCodeFlatbuffer> mOpCodesVector;
+    std::vector<int> mOpCodeIndexForOperationType;
+    std::vector<BufferFlatbuffer> mBufferVector;
+};
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_FLATBUFFER_MODEL_BUILDER_H
diff --git a/runtime/FlatbufferModelBuilderUtils.h b/runtime/FlatbufferModelBuilderUtils.h
new file mode 100644
index 0000000..106d931
--- /dev/null
+++ b/runtime/FlatbufferModelBuilderUtils.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_FLATBUFFER_MODEL_BUILDER_UTILS_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_FLATBUFFER_MODEL_BUILDER_UTILS_H
+
+#include <nnapi/Result.h>
+#include <nnapi/TypeUtils.h>
+#include <tensorflow/lite/schema/schema_generated.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "NeuralNetworks.h"
+#include "TypeManager.h"
+
+namespace android {
+namespace nn {
+
+using SubGraphFlatbuffer = flatbuffers::Offset<tflite::SubGraph>;
+using SubGraphsFlatbuffer = flatbuffers::Offset<flatbuffers::Vector<SubGraphFlatbuffer>>;
+
+using OperatorCodeFlatbuffer = flatbuffers::Offset<tflite::OperatorCode>;
+using OperatorFlatbuffer = flatbuffers::Offset<tflite::Operator>;
+using OperatorsFlatbuffer = flatbuffers::Offset<flatbuffers::Vector<OperatorFlatbuffer>>;
+
+using TensorFlatbuffer = flatbuffers::Offset<tflite::Tensor>;
+using TensorsFlatbuffer = flatbuffers::Offset<flatbuffers::Vector<TensorFlatbuffer>>;
+
+using BufferFlatbuffer = flatbuffers::Offset<tflite::Buffer>;
+
+using MetadataFlatbuffer = flatbuffers::Offset<tflite::Metadata>;
+
+using ModelFlatbuffer = flatbuffers::Offset<tflite::Model>;
+
+// Only supports tensor types
+// Will crash if passed in a scalar type
+inline Result<tflite::TensorType> getTensorFlatbufferOperandType(const OperandType& type) {
+    CHECK(TypeManager::get()->isTensorType(type));
+
+    // TODO: Map more operands
+    switch (type) {
+        case OperandType::TENSOR_FLOAT32:
+            return tflite::TensorType::TensorType_FLOAT32;
+        case OperandType::TENSOR_INT32:
+            return tflite::TensorType::TensorType_INT32;
+        case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+            return tflite::TensorType::TensorType_INT8;
+        default:
+            NN_RET_CHECK_FAIL() << "OperandType not supported: " << type;
+    }
+}
+
+inline tflite::BuiltinOperator getFlatbufferOperator(const OperationType& type) {
+    // TODO: Add more operation types
+    switch (type) {
+        case OperationType::PAD:
+            return tflite::BuiltinOperator::BuiltinOperator_PAD;
+        case OperationType::CONV_2D:
+            return tflite::BuiltinOperator::BuiltinOperator_CONV_2D;
+        case OperationType::ADD:
+            return tflite::BuiltinOperator::BuiltinOperator_ADD;
+        case OperationType::DEPTHWISE_CONV_2D:
+            return tflite::BuiltinOperator::BuiltinOperator_DEPTHWISE_CONV_2D;
+        case OperationType::LOGISTIC:
+            return tflite::BuiltinOperator::BuiltinOperator_LOGISTIC;
+        default:
+            LOG(FATAL) << "OperationType not supported: " << type;
+            return {};
+    }
+}
+
+// Referenced from external/tensorflow/tensorflow/lite/tools/versioning/op_version.cc
+inline int32_t getMaxOperatorVersionCode(tflite::BuiltinOperator builtinCode) {
+    // TODO: Add more builtin_codes
+    switch (builtinCode) {
+        case tflite::BuiltinOperator::BuiltinOperator_CONV_2D:
+            return 5;
+        case tflite::BuiltinOperator::BuiltinOperator_DEPTHWISE_CONV_2D:
+            return 6;
+        case tflite::BuiltinOperator::BuiltinOperator_ADD:
+            return 4;
+        case tflite::BuiltinOperator::BuiltinOperator_PAD:
+            return 4;
+        case tflite::BuiltinOperator::BuiltinOperator_LOGISTIC:
+            return 3;
+        default:
+            LOG(FATAL) << "BuiltinOperator not supported: " << builtinCode;
+            return {};
+    }
+}
+
+inline Result<tflite::ActivationFunctionType> getTfliteActivation(FusedActivationFunc activation) {
+    switch (activation) {
+        case FusedActivationFunc::NONE:
+            return tflite::ActivationFunctionType::ActivationFunctionType_NONE;
+        case FusedActivationFunc::RELU:
+            return tflite::ActivationFunctionType::ActivationFunctionType_RELU;
+        case FusedActivationFunc::RELU1:
+            return tflite::ActivationFunctionType::ActivationFunctionType_RELU_N1_TO_1;
+        case FusedActivationFunc::RELU6:
+            return tflite::ActivationFunctionType::ActivationFunctionType_RELU6;
+        default:
+            NN_RET_CHECK_FAIL() << "FusedActivationFunc not supported: " << activation;
+    }
+}
+
+inline bool tensorOperandHasUnspecifiedRank(const Operand& operand) {
+    return TypeManager::get()->isTensorType(operand.type) && operand.dimensions.empty();
+}
+
+inline Result<void> checkAllTensorOperandsHaveSpecifiedRank(const std::vector<Operand>& operands) {
+    NN_RET_CHECK(std::none_of(operands.begin(), operands.end(), &tensorOperandHasUnspecifiedRank))
+            << "At least one Operand has unspecified rank";
+    return {};
+}
+
+inline bool subgraphOutputOperandHasDynamicShape(const Operand& operand) {
+    return operand.lifetime == Operand::LifeTime::SUBGRAPH_OUTPUT &&
+           std::any_of(operand.dimensions.begin(), operand.dimensions.end(),
+                       [](const uint32_t& dim) { return dim == 0; });
+}
+
+inline Result<void> checkNoSubgraphOutputOperandsHaveDynamicShape(
+        const std::vector<Operand>& operands) {
+    NN_RET_CHECK(
+            std::none_of(operands.begin(), operands.end(), &subgraphOutputOperandHasDynamicShape))
+            << "At least one subgraph output Operand has dynamic shape";
+    return {};
+}
+
+inline bool isOperandConstant(const Operand& operand) {
+    return operand.lifetime == Operand::LifeTime::CONSTANT_COPY ||
+           operand.lifetime == Operand::LifeTime::CONSTANT_REFERENCE;
+}
+
+inline tflite::Padding getTFLitePadding(int32_t paddingType) {
+    switch (paddingType) {
+        case ANEURALNETWORKS_PADDING_VALID:  // VALID
+        case 0:
+            return tflite::Padding::Padding_VALID;
+        case ANEURALNETWORKS_PADDING_SAME:  // SAME
+            return tflite::Padding::Padding_SAME;
+        default:
+            LOG(FATAL) << "Unsupported NNAPI NDK padding type: " << paddingType;
+            return {};
+    }
+}
+
+// Replace all 0 dimensions to -1 since TFLite only supports -1 as an unknown dimension
+inline void replaceZeroDimensions(std::vector<int32_t>* dims) {
+    std::replace(dims->begin(), dims->end(), 0, -1);
+}
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_FLATBUFFER_MODEL_BUILDER_UTILS_H
diff --git a/runtime/NeuralNetworks.cpp b/runtime/NeuralNetworks.cpp
index 28b7a69..127a452 100644
--- a/runtime/NeuralNetworks.cpp
+++ b/runtime/NeuralNetworks.cpp
@@ -716,6 +716,29 @@
     return ANEURALNETWORKS_NO_ERROR;
 }
 
+#ifdef NN_DEBUGGABLE
+static int64_t sRuntimeFeatureLevel = 0;
+void forTest_setRuntimeFeatureLevel(int64_t level) {
+    sRuntimeFeatureLevel = level;
+}
+#endif
+
+// Since ANeuralNetworks_getRuntimeFeatureLevel is new in 31 while libneuralnetwork targets
+// "min_sdk_version: 30", calling it should be properly guarded (e.g. __builtin_available).
+// But calling it within the same compilation unit is perfectly fine. Guarding it doesn't
+// make any sense and is simply wrong. (It's available on a system where __builtin_available(30)
+// evaluates to false.)
+// To make the compiler happy we introduce getRuntimeFeatureLevelImpl() and call it within the
+// library.
+static inline int64_t getRuntimeFeatureLevelImpl() {
+#ifdef NN_DEBUGGABLE
+    if (sRuntimeFeatureLevel) {
+        return sRuntimeFeatureLevel;
+    }
+#endif
+    return DeviceManager::get()->getRuntimeFeatureLevel();
+}
+
 int ANeuralNetworksDevice_getFeatureLevel(const ANeuralNetworksDevice* device,
                                           int64_t* featureLevel) {
     if (device == nullptr || featureLevel == nullptr) {
@@ -727,7 +750,7 @@
     if (dFeatureLevel < 0) {
         return ANEURALNETWORKS_BAD_STATE;
     }
-    *featureLevel = std::min(ANeuralNetworks_getRuntimeFeatureLevel(), dFeatureLevel);
+    *featureLevel = std::min(getRuntimeFeatureLevelImpl(), dFeatureLevel);
     return ANEURALNETWORKS_NO_ERROR;
 }
 
@@ -1661,20 +1684,8 @@
     return n;
 }
 
-#ifdef NN_DEBUGGABLE
-static int64_t sRuntimeFeatureLevel = 0;
-void forTest_setRuntimeFeatureLevel(int64_t level) {
-    sRuntimeFeatureLevel = level;
-}
-#endif
-
 int64_t ANeuralNetworks_getRuntimeFeatureLevel() {
-#ifdef NN_DEBUGGABLE
-    if (sRuntimeFeatureLevel) {
-        return sRuntimeFeatureLevel;
-    }
-#endif
-    return DeviceManager::get()->getRuntimeFeatureLevel();
+    return getRuntimeFeatureLevelImpl();
 }
 
 int ANeuralNetworksExecution_enableInputAndOutputPadding(ANeuralNetworksExecution* execution,
diff --git a/runtime/NeuralNetworksV2.cpp b/runtime/NeuralNetworksV2.cpp
new file mode 100644
index 0000000..2104994
--- /dev/null
+++ b/runtime/NeuralNetworksV2.cpp
@@ -0,0 +1,1674 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contains all the entry points to the C Neural Networks API.
+// We do basic validation of the operands and then call the class
+// that implements the functionality.
+
+#define LOG_TAG "NeuralNetworks"
+
+#include <ControlFlow.h>
+#include <LegacyUtils.h>
+#include <MetaModel.h>
+#include <Tracing.h>
+#include <nnapi/Types.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "BurstBuilder.h"
+#include "CompilationBuilder.h"
+#include "Event.h"
+#include "ExecutionBuilder.h"
+#include "ExecutionCallback.h"
+#include "FlatbufferModelBuilder.h"
+#include "Manager.h"
+#include "Memory.h"
+#include "NeuralNetworks.h"
+#include "NeuralNetworksExtensions.h"
+#include "NeuralNetworksOEM.h"
+#include "Telemetry.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#include "tensorflow/lite/interpreter.h"
+#include "tensorflow/lite/kernels/register.h"
+#include "tensorflow/lite/model.h"
+#pragma clang diagnostic pop
+
+using namespace android::nn;
+
+// Make sure the constants defined in the header files have not changed values.
+// IMPORTANT: When adding new values, update kNumberOfDataTypes or kNumberOfDataTypesOEM
+// in Utils.h.
+static_assert(ANEURALNETWORKS_FLOAT32 == 0, "ANEURALNETWORKS_FLOAT32 has changed");
+static_assert(ANEURALNETWORKS_INT32 == 1, "ANEURALNETWORKS_INT32 has changed");
+static_assert(ANEURALNETWORKS_UINT32 == 2, "ANEURALNETWORKS_UINT32 has changed");
+static_assert(ANEURALNETWORKS_TENSOR_FLOAT32 == 3, "ANEURALNETWORKS_TENSOR_FLOAT32 has changed");
+static_assert(ANEURALNETWORKS_TENSOR_INT32 == 4, "ANEURALNETWORKS_TENSOR_INT32 has changed");
+static_assert(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM == 5,
+              "ANEURALNETWORKS_TENSOR_QUANT8_ASYMM has changed");
+static_assert(ANEURALNETWORKS_BOOL == 6, "ANEURALNETWORKS_BOOL has changed");
+static_assert(ANEURALNETWORKS_TENSOR_QUANT16_SYMM == 7,
+              "ANEURALNETWORKS_TENSOR_QUANT16_SYMM has changed");
+static_assert(ANEURALNETWORKS_TENSOR_FLOAT16 == 8, "ANEURALNETWORKS_TENSOR_FLOAT16 has changed");
+static_assert(ANEURALNETWORKS_TENSOR_BOOL8 == 9, "ANEURALNETWORKS_TENSOR_BOOL8 has changed");
+static_assert(ANEURALNETWORKS_FLOAT16 == 10, "ANEURALNETWORKS_FLOAT16 has changed");
+static_assert(ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL == 11,
+              "ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL has changed");
+static_assert(ANEURALNETWORKS_TENSOR_QUANT16_ASYMM == 12,
+              "ANEURALNETWORKS_TENSOR_QUANT16_ASYMM has changed");
+static_assert(ANEURALNETWORKS_TENSOR_QUANT8_SYMM == 13,
+              "ANEURALNETWORKS_TENSOR_QUANT8_SYMM has changed");
+static_assert(ANEURALNETWORKS_OEM_SCALAR == 10000, "ANEURALNETWORKS_OEM_SCALAR has changed");
+static_assert(ANEURALNETWORKS_TENSOR_OEM_BYTE == 10001,
+              "ANEURALNETWORKS_TENSOR_OEM_BYTE has changed");
+
+// IMPORTANT: When adding new values, update kNumberOfOperationTypes or
+// kNumberOfOperationTypesOEMin Utils.h.
+static_assert(ANEURALNETWORKS_ADD == 0, "ANEURALNETWORKS_ADD has changed");
+static_assert(ANEURALNETWORKS_AVERAGE_POOL_2D == 1, "ANEURALNETWORKS_AVERAGE_POOL_2D has changed");
+static_assert(ANEURALNETWORKS_CONCATENATION == 2, "ANEURALNETWORKS_CONCATENATION has changed");
+static_assert(ANEURALNETWORKS_CONV_2D == 3, "ANEURALNETWORKS_CONV_2D has changed");
+static_assert(ANEURALNETWORKS_DEPTHWISE_CONV_2D == 4,
+              "ANEURALNETWORKS_DEPTHWISE_CONV_2D has changed");
+static_assert(ANEURALNETWORKS_DEPTH_TO_SPACE == 5, "ANEURALNETWORKS_DEPTH_TO_SPACE has changed");
+static_assert(ANEURALNETWORKS_DEQUANTIZE == 6, "ANEURALNETWORKS_DEQUANTIZE has changed");
+static_assert(ANEURALNETWORKS_EMBEDDING_LOOKUP == 7,
+              "ANEURALNETWORKS_EMBEDDING_LOOKUP has changed");
+static_assert(ANEURALNETWORKS_FLOOR == 8, "ANEURALNETWORKS_FLOOR has changed");
+static_assert(ANEURALNETWORKS_FULLY_CONNECTED == 9, "ANEURALNETWORKS_FULLY_CONNECTED has changed");
+static_assert(ANEURALNETWORKS_HASHTABLE_LOOKUP == 10,
+              "ANEURALNETWORKS_HASHTABLE_LOOKUP has changed");
+static_assert(ANEURALNETWORKS_L2_NORMALIZATION == 11,
+              "ANEURALNETWORKS_L2_NORMALIZATION has changed");
+static_assert(ANEURALNETWORKS_L2_POOL_2D == 12, "ANEURALNETWORKS_L2_POOL has changed");
+static_assert(ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION == 13,
+              "ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION has changed");
+static_assert(ANEURALNETWORKS_LOGISTIC == 14, "ANEURALNETWORKS_LOGISTIC has changed");
+static_assert(ANEURALNETWORKS_LSH_PROJECTION == 15, "ANEURALNETWORKS_LSH_PROJECTION has changed");
+static_assert(ANEURALNETWORKS_LSTM == 16, "ANEURALNETWORKS_LSTM has changed");
+static_assert(ANEURALNETWORKS_MAX_POOL_2D == 17, "ANEURALNETWORKS_MAX_POOL has changed");
+static_assert(ANEURALNETWORKS_MUL == 18, "ANEURALNETWORKS_MUL has changed");
+static_assert(ANEURALNETWORKS_RELU == 19, "ANEURALNETWORKS_RELU has changed");
+static_assert(ANEURALNETWORKS_RELU1 == 20, "ANEURALNETWORKS_RELU1 has changed");
+static_assert(ANEURALNETWORKS_RELU6 == 21, "ANEURALNETWORKS_RELU6 has changed");
+static_assert(ANEURALNETWORKS_RESHAPE == 22, "ANEURALNETWORKS_RESHAPE has changed");
+static_assert(ANEURALNETWORKS_RESIZE_BILINEAR == 23, "ANEURALNETWORKS_RESIZE_BILINEAR has changed");
+static_assert(ANEURALNETWORKS_RNN == 24, "ANEURALNETWORKS_RNN has changed");
+static_assert(ANEURALNETWORKS_SOFTMAX == 25, "ANEURALNETWORKS_SOFTMAX has changed");
+static_assert(ANEURALNETWORKS_SPACE_TO_DEPTH == 26, "ANEURALNETWORKS_SPACE_TO_DEPTH has changed");
+static_assert(ANEURALNETWORKS_SVDF == 27, "ANEURALNETWORKS_SVDF has changed");
+static_assert(ANEURALNETWORKS_TANH == 28, "ANEURALNETWORKS_TANH has changed");
+
+static_assert(ANEURALNETWORKS_BATCH_TO_SPACE_ND == 29,
+              "ANEURALNETWORKS_BATCH_TO_SPACE_ND has changed");
+static_assert(ANEURALNETWORKS_DIV == 30, "ANEURALNETWORKS_DIV has changed");
+static_assert(ANEURALNETWORKS_MEAN == 31, "ANEURALNETWORKS_MEAN has changed");
+static_assert(ANEURALNETWORKS_PAD == 32, "ANEURALNETWORKS_PAD has changed");
+static_assert(ANEURALNETWORKS_SPACE_TO_BATCH_ND == 33,
+              "ANEURALNETWORKS_SPACE_TO_BATCH_ND has changed");
+static_assert(ANEURALNETWORKS_SQUEEZE == 34, "ANEURALNETWORKS_SQUEEZE has changed");
+static_assert(ANEURALNETWORKS_STRIDED_SLICE == 35, "ANEURALNETWORKS_STRIDED_SLICE has changed");
+static_assert(ANEURALNETWORKS_SUB == 36, "ANEURALNETWORKS_TANH has changed");
+static_assert(ANEURALNETWORKS_TRANSPOSE == 37, "ANEURALNETWORKS_TRANSPOSE has changed");
+
+static_assert(ANEURALNETWORKS_ABS == 38, "ANEURALNETWORKS_ABS has changed");
+static_assert(ANEURALNETWORKS_ARGMAX == 39, "ANEURALNETWORKS_ARGMAX has changed");
+static_assert(ANEURALNETWORKS_ARGMIN == 40, "ANEURALNETWORKS_ARGMIN has changed");
+static_assert(ANEURALNETWORKS_AXIS_ALIGNED_BBOX_TRANSFORM == 41,
+              "ANEURALNETWORKS_AXIS_ALIGNED_BBOX_TRANSFORM has changed");
+static_assert(ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_LSTM == 42,
+              "ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_LSTM has changed");
+static_assert(ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_RNN == 43,
+              "ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_RNN has changed");
+static_assert(ANEURALNETWORKS_BOX_WITH_NMS_LIMIT == 44,
+              "ANEURALNETWORKS_BOX_WITH_NMS_LIMIT has changed");
+static_assert(ANEURALNETWORKS_CAST == 45, "ANEURALNETWORKS_CAST has changed");
+static_assert(ANEURALNETWORKS_CHANNEL_SHUFFLE == 46, "ANEURALNETWORKS_CHANNEL_SHUFFLE has changed");
+static_assert(ANEURALNETWORKS_DETECTION_POSTPROCESSING == 47,
+              "ANEURALNETWORKS_DETECTION_POSTPROCESSING has changed");
+static_assert(ANEURALNETWORKS_EQUAL == 48, "ANEURALNETWORKS_EQUAL has changed");
+static_assert(ANEURALNETWORKS_EXP == 49, "ANEURALNETWORKS_EXP has changed");
+static_assert(ANEURALNETWORKS_EXPAND_DIMS == 50, "ANEURALNETWORKS_EXPAND_DIMS has changed");
+static_assert(ANEURALNETWORKS_GATHER == 51, "ANEURALNETWORKS_GATHER has changed");
+static_assert(ANEURALNETWORKS_GENERATE_PROPOSALS == 52,
+              "ANEURALNETWORKS_GENERATE_PROPOSALS has changed");
+static_assert(ANEURALNETWORKS_GREATER == 53, "ANEURALNETWORKS_GREATER has changed");
+static_assert(ANEURALNETWORKS_GREATER_EQUAL == 54, "ANEURALNETWORKS_GREATER_EQUAL has changed");
+static_assert(ANEURALNETWORKS_GROUPED_CONV_2D == 55, "ANEURALNETWORKS_GROUPED_CONV_2D has changed");
+static_assert(ANEURALNETWORKS_HEATMAP_MAX_KEYPOINT == 56,
+              "ANEURALNETWORKS_HEATMAP_MAX_KEYPOINT has changed");
+static_assert(ANEURALNETWORKS_INSTANCE_NORMALIZATION == 57,
+              "ANEURALNETWORKS_INSTANCE_NORMALIZATION has changed");
+static_assert(ANEURALNETWORKS_LESS == 58, "ANEURALNETWORKS_LESS has changed");
+static_assert(ANEURALNETWORKS_LESS_EQUAL == 59, "ANEURALNETWORKS_LESS_EQUAL has changed");
+static_assert(ANEURALNETWORKS_LOG == 60, "ANEURALNETWORKS_LOG has changed");
+static_assert(ANEURALNETWORKS_LOGICAL_AND == 61, "ANEURALNETWORKS_LOGICAL_AND has changed");
+static_assert(ANEURALNETWORKS_LOGICAL_NOT == 62, "ANEURALNETWORKS_LOGICAL_NOT has changed");
+static_assert(ANEURALNETWORKS_LOGICAL_OR == 63, "ANEURALNETWORKS_LOGICAL_OR has changed");
+static_assert(ANEURALNETWORKS_LOG_SOFTMAX == 64, "ANEURALNETWORKS_LOG_SOFTMAX has changed");
+static_assert(ANEURALNETWORKS_MAXIMUM == 65, "ANEURALNETWORKS_MAXIMUM has changed");
+static_assert(ANEURALNETWORKS_MINIMUM == 66, "ANEURALNETWORKS_MINIMUM has changed");
+static_assert(ANEURALNETWORKS_NEG == 67, "ANEURALNETWORKS_NEG has changed");
+static_assert(ANEURALNETWORKS_NOT_EQUAL == 68, "ANEURALNETWORKS_NOT_EQUAL has changed");
+static_assert(ANEURALNETWORKS_PAD_V2 == 69, "ANEURALNETWORKS_PAD_V2 has changed");
+static_assert(ANEURALNETWORKS_POW == 70, "ANEURALNETWORKS_POW has changed");
+static_assert(ANEURALNETWORKS_PRELU == 71, "ANEURALNETWORKS_PRELU has changed");
+static_assert(ANEURALNETWORKS_QUANTIZE == 72, "ANEURALNETWORKS_QUANTIZE has changed");
+static_assert(ANEURALNETWORKS_QUANTIZED_16BIT_LSTM == 73,
+              "ANEURALNETWORKS_QUANTIZED_16BIT_LSTM has changed");
+static_assert(ANEURALNETWORKS_RANDOM_MULTINOMIAL == 74,
+              "ANEURALNETWORKS_RANDOM_MULTINOMIAL has changed");
+static_assert(ANEURALNETWORKS_REDUCE_ALL == 75, "ANEURALNETWORKS_REDUCE_ALL has changed");
+static_assert(ANEURALNETWORKS_REDUCE_ANY == 76, "ANEURALNETWORKS_REDUCE_ANY has changed");
+static_assert(ANEURALNETWORKS_REDUCE_MAX == 77, "ANEURALNETWORKS_REDUCE_MAX has changed");
+static_assert(ANEURALNETWORKS_REDUCE_MIN == 78, "ANEURALNETWORKS_REDUCE_MIN has changed");
+static_assert(ANEURALNETWORKS_REDUCE_PROD == 79, "ANEURALNETWORKS_REDUCE_PROD has changed");
+static_assert(ANEURALNETWORKS_REDUCE_SUM == 80, "ANEURALNETWORKS_REDUCE_SUM has changed");
+static_assert(ANEURALNETWORKS_ROI_ALIGN == 81, "ANEURALNETWORKS_ROI_ALIGN has changed");
+static_assert(ANEURALNETWORKS_ROI_POOLING == 82, "ANEURALNETWORKS_ROI_POOLING has changed");
+static_assert(ANEURALNETWORKS_RSQRT == 83, "ANEURALNETWORKS_RSQRT has changed");
+static_assert(ANEURALNETWORKS_SELECT == 84, "ANEURALNETWORKS_SELECT has changed");
+static_assert(ANEURALNETWORKS_SIN == 85, "ANEURALNETWORKS_SIN has changed");
+static_assert(ANEURALNETWORKS_SLICE == 86, "ANEURALNETWORKS_SLICE has changed");
+static_assert(ANEURALNETWORKS_SPLIT == 87, "ANEURALNETWORKS_SPLIT has changed");
+static_assert(ANEURALNETWORKS_SQRT == 88, "ANEURALNETWORKS_SQRT has changed");
+static_assert(ANEURALNETWORKS_TILE == 89, "ANEURALNETWORKS_TILE has changed");
+static_assert(ANEURALNETWORKS_TOPK_V2 == 90, "ANEURALNETWORKS_TOPK_V2 has changed");
+static_assert(ANEURALNETWORKS_TRANSPOSE_CONV_2D == 91,
+              "ANEURALNETWORKS_TRANSPOSE_CONV_2D has changed");
+static_assert(ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_LSTM == 92,
+              "ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_LSTM has changed");
+static_assert(ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_RNN == 93,
+              "ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_RNN has changed");
+static_assert(ANEURALNETWORKS_RESIZE_NEAREST_NEIGHBOR == 94,
+              "ANEURALNETWORKS_RESIZE_NEAREST_NEIGHBOR has changed");
+static_assert(ANEURALNETWORKS_QUANTIZED_LSTM == 95, "ANEURALNETWORKS_QUANTIZED_LSTM has changed");
+static_assert(ANEURALNETWORKS_IF == 96, "ANEURALNETWORKS_IF has changed");
+static_assert(ANEURALNETWORKS_WHILE == 97, "ANEURALNETWORKS_WHILE has changed");
+static_assert(ANEURALNETWORKS_ELU == 98, "ANEURALNETWORKS_ELU has changed");
+static_assert(ANEURALNETWORKS_HARD_SWISH == 99, "ANEURALNETWORKS_HARD_SWISH has changed");
+static_assert(ANEURALNETWORKS_FILL == 100, "ANEURALNETWORKS_FILL has changed");
+static_assert(ANEURALNETWORKS_RANK == 101, "ANEURALNETWORKS_RANK has changed");
+static_assert(ANEURALNETWORKS_BATCH_MATMUL == 102, "ANEURALNETWORKS_BATCH_MATMUL has changed");
+static_assert(ANEURALNETWORKS_PACK == 103, "ANEURALNETWORKS_PACK has changed");
+static_assert(ANEURALNETWORKS_MIRROR_PAD == 104, "ANEURALNETWORKS_MIRROR_PAD has changed");
+static_assert(ANEURALNETWORKS_REVERSE == 105, "ANEURALNETWORKS_REVERSE has changed");
+static_assert(ANEURALNETWORKS_OEM_OPERATION == 10000, "ANEURALNETWORKS_OEM_OPERATION has changed");
+
+static_assert(ANEURALNETWORKS_FUSED_NONE == 0, "ANEURALNETWORKS_FUSED_NONE has changed");
+static_assert(ANEURALNETWORKS_FUSED_RELU == 1, "ANEURALNETWORKS_FUSED_RELU has changed");
+static_assert(ANEURALNETWORKS_FUSED_RELU1 == 2, "ANEURALNETWORKS_FUSED_RELU1 has changed");
+static_assert(ANEURALNETWORKS_FUSED_RELU6 == 3, "ANEURALNETWORKS_FUSED_RELU6 has changed");
+
+static_assert(ANEURALNETWORKS_PREFER_LOW_POWER == 0,
+              "ANEURALNETWORKS_PREFER_LOW_POWER has changed");
+static_assert(ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER == 1,
+              "ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER has changed");
+static_assert(ANEURALNETWORKS_PREFER_SUSTAINED_SPEED == 2,
+              "ANEURALNETWORKS_PREFER_SUSTAINED_SPEED has changed");
+
+static_assert(ANEURALNETWORKS_NO_ERROR == 0, "ANEURALNETWORKS_NO_ERROR has changed");
+static_assert(ANEURALNETWORKS_OUT_OF_MEMORY == 1, "ANEURALNETWORKS_OUT_OF_MEMORY has changed");
+static_assert(ANEURALNETWORKS_INCOMPLETE == 2, "ANEURALNETWORKS_INCOMPLETE has changed");
+static_assert(ANEURALNETWORKS_UNEXPECTED_NULL == 3, "ANEURALNETWORKS_UNEXPECTED_NULL has changed");
+static_assert(ANEURALNETWORKS_BAD_DATA == 4, "ANEURALNETWORKS_BAD_DATA has changed");
+static_assert(ANEURALNETWORKS_OP_FAILED == 5, "ANEURALNETWORKS_OP_FAILED has changed");
+static_assert(ANEURALNETWORKS_BAD_STATE == 6, "ANEURALNETWORKS_BAD_STATE has changed");
+static_assert(ANEURALNETWORKS_UNMAPPABLE == 7, "ANEURALNETWORKS_UNMAPPABLE has changed");
+static_assert(ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE == 8,
+              "ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE has changed");
+static_assert(ANEURALNETWORKS_UNAVAILABLE_DEVICE == 9,
+              "ANEURALNETWORKS_UNAVAILABLE_DEVICE has changed");
+static_assert(ANEURALNETWORKS_MISSED_DEADLINE_TRANSIENT == 10,
+              "ANEURALNETWORKS_MISSED_DEADLINE_TRANSIENT has changed");
+static_assert(ANEURALNETWORKS_MISSED_DEADLINE_PERSISTENT == 11,
+              "ANEURALNETWORKS_MISSED_DEADLINE_PERSISTENT has changed");
+static_assert(ANEURALNETWORKS_RESOURCE_EXHAUSTED_TRANSIENT == 12,
+              "ANEURALNETWORKS_RESOURCE_EXHAUSTED_TRANSIENT has changed");
+static_assert(ANEURALNETWORKS_RESOURCE_EXHAUSTED_PERSISTENT == 13,
+              "ANEURALNETWORKS_RESOURCE_EXHAUSTED_PERSISTENT has changed");
+static_assert(ANEURALNETWORKS_DEAD_OBJECT == 14, "ANEURALNETWORKS_DEAD_OBJECT has changed");
+
+static_assert(ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES == 128,
+              "ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES has changed");
+
+static_assert(ANEURALNETWORKS_DEVICE_UNKNOWN == 0, "ANEURALNETWORKS_DEVICE_UNKNOWN has changed");
+static_assert(ANEURALNETWORKS_DEVICE_OTHER == 1, "ANEURALNETWORKS_DEVICE_OTHER has changed");
+static_assert(ANEURALNETWORKS_DEVICE_CPU == 2, "ANEURALNETWORKS_DEVICE_CPU has changed");
+static_assert(ANEURALNETWORKS_DEVICE_GPU == 3, "ANEURALNETWORKS_DEVICE_GPU has changed");
+static_assert(ANEURALNETWORKS_DEVICE_ACCELERATOR == 4,
+              "ANEURALNETWORKS_DEVICE_ACCELERATOR has changed");
+
+static_assert(ANEURALNETWORKS_DURATION_ON_HARDWARE == 0,
+              "ANEURALNETWORKS_DURATION_ON_HARDWARE has changed");
+static_assert(ANEURALNETWORKS_DURATION_IN_DRIVER == 1,
+              "ANEURALNETWORKS_DURATION_IN_DRIVER has changed");
+static_assert(ANEURALNETWORKS_FENCED_DURATION_ON_HARDWARE == 2,
+              "ANEURALNETWORKS_FENCED_DURATION_ON_HARDWARE has changed");
+static_assert(ANEURALNETWORKS_FENCED_DURATION_IN_DRIVER == 3,
+              "ANEURALNETWORKS_FENCED_DURATION_IN_DRIVER has changed");
+
+// Make sure that the constants are compatible with the values defined in
+// hardware/interfaces/neuralnetworks/1.0/types.hal.
+static_assert(static_cast<int32_t>(OperandType::OEM) == ANEURALNETWORKS_OEM_SCALAR,
+              "OEM != ANEURALNETWORKS_OEM");
+static_assert(static_cast<int32_t>(OperandType::FLOAT32) == ANEURALNETWORKS_FLOAT32,
+              "FLOAT32 != ANEURALNETWORKS_FLOAT32");
+static_assert(static_cast<int32_t>(OperandType::INT32) == ANEURALNETWORKS_INT32,
+              "INT32 != ANEURALNETWORKS_INT32");
+static_assert(static_cast<int32_t>(OperandType::UINT32) == ANEURALNETWORKS_UINT32,
+              "UINT32 != ANEURALNETWORKS_UINT32");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_OEM_BYTE) == ANEURALNETWORKS_TENSOR_OEM_BYTE,
+              "TENSOR_OEM_BYTE != ANEURALNETWORKS_TENSOR_OEM_BYTE");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_FLOAT32) == ANEURALNETWORKS_TENSOR_FLOAT32,
+              "TENSOR_FLOAT32 != ANEURALNETWORKS_TENSOR_FLOAT32");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_QUANT8_ASYMM) ==
+                      ANEURALNETWORKS_TENSOR_QUANT8_ASYMM,
+              "TENSOR_QUANT8_ASYMM != ANEURALNETWORKS_TENSOR_QUANT8_ASYMM");
+
+static_assert(static_cast<int32_t>(OperationType::ADD) == ANEURALNETWORKS_ADD,
+              "OperationType::ADD != ANEURALNETWORKS_ADD");
+static_assert(static_cast<int32_t>(OperationType::AVERAGE_POOL_2D) ==
+                      ANEURALNETWORKS_AVERAGE_POOL_2D,
+              "OperationType::AVERAGE_POOL_2D != ANEURALNETWORKS_AVERAGE_POOL_2D");
+static_assert(static_cast<int32_t>(OperationType::CONV_2D) == ANEURALNETWORKS_CONV_2D,
+              "OperationType::CONV_2D != ANEURALNETWORKS_CONV_2D");
+static_assert(static_cast<int32_t>(OperationType::DEPTHWISE_CONV_2D) ==
+                      ANEURALNETWORKS_DEPTHWISE_CONV_2D,
+              "OperationType::DEPTHWISE_CONV_2D != ANEURALNETWORKS_DEPTHWISE_CONV_2D");
+static_assert(static_cast<int32_t>(OperationType::DEPTH_TO_SPACE) == ANEURALNETWORKS_DEPTH_TO_SPACE,
+              "OperationType::DEPTH_TO_SPACE != ANEURALNETWORKS_DEPTH_TO_SPACE");
+static_assert(static_cast<int32_t>(OperationType::DEQUANTIZE) == ANEURALNETWORKS_DEQUANTIZE,
+              "OperationType::DEQUANTIZE != ANEURALNETWORKS_DEQUANTIZE");
+static_assert(static_cast<int32_t>(OperationType::EMBEDDING_LOOKUP) ==
+                      ANEURALNETWORKS_EMBEDDING_LOOKUP,
+              "OperationType::EMBEDDING_LOOKUP != ANEURALNETWORKS_EMBEDDING_LOOKUP");
+static_assert(static_cast<int32_t>(OperationType::FLOOR) == ANEURALNETWORKS_FLOOR,
+              "OperationType::FLOOR != ANEURALNETWORKS_FLOOR");
+static_assert(static_cast<int32_t>(OperationType::FULLY_CONNECTED) ==
+                      ANEURALNETWORKS_FULLY_CONNECTED,
+              "OperationType::FULLY_CONNECTED != ANEURALNETWORKS_FULLY_CONNECTED");
+static_assert(static_cast<int32_t>(OperationType::HASHTABLE_LOOKUP) ==
+                      ANEURALNETWORKS_HASHTABLE_LOOKUP,
+              "OperationType::HASHTABLE_LOOKUP != ANEURALNETWORKS_HASHTABLE_LOOKUP");
+static_assert(static_cast<int32_t>(OperationType::L2_NORMALIZATION) ==
+                      ANEURALNETWORKS_L2_NORMALIZATION,
+              "OperationType::L2_NORMALIZATION != ANEURALNETWORKS_L2_NORMALIZATION");
+static_assert(static_cast<int32_t>(OperationType::L2_POOL_2D) == ANEURALNETWORKS_L2_POOL_2D,
+              "OperationType::L2_POOL_2D != ANEURALNETWORKS_L2_POOL_2D");
+static_assert(static_cast<int32_t>(OperationType::LOCAL_RESPONSE_NORMALIZATION) ==
+                      ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION,
+              "OperationType::LOCAL_RESPONSE_NORMALIZATION != "
+              "ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION");
+static_assert(static_cast<int32_t>(OperationType::LOGISTIC) == ANEURALNETWORKS_LOGISTIC,
+              "OperationType::LOGISTIC != ANEURALNETWORKS_LOGISTIC");
+static_assert(static_cast<int32_t>(OperationType::LSH_PROJECTION) == ANEURALNETWORKS_LSH_PROJECTION,
+              "OperationType::LSH_PROJECTION != ANEURALNETWORKS_LSH_PROJECTION");
+static_assert(static_cast<int32_t>(OperationType::LSTM) == ANEURALNETWORKS_LSTM,
+              "OperationType::LSTM != ANEURALNETWORKS_LSTM");
+static_assert(static_cast<int32_t>(OperationType::MAX_POOL_2D) == ANEURALNETWORKS_MAX_POOL_2D,
+              "OperationType::MAX_POOL_2D != ANEURALNETWORKS_MAX_POOL_2D");
+static_assert(static_cast<int32_t>(OperationType::MUL) == ANEURALNETWORKS_MUL,
+              "OperationType::MUL != ANEURALNETWORKS_MUL");
+static_assert(static_cast<int32_t>(OperationType::RELU) == ANEURALNETWORKS_RELU,
+              "OperationType::RELU != ANEURALNETWORKS_RELU");
+static_assert(static_cast<int32_t>(OperationType::RELU1) == ANEURALNETWORKS_RELU1,
+              "OperationType::RELU1 != ANEURALNETWORKS_RELU1");
+static_assert(static_cast<int32_t>(OperationType::RELU6) == ANEURALNETWORKS_RELU6,
+              "OperationType::RELU6 != ANEURALNETWORKS_RELU6");
+static_assert(static_cast<int32_t>(OperationType::RESHAPE) == ANEURALNETWORKS_RESHAPE,
+              "OperationType::RESHAPE != ANEURALNETWORKS_RESHAPE");
+static_assert(static_cast<int32_t>(OperationType::RESIZE_BILINEAR) ==
+                      ANEURALNETWORKS_RESIZE_BILINEAR,
+              "OperationType::RESIZE_BILINEAR != ANEURALNETWORKS_RESIZE_BILINEAR");
+static_assert(static_cast<int32_t>(OperationType::RNN) == ANEURALNETWORKS_RNN,
+              "OperationType::RNN != ANEURALNETWORKS_RNN");
+static_assert(static_cast<int32_t>(OperationType::SOFTMAX) == ANEURALNETWORKS_SOFTMAX,
+              "OperationType::SOFTMAX != ANEURALNETWORKS_SOFTMAX");
+static_assert(static_cast<int32_t>(OperationType::SPACE_TO_DEPTH) == ANEURALNETWORKS_SPACE_TO_DEPTH,
+              "OperationType::SPACE_TO_DEPTH != ANEURALNETWORKS_SPACE_TO_DEPTH");
+static_assert(static_cast<int32_t>(OperationType::SVDF) == ANEURALNETWORKS_SVDF,
+              "OperationType::SVDF != ANEURALNETWORKS_SVDF");
+static_assert(static_cast<int32_t>(OperationType::TANH) == ANEURALNETWORKS_TANH,
+              "OperationType::TANH != ANEURALNETWORKS_TANH");
+
+static_assert(static_cast<int32_t>(FusedActivationFunc::NONE) == ANEURALNETWORKS_FUSED_NONE,
+              "FusedActivationFunc::NONE != ANEURALNETWORKS_FUSED_NONE");
+static_assert(static_cast<int32_t>(FusedActivationFunc::RELU) == ANEURALNETWORKS_FUSED_RELU,
+              "FusedActivationFunc::RELU != ANEURALNETWORKS_FUSED_RELU");
+static_assert(static_cast<int32_t>(FusedActivationFunc::RELU1) == ANEURALNETWORKS_FUSED_RELU1,
+              "FusedActivationFunc::RELU1 != ANEURALNETWORKS_FUSED_RELU1");
+static_assert(static_cast<int32_t>(FusedActivationFunc::RELU6) == ANEURALNETWORKS_FUSED_RELU6,
+              "FusedActivationFunc::RELU6 != ANEURALNETWORKS_FUSED_RELU6");
+
+// Make sure that the constants are compatible with the values defined in
+// hardware/interfaces/neuralnetworks/1.1/types.hal.
+static_assert(static_cast<int32_t>(OperationType::BATCH_TO_SPACE_ND) ==
+                      ANEURALNETWORKS_BATCH_TO_SPACE_ND,
+              "OperationType::BATCH_TO_SPACE_ND != ANEURALNETWORKS_BATCH_TO_SPACE_ND");
+static_assert(static_cast<int32_t>(OperationType::DIV) == ANEURALNETWORKS_DIV,
+              "OperationType::DIV != ANEURALNETWORKS_DIV");
+static_assert(static_cast<int32_t>(OperationType::MEAN) == ANEURALNETWORKS_MEAN,
+              "OperationType::MEAN != ANEURALNETWORKS_MEAN");
+static_assert(static_cast<int32_t>(OperationType::PAD) == ANEURALNETWORKS_PAD,
+              "OperationType::PAD != ANEURALNETWORKS_PAD");
+static_assert(static_cast<int32_t>(OperationType::SPACE_TO_BATCH_ND) ==
+                      ANEURALNETWORKS_SPACE_TO_BATCH_ND,
+              "OperationType::SPACE_TO_BATCH_ND != ANEURALNETWORKS_SPACE_TO_BATCH_ND");
+static_assert(static_cast<int32_t>(OperationType::SQUEEZE) == ANEURALNETWORKS_SQUEEZE,
+              "OperationType::SQUEEZE != ANEURALNETWORKS_SQUEEZE");
+static_assert(static_cast<int32_t>(OperationType::STRIDED_SLICE) == ANEURALNETWORKS_STRIDED_SLICE,
+              "OperationType::STRIDED_SLICE != ANEURALNETWORKS_STRIDED_SLICE");
+static_assert(static_cast<int32_t>(OperationType::SUB) == ANEURALNETWORKS_SUB,
+              "OperationType::SUB != ANEURALNETWORKS_SUB");
+static_assert(static_cast<int32_t>(OperationType::TRANSPOSE) == ANEURALNETWORKS_TRANSPOSE,
+              "OperationType::TRANSPOSE != ANEURALNETWORKS_TRANSPOSE");
+
+// Make sure that the constants are compatible with the values defined in
+// hardware/interfaces/neuralnetworks/1.2/types.hal.
+static_assert(static_cast<int32_t>(OperandType::BOOL) == ANEURALNETWORKS_BOOL,
+              "BOOL != ANEURALNETWORKS_BOOL");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_QUANT16_SYMM) ==
+                      ANEURALNETWORKS_TENSOR_QUANT16_SYMM,
+              "TENSOR_QUANT16_SYMM != ANEURALNETWORKS_TENSOR_QUANT16_SYMM");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_FLOAT16) == ANEURALNETWORKS_TENSOR_FLOAT16,
+              "TENSOR_FLOAT16 != ANEURALNETWORKS_TENSOR_FLOAT16");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_BOOL8) == ANEURALNETWORKS_TENSOR_BOOL8,
+              "TENSOR_BOOL8 != ANEURALNETWORKS_TENSOR_BOOL8");
+static_assert(static_cast<int32_t>(OperandType::FLOAT16) == ANEURALNETWORKS_FLOAT16,
+              "FLOAT16 != ANEURALNETWORKS_FLOAT16");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) ==
+                      ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL,
+              "TENSOR_QUANT8_SYMM_PER_CHANNEL != ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_QUANT16_ASYMM) ==
+                      ANEURALNETWORKS_TENSOR_QUANT16_ASYMM,
+              "TENSOR_QUANT16_ASYMM != ANEURALNETWORKS_TENSOR_QUANT16_ASYMM");
+static_assert(static_cast<int32_t>(OperandType::TENSOR_QUANT8_SYMM) ==
+                      ANEURALNETWORKS_TENSOR_QUANT8_SYMM,
+              "TENSOR_QUANT8_SYMM != ANEURALNETWORKS_TENSOR_QUANT8_SYMM");
+
+static_assert(static_cast<int32_t>(OperationType::ABS) == ANEURALNETWORKS_ABS,
+              "OperationType::ABS != ANEURALNETWORKS_ABS");
+static_assert(static_cast<int32_t>(OperationType::ARGMAX) == ANEURALNETWORKS_ARGMAX,
+              "OperationType::ARGMAX != ANEURALNETWORKS_ARGMAX");
+static_assert(static_cast<int32_t>(OperationType::ARGMIN) == ANEURALNETWORKS_ARGMIN,
+              "OperationType::ARGMIN != ANEURALNETWORKS_ARGMIN");
+static_assert(static_cast<int32_t>(OperationType::AXIS_ALIGNED_BBOX_TRANSFORM) ==
+                      ANEURALNETWORKS_AXIS_ALIGNED_BBOX_TRANSFORM,
+              "OperationType::AXIS_ALIGNED_BBOX_TRANSFORM != "
+              "ANEURALNETWORKS_AXIS_ALIGNED_BBOX_TRANSFORM");
+static_assert(static_cast<int32_t>(OperationType::BIDIRECTIONAL_SEQUENCE_LSTM) ==
+                      ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_LSTM,
+              "OperationType::BIDIRECTIONAL_SEQUENCE_LSTM != "
+              "ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_LSTM");
+static_assert(
+        static_cast<int32_t>(OperationType::BIDIRECTIONAL_SEQUENCE_RNN) ==
+                ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_RNN,
+        "OperationType::BIDIRECTIONAL_SEQUENCE_RNN != ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_RNN");
+static_assert(static_cast<int32_t>(OperationType::BOX_WITH_NMS_LIMIT) ==
+                      ANEURALNETWORKS_BOX_WITH_NMS_LIMIT,
+              "OperationType::BOX_WITH_NMS_LIMIT != ANEURALNETWORKS_BOX_WITH_NMS_LIMIT");
+static_assert(static_cast<int32_t>(OperationType::CAST) == ANEURALNETWORKS_CAST,
+              "OperationType::CAST != ANEURALNETWORKS_CAST");
+static_assert(static_cast<int32_t>(OperationType::CHANNEL_SHUFFLE) ==
+                      ANEURALNETWORKS_CHANNEL_SHUFFLE,
+              "OperationType::CHANNEL_SHUFFLE != ANEURALNETWORKS_CHANNEL_SHUFFLE");
+static_assert(
+        static_cast<int32_t>(OperationType::DETECTION_POSTPROCESSING) ==
+                ANEURALNETWORKS_DETECTION_POSTPROCESSING,
+        "OperationType::DETECTION_POSTPROCESSING != ANEURALNETWORKS_DETECTION_POSTPROCESSING");
+static_assert(static_cast<int32_t>(OperationType::EQUAL) == ANEURALNETWORKS_EQUAL,
+              "OperationType::EQUAL != ANEURALNETWORKS_EQUAL");
+static_assert(static_cast<int32_t>(OperationType::EXP) == ANEURALNETWORKS_EXP,
+              "OperationType::EXP != ANEURALNETWORKS_EXP");
+static_assert(static_cast<int32_t>(OperationType::EXPAND_DIMS) == ANEURALNETWORKS_EXPAND_DIMS,
+              "OperationType::EXPAND_DIMS != ANEURALNETWORKS_EXPAND_DIMS");
+static_assert(static_cast<int32_t>(OperationType::GATHER) == ANEURALNETWORKS_GATHER,
+              "OperationType::GATHER != ANEURALNETWORKS_GATHER");
+static_assert(static_cast<int32_t>(OperationType::GENERATE_PROPOSALS) ==
+                      ANEURALNETWORKS_GENERATE_PROPOSALS,
+              "OperationType::GENERATE_PROPOSALS != ANEURALNETWORKS_GENERATE_PROPOSALS");
+static_assert(static_cast<int32_t>(OperationType::GREATER) == ANEURALNETWORKS_GREATER,
+              "OperationType::GREATER != ANEURALNETWORKS_GREATER");
+static_assert(static_cast<int32_t>(OperationType::GREATER_EQUAL) == ANEURALNETWORKS_GREATER_EQUAL,
+              "OperationType::GREATER_EQUAL != ANEURALNETWORKS_GREATER_EQUAL");
+static_assert(static_cast<int32_t>(OperationType::GROUPED_CONV_2D) ==
+                      ANEURALNETWORKS_GROUPED_CONV_2D,
+              "OperationType::GROUPED_CONV_2D != ANEURALNETWORKS_GROUPED_CONV_2D");
+static_assert(static_cast<int32_t>(OperationType::HEATMAP_MAX_KEYPOINT) ==
+                      ANEURALNETWORKS_HEATMAP_MAX_KEYPOINT,
+              "OperationType::HEATMAP_MAX_KEYPOINT != ANEURALNETWORKS_HEATMAP_MAX_KEYPOINT");
+static_assert(static_cast<int32_t>(OperationType::INSTANCE_NORMALIZATION) ==
+                      ANEURALNETWORKS_INSTANCE_NORMALIZATION,
+              "OperationType::INSTANCE_NORMALIZATION != ANEURALNETWORKS_INSTANCE_NORMALIZATION");
+static_assert(static_cast<int32_t>(OperationType::LESS) == ANEURALNETWORKS_LESS,
+              "OperationType::LESS != ANEURALNETWORKS_LESS");
+static_assert(static_cast<int32_t>(OperationType::LESS_EQUAL) == ANEURALNETWORKS_LESS_EQUAL,
+              "OperationType::LESS_EQUAL != ANEURALNETWORKS_LESS_EQUAL");
+static_assert(static_cast<int32_t>(OperationType::LOG) == ANEURALNETWORKS_LOG,
+              "OperationType::LOG != ANEURALNETWORKS_LOG");
+static_assert(static_cast<int32_t>(OperationType::LOGICAL_AND) == ANEURALNETWORKS_LOGICAL_AND,
+              "OperationType::LOGICAL_AND != ANEURALNETWORKS_LOGICAL_AND");
+static_assert(static_cast<int32_t>(OperationType::LOGICAL_NOT) == ANEURALNETWORKS_LOGICAL_NOT,
+              "OperationType::LOGICAL_NOT != ANEURALNETWORKS_LOGICAL_NOT");
+static_assert(static_cast<int32_t>(OperationType::LOGICAL_OR) == ANEURALNETWORKS_LOGICAL_OR,
+              "OperationType::LOGICAL_OR != ANEURALNETWORKS_LOGICAL_OR");
+static_assert(static_cast<int32_t>(OperationType::LOG_SOFTMAX) == ANEURALNETWORKS_LOG_SOFTMAX,
+              "OperationType::LOG_SOFTMAX != ANEURALNETWORKS_LOG_SOFTMAX");
+static_assert(static_cast<int32_t>(OperationType::MAXIMUM) == ANEURALNETWORKS_MAXIMUM,
+              "OperationType::MAXIMUM != ANEURALNETWORKS_MAXIMUM");
+static_assert(static_cast<int32_t>(OperationType::MINIMUM) == ANEURALNETWORKS_MINIMUM,
+              "OperationType::MINIMUM != ANEURALNETWORKS_MINIMUM");
+static_assert(static_cast<int32_t>(OperationType::NEG) == ANEURALNETWORKS_NEG,
+              "OperationType::NEG != ANEURALNETWORKS_NEG");
+static_assert(static_cast<int32_t>(OperationType::NOT_EQUAL) == ANEURALNETWORKS_NOT_EQUAL,
+              "OperationType::NOT_EQUAL != ANEURALNETWORKS_NOT_EQUAL");
+static_assert(static_cast<int32_t>(OperationType::PAD_V2) == ANEURALNETWORKS_PAD_V2,
+              "OperationType::PAD_V2 != ANEURALNETWORKS_PAD_V2");
+static_assert(static_cast<int32_t>(OperationType::POW) == ANEURALNETWORKS_POW,
+              "OperationType::POW != ANEURALNETWORKS_POW");
+static_assert(static_cast<int32_t>(OperationType::PRELU) == ANEURALNETWORKS_PRELU,
+              "OperationType::PRELU != ANEURALNETWORKS_PRELU");
+static_assert(static_cast<int32_t>(OperationType::QUANTIZE) == ANEURALNETWORKS_QUANTIZE,
+              "OperationType::QUANTIZE != ANEURALNETWORKS_QUANTIZE");
+static_assert(static_cast<int32_t>(OperationType::QUANTIZED_16BIT_LSTM) ==
+                      ANEURALNETWORKS_QUANTIZED_16BIT_LSTM,
+              "OperationType::QUANTIZED_16BIT_LSTM != ANEURALNETWORKS_QUANTIZED_16BIT_LSTM");
+static_assert(static_cast<int32_t>(OperationType::RANDOM_MULTINOMIAL) ==
+                      ANEURALNETWORKS_RANDOM_MULTINOMIAL,
+              "OperationType::RANDOM_MULTINOMIAL != ANEURALNETWORKS_RANDOM_MULTINOMIAL");
+static_assert(static_cast<int32_t>(OperationType::REDUCE_ALL) == ANEURALNETWORKS_REDUCE_ALL,
+              "OperationType::REDUCE_ALL != ANEURALNETWORKS_REDUCE_ALL");
+static_assert(static_cast<int32_t>(OperationType::REDUCE_ANY) == ANEURALNETWORKS_REDUCE_ANY,
+              "OperationType::REDUCE_ANY != ANEURALNETWORKS_REDUCE_ANY");
+static_assert(static_cast<int32_t>(OperationType::REDUCE_MAX) == ANEURALNETWORKS_REDUCE_MAX,
+              "OperationType::REDUCE_MAX != ANEURALNETWORKS_REDUCE_MAX");
+static_assert(static_cast<int32_t>(OperationType::REDUCE_MIN) == ANEURALNETWORKS_REDUCE_MIN,
+              "OperationType::REDUCE_MIN != ANEURALNETWORKS_REDUCE_MIN");
+static_assert(static_cast<int32_t>(OperationType::REDUCE_PROD) == ANEURALNETWORKS_REDUCE_PROD,
+              "OperationType::REDUCE_PROD != ANEURALNETWORKS_REDUCE_PROD");
+static_assert(static_cast<int32_t>(OperationType::REDUCE_SUM) == ANEURALNETWORKS_REDUCE_SUM,
+              "OperationType::REDUCE_SUM != ANEURALNETWORKS_REDUCE_SUM");
+static_assert(static_cast<int32_t>(OperationType::ROI_ALIGN) == ANEURALNETWORKS_ROI_ALIGN,
+              "OperationType::ROI_ALIGN != ANEURALNETWORKS_ROI_ALIGN");
+static_assert(static_cast<int32_t>(OperationType::ROI_POOLING) == ANEURALNETWORKS_ROI_POOLING,
+              "OperationType::ROI_POOLING != ANEURALNETWORKS_ROI_POOLING");
+static_assert(static_cast<int32_t>(OperationType::RSQRT) == ANEURALNETWORKS_RSQRT,
+              "OperationType::RSQRT != ANEURALNETWORKS_RSQRT");
+static_assert(static_cast<int32_t>(OperationType::SELECT) == ANEURALNETWORKS_SELECT,
+              "OperationType::SELECT != ANEURALNETWORKS_SELECT");
+static_assert(static_cast<int32_t>(OperationType::SIN) == ANEURALNETWORKS_SIN,
+              "OperationType::SIN != ANEURALNETWORKS_SIN");
+static_assert(static_cast<int32_t>(OperationType::SLICE) == ANEURALNETWORKS_SLICE,
+              "OperationType::SLICE != ANEURALNETWORKS_SLICE");
+static_assert(static_cast<int32_t>(OperationType::SPLIT) == ANEURALNETWORKS_SPLIT,
+              "OperationType::SPLIT != ANEURALNETWORKS_SPLIT");
+static_assert(static_cast<int32_t>(OperationType::SQRT) == ANEURALNETWORKS_SQRT,
+              "OperationType::SQRT != ANEURALNETWORKS_SQRT");
+static_assert(static_cast<int32_t>(OperationType::TILE) == ANEURALNETWORKS_TILE,
+              "OperationType::TILE != ANEURALNETWORKS_TILE");
+static_assert(static_cast<int32_t>(OperationType::TOPK_V2) == ANEURALNETWORKS_TOPK_V2,
+              "OperationType::TOPK_V2 != ANEURALNETWORKS_TOPK_V2");
+static_assert(static_cast<int32_t>(OperationType::TRANSPOSE_CONV_2D) ==
+                      ANEURALNETWORKS_TRANSPOSE_CONV_2D,
+              "OperationType::TRANSPOSE_CONV_2D != ANEURALNETWORKS_TRANSPOSE_CONV_2D");
+static_assert(static_cast<int32_t>(OperationType::UNIDIRECTIONAL_SEQUENCE_LSTM) ==
+                      ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_LSTM,
+              "OperationType::UNIDIRECTIONAL_SEQUENCE_LSTM != "
+              "ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_LSTM");
+static_assert(static_cast<int32_t>(OperationType::UNIDIRECTIONAL_SEQUENCE_RNN) ==
+                      ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_RNN,
+              "OperationType::UNIDIRECTIONAL_SEQUENCE_RNN != "
+              "ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_RNN");
+static_assert(static_cast<int32_t>(OperationType::RESIZE_NEAREST_NEIGHBOR) ==
+                      ANEURALNETWORKS_RESIZE_NEAREST_NEIGHBOR,
+              "OperationType::RESIZE_NEAREST_NEIGHBOR != ANEURALNETWORKS_RESIZE_NEAREST_NEIGHBOR");
+static_assert(static_cast<int32_t>(OperationType::QUANTIZED_LSTM) == ANEURALNETWORKS_QUANTIZED_LSTM,
+              "OperationType::QUANTIZED_LSTM != ANEURALNETWORKS_QUANTIZED_LSTM");
+static_assert(static_cast<int32_t>(OperationType::IF) == ANEURALNETWORKS_IF,
+              "OperationType::IF != ANEURALNETWORKS_IF");
+static_assert(static_cast<int32_t>(OperationType::WHILE) == ANEURALNETWORKS_WHILE,
+              "OperationType::WHILE != ANEURALNETWORKS_WHILE");
+static_assert(static_cast<int32_t>(OperationType::ELU) == ANEURALNETWORKS_ELU,
+              "OperationType::ELU != ANEURALNETWORKS_ELU");
+static_assert(static_cast<int32_t>(OperationType::HARD_SWISH) == ANEURALNETWORKS_HARD_SWISH,
+              "OperationType::HARD_SWISH != ANEURALNETWORKS_HARD_SWISH");
+static_assert(static_cast<int32_t>(OperationType::FILL) == ANEURALNETWORKS_FILL,
+              "OperationType::FILL != ANEURALNETWORKS_FILL");
+static_assert(static_cast<int32_t>(OperationType::RANK) == ANEURALNETWORKS_RANK,
+              "OperationType::RANK != ANEURALNETWORKS_RANK");
+static_assert(static_cast<int32_t>(OperationType::BATCH_MATMUL) == ANEURALNETWORKS_BATCH_MATMUL,
+              "OperationType::BATCH_MATMUL != ANEURALNETWORKS_BATCH_MATMUL");
+static_assert(static_cast<int32_t>(OperationType::PACK) == ANEURALNETWORKS_PACK,
+              "OperationType::PACK != ANEURALNETWORKS_PACK");
+static_assert(static_cast<int32_t>(OperationType::MIRROR_PAD) == ANEURALNETWORKS_MIRROR_PAD,
+              "OperationType::MIRROR_PAD != ANEURALNETWORKS_MIRROR_PAD");
+static_assert(static_cast<int32_t>(OperationType::REVERSE) == ANEURALNETWORKS_REVERSE,
+              "OperationType::REVERSE != ANEURALNETWORKS_REVERSE");
+
+static_assert(static_cast<int32_t>(DeviceType::OTHER) == ANEURALNETWORKS_DEVICE_OTHER,
+              "DeviceType::OTHER != ANEURALNETWORKS_DEVICE_OTHER");
+static_assert(static_cast<int32_t>(DeviceType::CPU) == ANEURALNETWORKS_DEVICE_CPU,
+              "DeviceType::CPU != ANEURALNETWORKS_DEVICE_CPU");
+static_assert(static_cast<int32_t>(DeviceType::GPU) == ANEURALNETWORKS_DEVICE_GPU,
+              "DeviceType::GPU != ANEURALNETWORKS_DEVICE_GPU");
+static_assert(static_cast<int32_t>(DeviceType::ACCELERATOR) == ANEURALNETWORKS_DEVICE_ACCELERATOR,
+              "DeviceType::ACCELERATOR != ANEURALNETWORKS_DEVICE_ACCELERATOR");
+
+// Make sure that the constants are compatible with the values defined in
+// hardware/interfaces/neuralnetworks/1.3/types.hal.
+static_assert(android::nn::convertToCanonicalPriority(ANEURALNETWORKS_PRIORITY_LOW) ==
+                      Priority::LOW,
+              "ANEURALNETWORKS_PRIORITY_LOW does not map to Priority::LOW");
+static_assert(android::nn::convertToCanonicalPriority(ANEURALNETWORKS_PRIORITY_MEDIUM) ==
+                      Priority::MEDIUM,
+              "ANEURALNETWORKS_PRIORITY_MEDIUM does not map to Priority::MEDIUM");
+static_assert(android::nn::convertToCanonicalPriority(ANEURALNETWORKS_PRIORITY_HIGH) ==
+                      Priority::HIGH,
+              "ANEURALNETWORKS_PRIORITY_HIGH does not map to Priority::HIGH");
+
+// Asserts for ANeuralNetworksOperandType memory layout
+static_assert(offsetof(ANeuralNetworksOperandType, type) == 0,
+              "ANeuralNetworksOperandType.type offset != 0");
+static_assert(offsetof(ANeuralNetworksOperandType, dimensionCount) == 4,
+              "ANeuralNetworksOperandType.dimensionCount offset != 4");
+static_assert(offsetof(ANeuralNetworksOperandType, dimensions) == 8,
+              "ANeuralNetworksOperandType.dimensions offset != 8");
+static_assert(offsetof(ANeuralNetworksOperandType, scale) == 8 + sizeof(void*),
+              "ANeuralNetworksOperandType.scale offset != 8 + sizeof(void*)");
+static_assert(offsetof(ANeuralNetworksOperandType, zeroPoint) == 12 + sizeof(void*),
+              "ANeuralNetworksOperandType.zeroPoint offset != 12 + sizeof(void*)");
+static_assert(sizeof(ANeuralNetworksOperandType) == 16 + sizeof(void*),
+              "ANeuralNetworksOperandType size changed");
+static_assert(alignof(ANeuralNetworksOperandType) == alignof(void*),
+              "ANeuralNetworksOperandType alignment changed");
+
+// Asserts for ANeuralNetworksSymmPerChannelQuantParams memory layout
+static_assert(offsetof(ANeuralNetworksSymmPerChannelQuantParams, channelDim) == 0,
+              "ANeuralNetworksSymmPerChannelQuantParams.channelDim offset != 4 + sizeof(void*)");
+static_assert(offsetof(ANeuralNetworksSymmPerChannelQuantParams, scaleCount) == 4,
+              "ANeuralNetworksSymmPerChannelQuantParams.scaleCount offset != 0");
+static_assert(offsetof(ANeuralNetworksSymmPerChannelQuantParams, scales) == 8,
+              "ANeuralNetworksSymmPerChannelQuantParams.scales offset != 4");
+static_assert(sizeof(ANeuralNetworksSymmPerChannelQuantParams) == 8 + sizeof(void*),
+              "ANeuralNetworksSymmPerChannelQuantParams size != 8 + sizeof(void*)");
+static_assert(alignof(ANeuralNetworksSymmPerChannelQuantParams) == alignof(void*),
+              "ANeuralNetworksOperandType alignment changed");
+
+// Asserts for compilation caching
+static_assert(ANEURALNETWORKS_BYTE_SIZE_OF_CACHE_TOKEN == 32,
+              "ANEURALNETWORKS_BYTE_SIZE_OF_CACHE_TOKEN has changed");
+static_assert(ANEURALNETWORKS_BYTE_SIZE_OF_CACHE_TOKEN == kByteSizeOfCacheToken,
+              "ANEURALNETWORKS_BYTE_SIZE_OF_CACHE_TOKEN != kByteSizeOfCacheToken");
+
+// Asserts for compilation priority
+static_assert(ANEURALNETWORKS_PRIORITY_LOW == 90, "ANEURALNETWORKS_PRIORITY_LOW has changed");
+static_assert(ANEURALNETWORKS_PRIORITY_MEDIUM == 100,
+              "ANEURALNETWORKS_PRIORITY_MEDIUM has changed");
+static_assert(ANEURALNETWORKS_PRIORITY_HIGH == 110, "ANEURALNETWORKS_PRIORITY_HIGH has changed");
+static_assert(ANEURALNETWORKS_PRIORITY_DEFAULT == ANEURALNETWORKS_PRIORITY_MEDIUM,
+              "ANEURALNETWORKS_PRIORITY_DEFAULT has changed");
+
+// Asserts for feature levels
+static_assert(ANEURALNETWORKS_FEATURE_LEVEL_1 == 27, "ANEURALNETWORKS_FEATURE_LEVEL_1 has changed");
+static_assert(ANEURALNETWORKS_FEATURE_LEVEL_2 == 28, "ANEURALNETWORKS_FEATURE_LEVEL_2 has changed");
+static_assert(ANEURALNETWORKS_FEATURE_LEVEL_3 == 29, "ANEURALNETWORKS_FEATURE_LEVEL_3 has changed");
+static_assert(ANEURALNETWORKS_FEATURE_LEVEL_4 == 30, "ANEURALNETWORKS_FEATURE_LEVEL_4 has changed");
+static_assert(ANEURALNETWORKS_FEATURE_LEVEL_5 == 31, "ANEURALNETWORKS_FEATURE_LEVEL_5 has changed");
+static_assert(ANEURALNETWORKS_FEATURE_LEVEL_6 == 1000006,
+              "ANEURALNETWORKS_FEATURE_LEVEL_6 has changed");
+static_assert(ANEURALNETWORKS_FEATURE_LEVEL_7 == 1000007,
+              "ANEURALNETWORKS_FEATURE_LEVEL_7 has changed");
+static_assert(ANEURALNETWORKS_FEATURE_LEVEL_8 == 1000008,
+              "ANEURALNETWORKS_FEATURE_LEVEL_8 has changed");
+
+int ANeuralNetworks_getDeviceCount(uint32_t* numDevices) {
+    if (numDevices == nullptr) {
+        LOG(ERROR) << "ANeuralNetworks_getDeviceCount passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    *numDevices = DeviceManager::get()->getDrivers().size();
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworks_getDevice(uint32_t devIndex, ANeuralNetworksDevice** device) {
+    if (device == nullptr) {
+        LOG(ERROR) << "ANeuralNetworks_getDevice passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const std::vector<std::shared_ptr<Device>>& devices = DeviceManager::get()->getDrivers();
+    if (devIndex >= devices.size()) {
+        LOG(ERROR) << "ANeuralNetworks_getDevice passed an invalid device index";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+    *device = reinterpret_cast<ANeuralNetworksDevice*>(devices.at(devIndex).get());
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksDevice_getName(const ANeuralNetworksDevice* device, const char** name) {
+    if (device == nullptr || name == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksDevice_getName passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const Device* d = reinterpret_cast<const Device*>(device);
+    *name = d->getName().c_str();
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksDevice_getVersion(const ANeuralNetworksDevice* device, const char** version) {
+    if (device == nullptr || version == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksDevice_getVersion passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const Device* d = reinterpret_cast<const Device*>(device);
+    *version = d->getVersionString().c_str();
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksDevice_getType(const ANeuralNetworksDevice* device, int32_t* type) {
+    if (device == nullptr || type == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksDevice_getType passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const Device* d = reinterpret_cast<const Device*>(device);
+    int32_t dType = d->getType();
+    if (dType < 0) {
+        return ANEURALNETWORKS_OP_FAILED;
+    }
+    *type = d->getType();
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+#ifdef NN_DEBUGGABLE
+static int64_t sRuntimeFeatureLevel = 0;
+void forTest_setRuntimeFeatureLevel(int64_t level) {
+    sRuntimeFeatureLevel = level;
+}
+#endif
+
+// Since ANeuralNetworks_getRuntimeFeatureLevel is new in 31 while libneuralnetwork targets
+// "min_sdk_version: 30", calling it should be properly guarded (e.g. __builtin_available).
+// But calling it within the same compilation unit is perfectly fine. Guarding it doesn't
+// make any sense and is simply wrong. (It's available on a system where __builtin_available(30)
+// evaluates to false.)
+// To make the compiler happy we introduce getRuntimeFeatureLevelImpl() and call it within the
+// library.
+static inline int64_t getRuntimeFeatureLevelImpl() {
+#ifdef NN_DEBUGGABLE
+    if (sRuntimeFeatureLevel) {
+        return sRuntimeFeatureLevel;
+    }
+#endif
+    return DeviceManager::get()->getRuntimeFeatureLevel();
+}
+
+int ANeuralNetworksDevice_getFeatureLevel(const ANeuralNetworksDevice* device,
+                                          int64_t* featureLevel) {
+    if (device == nullptr || featureLevel == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksDevice_getFeatureLevel passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    Device* d = reinterpret_cast<Device*>(const_cast<ANeuralNetworksDevice*>(device));
+    int64_t dFeatureLevel = DeviceManager::versionToFeatureLevel(d->getFeatureLevel().level);
+    if (dFeatureLevel < 0) {
+        return ANEURALNETWORKS_BAD_STATE;
+    }
+    *featureLevel = std::min(getRuntimeFeatureLevelImpl(), dFeatureLevel);
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksDevice_wait(const ANeuralNetworksDevice* device) {
+    if (device == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksDevice_wait passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const Device* d = reinterpret_cast<const Device*>(device);
+    return d->wait();
+}
+
+int ANeuralNetworksModel_getSupportedOperationsForDevices(
+        const ANeuralNetworksModel* model, const ANeuralNetworksDevice* const* devices,
+        uint32_t numDevices, bool* supportedOps) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksModel_getSupportedOperationsForDevices");
+    if (model == nullptr || devices == nullptr || supportedOps == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksModel_getSupportedOperationsForDevices passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    if (numDevices == 0) {
+        LOG(ERROR) << "ANeuralNetworksModel_getSupportedOperationsForDevices passed an empty "
+                      "device list";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+    const FlatbufferModelBuilder* m = reinterpret_cast<const FlatbufferModelBuilder*>(model);
+    if (!m->isFinished() || !m->isValid()) {
+        LOG(ERROR) << "ANeuralNetworksModel_getSupportedOperationsForDevices passed an unfinished "
+                      "or invalid Model";
+        return ANEURALNETWORKS_BAD_STATE;
+    }
+
+    const Model canonicalModel = m->makeModel();
+    const std::vector<uint32_t>& opMap = m->getSortedOperationMapping();
+    // init the output array to false for all the operations.
+    std::fill(supportedOps, supportedOps + opMap.size(), false);
+    for (uint32_t i = 0; i < numDevices; i++) {
+        if (devices[i] == nullptr) {
+            LOG(ERROR) << "ANeuralNetworksModel_getSupportedOperationsForDevices passed a nullptr "
+                          "as a device";
+            return ANEURALNETWORKS_UNEXPECTED_NULL;
+        }
+        for (uint32_t j = i + 1; j < numDevices; j++) {
+            if (devices[i] == devices[j]) {
+                LOG(ERROR) << "ANeuralNetworksModel_getSupportedOperationsForDevices passed "
+                              "duplicate devices";
+                return ANEURALNETWORKS_BAD_DATA;
+            }
+        }
+
+        Device* d = reinterpret_cast<Device*>(const_cast<ANeuralNetworksDevice*>(devices[i]));
+        const MetaModel metaModel(canonicalModel, DeviceManager::get()->strictSlicing());
+        const std::vector<bool> supportsByDevice = d->getSupportedOperations(metaModel);
+        for (uint32_t j = 0; j < supportsByDevice.size(); j++) {
+            uint32_t originalIdx = opMap[j];
+            supportedOps[originalIdx] |= supportsByDevice[j];
+        }
+    }
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksCompilation_createForDevices(ANeuralNetworksModel* /* model */,
+                                                const ANeuralNetworksDevice* const* /* devices */,
+                                                uint32_t /* numDevices */,
+                                                ANeuralNetworksCompilation** /* compilation */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksCompilation_createForDevices");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_createForDevices unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+struct ExecutionContext {
+    // inputs are always copied before execution while outputs may be set by custom allocation
+    std::vector<void*> outputs;
+    std::vector<size_t> outputSizes;
+    std::vector<bool> isOutputSpecifiedAtIndex;
+    std::vector<const void*> inputs;
+    std::vector<size_t> inputSizes;
+
+    std::unique_ptr<tflite::Interpreter> interpreter;
+
+    ExecutionContext(std::unique_ptr<tflite::Interpreter> interpreter)
+        : outputs(interpreter->outputs().size()),
+          outputSizes(interpreter->outputs().size()),
+          isOutputSpecifiedAtIndex(interpreter->outputs().size(), false),
+          inputs(interpreter->inputs().size()),
+          inputSizes(interpreter->inputs().size()),
+          interpreter(std::move(interpreter)) {}
+};
+
+int ANeuralNetworksExecution_compute(ANeuralNetworksExecution* execution) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_compute");
+    if (!execution) {
+        LOG(ERROR) << "ANeuralNetworksExecution_compute passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+
+    auto context = reinterpret_cast<ExecutionContext*>(execution);
+    if (std::any_of(context->isOutputSpecifiedAtIndex.begin(),
+                    context->isOutputSpecifiedAtIndex.end(), [](bool isSet) { return !isSet; })) {
+        LOG(ERROR) << "ANeuralNetworksExecution_compute not all output buffers are specified";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+
+    auto result = context->interpreter->AllocateTensors();
+    if (result != kTfLiteOk) {
+        LOG(ERROR) << "ANeuralNetworksExecution_compute allocate tensors failed";
+        return ANEURALNETWORKS_OP_FAILED;
+    }
+
+    for (uint32_t index = 0; index < context->interpreter->inputs().size(); index++) {
+        const void* buffer = context->inputs[index];
+        if (buffer == nullptr) {
+            LOG(ERROR) << "ANeuralNetworksExecution_compute not all input buffers are specified";
+            return ANEURALNETWORKS_BAD_DATA;
+        }
+        size_t length = context->inputSizes[index];
+        std::memcpy(context->interpreter->input_tensor(index)->data.raw, buffer, length);
+    }
+
+    if (context->interpreter->Invoke() != kTfLiteOk) {
+        return ANEURALNETWORKS_OP_FAILED;
+    }
+
+    for (uint32_t i = 0; i < context->interpreter->outputs().size(); i++) {
+        if (context->outputs[i] == nullptr) {
+            continue;
+        }
+
+        const size_t bufferSize = context->outputSizes[i];
+        std::memcpy(context->outputs[i], context->interpreter->output_tensor(i)->data.raw,
+                    bufferSize);
+    }
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksExecution_setMeasureTiming(ANeuralNetworksExecution* /* execution */,
+                                              bool /* measure */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_setMeasureTiming");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_setMeasureTiming unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_getDuration(const ANeuralNetworksExecution* /* execution */,
+                                         int32_t /* durationCode */, uint64_t* /* duration */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_getDuration");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_getDuration unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksBurst_create(ANeuralNetworksCompilation* compilation,
+                                ANeuralNetworksBurst** burst) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksBurst_create");
+    if (!compilation || !burst) {
+        LOG(ERROR) << "ANeuralNetworksBurst_create passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+
+    CompilationBuilder* c = reinterpret_cast<CompilationBuilder*>(compilation);
+    BurstBuilder* b = nullptr;
+    int result = c->createBurst(&b);
+    *burst = reinterpret_cast<ANeuralNetworksBurst*>(b);
+    return result;
+}
+
+void ANeuralNetworksBurst_free(ANeuralNetworksBurst* burst) {
+    NNTRACE_RT(NNTRACE_PHASE_TERMINATION, "ANeuralNetworksBurst_free");
+    // No validation.  Free of nullptr is valid.
+    BurstBuilder* b = reinterpret_cast<BurstBuilder*>(burst);
+    delete b;
+}
+
+int ANeuralNetworksExecution_burstCompute(ANeuralNetworksExecution* /* execution */,
+                                          ANeuralNetworksBurst* /* burst */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_burstCompute");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_burstCompute unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksMemoryDesc_create(ANeuralNetworksMemoryDesc** desc) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksMemoryDesc_create");
+    if (desc != nullptr) {
+        *desc = nullptr;
+    }
+    if (!desc) {
+        LOG(ERROR) << "ANeuralNetworksMemoryDesc_create passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    auto mb = std::make_unique<MemoryBuilder>();
+    *desc = reinterpret_cast<ANeuralNetworksMemoryDesc*>(mb.release());
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+void ANeuralNetworksMemoryDesc_free(ANeuralNetworksMemoryDesc* desc) {
+    NNTRACE_RT(NNTRACE_PHASE_TERMINATION, "ANeuralNetworksMemoryDesc_free");
+    // No validation.  Free of nullptr is valid.
+    MemoryBuilder* mb = reinterpret_cast<MemoryBuilder*>(desc);
+    delete mb;
+}
+
+int ANeuralNetworksMemoryDesc_addInputRole(ANeuralNetworksMemoryDesc* desc,
+                                           const ANeuralNetworksCompilation* compilation,
+                                           uint32_t index, float frequency) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksMemoryDesc_addInputRole");
+    if (!desc || !compilation) {
+        LOG(ERROR) << "ANeuralNetworksMemoryDesc_addInputRole passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    MemoryBuilder* mb = reinterpret_cast<MemoryBuilder*>(desc);
+    const CompilationBuilder* c = reinterpret_cast<const CompilationBuilder*>(compilation);
+    return mb->addRole(*c, IOType::INPUT, index, frequency);
+}
+
+int ANeuralNetworksMemoryDesc_addOutputRole(ANeuralNetworksMemoryDesc* desc,
+                                            const ANeuralNetworksCompilation* compilation,
+                                            uint32_t index, float frequency) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksMemoryDesc_addOutputRole");
+    if (!desc || !compilation) {
+        LOG(ERROR) << "ANeuralNetworksMemoryDesc_addOutputRole passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    MemoryBuilder* mb = reinterpret_cast<MemoryBuilder*>(desc);
+    const CompilationBuilder* c = reinterpret_cast<const CompilationBuilder*>(compilation);
+    return mb->addRole(*c, IOType::OUTPUT, index, frequency);
+}
+
+int ANeuralNetworksMemoryDesc_setDimensions(ANeuralNetworksMemoryDesc* desc, uint32_t rank,
+                                            const uint32_t* dimensions) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksMemoryDesc_setDimensions");
+    if (!desc || (!dimensions && rank > 0)) {
+        LOG(ERROR) << "ANeuralNetworksMemoryDesc_setDimensions passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const std::vector<uint32_t> dims(dimensions, dimensions + rank);
+    MemoryBuilder* mb = reinterpret_cast<MemoryBuilder*>(desc);
+    return mb->setDimensions(dims);
+}
+
+int ANeuralNetworksMemoryDesc_finish(ANeuralNetworksMemoryDesc* desc) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksMemoryDesc_finish");
+    if (!desc) {
+        LOG(ERROR) << "ANeuralNetworksMemoryDesc_finish passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    MemoryBuilder* mb = reinterpret_cast<MemoryBuilder*>(desc);
+    return mb->finish();
+}
+
+int ANeuralNetworksMemory_createFromDesc(const ANeuralNetworksMemoryDesc* desc,
+                                         ANeuralNetworksMemory** memory) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksMemory_createFromDesc");
+    if (memory != nullptr) {
+        *memory = nullptr;
+    }
+    if (!desc || !memory) {
+        LOG(ERROR) << "ANeuralNetworksMemory_createFromDesc passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const MemoryBuilder* mb = reinterpret_cast<const MemoryBuilder*>(desc);
+    auto [n, m] = mb->allocate();
+    if (n != ANEURALNETWORKS_NO_ERROR) {
+        return n;
+    }
+    *memory = reinterpret_cast<ANeuralNetworksMemory*>(m.release());
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksMemory_copy(const ANeuralNetworksMemory* src, const ANeuralNetworksMemory* dst) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksMemory_copy");
+    if (!src || !dst) {
+        LOG(ERROR) << "ANeuralNetworksMemory_copy passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const RuntimeMemory* s = reinterpret_cast<const RuntimeMemory*>(src);
+    const RuntimeMemory* d = reinterpret_cast<const RuntimeMemory*>(dst);
+    return RuntimeMemory::copy(*s, *d);
+}
+
+int ANeuralNetworksMemory_createFromFd(size_t size, int prot, int fd, size_t offset,
+                                       ANeuralNetworksMemory** memory) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksMemory_createFromFd");
+    if (memory != nullptr) {
+        *memory = nullptr;
+    }
+    if (!memory) {
+        LOG(ERROR) << "ANeuralNetworksMemory_createFromFd passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    int n = ANEURALNETWORKS_NO_ERROR;
+    std::unique_ptr<MemoryFd> m;
+    std::tie(n, m) = MemoryFd::create(size, prot, fd, offset);
+    if (n != ANEURALNETWORKS_NO_ERROR) {
+        return n;
+    }
+    *memory = reinterpret_cast<ANeuralNetworksMemory*>(m.release());
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksMemory_createFromAHardwareBuffer(const AHardwareBuffer* ahwb,
+                                                    ANeuralNetworksMemory** memory) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksMemory_createFromAHardwareBuffer");
+    if (memory != nullptr) {
+        *memory = nullptr;
+    }
+    if (!ahwb || !memory) {
+        LOG(ERROR) << "ANeuralNetworksMemory_createFromAHardwareBuffer passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    int n = ANEURALNETWORKS_NO_ERROR;
+    std::unique_ptr<MemoryAHWB> m;
+    std::tie(n, m) = MemoryAHWB::create(*ahwb);
+    if (n != ANEURALNETWORKS_NO_ERROR) {
+        return n;
+    }
+    *memory = reinterpret_cast<ANeuralNetworksMemory*>(m.release());
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+void ANeuralNetworksMemory_free(ANeuralNetworksMemory* memory) {
+    NNTRACE_RT(NNTRACE_PHASE_TERMINATION, "ANeuralNetworksMemory_free");
+    // No validation.  Free of nullptr is valid.
+    RuntimeMemory* m = reinterpret_cast<RuntimeMemory*>(memory);
+    delete m;
+}
+
+int ANeuralNetworksModel_create(ANeuralNetworksModel** model) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_create");
+    initVLogMask();
+    if (!model) {
+        LOG(ERROR) << "ANeuralNetworksModel_create passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = new (std::nothrow) FlatbufferModelBuilder();
+    if (m == nullptr) {
+        *model = nullptr;
+        return ANEURALNETWORKS_OUT_OF_MEMORY;
+    }
+    *model = reinterpret_cast<ANeuralNetworksModel*>(m);
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+void ANeuralNetworksModel_free(ANeuralNetworksModel* model) {
+    NNTRACE_RT(NNTRACE_PHASE_TERMINATION, "ANeuralNetworksModel_free");
+    // No validation.  Free of nullptr is valid.
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    delete m;
+}
+
+int ANeuralNetworksModel_finish(ANeuralNetworksModel* model) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_finish");
+    if (!model) {
+        LOG(ERROR) << "ANeuralNetworksModel_finish passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->finish();
+}
+
+int ANeuralNetworksModel_addOperand(ANeuralNetworksModel* model,
+                                    const ANeuralNetworksOperandType* type) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_addOperand");
+    if (!model || !type) {
+        LOG(ERROR) << "ANeuralNetworksModel_addOperand passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->addOperand(*type);
+}
+
+int ANeuralNetworksModel_setOperandValue(ANeuralNetworksModel* model, int32_t index,
+                                         const void* buffer, size_t length) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_setOperandValue");
+    if (!model || (!buffer && length != 0)) {
+        LOG(ERROR) << "ANeuralNetworksModel_setOperandValue passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->setOperandValue(index, buffer, length);
+}
+
+int ANeuralNetworksModel_setOperandValueFromMemory(ANeuralNetworksModel* model, int32_t index,
+                                                   const ANeuralNetworksMemory* memory,
+                                                   size_t offset, size_t length) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_setOperandValueFromMemory");
+    if (!model || !memory) {
+        LOG(ERROR) << "ANeuralNetworksModel_setOperandValue passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const RuntimeMemory* mem = reinterpret_cast<const RuntimeMemory*>(memory);
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->setOperandValueFromMemory(index, mem, offset, length);
+}
+
+int ANeuralNetworksModel_setOperandValueFromModel(ANeuralNetworksModel* model, int32_t index,
+                                                  const ANeuralNetworksModel* value) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_setOperandValueFromModel");
+    if (!model || !value) {
+        LOG(ERROR) << "ANeuralNetworksModel_setOperandValueFromModel passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const FlatbufferModelBuilder* val = reinterpret_cast<const FlatbufferModelBuilder*>(value);
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->setOperandValueFromModel(index, val);
+}
+
+int ANeuralNetworksModel_addOperation(ANeuralNetworksModel* model,
+                                      ANeuralNetworksOperationType type, uint32_t inputCount,
+                                      const uint32_t* inputs, uint32_t outputCount,
+                                      const uint32_t* outputs) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_addOperation");
+    if (!model || !inputs || !outputs) {
+        LOG(ERROR) << "ANeuralNetworksModel_addOperation passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->addOperation(type, inputCount, inputs, outputCount, outputs);
+}
+
+int ANeuralNetworksModel_setOperandSymmPerChannelQuantParams(
+        ANeuralNetworksModel* model, int32_t index,
+        const ANeuralNetworksSymmPerChannelQuantParams* channelQuant) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION,
+               "ANeuralNetworksModel_setOperandSymmPerChannelQuantParams");
+    if (!model || !channelQuant) {
+        LOG(ERROR) << "ANeuralNetworksModel_setOperandSymmPerChannelQuantParams passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->setOperandSymmPerChannelQuantParams(index, *channelQuant);
+}
+
+int ANeuralNetworksModel_identifyInputsAndOutputs(ANeuralNetworksModel* model, uint32_t inputCount,
+                                                  const uint32_t* inputs, uint32_t outputCount,
+                                                  const uint32_t* outputs) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_identifyInputsAndOutputs");
+    if (!model || !inputs || !outputs) {
+        LOG(ERROR) << ("ANeuralNetworksModel_identifyInputsAndOutputs passed a nullptr");
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->identifyInputsAndOutputs(inputCount, inputs, outputCount, outputs);
+}
+
+int ANeuralNetworksModel_relaxComputationFloat32toFloat16(ANeuralNetworksModel* model, bool allow) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_relaxComputationFloat32toFloat16");
+    if (!model) {
+        LOG(ERROR) << ("ANeuralNetworksModel_relaxComputationFloat32toFloat16 passed a nullptr");
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->relaxComputationFloat32toFloat16(allow);
+}
+
+struct CompilationContext {
+    std::unique_ptr<tflite::FlatBufferModel> flatBufferModel;
+    bool isFinished;
+
+    CompilationContext(std::unique_ptr<tflite::FlatBufferModel> flatBufferModel)
+        : flatBufferModel(std::move(flatBufferModel)), isFinished(false) {}
+};
+
+int ANeuralNetworksCompilation_create(ANeuralNetworksModel* model,
+                                      ANeuralNetworksCompilation** compilation) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksCompilation_create");
+    if (!model || !compilation) {
+        LOG(ERROR) << "ANeuralNetworksCompilation_create passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+
+    auto tfliteModel = m->createTfliteModel();
+    if (!tfliteModel.ok()) {
+        LOG(ERROR) << "ANeuralNetworksCompilation_create error: " << tfliteModel.error();
+        return ANEURALNETWORKS_OP_FAILED;
+    }
+
+    std::unique_ptr<tflite::FlatBufferModel> flatBufferModel =
+            tflite::FlatBufferModel::BuildFromModel(tfliteModel.value());
+    if (!flatBufferModel) {
+        LOG(ERROR) << "ANeuralNetworksCompilation_create error: tflite::BuildFromModel error";
+        return ANEURALNETWORKS_OP_FAILED;
+    }
+
+    std::unique_ptr<CompilationContext> context =
+            std::make_unique<CompilationContext>(std::move(flatBufferModel));
+    *compilation = reinterpret_cast<ANeuralNetworksCompilation*>(context.release());
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+void ANeuralNetworksCompilation_free(ANeuralNetworksCompilation* compilation) {
+    NNTRACE_RT(NNTRACE_PHASE_TERMINATION, "ANeuralNetworksCompilation_free");
+    // No validation.  Free of nullptr is valid.
+    auto c = reinterpret_cast<CompilationContext*>(compilation);
+    delete c;
+}
+
+int ANeuralNetworksCompilation_setPreference(ANeuralNetworksCompilation* /* compilation */,
+                                             int32_t /* preference */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksCompilation_setPreference");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_setPreference unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksCompilation_setCaching(ANeuralNetworksCompilation* /* compilation */,
+                                          const char* /* cacheDir */, const uint8_t* /* token */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksCompilation_setCaching");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_setCaching unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksCompilation_finish(ANeuralNetworksCompilation* compilation) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksCompilation_finish");
+    if (!compilation) {
+        LOG(ERROR) << "ANeuralNetworksCompilation_finish passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+
+    auto context = reinterpret_cast<CompilationContext*>(compilation);
+    if (context->isFinished) {
+        LOG(ERROR) << "ANeuralNetworksCompilation_finish has already been called";
+        return ANEURALNETWORKS_BAD_STATE;
+    }
+    context->isFinished = true;
+
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksCompilation_setPriority(ANeuralNetworksCompilation* /* compilation */,
+                                           int /* priority */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksCompilation_setPriority");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_setPriority unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksCompilation_setTimeout(ANeuralNetworksCompilation* /* compilation */,
+                                          uint64_t /* duration */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksCompilation_setTimeout");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_setTimeout unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_create(ANeuralNetworksCompilation* compilation,
+                                    ANeuralNetworksExecution** execution) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_create");
+    if (!compilation || !execution) {
+        LOG(ERROR) << "ANeuralNetworksExecution_create passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    auto c = reinterpret_cast<CompilationContext*>(compilation);
+
+    tflite::ops::builtin::BuiltinOpResolver resolver;
+    std::unique_ptr<tflite::Interpreter> interpreter;
+    auto status = tflite::InterpreterBuilder(*c->flatBufferModel, resolver)(&interpreter);
+    if (status != kTfLiteOk) {
+        LOG(ERROR) << "ANeuralNetworksExecution_create error: interpreter build status " << status
+                   << " != " << kTfLiteOk;
+        return ANEURALNETWORKS_OP_FAILED;
+    }
+
+    std::unique_ptr<ExecutionContext> context =
+            std::make_unique<ExecutionContext>(std::move(interpreter));
+    *execution = reinterpret_cast<ANeuralNetworksExecution*>(context.release());
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+void ANeuralNetworksExecution_free(ANeuralNetworksExecution* execution) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_free");
+    // Free of nullptr is valid.
+    auto r = reinterpret_cast<ExecutionContext*>(execution);
+    delete r;
+}
+
+int ANeuralNetworksExecution_getOutputOperandRank(ANeuralNetworksExecution* /* execution */,
+                                                  int32_t /* index */, uint32_t* /* rank */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_getOutputOperandRank");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR)
+            << "ANeuralNetworksExecution_getOutputOperandRank unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_getOutputOperandDimensions(ANeuralNetworksExecution* /* execution */,
+                                                        int32_t /* index */,
+                                                        uint32_t* /* dimensions */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_getOutputOperandDimensions");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_getOutputOperandDimensions unimplemented in Neural "
+                  "Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_setInput(ANeuralNetworksExecution* execution, int32_t index,
+                                      const ANeuralNetworksOperandType* type, const void* buffer,
+                                      size_t length) {
+    NNTRACE_RT(NNTRACE_PHASE_INPUTS_AND_OUTPUTS, "ANeuralNetworksExecution_setInput");
+    // We do not support dynamic shapes
+    if (type != nullptr) {
+        LOG(ERROR) << "ANeuralNetworksExecution_setInput expected a nullptr for "
+                      "ANeuralNetworksOperandType* argument";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+    if (!execution || (!buffer && length != 0)) {
+        LOG(ERROR) << "ANeuralNetworksExecution_setInput passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    auto context = reinterpret_cast<ExecutionContext*>(execution);
+    if (index < 0 || index >= static_cast<int32_t>(context->interpreter->inputs().size())) {
+        LOG(ERROR) << "ANeuralNetworksExecution_setInput index out of bounds";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+
+    if (context->interpreter->input_tensor(index)->bytes != length) {
+        LOG(ERROR)
+                << "ANeuralNetworksExecution_setInput input bytes is different from buffer length";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+    context->inputs[index] = buffer;
+    context->inputSizes[index] = length;
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksExecution_setInputFromMemory(ANeuralNetworksExecution* /* execution */,
+                                                int32_t /* index */,
+                                                const ANeuralNetworksOperandType* /* type */,
+                                                const ANeuralNetworksMemory* /* memory */,
+                                                size_t /* offset */, size_t /* length */) {
+    NNTRACE_RT(NNTRACE_PHASE_INPUTS_AND_OUTPUTS, "ANeuralNetworksExecution_setInputFromMemory");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_setInputFromMemory unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_setOutput(ANeuralNetworksExecution* execution, int32_t index,
+                                       const ANeuralNetworksOperandType* type, void* buffer,
+                                       size_t length) {
+    NNTRACE_RT(NNTRACE_PHASE_INPUTS_AND_OUTPUTS, "ANeuralNetworksExecution_setOutput");
+    // We do not support dynamic shapes
+    if (type != nullptr) {
+        LOG(ERROR) << "ANeuralNetworksExecution_setOutput expected a nullptr for "
+                      "ANeuralNetworksOperandType* argument";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+
+    if (!execution || (!buffer && length != 0)) {
+        LOG(ERROR) << "ANeuralNetworksExecution_setOutput passed a nullptr ";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+
+    auto context = reinterpret_cast<ExecutionContext*>(execution);
+    if (index < 0 || index >= static_cast<int32_t>(context->interpreter->outputs().size())) {
+        LOG(ERROR) << "ANeuralNetworksExecution_setOutput index out of bounds";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+
+    const size_t bufferSize = std::max<size_t>(length, 1);
+    if (bufferSize != context->interpreter->output_tensor(index)->bytes) {
+        LOG(ERROR) << "ANeuralNetworksExecution_setOutput length is not equal to the output tensor "
+                      "size";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+
+    const intptr_t dataPtrValue = reinterpret_cast<intptr_t>(buffer);
+    if (dataPtrValue % tflite::kDefaultTensorAlignment != 0) {
+        context->outputs[index] = buffer;
+        context->outputSizes[index] = bufferSize;
+    } else {
+        TfLiteCustomAllocation allocation = {.data = buffer, .bytes = bufferSize};
+        context->interpreter->SetCustomAllocationForTensor(context->interpreter->outputs()[index],
+                                                           allocation,
+                                                           kTfLiteCustomAllocationFlagsNone);
+    }
+
+    context->isOutputSpecifiedAtIndex[index] = true;
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksExecution_setOutputFromMemory(ANeuralNetworksExecution* /* execution */,
+                                                 int32_t /* index */,
+                                                 const ANeuralNetworksOperandType* /* type */,
+                                                 const ANeuralNetworksMemory* /* memory */,
+                                                 size_t /* offset */, size_t /* length */) {
+    NNTRACE_RT(NNTRACE_PHASE_INPUTS_AND_OUTPUTS, "ANeuralNetworksExecution_setOutputFromMemory");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR)
+            << "ANeuralNetworksExecution_setOutputFromMemory unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_startCompute(ANeuralNetworksExecution* /* execution */,
+                                          ANeuralNetworksEvent** /* event */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_startCompute");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_startCompute unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_setTimeout(ANeuralNetworksExecution* /* execution */,
+                                        uint64_t /* duration */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_setTimeout");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_setTimeout unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksEvent_wait(ANeuralNetworksEvent* event) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksEvent_wait");
+    if (event == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksEvent_wait passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+
+    IEvent* e = reinterpret_cast<IEvent*>(event);
+    return convertErrorStatusToResultCode(e->wait());
+}
+
+void ANeuralNetworksEvent_free(ANeuralNetworksEvent* event) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksEvent_free");
+    // No validation.  Free of nullptr is valid.
+    if (event) {
+        IEvent* e = reinterpret_cast<IEvent*>(event);
+        e->wait();
+        delete e;
+    }
+}
+
+int ANeuralNetworksExecution_setLoopTimeout(ANeuralNetworksExecution* /* execution */,
+                                            uint64_t /* duration */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_setLoopTimeout");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_setLoopTimeout unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+uint64_t ANeuralNetworks_getDefaultLoopTimeout() {
+    return operation_while::kTimeoutNsDefault;
+}
+
+uint64_t ANeuralNetworks_getMaximumLoopTimeout() {
+    return operation_while::kTimeoutNsMaximum;
+}
+
+int ANeuralNetworksDevice_getExtensionSupport(const ANeuralNetworksDevice* device,
+                                              const char* extensionName,
+                                              bool* isExtensionSupported) {
+    if (device == nullptr || extensionName == nullptr || isExtensionSupported == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksDevice_getExtensionSupport passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+
+    const Device* d = reinterpret_cast<const Device*>(device);
+    const auto& supportedExtensions = d->getSupportedExtensions();
+    *isExtensionSupported = std::any_of(supportedExtensions.begin(), supportedExtensions.end(),
+                                        [extensionName](const auto& supportedExtension) {
+                                            return supportedExtension.name == extensionName;
+                                        });
+
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksModel_getExtensionOperandType(ANeuralNetworksModel* model,
+                                                 const char* extensionName,
+                                                 uint16_t operandCodeWithinExtension,
+                                                 int32_t* type) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_getExtensionOperandType");
+    if (!model || !extensionName || !type) {
+        LOG(ERROR) << "ANeuralNetworksModel_getExtensionOperandType passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->getExtensionType(extensionName, operandCodeWithinExtension, type);
+}
+
+int ANeuralNetworksModel_getExtensionOperationType(ANeuralNetworksModel* model,
+                                                   const char* extensionName,
+                                                   uint16_t operationCodeWithinExtension,
+                                                   ANeuralNetworksOperationType* type) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_getExtensionOperationType");
+    if (!model || !extensionName || !type) {
+        LOG(ERROR) << "ANeuralNetworksModel_getExtensionOperationType passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->getExtensionType(extensionName, operationCodeWithinExtension, type);
+}
+
+int ANeuralNetworksModel_setOperandExtensionData(ANeuralNetworksModel* model, int32_t index,
+                                                 const void* data, size_t length) {
+    NNTRACE_RT(NNTRACE_PHASE_PREPARATION, "ANeuralNetworksModel_setOperandExtensionData");
+    if (!model || (!data && length != 0)) {
+        LOG(ERROR) << "ANeuralNetworksModel_setOperandExtensionData passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    FlatbufferModelBuilder* m = reinterpret_cast<FlatbufferModelBuilder*>(model);
+    return m->setOperandExtensionData(index, data, length);
+}
+
+int ANeuralNetworksCompilation_addExtensionAttribute(ANeuralNetworksCompilation* /* compilation */,
+                                                     const char* /* extensionName */,
+                                                     uint16_t /* attributeCodeWithinExtension */,
+                                                     const void* /* data */, size_t /* length */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION, "ANeuralNetworksCompilation_addExtensionAttribute");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_addExtensionAttribute unimplemented in Neural "
+                  "Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_addExtensionAttribute(ANeuralNetworksExecution* /* execution */,
+                                                   const char* /* extensionName */,
+                                                   uint16_t /* attributeCodeWithinExtension */,
+                                                   const void* /* data */, size_t /* length */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_addExtensionAttribute");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR)
+            << "ANeuralNetworksExecution_addExtensionAttribute unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksEvent_createFromSyncFenceFd(int syncFenceFd, ANeuralNetworksEvent** event) {
+    if (event == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksEvent_createFromSyncFenceFd passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    if (syncFenceFd <= 0) {
+        LOG(ERROR) << "ANeuralNetworksEvent_createFromSyncFenceFd passed an invalid fd: "
+                   << syncFenceFd;
+        *event = nullptr;
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+    std::unique_ptr<SyncFenceEvent> e =
+            std::make_unique<SyncFenceEvent>(syncFenceFd, nullptr, nullptr);
+    *event = reinterpret_cast<ANeuralNetworksEvent*>(e.release());
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksEvent_getSyncFenceFd(const ANeuralNetworksEvent* event, int* syncFenceFd) {
+    if (syncFenceFd == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksEvent_getSyncFenceFd passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    *syncFenceFd = -1;
+    if (event == nullptr) {
+        LOG(ERROR) << "ANeuralNetworksEvent_getSyncFenceFd passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const IEvent* e = reinterpret_cast<const IEvent*>(event);
+    // The client owns the dupped fd, and is responsible for closing it.
+    *syncFenceFd = e->getSyncFenceFd(/*shouldDup*/ true);
+    if (*syncFenceFd <= 0) {
+        LOG(ERROR) << "ANeuralNetworksEvent_getSyncFenceFd unable to get valid sync_fence fd";
+        *syncFenceFd = -1;
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ANeuralNetworksExecution_startComputeWithDependencies(
+        ANeuralNetworksExecution* /* execution */,
+        const ANeuralNetworksEvent* const* /* dependencies */, uint32_t /* numOfDependencies */,
+        uint64_t /* duration */, ANeuralNetworksEvent** /* event */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_startComputeWithDependencies");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_startComputeWithDependencies unimplemented in Neural "
+                  "Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int64_t ANeuralNetworks_getRuntimeFeatureLevel() {
+    return getRuntimeFeatureLevelImpl();
+}
+
+int ANeuralNetworksExecution_enableInputAndOutputPadding(ANeuralNetworksExecution* /* execution */,
+                                                         bool /* enable */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_enableInputAndOutputPadding");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_enableInputAndOutputPadding unimplemented in Neural "
+                  "Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksCompilation_getPreferredMemoryAlignmentForInput(
+        const ANeuralNetworksCompilation* /* compilation */, uint32_t /* index */,
+        uint32_t* /* alignment */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION,
+               "ANeuralNetworksCompilation_getPreferredMemoryAlignmentForInput");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_getPreferredMemoryAlignmentForInput unimplemented in "
+                  "Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksCompilation_getPreferredMemoryPaddingForInput(
+        const ANeuralNetworksCompilation* /* compilation */, uint32_t /* index */,
+        uint32_t* /* padding */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION,
+               "ANeuralNetworksCompilation_getPreferredMemoryPaddingForInput");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_getPreferredMemoryPaddingForInput unimplemented in "
+                  "Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksCompilation_getPreferredMemoryAlignmentForOutput(
+        const ANeuralNetworksCompilation* /* compilation */, uint32_t /* index */,
+        uint32_t* /* alignment */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION,
+               "ANeuralNetworksCompilation_getPreferredMemoryAlignmentForOutput");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR)
+            << "ANeuralNetworksCompilation_getPreferredMemoryAlignmentForOutput unimplemented in "
+               "Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksCompilation_getPreferredMemoryPaddingForOutput(
+        const ANeuralNetworksCompilation* /* compilation */, uint32_t /* index */,
+        uint32_t* /* padding */) {
+    NNTRACE_RT(NNTRACE_PHASE_COMPILATION,
+               "ANeuralNetworksCompilation_getPreferredMemoryPaddingForOutput");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksCompilation_getPreferredMemoryPaddingForOutput unimplemented in "
+                  "Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int ANeuralNetworksExecution_setReusable(ANeuralNetworksExecution* /* execution */,
+                                         bool /* reusable */) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_setReusable");
+    // Not supported yet in NNAPI v2
+    LOG(ERROR) << "ANeuralNetworksExecution_setReusable unimplemented in Neural Networks V2";
+    return ANEURALNETWORKS_OP_FAILED;
+}
diff --git a/runtime/operation_converters/AddOperationConverter.cpp b/runtime/operation_converters/AddOperationConverter.cpp
new file mode 100644
index 0000000..4203a73
--- /dev/null
+++ b/runtime/operation_converters/AddOperationConverter.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AddOperationConverter.h"
+
+#include <vector>
+
+#include "OperationConverterResolver.h"
+#include "SubGraphContext.h"
+
+namespace android {
+namespace nn {
+
+Result<void> AddOperationConverter::convert(const Operation& operation,
+                                            SubGraphContext* context) const {
+    const Model::Subgraph* subgraph = context->getSubgraph();
+
+    // add opcode for ADD if not added yet
+    uint32_t opCodeIdx = context->addOpCode(OperationType::ADD);
+
+    std::vector<int32_t> inputs = NN_TRY(getArithmeticInputs(operation, context));
+    std::vector<int32_t> outputs = NN_TRY(getArithmeticOutputs(operation, context));
+
+    int baseOptionsIdx = 2;
+
+    // activation
+    const Operand& activationOperand =
+            subgraph->operands[operation.inputs[baseOptionsIdx + kActivationOffset]];
+    NN_RET_CHECK(isOperandConstant(activationOperand));
+    FusedActivationFunc activation = static_cast<FusedActivationFunc>(
+            context->getConstantScalar<int32_t>(activationOperand));
+
+    auto optionsFlatbuffer = tflite::CreateAddOptions(
+            context->getBuilder(),
+            NN_TRY(getTfliteActivation(activation)) /* fused_activation_function */);
+    auto operatorFlatbuffer = tflite::CreateOperatorDirect(
+            context->getBuilder() /* builder */, opCodeIdx /* opcode_index */, &inputs /* inputs */,
+            &outputs /* outputs */,
+            tflite::BuiltinOptions::BuiltinOptions_AddOptions /* builtin_options_type */,
+            optionsFlatbuffer.Union() /* builtin_options */);
+    context->addOperatorFlatbuffer(operatorFlatbuffer);
+
+    return {};
+}
+
+NN_REGISTER_OPERATION_CONVERTER(ADD, AddOperationConverter);
+
+}  // namespace nn
+}  // namespace android
\ No newline at end of file
diff --git a/runtime/operation_converters/AddOperationConverter.h b/runtime/operation_converters/AddOperationConverter.h
new file mode 100644
index 0000000..ab82bfb
--- /dev/null
+++ b/runtime/operation_converters/AddOperationConverter.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_ADD_OPERATION_CONVERTER_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_ADD_OPERATION_CONVERTER_H
+
+#include <vector>
+
+#include "ArithmeticOperationConverter.h"
+
+namespace android {
+namespace nn {
+
+class AddOperationConverter : public ArithmeticOperationConverterBase {
+   public:
+    Result<void> convert(const Operation& operation, SubGraphContext* context) const override;
+};
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_ADD_OPERATION_CONVERTER_H
\ No newline at end of file
diff --git a/runtime/operation_converters/ArithmeticOperationConverter.cpp b/runtime/operation_converters/ArithmeticOperationConverter.cpp
new file mode 100644
index 0000000..e97a302
--- /dev/null
+++ b/runtime/operation_converters/ArithmeticOperationConverter.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ArithmeticOperationConverter.h"
+
+#include <vector>
+
+#include "OperationConverterResolver.h"
+#include "SubGraphContext.h"
+
+namespace android {
+namespace nn {
+
+Result<std::vector<int32_t>> ArithmeticOperationConverterBase::getArithmeticInputs(
+        const Operation& operation, SubGraphContext* context) const {
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.inputs[kInput1TensorIdx]));
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.inputs[kInput2TensorIdx]));
+    std::vector<int32_t> inputs{
+            context->getTensorIdxFromOperandIdx(operation.inputs[kInput1TensorIdx]),
+            context->getTensorIdxFromOperandIdx(operation.inputs[kInput2TensorIdx])};
+    return inputs;
+}
+
+Result<std::vector<int32_t>> ArithmeticOperationConverterBase::getArithmeticOutputs(
+        const Operation& operation, SubGraphContext* context) const {
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.outputs[kOutputTensorIdx]));
+    std::vector<int32_t> outputs{
+            context->getTensorIdxFromOperandIdx(operation.outputs[kOutputTensorIdx])};
+    return outputs;
+}
+
+}  // namespace nn
+}  // namespace android
\ No newline at end of file
diff --git a/runtime/operation_converters/ArithmeticOperationConverter.h b/runtime/operation_converters/ArithmeticOperationConverter.h
new file mode 100644
index 0000000..d5dbcf6
--- /dev/null
+++ b/runtime/operation_converters/ArithmeticOperationConverter.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_ARITHMETIC_OPERATION_CONVERTER_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_ARITHMETIC_OPERATION_CONVERTER_H
+
+#include <vector>
+
+#include "OperationConverter.h"
+
+namespace android {
+namespace nn {
+
+class ArithmeticOperationConverterBase : public IOperationConverter {
+   protected:
+    Result<std::vector<int32_t>> getArithmeticInputs(const Operation& operation,
+                                                     SubGraphContext* context) const;
+    Result<std::vector<int32_t>> getArithmeticOutputs(const Operation& operation,
+                                                      SubGraphContext* context) const;
+
+    // Offset locations of BuiltinOption parameters in NNAPI Operand inputs
+    static constexpr int kActivationOffset = 0;
+
+   private:
+    // Locations of Operator inputs in a NNAPI Operation
+    static constexpr int kInput1TensorIdx = 0;
+    static constexpr int kInput2TensorIdx = 1;
+
+    // Location of Operator outputs in a NNAPI Operation
+    static constexpr int kOutputTensorIdx = 0;
+};
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_ARITHMETIC_OPERATION_CONVERTER_H
\ No newline at end of file
diff --git a/runtime/operation_converters/Conv2DOperationConverter.cpp b/runtime/operation_converters/Conv2DOperationConverter.cpp
new file mode 100644
index 0000000..c88ff05
--- /dev/null
+++ b/runtime/operation_converters/Conv2DOperationConverter.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Conv2DOperationConverter.h"
+
+#include <vector>
+
+#include "OperationConverterResolver.h"
+#include "SubGraphContext.h"
+
+namespace android {
+namespace nn {
+
+Result<std::vector<int32_t>> Conv2DOperationConverter::getConv2DInputs(
+        const Operation& operation, SubGraphContext* context) const {
+    NN_RET_CHECK(isOperandConstant(
+            context->getSubgraph()->operands[operation.inputs[kFilterTensorIdx]]));
+
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.inputs[kInputTensorIdx]));
+    // TFLite does not support asymmetric tensors for convolution filters
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.inputs[kFilterTensorIdx],
+                                                      true /* makeSymmetric */));
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.inputs[kBiasTensorIdx]));
+    std::vector<int32_t> inputs{
+            context->getTensorIdxFromOperandIdx(operation.inputs[kInputTensorIdx]),
+            context->getTensorIdxFromOperandIdx(operation.inputs[kFilterTensorIdx]),
+            context->getTensorIdxFromOperandIdx(operation.inputs[kBiasTensorIdx])};
+    return inputs;
+}
+
+Result<std::vector<int32_t>> Conv2DOperationConverter::getConv2DOutputs(
+        const Operation& operation, SubGraphContext* context) const {
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.outputs[kOutputTensorIdx]));
+    std::vector<int32_t> outputs{
+            context->getTensorIdxFromOperandIdx(operation.outputs[kOutputTensorIdx])};
+    return outputs;
+}
+
+Result<int> Conv2DOperationConverter::decomposeExplicitPadding(const Operation& operation,
+                                                               SubGraphContext* context) const {
+    const Model::Subgraph* subgraph = context->getSubgraph();
+    const Operand& inputOperand = subgraph->operands[operation.inputs[0]];
+
+    // add opcode for PAD if it does not exist yet
+    uint32_t opCodeIdx = context->addOpCode(OperationType::PAD);
+
+    // pad options
+    auto padOptionsFlatbuffer = tflite::CreatePadOptions(context->getBuilder());
+
+    // check to make sure padding Operands are constants
+    const Operand& frontWidthPaddingOperand = subgraph->operands[operation.inputs[3]];
+    const Operand& backWidthPaddingOperand = subgraph->operands[operation.inputs[4]];
+    const Operand& frontHeightPaddingOperand = subgraph->operands[operation.inputs[5]];
+    const Operand& backHeightPaddingOperand = subgraph->operands[operation.inputs[6]];
+    NN_RET_CHECK(isOperandConstant(frontWidthPaddingOperand));
+    NN_RET_CHECK(isOperandConstant(backWidthPaddingOperand));
+    NN_RET_CHECK(isOperandConstant(frontHeightPaddingOperand));
+    NN_RET_CHECK(isOperandConstant(backHeightPaddingOperand));
+
+    // get padding params
+    int32_t frontHeightPadding = context->getConstantScalar<int32_t>(frontHeightPaddingOperand);
+    int32_t backHeightPadding = context->getConstantScalar<int32_t>(backHeightPaddingOperand);
+    int32_t frontWidthPadding = context->getConstantScalar<int32_t>(frontWidthPaddingOperand);
+    int32_t backWidthPadding = context->getConstantScalar<int32_t>(backWidthPaddingOperand);
+
+    // build padding buffer
+    const Dimensions& dims = inputOperand.dimensions;
+    int numDimensionsInput = static_cast<int>(dims.size());
+    std::vector<int32_t> paddingData(numDimensionsInput * 2, 0);
+    paddingData[2] = frontHeightPadding;
+    paddingData[3] = backHeightPadding;
+    paddingData[4] = frontWidthPadding;
+    paddingData[5] = backWidthPadding;
+    uint32_t paddingBufferIdx = context->addBufferFromData(
+            reinterpret_cast<uint8_t*>(paddingData.data()), paddingData.size() * sizeof(int32_t));
+
+    // create new tensor for padding
+    std::vector<int32_t> padShape{numDimensionsInput, 2};
+    auto padTensor = tflite::CreateTensorDirect(context->getBuilder(), &padShape /* shape */,
+                                                tflite::TensorType::TensorType_INT32 /* type */,
+                                                paddingBufferIdx /* buffer */);
+    int padTensorIdx = context->addTensorFlatbuffer(padTensor);
+
+    // add inputs for padding operation
+    std::vector<int32_t> padInputs = {context->getTensorIdxFromOperandIdx(operation.inputs[0]),
+                                      padTensorIdx};
+
+    // get dimensions of output of pad operation
+    std::vector<int32_t> padToConv2dShape(dims.begin(), dims.end());
+    // keep unknown height and width dimensions unknown
+    padToConv2dShape[1] = padToConv2dShape[1] != 0
+                                  ? frontHeightPadding + padToConv2dShape[1] + backHeightPadding
+                                  : -1;
+    padToConv2dShape[2] = padToConv2dShape[2] != 0
+                                  ? frontWidthPadding + padToConv2dShape[2] + backWidthPadding
+                                  : -1;
+    replaceZeroDimensions(&padToConv2dShape);
+
+    // build quantization parameters
+    std::vector<float> scaleVector{inputOperand.scale};
+    std::vector<int64_t> zeroPointVector{inputOperand.zeroPoint};
+    // min and max used to convert TFLite models to TF models, so it is unused in this case and can
+    // be set to 0
+    std::vector<float> minVector{0};
+    std::vector<float> maxVector{0};
+    auto quantizationParams = tflite::CreateQuantizationParametersDirect(
+            context->getBuilder(), &minVector /* min */, &maxVector /* max */,
+            &scaleVector /* scale */, &zeroPointVector /* zero_point */,
+            tflite::QuantizationDetails::QuantizationDetails_NONE /* details_type */);
+
+    // create new tensor to be output of pad & input for conv2d
+    auto padToConv2dTensor = tflite::CreateTensorDirect(
+            context->getBuilder(), &padToConv2dShape /* shape */,
+            NN_TRY(getTensorFlatbufferOperandType(inputOperand.type)) /* type */, 0 /* buffer */,
+            0 /* name */, quantizationParams /* quantization */);
+    int padToConv2dTensorIdx = context->addTensorFlatbuffer(padToConv2dTensor);
+
+    // set output for padding operation and add to operators
+    std::vector<int32_t> padOutputs{padToConv2dTensorIdx};
+
+    OperatorFlatbuffer padOp = tflite::CreateOperatorDirect(
+            context->getBuilder(), opCodeIdx, &padInputs, &padOutputs,
+            tflite::BuiltinOptions::BuiltinOptions_PadOptions, padOptionsFlatbuffer.Union());
+    context->addOperatorFlatbuffer(padOp);
+
+    // Return tensor index of pad output created
+    return padToConv2dTensorIdx;
+}
+
+Result<void> Conv2DOperationConverter::convert(const Operation& operation,
+                                               SubGraphContext* context) const {
+    const Model::Subgraph* subgraph = context->getSubgraph();
+
+    // add opcode for CONV_2D if not added yet
+    uint32_t opCodeIdx = context->addOpCode(OperationType::CONV_2D);
+
+    // if there are less than 8 inputs or the input at the 7th index is a BOOL, there is implicit
+    // padding
+    bool isImplicitPadding = false;
+    if (operation.inputs.size() < 8 ||
+        subgraph->operands[operation.inputs[7]].type == OperandType::BOOL) {
+        isImplicitPadding = true;
+    }
+
+    std::vector<int32_t> inputs = NN_TRY(getConv2DInputs(operation, context));
+    std::vector<int32_t> outputs = NN_TRY(getConv2DOutputs(operation, context));
+
+    // if explicit padding, we need to decompose the operation to a separate padding op and a conv2d
+    // op
+    if (!isImplicitPadding) {
+        auto padOpIdx = NN_TRY(decomposeExplicitPadding(operation, context));
+        inputs[0] = padOpIdx;
+    }
+
+    int baseOptionsIdx = 4;
+    tflite::Padding padding;
+    if (isImplicitPadding) {
+        const Operand& paddingTypeOperand = subgraph->operands[operation.inputs[3]];
+        NN_RET_CHECK(isOperandConstant(paddingTypeOperand));
+
+        int32_t paddingType = context->getConstantScalar<int32_t>(paddingTypeOperand);
+        padding = getTFLitePadding(paddingType);
+    } else {
+        padding = tflite::Padding::Padding_VALID;
+        baseOptionsIdx = 7;
+    }
+
+    // check if stride and activation Operands are constant
+    const Operand& strideWOperand =
+            subgraph->operands[operation.inputs[baseOptionsIdx + kStrideWOffset]];
+    const Operand& strideHOperand =
+            subgraph->operands[operation.inputs[baseOptionsIdx + kStrideHOffset]];
+    const Operand& activationOperand =
+            subgraph->operands[operation.inputs[baseOptionsIdx + kActivationOffset]];
+    NN_RET_CHECK(isOperandConstant(strideWOperand));
+    NN_RET_CHECK(isOperandConstant(strideHOperand));
+    NN_RET_CHECK(isOperandConstant(activationOperand));
+
+    // get strides and activation
+    int32_t strideW = context->getConstantScalar<int32_t>(strideWOperand);
+    int32_t strideH = context->getConstantScalar<int32_t>(strideHOperand);
+    FusedActivationFunc activation = static_cast<FusedActivationFunc>(
+            context->getConstantScalar<int32_t>(activationOperand));
+
+    // check for nchw
+    int isNchwIdx = baseOptionsIdx + kIsNchwOffset;
+    if (operation.inputs.size() > static_cast<uint32_t>(isNchwIdx)) {
+        const Operand& isNchwOperand = subgraph->operands[operation.inputs[isNchwIdx]];
+        NN_RET_CHECK(isOperandConstant(isNchwOperand));
+
+        bool isNchw = context->getConstantScalar<bool>(isNchwOperand);
+        NN_RET_CHECK(!isNchw) << "TFLite does not support NCHW formatted input tensors";
+    }
+
+    // dilations
+    int dilationWIdx = baseOptionsIdx + kDilationWOffset;
+    int dilationHIdx = baseOptionsIdx + kDilationHOffset;
+    // default dilation factors are 1
+    int32_t dilationW = 1;
+    int32_t dilationH = 1;
+    if (operation.inputs.size() > static_cast<uint32_t>(dilationWIdx)) {
+        const Operand& dilationWOperand = subgraph->operands[operation.inputs[dilationWIdx]];
+        NN_RET_CHECK(isOperandConstant(dilationWOperand));
+
+        dilationW = context->getConstantScalar<int32_t>(dilationWOperand);
+    }
+    if (operation.inputs.size() > static_cast<uint32_t>(dilationHIdx)) {
+        const Operand& dilationHOperand = subgraph->operands[operation.inputs[dilationHIdx]];
+        NN_RET_CHECK(isOperandConstant(dilationHOperand));
+
+        dilationH = context->getConstantScalar<int32_t>(dilationHOperand);
+    }
+
+    flatbuffers::Offset<tflite::Conv2DOptions> optionsFlatbuffer = tflite::CreateConv2DOptions(
+            context->getBuilder(), padding, strideW, strideH,
+            NN_TRY(getTfliteActivation(activation)) /* fused_activation_function */, dilationW,
+            dilationH);
+    auto operatorFlatbuffer = tflite::CreateOperatorDirect(
+            context->getBuilder() /* builder */, opCodeIdx /* opcode_index */, &inputs /* inputs */,
+            &outputs /* outputs */,
+            tflite::BuiltinOptions::BuiltinOptions_Conv2DOptions /* builtin_options_type */,
+            optionsFlatbuffer.Union() /* builtin_options */);
+    context->addOperatorFlatbuffer(operatorFlatbuffer);
+
+    return {};
+}
+
+NN_REGISTER_OPERATION_CONVERTER(CONV_2D, Conv2DOperationConverter);
+
+}  // namespace nn
+}  // namespace android
\ No newline at end of file
diff --git a/runtime/operation_converters/Conv2DOperationConverter.h b/runtime/operation_converters/Conv2DOperationConverter.h
new file mode 100644
index 0000000..398aaff
--- /dev/null
+++ b/runtime/operation_converters/Conv2DOperationConverter.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_CONV2D_OPERATION_CONVERTER_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_CONV2D_OPERATION_CONVERTER_H
+
+#include <vector>
+
+#include "OperationConverter.h"
+
+namespace android {
+namespace nn {
+
+class Conv2DOperationConverter : public IOperationConverter {
+   public:
+    Result<void> convert(const Operation& operation, SubGraphContext* context) const override;
+
+   protected:
+    Result<std::vector<int32_t>> getConv2DInputs(const Operation& operation,
+                                                 SubGraphContext* context) const;
+    Result<std::vector<int32_t>> getConv2DOutputs(const Operation& operation,
+                                                  SubGraphContext* context) const;
+
+    // Returns the output Tensor index of created Padding Operator if successful
+    Result<int> decomposeExplicitPadding(const Operation& operation,
+                                         SubGraphContext* context) const;
+
+   private:
+    // Offset locations of BuiltinOption parameters in NNAPI Operand inputs
+    static constexpr int kStrideWOffset = 0;
+    static constexpr int kStrideHOffset = 1;
+    static constexpr int kActivationOffset = 2;
+    static constexpr int kIsNchwOffset = 3;
+    static constexpr int kDilationWOffset = 4;
+    static constexpr int kDilationHOffset = 5;
+
+    // Locations of Operator inputs in a NNAPI Operation
+    static constexpr int kInputTensorIdx = 0;
+    static constexpr int kFilterTensorIdx = 1;
+    static constexpr int kBiasTensorIdx = 2;
+
+    // Location of Operator outputs in a NNAPI Operation
+    static constexpr int kOutputTensorIdx = 0;
+};
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_CONV2D_OPERATION_CONVERTER_H
\ No newline at end of file
diff --git a/runtime/operation_converters/DepthwiseConv2DOperationConverter.cpp b/runtime/operation_converters/DepthwiseConv2DOperationConverter.cpp
new file mode 100644
index 0000000..eb0e3b5
--- /dev/null
+++ b/runtime/operation_converters/DepthwiseConv2DOperationConverter.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "DepthwiseConv2DOperationConverter.h"
+
+#include <vector>
+
+#include "OperationConverterResolver.h"
+#include "SubGraphContext.h"
+
+namespace android {
+namespace nn {
+
+Result<void> DepthwiseConv2DOperationConverter::convert(const Operation& operation,
+                                                        SubGraphContext* context) const {
+    const Model::Subgraph* subgraph = context->getSubgraph();
+
+    // add opcode for DEPTHWISE_CONV_2D if not added yet
+    uint32_t opCodeIdx = context->addOpCode(OperationType::DEPTHWISE_CONV_2D);
+
+    // if there are less than 9 inputs or the input at the 8th index is a BOOL, there is implicit
+    // padding
+    const bool isImplicitPadding =
+            (operation.inputs.size() < 9 ||
+             subgraph->operands[operation.inputs[8]].type == OperandType::BOOL);
+
+    std::vector<int32_t> inputs = NN_TRY(getConv2DInputs(operation, context));
+    std::vector<int32_t> outputs = NN_TRY(getConv2DOutputs(operation, context));
+
+    // if explicit padding, we need to decompose the operation to a separate padding op and a conv2d
+    // op
+    if (!isImplicitPadding) {
+        auto padOpIdx = NN_TRY(decomposeExplicitPadding(operation, context));
+        inputs[0] = padOpIdx;
+    }
+
+    int baseOptionsIdx = 4;
+    tflite::Padding padding;
+    if (isImplicitPadding) {
+        const Operand& paddingTypeOperand = subgraph->operands[operation.inputs[3]];
+        NN_RET_CHECK(isOperandConstant(paddingTypeOperand));
+
+        int32_t paddingType = context->getConstantScalar<int32_t>(paddingTypeOperand);
+        padding = getTFLitePadding(paddingType);
+    } else {
+        padding = tflite::Padding::Padding_VALID;
+        baseOptionsIdx = 7;
+    }
+
+    // check if stride, depthwise multiplier, and activation Operands are constant
+    const Operand& strideWOperand =
+            subgraph->operands[operation.inputs[baseOptionsIdx + kStrideWOffset]];
+    const Operand& strideHOperand =
+            subgraph->operands[operation.inputs[baseOptionsIdx + kStrideHOffset]];
+    const Operand& activationOperand =
+            subgraph->operands[operation.inputs[baseOptionsIdx + kActivationOffset]];
+    const Operand& depthwiseMultiplierOperand =
+            subgraph->operands[operation.inputs[baseOptionsIdx + kDepthwiseMultiplier]];
+    NN_RET_CHECK(isOperandConstant(strideWOperand));
+    NN_RET_CHECK(isOperandConstant(strideHOperand));
+    NN_RET_CHECK(isOperandConstant(activationOperand));
+    NN_RET_CHECK(isOperandConstant(depthwiseMultiplierOperand));
+
+    // get strides and activation
+    int32_t strideW = context->getConstantScalar<int32_t>(strideWOperand);
+    int32_t strideH = context->getConstantScalar<int32_t>(strideHOperand);
+    int32_t depthwiseMultiplier = context->getConstantScalar<int32_t>(depthwiseMultiplierOperand);
+    FusedActivationFunc activation = static_cast<FusedActivationFunc>(
+            context->getConstantScalar<int32_t>(activationOperand));
+
+    // check for nchw
+    int isNchwIdx = baseOptionsIdx + kIsNchwOffset;
+    if (operation.inputs.size() > static_cast<uint32_t>(isNchwIdx)) {
+        const Operand& isNchwOperand = subgraph->operands[operation.inputs[isNchwIdx]];
+        NN_RET_CHECK(isOperandConstant(isNchwOperand));
+
+        bool isNchw = context->getConstantScalar<bool>(isNchwOperand);
+        NN_RET_CHECK(!isNchw) << "TFLite does not support NCHW formatted input tensors";
+    }
+
+    // dilations
+    int dilationWIdx = baseOptionsIdx + kDilationWOffset;
+    int dilationHIdx = baseOptionsIdx + kDilationHOffset;
+    // default dilation factors are 1
+    int32_t dilationW = 1;
+    int32_t dilationH = 1;
+    if (operation.inputs.size() > static_cast<uint32_t>(dilationWIdx)) {
+        const Operand& dilationWOperand = subgraph->operands[operation.inputs[dilationWIdx]];
+        NN_RET_CHECK(isOperandConstant(dilationWOperand));
+
+        dilationW = context->getConstantScalar<int32_t>(dilationWOperand);
+    }
+    if (operation.inputs.size() > static_cast<uint32_t>(dilationHIdx)) {
+        const Operand& dilationHOperand = subgraph->operands[operation.inputs[dilationHIdx]];
+        NN_RET_CHECK(isOperandConstant(dilationHOperand));
+
+        dilationH = context->getConstantScalar<int32_t>(dilationHOperand);
+    }
+
+    flatbuffers::Offset<tflite::DepthwiseConv2DOptions> optionsFlatbuffer =
+            tflite::CreateDepthwiseConv2DOptions(
+                    context->getBuilder(), padding, strideW, strideH, depthwiseMultiplier,
+                    NN_TRY(getTfliteActivation(activation)) /* fused_activation_function */,
+                    dilationW, dilationH);
+    auto operatorFlatbuffer = tflite::CreateOperatorDirect(
+            context->getBuilder() /* builder */, opCodeIdx /* opcode_index */, &inputs /* inputs */,
+            &outputs /* outputs */,
+            tflite::BuiltinOptions::
+                    BuiltinOptions_DepthwiseConv2DOptions /* builtin_options_type */,
+            optionsFlatbuffer.Union() /* builtin_options */);
+    context->addOperatorFlatbuffer(operatorFlatbuffer);
+
+    return {};
+}
+
+NN_REGISTER_OPERATION_CONVERTER(DEPTHWISE_CONV_2D, DepthwiseConv2DOperationConverter);
+
+}  // namespace nn
+}  // namespace android
\ No newline at end of file
diff --git a/runtime/operation_converters/DepthwiseConv2DOperationConverter.h b/runtime/operation_converters/DepthwiseConv2DOperationConverter.h
new file mode 100644
index 0000000..37302d7
--- /dev/null
+++ b/runtime/operation_converters/DepthwiseConv2DOperationConverter.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_DEPTHWISE_CONV2D_OPERATION_CONVERTER_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_DEPTHWISE_CONV2D_OPERATION_CONVERTER_H
+
+#include <vector>
+
+#include "Conv2DOperationConverter.h"
+
+namespace android {
+namespace nn {
+
+class DepthwiseConv2DOperationConverter : public Conv2DOperationConverter {
+   public:
+    Result<void> convert(const Operation& operation, SubGraphContext* context) const override;
+
+   private:
+    // Offset locations of BuiltinOption parameters in NNAPI Operand inputs
+    static constexpr int kStrideWOffset = 0;
+    static constexpr int kStrideHOffset = 1;
+    static constexpr int kDepthwiseMultiplier = 2;
+    static constexpr int kActivationOffset = 3;
+    static constexpr int kIsNchwOffset = 4;
+    static constexpr int kDilationWOffset = 5;
+    static constexpr int kDilationHOffset = 6;
+};
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_DEPTHWISE_CONV2D_OPERATION_CONVERTER_H
\ No newline at end of file
diff --git a/runtime/operation_converters/LogisticOperationConverter.cpp b/runtime/operation_converters/LogisticOperationConverter.cpp
new file mode 100644
index 0000000..20528f4
--- /dev/null
+++ b/runtime/operation_converters/LogisticOperationConverter.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LogisticOperationConverter.h"
+
+#include <vector>
+
+#include "OperationConverterResolver.h"
+#include "SubGraphContext.h"
+
+namespace android {
+namespace nn {
+
+Result<std::vector<int32_t>> LogisticOperationConverter::getLogisticInputs(
+        const Operation& operation, SubGraphContext* context) const {
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.inputs[kInputTensorIdx]));
+    std::vector<int32_t> inputs{
+            context->getTensorIdxFromOperandIdx(operation.inputs[kInputTensorIdx])};
+    return inputs;
+}
+
+Result<std::vector<int32_t>> LogisticOperationConverter::getLogisticOutputs(
+        const Operation& operation, SubGraphContext* context) const {
+    NN_TRY(context->createTensorFlatbufferFromOperand(operation.outputs[kOutputTensorIdx]));
+    std::vector<int32_t> outputs{
+            context->getTensorIdxFromOperandIdx(operation.outputs[kOutputTensorIdx])};
+    return outputs;
+}
+
+Result<void> LogisticOperationConverter::convert(const Operation& operation,
+                                                 SubGraphContext* context) const {
+    // add opcode for LOGISTIC if not added yet
+    uint32_t opCodeIdx = context->addOpCode(OperationType::LOGISTIC);
+
+    std::vector<int32_t> inputs = NN_TRY(getLogisticInputs(operation, context));
+    std::vector<int32_t> outputs = NN_TRY(getLogisticOutputs(operation, context));
+
+    auto optionsFlatbuffer = tflite::CreateLogSoftmaxOptions(context->getBuilder());
+    auto operatorFlatbuffer = tflite::CreateOperatorDirect(
+            context->getBuilder() /* builder */, opCodeIdx /* opcode_index */, &inputs /* inputs */,
+            &outputs /* outputs */,
+            tflite::BuiltinOptions::BuiltinOptions_LogSoftmaxOptions /* builtin_options_type */,
+            optionsFlatbuffer.Union() /* builtin_options */);
+    context->addOperatorFlatbuffer(operatorFlatbuffer);
+
+    return {};
+}
+
+NN_REGISTER_OPERATION_CONVERTER(LOGISTIC, LogisticOperationConverter);
+
+}  // namespace nn
+}  // namespace android
\ No newline at end of file
diff --git a/runtime/operation_converters/LogisticOperationConverter.h b/runtime/operation_converters/LogisticOperationConverter.h
new file mode 100644
index 0000000..dc8dccc
--- /dev/null
+++ b/runtime/operation_converters/LogisticOperationConverter.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_LOGISTIC_OPERATION_CONVERTER_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_LOGISTIC_OPERATION_CONVERTER_H
+
+#include <vector>
+
+#include "OperationConverter.h"
+
+namespace android {
+namespace nn {
+
+class LogisticOperationConverter : public IOperationConverter {
+   public:
+    Result<void> convert(const Operation& operation, SubGraphContext* context) const override;
+
+   private:
+    Result<std::vector<int32_t>> getLogisticInputs(const Operation& operation,
+                                                   SubGraphContext* context) const;
+    Result<std::vector<int32_t>> getLogisticOutputs(const Operation& operation,
+                                                    SubGraphContext* context) const;
+
+    // Location of Operator inputs in a NNAPI Operation
+    static constexpr int kInputTensorIdx = 0;
+
+    // Location of Operator outputs in a NNAPI Operation
+    static constexpr int kOutputTensorIdx = 0;
+};
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_LOGISTIC_OPERATION_CONVERTER_H
\ No newline at end of file
diff --git a/runtime/operation_converters/OperationConverter.h b/runtime/operation_converters/OperationConverter.h
new file mode 100644
index 0000000..abe8d4a
--- /dev/null
+++ b/runtime/operation_converters/OperationConverter.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_OPERATION_CONVERTER_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_OPERATION_CONVERTER_H
+
+#include "SubGraphContext.h"
+
+namespace android {
+namespace nn {
+
+class IOperationConverter {
+   public:
+    virtual ~IOperationConverter() = default;
+
+    virtual Result<void> convert(const Operation& operation, SubGraphContext* context) const = 0;
+};
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_OPERATION_CONVERTER_H
\ No newline at end of file
diff --git a/runtime/operation_converters/OperationConverterResolver.cpp b/runtime/operation_converters/OperationConverterResolver.cpp
new file mode 100644
index 0000000..530b959
--- /dev/null
+++ b/runtime/operation_converters/OperationConverterResolver.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OperationConverterResolver"
+
+#include "OperationConverterResolver.h"
+
+#include "OperationsUtils.h"
+
+namespace android {
+namespace nn {
+
+#define NN_FORWARD_DECLARE_OPERATION_CONVERTER_REGISTRATION_FUNCTION(opType) \
+    const IOperationConverter* registerConverter_##opType();
+
+NN_FOR_EACH_OPERATION(NN_FORWARD_DECLARE_OPERATION_CONVERTER_REGISTRATION_FUNCTION)
+
+#undef NN_FORWARD_DECLARE_OPERATION_CONVERTER_REGISTRATION_FUNCTION
+
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(AVERAGE_POOL_2D);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(CONCATENATION);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(DEPTH_TO_SPACE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(DEQUANTIZE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(EMBEDDING_LOOKUP);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(FLOOR);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(FULLY_CONNECTED);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(HASHTABLE_LOOKUP);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(L2_NORMALIZATION);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(L2_POOL_2D);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LOCAL_RESPONSE_NORMALIZATION);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LSH_PROJECTION);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LSTM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(MAX_POOL_2D);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(MUL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RELU);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RELU1);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RELU6);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RESHAPE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RESIZE_BILINEAR);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RNN);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SOFTMAX);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SPACE_TO_DEPTH);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SVDF);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(TANH);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(BATCH_TO_SPACE_ND);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(DIV);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(MEAN);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(PAD);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SPACE_TO_BATCH_ND);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SQUEEZE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(STRIDED_SLICE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SUB);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(TRANSPOSE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(ABS);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(ARGMAX);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(ARGMIN);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(AXIS_ALIGNED_BBOX_TRANSFORM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(BIDIRECTIONAL_SEQUENCE_LSTM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(BIDIRECTIONAL_SEQUENCE_RNN);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(BOX_WITH_NMS_LIMIT);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(CAST);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(CHANNEL_SHUFFLE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(DENSIFY);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(DETECTION_POSTPROCESSING);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(EQUAL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(EXP);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(EXPAND_DIMS);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(GATHER);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(GENERATE_PROPOSALS);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(GREATER);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(GREATER_EQUAL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(GROUPED_CONV_2D);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(HEATMAP_MAX_KEYPOINT);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(INSTANCE_NORMALIZATION);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LESS);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LESS_EQUAL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LOG);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LOGICAL_AND);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LOGICAL_NOT);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LOGICAL_OR);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(LOG_SOFTMAX);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(MAXIMUM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(MINIMUM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(NEG);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(NOT_EQUAL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(PAD_V2);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(POW);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(PRELU);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(QUANTIZE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(QUANTIZED_16BIT_LSTM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RANDOM_MULTINOMIAL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(REDUCE_ALL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(REDUCE_ANY);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(REDUCE_MAX);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(REDUCE_MIN);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(REDUCE_PROD);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(REDUCE_SUM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(ROI_ALIGN);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(ROI_POOLING);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RSQRT);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SELECT);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SIN);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SLICE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SPLIT);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(SQRT);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(TILE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(TOPK_V2);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(TRANSPOSE_CONV_2D);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(UNIDIRECTIONAL_SEQUENCE_LSTM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(UNIDIRECTIONAL_SEQUENCE_RNN);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RESIZE_NEAREST_NEIGHBOR);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(QUANTIZED_LSTM);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(IF);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(WHILE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(ELU);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(HARD_SWISH);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(FILL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(RANK);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(BATCH_MATMUL);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(PACK);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(MIRROR_PAD);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(REVERSE);
+NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(OEM_OPERATION);
+
+OperationConverterResolver::OperationConverterResolver() {
+#define NN_REGISTER_OPERATION_CONVERTER_TO_RESOLVER(operationType) \
+    registerOperationConverter(registerConverter_##operationType(), OperationType::operationType);
+    NN_FOR_EACH_OPERATION(NN_REGISTER_OPERATION_CONVERTER_TO_RESOLVER)
+#undef NN_REGISTER_OPERATION_CONVERTER_TO_RESOLVER
+}
+
+const IOperationConverter* OperationConverterResolver::findOperationConverter(
+        OperationType operationType) const {
+    int32_t index = static_cast<int32_t>(operationType);
+    if (index >= 0 && index < kNumberOfOperationTypes) {
+        return mConverters[index];
+    }
+    return nullptr;
+}
+
+void OperationConverterResolver::registerOperationConverter(
+        const IOperationConverter* operationConverter, OperationType operationType) {
+    if (operationConverter == nullptr) {
+        return;
+    }
+
+    int32_t index = static_cast<int32_t>(operationType);
+    CHECK(mConverters[index] == nullptr);
+    mConverters[index] = operationConverter;
+}
+
+}  // namespace nn
+}  // namespace android
\ No newline at end of file
diff --git a/runtime/operation_converters/OperationConverterResolver.h b/runtime/operation_converters/OperationConverterResolver.h
new file mode 100644
index 0000000..4057799
--- /dev/null
+++ b/runtime/operation_converters/OperationConverterResolver.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_OPERATION_CONVERTER_RESOLVER_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_OPERATION_CONVERTER_RESOLVER_H
+
+#include "OperationConverter.h"
+#include "SubGraphContext.h"
+
+namespace android {
+namespace nn {
+
+// OperationConverterResolver is used to register all operation converters that implement
+// IOperationConverter. This retrieves the correct converter to use based on OperationType
+class OperationConverterResolver {
+   public:
+    static const OperationConverterResolver* get() {
+        static OperationConverterResolver instance;
+        return &instance;
+    }
+    const IOperationConverter* findOperationConverter(OperationType operationType) const;
+
+   private:
+    OperationConverterResolver();
+
+    void registerOperationConverter(const IOperationConverter* operationConverter,
+                                    OperationType operationType);
+
+    const IOperationConverter* mConverters[kNumberOfOperationTypes] = {};
+};
+
+// Use to register operation converter into OperationConverterResolver
+#define NN_REGISTER_OPERATION_CONVERTER(identifier, OperationConverterClass) \
+    const IOperationConverter* registerConverter_##identifier() {            \
+        static OperationConverterClass converter;                            \
+        return &converter;                                                   \
+    }
+
+// Use to indicate which operations are not supported
+#define NN_OPERATION_CONVERTER_NOT_IMPLEMENTED(identifier)        \
+    const IOperationConverter* registerConverter_##identifier() { \
+        return nullptr;                                           \
+    }
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_OPERATION_CONVERTER_RESOLVER_H
\ No newline at end of file
diff --git a/runtime/operation_converters/SubGraphContext.cpp b/runtime/operation_converters/SubGraphContext.cpp
new file mode 100644
index 0000000..c4ccb50
--- /dev/null
+++ b/runtime/operation_converters/SubGraphContext.cpp
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SubGraphContext"
+
+#include "SubGraphContext.h"
+
+#include <limits>
+
+#include "FlatbufferModelBuilderUtils.h"
+
+namespace android {
+namespace nn {
+
+SubGraphContext::SubGraphContext(const Model* model, const Model::Subgraph* subgraph,
+                                 flatbuffers::FlatBufferBuilder* builder,
+                                 std::vector<OperatorCodeFlatbuffer>* opCodesVector,
+                                 std::vector<int>* opCodeIndexForOperationType,
+                                 std::vector<BufferFlatbuffer>* bufferVector)
+    : mModel(model),
+      mSubgraph(subgraph),
+      mBuilder(builder),
+      mOpCodesVector(opCodesVector),
+      mOpCodeIndexForOperationType(opCodeIndexForOperationType),
+      mBufferVector(bufferVector) {
+    CHECK(model != nullptr);
+    CHECK(subgraph != nullptr);
+    CHECK(opCodesVector != nullptr);
+    CHECK(opCodeIndexForOperationType != nullptr);
+    CHECK(bufferVector != nullptr);
+
+    mOperandToTensorIdx.resize(subgraph->operands.size(), -1);
+    mMappings.resize(model->pools.size());
+}
+
+SubGraphFlatbuffer SubGraphContext::finish() {
+    return tflite::CreateSubGraphDirect(*mBuilder, &mTensorVector, &mInputTensors, &mOutputTensors,
+                                        &mOperatorVector);
+}
+
+int SubGraphContext::addTensorFlatbuffer(TensorFlatbuffer tensor, int32_t operandIdx) {
+    mTensorVector.push_back(tensor);
+
+    int tensorIdx = mTensorVector.size() - 1;
+    if (operandIdx >= 0) {
+        CHECK(mOperandToTensorIdx[operandIdx] == -1);
+        mOperandToTensorIdx[operandIdx] = tensorIdx;
+    }
+    return tensorIdx;
+}
+
+void SubGraphContext::addOperatorFlatbuffer(OperatorFlatbuffer opFlatbuffer) {
+    mOperatorVector.push_back(opFlatbuffer);
+}
+
+void SubGraphContext::addSubGraphInput(int32_t operandIdx) {
+    CHECK(mOperandToTensorIdx[operandIdx] != -1);
+    mInputTensors.push_back(mOperandToTensorIdx[operandIdx]);
+}
+
+void SubGraphContext::addSubGraphOutput(int32_t operandIdx) {
+    CHECK(mOperandToTensorIdx[operandIdx] != -1);
+    mOutputTensors.push_back(mOperandToTensorIdx[operandIdx]);
+}
+
+uint32_t SubGraphContext::addOpCode(OperationType operationType) {
+    uint32_t idx = static_cast<uint32_t>(operationType);
+    if (mOpCodeIndexForOperationType->at(idx) != -1) {
+        return mOpCodeIndexForOperationType->at(idx);
+    }
+
+    OperatorCodeFlatbuffer opCode;
+
+    tflite::BuiltinOperator builtinCode = getFlatbufferOperator(operationType);
+    if (builtinCode < tflite::BuiltinOperator::BuiltinOperator_PLACEHOLDER_FOR_GREATER_OP_CODES)
+        opCode = tflite::CreateOperatorCode(
+                *mBuilder, static_cast<int8_t>(builtinCode) /* deprecated_builtin_code */,
+                0 /* custom_code */, getMaxOperatorVersionCode(builtinCode) /* version */);
+    else
+        opCode = tflite::CreateOperatorCode(*mBuilder, 0 /* deprecated_builtin_code */,
+                                            0 /* custom_code */,
+                                            getMaxOperatorVersionCode(builtinCode) /* version */,
+                                            builtinCode /* builtin_code */);
+
+    mOpCodesVector->push_back(opCode);
+    uint32_t opCodeIdx = mOpCodesVector->size() - 1;
+    (*mOpCodeIndexForOperationType)[idx] = opCodeIdx;
+    return opCodeIdx;
+}
+
+int SubGraphContext::getTensorIdxFromOperandIdx(int operandIdx) const {
+    return mOperandToTensorIdx[operandIdx];
+}
+
+const Mapping& SubGraphContext::getMapping(uint32_t poolIndex) {
+    if (mMappings[poolIndex].size > 0) {
+        return mMappings[poolIndex];
+    }
+
+    SharedMemory memory = mModel->pools[poolIndex];
+    GeneralResult<Mapping> mapping = map(memory);
+    CHECK(mapping.has_value()) << "CONSTANT_REFERENCE memory mapping error: "
+                               << mapping.error().message;
+
+    mMappings[poolIndex] = std::move(mapping).value();
+    return mMappings[poolIndex];
+}
+
+std::pair<const uint8_t*, uint32_t> SubGraphContext::getConstantPointerAndLength(
+        const Operand& operand) {
+    CHECK(isOperandConstant(operand));
+
+    if (operand.lifetime == Operand::LifeTime::CONSTANT_COPY) {
+        return std::make_pair(mModel->operandValues.data() + operand.location.offset,
+                              operand.location.length);
+    }
+
+    const Mapping& mapping = getMapping(operand.location.poolIndex);
+    const uint8_t* memoryPtr = static_cast<const uint8_t*>(
+            std::visit([](auto ptr) { return static_cast<const void*>(ptr); }, mapping.pointer));
+
+    return std::make_pair(memoryPtr + operand.location.offset, operand.location.length);
+}
+
+uint32_t SubGraphContext::addBufferFromData(const uint8_t* data, uint32_t length) {
+    auto dataVectorFlatbuffer = mBuilder->CreateVector(data, length);
+
+    auto buffer = tflite::CreateBuffer(*mBuilder, dataVectorFlatbuffer);
+    mBufferVector->push_back(buffer);
+
+    return mBufferVector->size() - 1;
+}
+
+Result<void> SubGraphContext::createTensorFlatbufferFromOperand(uint32_t operandIdx,
+                                                                bool makeSymmetric) {
+    // An output Operand to one Operation can be an input Operand to
+    // another Operation, so this function can be run more than once.
+    // We simply return if the Tensor for the Operand is already created.
+    if (mOperandToTensorIdx[operandIdx] != -1) return {};
+
+    const Operand& operand = mSubgraph->operands[operandIdx];
+
+    std::vector<float> scaleVector{operand.scale};
+    std::vector<int64_t> zeroPointVector{operand.zeroPoint};
+    // min and max used to convert TFLite models to TF models, so it is unused in this case and can
+    // be set to 0
+    std::vector<float> minVector{0};
+    std::vector<float> maxVector{0};
+
+    // build quantization parameters
+    auto quantizationParams = tflite::CreateQuantizationParametersDirect(
+            *mBuilder, &minVector /* min */, &maxVector /* max */, &scaleVector /* scale */,
+            &zeroPointVector /* zero_point */,
+            tflite::QuantizationDetails::QuantizationDetails_NONE /* details_type */);
+
+    // add buffer if constant operand
+    // buffer at index 0 is reserved for tensors without a buffer
+    uint32_t bufferIdx = 0;
+    if (isOperandConstant(operand)) {
+        auto [data, dataLength] = getConstantPointerAndLength(operand);
+        if (makeSymmetric && operand.type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED) {
+            std::vector<int8_t> dataVector(reinterpret_cast<const int8_t*>(data),
+                                           reinterpret_cast<const int8_t*>(data) + dataLength);
+            bool emitWarning = false;
+            for (uint32_t i = 0; i < dataLength; i++) {
+                int32_t newValue = static_cast<int32_t>(dataVector[i]) - operand.zeroPoint;
+                if (newValue < std::numeric_limits<int8_t>::min() ||
+                    newValue > std::numeric_limits<int8_t>::max()) {
+                    emitWarning = true;
+                }
+                dataVector[i] = static_cast<int8_t>(std::clamp(
+                        newValue, static_cast<int32_t>(std::numeric_limits<int8_t>::min()),
+                        static_cast<int32_t>(std::numeric_limits<int8_t>::max())));
+            }
+
+            if (emitWarning) {
+                LOG(WARNING) << "Asymmetric to symmetric conversion will result in "
+                                "underflow/overflow. Clamping data";
+            }
+            bufferIdx = addBufferFromData(reinterpret_cast<const uint8_t*>(dataVector.data()),
+                                          dataLength);
+        } else {
+            bufferIdx = addBufferFromData(data, dataLength);
+        }
+    }
+
+    // shape of tensor
+    std::vector<int32_t> shape(operand.dimensions.begin(), operand.dimensions.end());
+    replaceZeroDimensions(&shape);
+
+    // build tensor
+    TensorFlatbuffer tensor = tflite::CreateTensorDirect(
+            *mBuilder, &shape, NN_TRY(getTensorFlatbufferOperandType(operand.type)) /* type */,
+            bufferIdx /* buffer */, 0 /* name */, quantizationParams /* quantization */);
+    addTensorFlatbuffer(tensor, operandIdx);
+
+    return {};
+}
+
+}  // namespace nn
+}  // namespace android
\ No newline at end of file
diff --git a/runtime/operation_converters/SubGraphContext.h b/runtime/operation_converters/SubGraphContext.h
new file mode 100644
index 0000000..17d3d0e
--- /dev/null
+++ b/runtime/operation_converters/SubGraphContext.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_SUBGRAPH_CONTEXT_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_SUBGRAPH_CONTEXT_H
+
+#include <utility>
+#include <vector>
+
+#include "FlatbufferModelBuilderUtils.h"
+#include "NeuralNetworks.h"
+
+namespace android {
+namespace nn {
+
+// This keeps track of all the data needed to convert NNAPI subgraphs to TFLite subgraphs
+// This also provides information needed to convert NNAPI Operations to TFLite Operators
+// Once the subgraph is done building, call finish() to return the flatbuffer
+class SubGraphContext {
+   public:
+    SubGraphContext(const Model* model, const Model::Subgraph* subgraph,
+                    flatbuffers::FlatBufferBuilder* builder,
+                    std::vector<OperatorCodeFlatbuffer>* opCodesVector,
+                    std::vector<int>* opCodeIndexForOperationType,
+                    std::vector<BufferFlatbuffer>* bufferVector);
+
+    SubGraphFlatbuffer finish();
+
+    // If the operandIdx is -1, it suggests that the tensor being added doesn't have a
+    // corresponding Operand from the NNAPI NDK model.
+    // Returns index of Tensor being added.
+    int addTensorFlatbuffer(TensorFlatbuffer tensor, int32_t operandIdx = -1);
+    void addOperatorFlatbuffer(OperatorFlatbuffer opFlatbuffer);
+    void addSubGraphInput(int32_t operandIdx);
+    void addSubGraphOutput(int32_t operandIdx);
+
+    const Model::Subgraph* getSubgraph() const { return mSubgraph; }
+    // Returns -1 if there is no corresponding tensor index
+    int getTensorIdxFromOperandIdx(int operandIdx) const;
+    uint32_t addOpCode(OperationType operationType);
+    flatbuffers::FlatBufferBuilder& getBuilder() { return *mBuilder; }
+
+    // OperandLifeTime must be CONSTANT_COPY or CONSTANT_REFERENCE
+    // Will crash if OperandLifeTime is not either of the two.
+    // dataSize is the size of data in bytes.
+    template <typename Type>
+    void copyConstantValueToData(const Operand& operand, Type* data, size_t dataSize);
+    template <typename Type>
+    Type getConstantScalar(const Operand& operand);
+
+    // Returns Buffer index
+    uint32_t addBufferFromData(const uint8_t* data, uint32_t length);
+    // makeSymmetric turns asymmetric tensors to symmetric by doing setting data = data - zeroPoint
+    // makeSymmetric is supported only for constant OperandType::TENSOR_QUANT8_ASYMM_SIGNED
+    // If unsupported type is passed, makeSymmetric is ignored
+    Result<void> createTensorFlatbufferFromOperand(uint32_t operandIdx, bool makeSymmetric = false);
+
+   private:
+    const Mapping& getMapping(uint32_t poolIndex);
+    std::pair<const uint8_t*, uint32_t> getConstantPointerAndLength(const Operand& operand);
+
+    const Model* mModel;
+    const Model::Subgraph* mSubgraph;
+    flatbuffers::FlatBufferBuilder* mBuilder;
+
+    std::vector<OperatorCodeFlatbuffer>* mOpCodesVector;
+    std::vector<int>* mOpCodeIndexForOperationType;
+    std::vector<BufferFlatbuffer>* mBufferVector;
+
+    std::vector<OperatorFlatbuffer> mOperatorVector;
+    std::vector<TensorFlatbuffer> mTensorVector;
+    std::vector<int32_t> mInputTensors;
+    std::vector<int32_t> mOutputTensors;
+    std::vector<int> mOperandToTensorIdx;
+    // Each index corresponds to the pool index of shared memory
+    std::vector<Mapping> mMappings;
+};
+
+template <typename Type>
+void SubGraphContext::copyConstantValueToData(const Operand& operand, Type* data, size_t dataSize) {
+    auto [pointer, length] = getConstantPointerAndLength(operand);
+    CHECK_GE(dataSize, length);
+
+    std::memcpy(data, pointer, length);
+}
+
+template <typename Type>
+Type SubGraphContext::getConstantScalar(const Operand& operand) {
+    Type data;
+    copyConstantValueToData(operand, &data, sizeof(Type));
+    return data;
+}
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_OPERATION_CONVERTERS_SUBGRAPH_CONTEXT_H
\ No newline at end of file
diff --git a/runtime/packageinfo/libneuralnetworks_packageinfo.map.txt b/runtime/packageinfo/libneuralnetworks_packageinfo.map.txt
index 803b345..95308d3 100644
--- a/runtime/packageinfo/libneuralnetworks_packageinfo.map.txt
+++ b/runtime/packageinfo/libneuralnetworks_packageinfo.map.txt
@@ -15,8 +15,8 @@
 #
 LIBNEURALNETWORKS_PACKAGE_INFO {
   global:
-    ANeuralNetworks_fetch_PackageInfo; # apex
-    ANeuralNetworks_free_PackageInfo; # apex
+    ANeuralNetworks_fetch_PackageInfo; # systemapi
+    ANeuralNetworks_free_PackageInfo; # systemapi
   local:
     *;
 };
diff --git a/runtime/test/Android.bp b/runtime/test/Android.bp
index 57addd9..7904484 100644
--- a/runtime/test/Android.bp
+++ b/runtime/test/Android.bp
@@ -201,6 +201,58 @@
     ],
 }
 
+cc_defaults {
+    name: "NeuralNetworksTest_v2_static_defaults",
+    defaults: ["NeuralNetworksTest_static_defaults"],
+    srcs: [
+        "TestCompatibilityLayer.cpp",
+    ],
+    exclude_srcs: [
+        "PreparedModelCallback.cpp",
+        "TestCompilationCaching.cpp",
+        "TestCompliance.cpp",
+        "TestControlFlow.cpp",
+        "TestExecution.cpp",
+        "TestExtensions.cpp",
+        "TestFailingDriver.cpp",
+        "TestFree.cpp",
+        "TestGenerated.cpp",
+        "TestIntrospectionControl.cpp",
+        "TestMemory.cpp",
+        "TestMemoryDomain.cpp",
+        "TestMemoryInternal.cpp",
+        "TestOperandExtraParams.cpp",
+        "TestPartitioning.cpp",
+        "TestPartitioningRandom.cpp",
+        "TestRemoveDefaultArguments.cpp",
+        "TestServerFlag.cpp",
+        "TestTelemetry.cpp",
+        "TestTrivialModel.cpp",
+        "TestUnknownDimensions.cpp",
+        "TestUnspecifiedDimensions.cpp",
+        "TestUpdatability.cpp",
+        "TestValidateModel.cpp",
+        "TestValidateOperations.cpp",
+        "TestValidation.cpp",
+        "fibonacci_extension/FibonacciDriver.cpp",
+        "fibonacci_extension/FibonacciExtensionTest.cpp",
+    ],
+
+    include_dirs: [
+        "external/flatbuffers/include",
+        "external/tensorflow",
+    ],
+
+    static_libs: [
+        "libflatbuffers-cpp",
+        "libneuralnetworks_v2_static_experimental",
+        "libtflite_static",
+    ],
+    exclude_static_libs: [
+        "libneuralnetworks_static",
+    ],
+}
+
 cc_test {
     name: "NeuralNetworksTest_static",
     defaults: ["NeuralNetworksTest_static_defaults"],
@@ -238,6 +290,41 @@
     },
 }
 
+cc_test {
+    name: "NeuralNetworksTest_v2_static",
+    defaults: ["NeuralNetworksTest_v2_static_defaults"],
+    test_suites: [
+        "general-tests",
+    ],
+    target: {
+        android: {
+            test_config: "AndroidTest_NeuralNetworksTest_v2_static.xml",
+            srcs: ["TestStatsdTelemetry.cpp"],
+        },
+        host: {
+            cflags: [
+                "-D__ANDROID_API__=10000",
+            ],
+        },
+    },
+    whole_static_libs: [
+        "neuralnetworks_generated_experimental_example",
+    ],
+    exclude_static_libs: [
+        "libneuralnetworks_common",
+        "neuralnetworks_types",
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "libneuralnetworks_common_experimental",
+        "neuralnetworks_types_experimental",
+    ],
+    cflags: ["-DNN_EXPERIMENTAL_FEATURE"],
+    test_options: {
+        unit_test: false,
+    },
+}
+
 tidy_disabled_operation_signatures_files = [
     // These took too much time with clang-tidy.
     "fuzzing/operation_signatures/Convolutions.cpp",
@@ -417,7 +504,6 @@
 cc_library_static {
     name: "CtsNNAPITests_static",
     host_supported: true,
-    defaults: ["neuralnetworks_float16"],
     srcs: [
         ":libneuralnetworks_generated_test_harness_for_cts",
         "CtsMain.cpp",
@@ -506,7 +592,6 @@
 
 cc_defaults {
     name: "neuralnetworks_generated_defaults",
-    defaults: ["neuralnetworks_float16"],
     tidy: false, // generated files are too big to run with clang-tidy
     host_supported: true,
     vendor_available: true,
@@ -552,7 +637,6 @@
 cc_library_static {
     name: "neuralnetworks_generated_V1_3_cts_only_example",
     host_supported: true,
-    defaults: ["neuralnetworks_float16"],
     tidy: false, // generated files are too big to run with clang-tidy
     srcs: ["generated/spec_V1_3_cts_only/*.example.cpp"],
     static_libs: ["libneuralnetworks_generated_test_harness"],
@@ -568,7 +652,6 @@
 cc_library_static {
     name: "NeuralNetworksTest_random_graph",
     host_supported: true,
-    defaults: ["neuralnetworks_float16"],
     srcs: [
         ":libneuralnetworks_generated_test_harness_for_cts",
         "GeneratedTestUtils.cpp",
diff --git a/runtime/test/AndroidTest_NeuralNetworksTest_v2_static.xml b/runtime/test/AndroidTest_NeuralNetworksTest_v2_static.xml
new file mode 100644
index 0000000..d0ca057
--- /dev/null
+++ b/runtime/test/AndroidTest_NeuralNetworksTest_v2_static.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs NeuralNetworksTest_v2_static.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="false" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="NeuralNetworksTest_v2_static->/data/local/tmp/NeuralNetworksTest_v2_static" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="NeuralNetworksTest_v2_static" />
+        <option name="native-test-timeout" value="3h" />
+    </test>
+</configuration>
diff --git a/runtime/test/SupportLibraryTestGenerated.cpp b/runtime/test/SupportLibraryTestGenerated.cpp
index ceff04a..609987f 100644
--- a/runtime/test/SupportLibraryTestGenerated.cpp
+++ b/runtime/test/SupportLibraryTestGenerated.cpp
@@ -61,6 +61,11 @@
 using namespace sl_wrapper;
 using namespace test_helper;
 
+enum ComputeWithDeviceMemoriesResult {
+    SKIP,
+    OK,
+};
+
 class GeneratedTests : public GeneratedTestBase {
    protected:
     void SetUp() override;
@@ -72,9 +77,9 @@
                                                       uint32_t index);
     ANeuralNetworksMemory* createDeviceMemoryForOutput(const Compilation& compilation,
                                                        uint32_t index);
-    void computeWithDeviceMemories(const Compilation& compilation, const TestModel& testModel,
-                                   Execution* execution, Execution::ComputeMode computeMode,
-                                   Result* result, std::vector<TestBuffer>* outputs);
+    ComputeWithDeviceMemoriesResult computeWithDeviceMemories(
+            const Compilation& compilation, const TestModel& testModel, Execution* execution,
+            Execution::ComputeMode computeMode, Result* result, std::vector<TestBuffer>* outputs);
     bool checkSupported(const Model& model, ANeuralNetworksDevice* device);
     std::optional<Compilation> compileModel(const Model& model, ANeuralNetworksDevice* device);
     void executeWithCompilation(const Compilation& compilation, const TestModel& testModel);
@@ -284,13 +289,12 @@
 }
 
 // Set result = Result::NO_ERROR and outputs = {} if the test should be skipped.
-void GeneratedTests::computeWithDeviceMemories(const Compilation& compilation,
-                                               const TestModel& testModel, Execution* execution,
-                                               Execution::ComputeMode computeMode, Result* result,
-                                               std::vector<TestBuffer>* outputs) {
-    ASSERT_NE(execution, nullptr);
-    ASSERT_NE(result, nullptr);
-    ASSERT_NE(outputs, nullptr);
+ComputeWithDeviceMemoriesResult GeneratedTests::computeWithDeviceMemories(
+        const Compilation& compilation, const TestModel& testModel, Execution* execution,
+        Execution::ComputeMode computeMode, Result* result, std::vector<TestBuffer>* outputs) {
+    EXPECT_NE(execution, nullptr);
+    EXPECT_NE(result, nullptr);
+    EXPECT_NE(outputs, nullptr);
     outputs->clear();
     std::vector<Memory> inputMemories, outputMemories;
 
@@ -302,30 +306,34 @@
             const auto& operand = testModel.main.operands[testModel.main.inputIndexes[i]];
             // Omitted input.
             if (operand.data.size() == 0) {
-                ASSERT_EQ(Result::NO_ERROR, execution->setInput(i, nullptr, 0));
+                EXPECT_EQ(Result::NO_ERROR, execution->setInput(i, nullptr, 0));
                 continue;
             }
 
             // Create device memory.
             ANeuralNetworksMemory* memory = createDeviceMemoryForInput(compilation, i);
-            ASSERT_NE(memory, nullptr);
+            if (memory == nullptr) {
+                return ComputeWithDeviceMemoriesResult::SKIP;
+            }
             auto& wrapperMemory = inputMemories.emplace_back(Memory(mNnApi.get(), memory));
 
             // Copy data from TestBuffer to device memory.
             auto ashmem = TestAshmem::createFrom(mNnApi.get(), operand.data);
-            ASSERT_NE(ashmem, nullptr);
-            ASSERT_EQ(mNnApi->getFL5()->ANeuralNetworksMemory_copy(ashmem->get()->get(), memory),
+            EXPECT_NE(ashmem, nullptr);
+            EXPECT_EQ(mNnApi->getFL5()->ANeuralNetworksMemory_copy(ashmem->get()->get(), memory),
                       ANEURALNETWORKS_NO_ERROR);
-            ASSERT_EQ(Result::NO_ERROR, execution->setInputFromMemory(i, &wrapperMemory, 0, 0));
+            EXPECT_EQ(Result::NO_ERROR, execution->setInputFromMemory(i, &wrapperMemory, 0, 0));
         }
 
         // Model outputs.
         for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
             SCOPED_TRACE("Output index: " + std::to_string(i));
             ANeuralNetworksMemory* memory = createDeviceMemoryForOutput(compilation, i);
-            ASSERT_NE(memory, nullptr);
+            if (memory == nullptr) {
+                return ComputeWithDeviceMemoriesResult::SKIP;
+            }
             auto& wrapperMemory = outputMemories.emplace_back(Memory(mNnApi.get(), memory));
-            ASSERT_EQ(Result::NO_ERROR, execution->setOutputFromMemory(i, &wrapperMemory, 0, 0));
+            EXPECT_EQ(Result::NO_ERROR, execution->setOutputFromMemory(i, &wrapperMemory, 0, 0));
         }
     }
 
@@ -339,13 +347,14 @@
         auto& output = outputs->emplace_back(bufferSize);
 
         auto ashmem = TestAshmem::createFrom(mNnApi.get(), output);
-        ASSERT_NE(ashmem, nullptr);
-        ASSERT_EQ(mNnApi->getFL5()->ANeuralNetworksMemory_copy(outputMemories[i].get(),
+        EXPECT_NE(ashmem, nullptr);
+        EXPECT_EQ(mNnApi->getFL5()->ANeuralNetworksMemory_copy(outputMemories[i].get(),
                                                                ashmem->get()->get()),
                   ANEURALNETWORKS_NO_ERROR);
         std::copy(ashmem->dataAs<uint8_t>(), ashmem->dataAs<uint8_t>() + bufferSize,
                   output.getMutable<uint8_t>());
     }
+    return ComputeWithDeviceMemoriesResult::OK;
 }
 
 void GeneratedTests::executeWithCompilation(const Compilation& compilation,
@@ -357,8 +366,11 @@
     std::vector<TestBuffer> outputs;
 
     if (mTestDeviceMemory) {
-        computeWithDeviceMemories(compilation, testModel, &execution, mComputeMode, &result,
-                                  &outputs);
+        if (computeWithDeviceMemories(compilation, testModel, &execution, mComputeMode, &result,
+                                      &outputs) == ComputeWithDeviceMemoriesResult::SKIP) {
+            std::cout << "\nModel not supported by device memories. Skipping" << std::endl;
+            return;
+        }
     } else {
         computeWithPtrs(testModel, &execution, mComputeMode, &result, &outputs);
     }
diff --git a/runtime/test/TestCompatibilityLayer.cpp b/runtime/test/TestCompatibilityLayer.cpp
new file mode 100644
index 0000000..99674a6
--- /dev/null
+++ b/runtime/test/TestCompatibilityLayer.cpp
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <ftw.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include "AndroidVersionUtil.h"
+#include "GeneratedTestUtils.h"
+#include "NeuralNetworks.h"
+#include "NeuralNetworksTypes.h"
+#include "TestHarness.h"
+#include "TestNeuralNetworksWrapper.h"
+#include "TestUtils.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#include "tensorflow/lite/interpreter.h"
+#include "tensorflow/lite/kernels/register.h"
+#include "tensorflow/lite/model.h"
+#pragma clang diagnostic pop
+
+#ifdef NNTEST_CTS
+#define NNTEST_COMPUTE_MODE
+#endif
+
+namespace android::nn::generated_tests {
+using namespace test_wrapper;
+using namespace test_helper;
+
+class CompatibilityLayerGeneratedTests : public GeneratedTestBase {
+   protected:
+    void SetUp() override;
+    void TearDown() override;
+
+    // Test driver for those generated from packages/modules/NeuralNetworks/runtime/test/specs
+    void execute(const TestModel& testModel);
+
+    bool mTestDynamicOutputShape = false;
+    bool mTestSupported = true;
+};
+
+class CompatibilityLayerGeneratedTestsSupported : public CompatibilityLayerGeneratedTests {};
+class CompatibilityLayerGeneratedTestsUnsupported : public CompatibilityLayerGeneratedTests {};
+class CompatibilityLayerGeneratedTestsDynamicOutput : public CompatibilityLayerGeneratedTests {};
+
+void CompatibilityLayerGeneratedTests::execute(const TestModel& testModel) {
+    GeneratedModel model;
+    createModel(testModel, mTestDynamicOutputShape, &model);
+    if (testModel.expectFailure && !model.isValid()) {
+        return;
+    }
+    ASSERT_EQ(model.finish(), Result::NO_ERROR);
+    ASSERT_TRUE(model.isValid());
+
+    Compilation compilation(&model);
+    Result result = compilation.finish();
+    if (!mTestSupported && result != Result::NO_ERROR) return;
+    ASSERT_EQ(result, Result::NO_ERROR);
+
+    Execution execution(&compilation);
+
+    // Model inputs.
+    for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+        const auto& operand = testModel.main.operands[testModel.main.inputIndexes[i]];
+        ASSERT_EQ(Result::NO_ERROR,
+                  execution.setInput(i, operand.data.get<void>(), operand.data.size()));
+    }
+
+    // Model outputs.
+    std::vector<TestBuffer> outputs;
+    for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
+        const auto& operand = testModel.main.operands[testModel.main.outputIndexes[i]];
+        const size_t bufferSize = std::max<size_t>(operand.data.size(), 1);
+        outputs.emplace_back(bufferSize);
+
+        ASSERT_EQ(Result::NO_ERROR,
+                  execution.setOutput(i, outputs.back().getMutable<void>(), bufferSize));
+    }
+
+    result = execution.compute(Execution::ComputeMode::SYNC);
+    ASSERT_EQ(result, Result::NO_ERROR);
+
+    // If a conv filter under/overflows, "compatibleTest" will report
+    // unsupported, but the actual conversion will result in NO_ERROR because
+    // it is treated as a warning, rather than an error. Because of the accuracy
+    // loss, we should not check test results in such a case.
+    //
+    // TODO(b/237410741): A potentially better approach is to have
+    // "compatibleTest" report three status: fully supported, supported with
+    // accuracy loss, and not supported.
+    if (mTestSupported) {
+        checkResults(testModel, outputs);
+    }
+}
+
+void CompatibilityLayerGeneratedTests::SetUp() {
+    GeneratedTestBase::SetUp();
+}
+
+void CompatibilityLayerGeneratedTests::TearDown() {
+    GeneratedTestBase::TearDown();
+}
+
+namespace {
+
+bool compatibleTest(const TestModel& testModel) {
+    static const std::vector<TestOperationType> kSupportedOperationTypes{
+            TestOperationType::CONV_2D, TestOperationType::ADD,
+            TestOperationType::DEPTHWISE_CONV_2D, TestOperationType::LOGISTIC};
+    static const std::vector<TestOperandType> kSupportedOperandTypes{
+            TestOperandType::TENSOR_FLOAT32, TestOperandType::TENSOR_INT32,
+            TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, TestOperandType::BOOL,
+            TestOperandType::INT32};
+
+    if (testModel.hasControlFlow()) {
+        return false;
+    }
+
+    bool result = true;
+    const TestSubgraph& mainSubgraph = testModel.main;
+
+    result &= std::all_of(
+            mainSubgraph.operations.begin(), mainSubgraph.operations.end(),
+            [&mainSubgraph](const TestOperation& operation) {
+                bool isOperationCompatible = true;
+                // ensure that tensors are nhwc and filter is constant
+                if (operation.type == TestOperationType::CONV_2D ||
+                    operation.type == TestOperationType::DEPTHWISE_CONV_2D) {
+                    size_t implicitIsNchwIdx =
+                            (operation.type == TestOperationType::CONV_2D) ? 7 : 8;
+                    size_t explicitIsNchwIdx = implicitIsNchwIdx + 3;
+                    bool isImplicitPadding =
+                            operation.inputs.size() <= implicitIsNchwIdx ||
+                            mainSubgraph.operands[operation.inputs[implicitIsNchwIdx]].type ==
+                                    TestOperandType::BOOL;
+                    size_t isNchwIdx = isImplicitPadding ? implicitIsNchwIdx : explicitIsNchwIdx;
+
+                    if (operation.inputs.size() > static_cast<uint32_t>(isNchwIdx)) {
+                        isOperationCompatible &=
+                                !(*mainSubgraph.operands[operation.inputs[isNchwIdx]]
+                                           .data.get<bool>());
+                    }
+
+                    const int kFilterIdx = 1;
+                    const TestOperand& filterOperand =
+                            mainSubgraph.operands[operation.inputs[kFilterIdx]];
+                    TestOperandLifeTime filterLifetime = filterOperand.lifetime;
+                    isOperationCompatible &=
+                            (filterLifetime == TestOperandLifeTime::CONSTANT_COPY) ||
+                            (filterLifetime == TestOperandLifeTime::CONSTANT_REFERENCE);
+
+                    // check that making filter operands symmetrical does not over/underflow
+                    // this is because the outputs of the model will be different from expected if
+                    // the operand value changes with the under/overflow
+                    if (filterOperand.type == TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED) {
+                        const int8_t* data = filterOperand.data.get<int8_t>();
+                        size_t dataSize = filterOperand.data.size();
+
+                        for (int32_t i = 0; i < static_cast<int32_t>(dataSize); i++) {
+                            int32_t newValue =
+                                    static_cast<int32_t>(data[i]) - filterOperand.zeroPoint;
+                            if (newValue < std::numeric_limits<int8_t>::min() ||
+                                newValue > std::numeric_limits<int8_t>::max()) {
+                                isOperationCompatible = false;
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                isOperationCompatible &=
+                        std::find(kSupportedOperationTypes.begin(), kSupportedOperationTypes.end(),
+                                  operation.type) != kSupportedOperationTypes.end();
+
+                return isOperationCompatible;
+            });
+
+    result &= std::all_of(mainSubgraph.operands.begin(), mainSubgraph.operands.end(),
+                          [](const TestOperand& operand) {
+                              return std::find(kSupportedOperandTypes.begin(),
+                                               kSupportedOperandTypes.end(),
+                                               operand.type) != kSupportedOperandTypes.end();
+                          });
+
+    return result;
+}
+
+}  // namespace
+
+TEST_P(CompatibilityLayerGeneratedTestsSupported, CompatibilityLayerSupported) {
+    mTestSupported = true;
+    execute(testModel);
+}
+
+TEST_P(CompatibilityLayerGeneratedTestsUnsupported, CompatibilityLayerUnsupported) {
+    mTestSupported = false;
+    execute(testModel);
+}
+
+TEST_P(CompatibilityLayerGeneratedTestsDynamicOutput, CompatibilityLayerDynamicOutput) {
+    mTestDynamicOutputShape = true;
+    mTestSupported = false;
+    execute(testModel);
+}
+
+INSTANTIATE_GENERATED_TEST(CompatibilityLayerGeneratedTestsSupported,
+                           [](const TestModel& testModel) {
+                               return !testModel.expectFailure && compatibleTest(testModel);
+                           });
+
+INSTANTIATE_GENERATED_TEST(CompatibilityLayerGeneratedTestsUnsupported,
+                           [](const TestModel& testModel) {
+                               return !testModel.expectFailure && !compatibleTest(testModel);
+                           });
+
+INSTANTIATE_GENERATED_TEST(CompatibilityLayerGeneratedTestsDynamicOutput,
+                           [](const TestModel& testModel) {
+                               return !testModel.expectFailure && !testModel.hasScalarOutputs();
+                           });
+
+}  // namespace android::nn::generated_tests
diff --git a/runtime/test/android_fuzzing/Android.bp b/runtime/test/android_fuzzing/Android.bp
index 0b0f9e8..1e86cda 100644
--- a/runtime/test/android_fuzzing/Android.bp
+++ b/runtime/test/android_fuzzing/Android.bp
@@ -25,6 +25,7 @@
 cc_library_static {
     name: "libneuralnetworks_fuzzer_proto",
     host_supported: true,
+    vendor_available: true,
     owner: "google",
     srcs: ["Model.proto"],
     proto: {
@@ -37,6 +38,7 @@
 cc_library_static {
     name: "libneuralnetworks_fuzzer_harness",
     host_supported: true,
+    vendor_available: true,
     owner: "google",
     srcs: [
         "Converter.cpp",
diff --git a/runtime/test/fuzzing/RandomGraphGeneratorUtils.h b/runtime/test/fuzzing/RandomGraphGeneratorUtils.h
index 39a3c77..c1de329 100644
--- a/runtime/test/fuzzing/RandomGraphGeneratorUtils.h
+++ b/runtime/test/fuzzing/RandomGraphGeneratorUtils.h
@@ -288,16 +288,35 @@
 
 // getUniform for integers operates on a closed interval [lower, upper].
 // This is important that 255 should be included as a valid candidate for QUANT8_ASYMM values.
+//
+// This template only accepts int8_t, uint8_t, bool, or the types accepted by stdlib's
+// uniform_int_distribution. `char` is not an officially supported type, but may be
+// supported depending on library implementation.
 template <typename T>
 inline std::enable_if_t<std::is_integral_v<T>, T> getUniform(T lower, T upper) {
-    std::uniform_int_distribution<T> dis(lower, upper);
-    return dis(RandomNumberGenerator::generator);
+    // uniform_int_distribution is only defined by the stdlib standard
+    // when T is of types short, int, long, long long,
+    // unsigned short, unsigned int, unsigned long, or unsigned long long.
+    //
+    // However, existing code relies on getUniform working for some smaller types,
+    // so we special case them here.
+    if constexpr (std::is_same_v<T, uint8_t> || std::is_same_v<T, int8_t> ||
+                  std::is_same_v<T, bool>) {
+        // We can get away with using bool here because lower and upper are 0 or 1.
+        // The uint8_t case can always be upsized to int16_t and then downsized because
+        // we'll never generate a random number lower than the minimum of 0 when T
+        // is unsigned.
+        std::uniform_int_distribution<int16_t> dis(lower, upper);
+        return static_cast<T>(dis(RandomNumberGenerator::generator));
+    } else {
+        std::uniform_int_distribution<T> dis(lower, upper);
+        return dis(RandomNumberGenerator::generator);
+    }
 }
 template <typename T>
 inline std::enable_if_t<std::is_integral_v<T>, T> getUniformNonZero(T lower, T upper, T zeroPoint) {
     if (upper >= zeroPoint) upper--;
-    std::uniform_int_distribution<T> dis(lower, upper);
-    const T value = dis(RandomNumberGenerator::generator);
+    const T value = getUniform(lower, upper);
     return value >= zeroPoint ? value + 1 : value;
 }
 
diff --git a/shim_and_sl/ShimConverter.cpp b/shim_and_sl/ShimConverter.cpp
index 4830c5d..9914af1 100644
--- a/shim_and_sl/ShimConverter.cpp
+++ b/shim_and_sl/ShimConverter.cpp
@@ -51,6 +51,10 @@
         size_t subgraphIndex, const std::vector<uint8_t>& copiedOperandValues,
         ErrorStatus* errorStatus) {
     *errorStatus = ErrorStatus::NONE;
+    if (allModels == nullptr || subgraphIndex >= (*allModels).size()) {
+        *errorStatus = ErrorStatus::INVALID_ARGUMENT;
+        return nullptr;
+    }
     if ((*allModels)[subgraphIndex].has_value()) {
         return (*allModels)[subgraphIndex]->getHandle();
     }
diff --git a/tools/nnapi_info/Android.bp b/tools/nnapi_info/Android.bp
new file mode 100644
index 0000000..048615f
--- /dev/null
+++ b/tools/nnapi_info/Android.bp
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    // Inherits all licenses from parent to get Apache 2.0 and package name
+    default_applicable_licenses: [
+        "packages_modules_NeuralNetworks_license",
+    ],
+}
+
+cc_binary {
+    name: "nnapi_info",
+    srcs: [
+        "nnapi_info.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libnativewindow",
+        "libneuralnetworks",
+        "libutils",
+    ],
+}
diff --git a/tools/nnapi_info/nnapi_info.cpp b/tools/nnapi_info/nnapi_info.cpp
new file mode 100644
index 0000000..e3170bb
--- /dev/null
+++ b/tools/nnapi_info/nnapi_info.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NnapiInfo"
+
+#define CONTINUE_IF_ERR(expr)                                                                \
+    {                                                                                        \
+        int _errCode = (expr);                                                               \
+        if (_errCode != ANEURALNETWORKS_NO_ERROR) {                                          \
+            std::cerr << #expr << " failed at " << __FILE__ << ":" << __LINE__ << std::endl; \
+            continue;                                                                        \
+        }                                                                                    \
+    }
+
+#include <iostream>
+#include <string>
+
+#include "NeuralNetworks.h"
+#include "NeuralNetworksTypes.h"
+
+namespace {
+std::string featureLevelString(int64_t featureLevel) {
+    switch (featureLevel) {
+        case ANEURALNETWORKS_FEATURE_LEVEL_1:
+            return "Level 1";
+        case ANEURALNETWORKS_FEATURE_LEVEL_2:
+            return "Level 2";
+        case ANEURALNETWORKS_FEATURE_LEVEL_3:
+            return "Level 3";
+        case ANEURALNETWORKS_FEATURE_LEVEL_4:
+            return "Level 4";
+        case ANEURALNETWORKS_FEATURE_LEVEL_5:
+            return "Level 5";
+        case ANEURALNETWORKS_FEATURE_LEVEL_6:
+            return "Level 6";
+        case ANEURALNETWORKS_FEATURE_LEVEL_7:
+            return "Level 7";
+        case ANEURALNETWORKS_FEATURE_LEVEL_8:
+            return "Level 8";
+        default:
+            return "Undefined feature level code";
+    }
+}
+
+std::string deviceTypeString(int32_t type) {
+    switch (type) {
+        case ANEURALNETWORKS_DEVICE_ACCELERATOR:
+            return "Accelerator";
+        case ANEURALNETWORKS_DEVICE_CPU:
+            return "CPU";
+        case ANEURALNETWORKS_DEVICE_GPU:
+            return "GPU";
+        case ANEURALNETWORKS_DEVICE_OTHER:
+            return "Other";
+        case ANEURALNETWORKS_DEVICE_UNKNOWN:
+        default:
+            return "Unknown";
+    }
+}
+}  // namespace
+
+int main() {
+    uint32_t numDevices;
+    int returnCode = ANeuralNetworks_getDeviceCount(&numDevices);
+    if (returnCode != ANEURALNETWORKS_NO_ERROR) {
+        std::cerr << "Error obtaining device count" << std::endl;
+        return 1;
+    }
+
+    std::cout << "Number of devices: " << numDevices << std::endl << std::endl;
+
+    ANeuralNetworksDevice* device = nullptr;
+    int64_t featureLevel;
+    const char* name;
+    int32_t type;
+    const char* version;
+    for (uint32_t i = 0; i < numDevices; i++) {
+        CONTINUE_IF_ERR(ANeuralNetworks_getDevice(i, &device));
+        CONTINUE_IF_ERR(ANeuralNetworksDevice_getFeatureLevel(device, &featureLevel));
+        CONTINUE_IF_ERR(ANeuralNetworksDevice_getName(device, &name));
+        CONTINUE_IF_ERR(ANeuralNetworksDevice_getType(device, &type));
+        CONTINUE_IF_ERR(ANeuralNetworksDevice_getVersion(device, &version));
+
+        std::cout << "Device: " << name << std::endl;
+        std::cout << "Feature Level: " << featureLevelString(featureLevel) << std::endl;
+        std::cout << "Type: " << deviceTypeString(type) << std::endl;
+        std::cout << "Version: " << version << std::endl;
+
+        std::cout << std::endl;
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/tools/test_generator/test_harness/include/TestHarness.h b/tools/test_generator/test_harness/include/TestHarness.h
index 743af80..d702c2a 100644
--- a/tools/test_generator/test_harness/include/TestHarness.h
+++ b/tools/test_generator/test_harness/include/TestHarness.h
@@ -366,6 +366,8 @@
         return newTestModel;
     }
 
+    bool hasControlFlow() const { return !referenced.empty(); }
+
     bool hasQuant8CoupledOperands() const {
         bool result = false;
         forEachSubgraph([&result](const TestSubgraph& subgraph) {