Introduce NNAPI Canonical Interface types

This CL introduces the canonical interface types: IDevice,
IPreparedModel, and IBuffer.

This CL also introduces three new types:
* SyncFence: wraps a NativeHandle to represent a sync_fence
* GeneralResult: a Result object where the error is extended with an
  ErrorStatus
* ExecutionResult: a Result object where the error is extended with an
  ErrorStatus and a vector of OutputShapes

Bug: 160668438
Test: mma
Test: NeuralNetworksTest_static
Change-Id: I99a6ccd88d0fa2f14296eb0019555b2aa46ab488
Merged-In: I99a6ccd88d0fa2f14296eb0019555b2aa46ab488
(cherry picked from commit 3fa5419e7a90fe98112c479e84952a4214496187)
diff --git a/nn/common/SharedMemory.cpp b/nn/common/SharedMemory.cpp
index 9186be2..3c848c3 100644
--- a/nn/common/SharedMemory.cpp
+++ b/nn/common/SharedMemory.cpp
@@ -67,7 +67,7 @@
     return mSize == 0;
 }
 
-Result<Memory> MutableMemoryBuilder::finish() {
+GeneralResult<Memory> MutableMemoryBuilder::finish() {
     return createSharedMemory(mSize);
 }
 
@@ -84,7 +84,7 @@
     return mBuilder.empty();
 }
 
-Result<Memory> ConstantMemoryBuilder::finish() {
+GeneralResult<Memory> ConstantMemoryBuilder::finish() {
     // Allocate the memory.
     auto memory = NN_TRY(mBuilder.finish());
 
@@ -92,10 +92,6 @@
     const auto [pointer, size, context] = NN_TRY(map(memory););
 
     // Get mutable pointer.
-    if (!std::holds_alternative<void*>(pointer)) {
-        return NN_ERROR()
-               << "MemoryBuilder::finish failed because the mapped pointer is not mutable";
-    }
     uint8_t* mutablePointer = static_cast<uint8_t*>(std::get<void*>(pointer));
 
     // Copy data to the memory pool.
diff --git a/nn/common/SharedMemoryAndroid.cpp b/nn/common/SharedMemoryAndroid.cpp
index caa83a6..9baca73 100644
--- a/nn/common/SharedMemoryAndroid.cpp
+++ b/nn/common/SharedMemoryAndroid.cpp
@@ -46,7 +46,7 @@
 
 const char* const kAllocatorService = "ashmem";
 
-Result<hidl_memory> allocateSharedMemory(size_t size) {
+GeneralResult<hidl_memory> allocateSharedMemory(size_t size) {
     static const auto allocator = IAllocator::getService(kAllocatorService);
     CHECK_GT(size, 0u);
 
@@ -59,7 +59,8 @@
     allocator->allocate(size, fn);
 
     if (!maybeMemory.valid()) {
-        return NN_ERROR() << "IAllocator::allocate returned an invalid (empty) memory object";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
+               << "IAllocator::allocate returned an invalid (empty) memory object";
     }
 
     return maybeMemory;
@@ -85,19 +86,19 @@
     return copiedMemory;
 }
 
-Result<Mapping> mapAshmem(const Memory& memory) {
+GeneralResult<Mapping> mapAshmem(const Memory& memory) {
     const auto hidlMemory = createHidlMemory(memory);
     const auto mapping = mapMemory(hidlMemory);
     if (mapping == nullptr) {
-        return NN_ERROR() << "Failed to map memory";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to map memory";
     }
     auto* const pointer = mapping->getPointer().withDefault(nullptr);
     if (pointer == nullptr) {
-        return NN_ERROR() << "Failed to get the mapped pointer";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to get the mapped pointer";
     }
     const auto fullSize = mapping->getSize().withDefault(0);
     if (fullSize == 0 || fullSize > std::numeric_limits<size_t>::max()) {
-        return NN_ERROR() << "Failed to get the mapped size";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to get the mapped size";
     }
     const size_t size = static_cast<size_t>(fullSize);
 
@@ -113,7 +114,7 @@
     std::any context;
 };
 
-Result<Mapping> mapMemFd(const Memory& memory) {
+GeneralResult<Mapping> mapMemFd(const Memory& memory) {
     const size_t size = memory.size;
     const native_handle_t* handle = memory.handle->handle();
     const int fd = handle->data[0];
@@ -122,7 +123,7 @@
 
     std::shared_ptr<base::MappedFile> mapping = base::MappedFile::FromFd(fd, offset, size, prot);
     if (mapping == nullptr) {
-        return NN_ERROR() << "Can't mmap the file descriptor.";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Can't mmap the file descriptor.";
     }
     void* data = mapping->data();
 
@@ -130,7 +131,7 @@
     return Mapping{.pointer = data, .size = size, .context = std::move(context)};
 }
 
-Result<Mapping> mapAhwbBlobMemory(const Memory& memory) {
+GeneralResult<Mapping> mapAhwbBlobMemory(const Memory& memory) {
     const auto* handle = memory.handle->handle();
     const auto size = memory.size;
     const auto format = AHARDWAREBUFFER_FORMAT_BLOB;
@@ -153,13 +154,15 @@
     status_t status = AHardwareBuffer_createFromHandle(
             &desc, handle, AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE, &hardwareBuffer);
     if (status != NO_ERROR) {
-        return NN_ERROR() << "Can't create AHardwareBuffer from handle. Error: " << status;
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
+               << "Can't create AHardwareBuffer from handle. Error: " << status;
     }
 
     void* data = nullptr;
     status = AHardwareBuffer_lock(hardwareBuffer, usage, -1, nullptr, &data);
     if (status != NO_ERROR) {
-        return NN_ERROR() << "Can't lock the AHardwareBuffer. Error: " << status;
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
+               << "Can't lock the AHardwareBuffer. Error: " << status;
         // TODO(b/169166682): do we need to call AHardwareBuffer_release?
     }
 
@@ -175,27 +178,28 @@
     return Mapping{.pointer = data, .size = size, .context = std::move(sharedScoped)};
 }
 
-Result<Mapping> mapAhwbMemory(const Memory& /*memory*/) {
-    return NN_ERROR() << "Unable to map non-BLOB AHardwareBuffer memory";
+GeneralResult<Mapping> mapAhwbMemory(const Memory& /*memory*/) {
+    return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
+           << "Unable to map non-BLOB AHardwareBuffer memory";
 }
 
 }  // namespace
 
-Result<Memory> createSharedMemory(size_t size) {
+GeneralResult<Memory> createSharedMemory(size_t size) {
     const auto memory = NN_TRY(allocateSharedMemory(size));
     return createSharedMemoryFromHidlMemory(memory);
 }
 
-Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) {
+GeneralResult<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) {
     if (size == 0 || fd < 0) {
-        return NN_ERROR() << "Invalid size or fd";
+        return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid size or fd";
     }
 
     // Duplicate the file descriptor so the resultant Memory owns its own version.
     int dupfd = dup(fd);
     if (dupfd == -1) {
         // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here?
-        return NN_ERROR() << "Failed to dup the fd";
+        return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Failed to dup the fd";
     }
 
     // Create a temporary native handle to own the dupfd.
@@ -203,7 +207,7 @@
     if (nativeHandle == nullptr) {
         close(dupfd);
         // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here?
-        return NN_ERROR() << "Failed to create native_handle";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle";
     }
 
     const auto [lowOffsetBits, highOffsetBits] = getIntsFromOffset(offset);
@@ -219,11 +223,11 @@
     return Memory{.handle = std::move(ownedHandle), .size = size, .name = "mmap_fd"};
 }
 
-Result<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& memory) {
+GeneralResult<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& memory) {
     return createMemory(memory);
 }
 
-Result<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb) {
+GeneralResult<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb) {
     AHardwareBuffer_Desc bufferDesc;
     AHardwareBuffer_describe(&ahwb, &bufferDesc);
     const native_handle_t* handle = AHardwareBuffer_getNativeHandle(&ahwb);
@@ -243,7 +247,7 @@
     return Memory{.handle = std::move(nativeHandle), .size = 0, .name = "hardware_buffer"};
 }
 
-Result<Mapping> map(const Memory& memory) {
+GeneralResult<Mapping> map(const Memory& memory) {
     if (memory.name == "ashmem") {
         return mapAshmem(memory);
     }
@@ -256,7 +260,7 @@
     if (memory.name == "hardware_buffer") {
         return mapAhwbMemory(memory);
     }
-    return NN_ERROR() << "Cannot map unknown memory " << memory.name;
+    return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Cannot map unknown memory " << memory.name;
 }
 
 bool flush(const Mapping& mapping) {
diff --git a/nn/common/SharedMemoryHost.cpp b/nn/common/SharedMemoryHost.cpp
index bc29d1f..231977c 100644
--- a/nn/common/SharedMemoryHost.cpp
+++ b/nn/common/SharedMemoryHost.cpp
@@ -32,7 +32,7 @@
 namespace android::nn {
 namespace {
 
-Result<Mapping> mapAshmem(const Memory& memory) {
+GeneralResult<Mapping> mapAshmem(const Memory& memory) {
     CHECK_LE(memory.size, std::numeric_limits<uint32_t>::max());
     const auto size = memory.size;
 
@@ -40,7 +40,7 @@
     std::shared_ptr<base::MappedFile> mapping =
             base::MappedFile::FromFd(fd, /*offset=*/0, size, PROT_READ | PROT_WRITE);
     if (mapping == nullptr) {
-        return NN_ERROR() << "Can't mmap the file descriptor.";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Can't mmap the file descriptor.";
     }
     void* data = mapping->data();
 
@@ -52,7 +52,7 @@
     std::any context;
 };
 
-Result<Mapping> mapMemFd(const Memory& memory) {
+GeneralResult<Mapping> mapMemFd(const Memory& memory) {
     const size_t size = memory.size;
     const native_handle_t* handle = memory.handle->handle();
     const int fd = handle->data[0];
@@ -61,7 +61,7 @@
 
     std::shared_ptr<base::MappedFile> mapping = base::MappedFile::FromFd(fd, offset, size, prot);
     if (mapping == nullptr) {
-        return NN_ERROR() << "Can't mmap the file descriptor.";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Can't mmap the file descriptor.";
     }
     void* data = mapping->data();
 
@@ -71,16 +71,17 @@
 
 }  // namespace
 
-Result<Memory> createSharedMemory(size_t size) {
+GeneralResult<Memory> createSharedMemory(size_t size) {
     int fd = ashmem_create_region("NnapiAshmem", size);
     if (fd < 0) {
-        return NN_ERROR() << "ashmem_create_region(" << size << ") fails with " << fd;
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
+               << "ashmem_create_region(" << size << ") fails with " << fd;
     }
 
     native_handle_t* handle = native_handle_create(1, 0);
     if (handle == nullptr) {
         // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here?
-        return NN_ERROR() << "Failed to create native_handle";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle";
     }
     handle->data[0] = fd;
 
@@ -91,16 +92,16 @@
     return Memory{.handle = std::move(nativeHandle), .size = size, .name = "ashmem"};
 }
 
-Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) {
+GeneralResult<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) {
     if (size == 0 || fd < 0) {
-        return NN_ERROR() << "Invalid size or fd";
+        return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid size or fd";
     }
 
     // Duplicate the file descriptor so the resultant Memory owns its own version.
     int dupfd = dup(fd);
     if (dupfd == -1) {
         // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here?
-        return NN_ERROR() << "Failed to dup the fd";
+        return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Failed to dup the fd";
     }
 
     // Create a temporary native handle to own the dupfd.
@@ -108,7 +109,7 @@
     if (nativeHandle == nullptr) {
         close(dupfd);
         // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here?
-        return NN_ERROR() << "Failed to create native_handle";
+        return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle";
     }
 
     const auto [lowOffsetBits, highOffsetBits] = getIntsFromOffset(offset);
@@ -124,22 +125,23 @@
     return Memory{.handle = std::move(ownedHandle), .size = size, .name = "mmap_fd"};
 }
 
-Result<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& /*memory*/) {
-    return NN_ERROR() << "hidl_memory not supported on host";
+GeneralResult<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& /*memory*/) {
+    return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "hidl_memory not supported on host";
 }
 
-Result<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& /*ahwb*/) {
-    return NN_ERROR() << "AHardwareBuffer memory not supported on host";
+GeneralResult<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& /*ahwb*/) {
+    return NN_ERROR(ErrorStatus::INVALID_ARGUMENT)
+           << "AHardwareBuffer memory not supported on host";
 }
 
-Result<Mapping> map(const Memory& memory) {
+GeneralResult<Mapping> map(const Memory& memory) {
     if (memory.name == "ashmem") {
         return mapAshmem(memory);
     }
     if (memory.name == "mmap_fd") {
         return mapMemFd(memory);
     }
-    return NN_ERROR() << "Cannot map unknown memory " << memory.name;
+    return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Cannot map unknown memory " << memory.name;
 }
 
 bool flush(const Mapping& mapping) {
diff --git a/nn/common/TypeUtils.cpp b/nn/common/TypeUtils.cpp
index ad17d94..6d089bd 100644
--- a/nn/common/TypeUtils.cpp
+++ b/nn/common/TypeUtils.cpp
@@ -782,6 +782,20 @@
     return os << timePoint.time_since_epoch().count() << "ns since epoch";
 }
 
+std::ostream& operator<<(std::ostream& os, const SyncFence::FenceState& fenceState) {
+    switch (fenceState) {
+        case SyncFence::FenceState::ACTIVE:
+            return os << "ACTIVE";
+        case SyncFence::FenceState::SIGNALED:
+            return os << "SIGNALED";
+        case SyncFence::FenceState::ERROR:
+            return os << "ERROR";
+        case SyncFence::FenceState::UNKNOWN:
+            return os << "UNKNOWN";
+    }
+    return os << "SyncFence::FenceState{" << underlyingType(fenceState) << "}";
+}
+
 std::ostream& operator<<(std::ostream& os, const OptionalTimePoint& optionalTimePoint) {
     if (!optionalTimePoint.has_value()) {
         return os << "<no time point>";
diff --git a/nn/common/Types.cpp b/nn/common/Types.cpp
index 761ffa7..17485f4 100644
--- a/nn/common/Types.cpp
+++ b/nn/common/Types.cpp
@@ -17,6 +17,9 @@
 #include "Types.h"
 
 #include <android-base/logging.h>
+#include <cutils/native_handle.h>
+#include <errno.h>
+#include <poll.h>
 
 #include <algorithm>
 #include <cstddef>
@@ -121,4 +124,66 @@
     return mSorted;
 }
 
+SyncFence SyncFence::createAsSignaled() {
+    return SyncFence(nullptr);
+}
+
+Result<SyncFence> SyncFence::create(NativeHandle syncFence) {
+    const bool isValid = (syncFence != nullptr && syncFence->handle() != nullptr &&
+                          syncFence->handle()->numFds == 1 && syncFence->handle()->numInts == 0 &&
+                          &syncFence->handle()->data[0] != nullptr);
+    if (!isValid) {
+        return NN_ERROR() << "Invalid sync fence handle passed to SyncFence::create";
+    }
+    return SyncFence(std::move(syncFence));
+}
+
+SyncFence::SyncFence(NativeHandle syncFence) : mSyncFence(std::move(syncFence)) {}
+
+SyncFence::FenceState SyncFence::syncWait(OptionalTimeout optionalTimeout) const {
+    if (mSyncFence == nullptr) {
+        return FenceState::SIGNALED;
+    }
+
+    const int fd = mSyncFence->handle()->data[0];
+    const int timeout = optionalTimeout.value_or(Timeout{-1}).count();
+
+    // This implementation is directly based on the ::sync_wait() implementation.
+
+    struct pollfd fds;
+    int ret;
+
+    if (fd < 0) {
+        errno = EINVAL;
+        return FenceState::UNKNOWN;
+    }
+
+    fds.fd = fd;
+    fds.events = POLLIN;
+
+    do {
+        ret = poll(&fds, 1, timeout);
+        if (ret > 0) {
+            if (fds.revents & POLLNVAL) {
+                errno = EINVAL;
+                return FenceState::UNKNOWN;
+            }
+            if (fds.revents & POLLERR) {
+                errno = EINVAL;
+                return FenceState::ERROR;
+            }
+            return FenceState::SIGNALED;
+        } else if (ret == 0) {
+            errno = ETIME;
+            return FenceState::ACTIVE;
+        }
+    } while (ret == -1 && (errno == EINTR || errno == EAGAIN));
+
+    return FenceState::UNKNOWN;
+}
+
+NativeHandle SyncFence::getHandle() const {
+    return mSyncFence;
+}
+
 }  // namespace android::nn
diff --git a/nn/common/include/nnapi/IBuffer.h b/nn/common/include/nnapi/IBuffer.h
new file mode 100644
index 0000000..295681e
--- /dev/null
+++ b/nn/common/include/nnapi/IBuffer.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IBUFFER_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IBUFFER_H
+
+#include "nnapi/Result.h"
+#include "nnapi/Types.h"
+
+namespace android::nn {
+
+/**
+ * This interface represents a device memory buffer.
+ *
+ * This interface is thread-safe, and any class that implements this interface must be thread-safe.
+ */
+class IBuffer {
+   public:
+    /**
+     * Retrieves the token corresponding to this buffer.
+     *
+     * @return MemoryDomainToken corresponding to this buffer.
+     */
+    virtual Request::MemoryDomainToken getToken() const = 0;
+
+    /**
+     * Retrieves the content of this buffer to a shared memory region.
+     *
+     * The IBuffer object must have been initialized before the call to IBuffer::copyTo.
+     * For more information on the state of the IBuffer object, refer to IDevice::allocate.
+     *
+     * @param dst The destination shared memory region.
+     * @return Nothing on success, otherwise GeneralError.
+     */
+    virtual GeneralResult<void> copyTo(const Memory& dst) const = 0;
+
+    /**
+     * Sets the content of this buffer from a shared memory region.
+     *
+     * @param src The source shared memory region.
+     * @param dimensions Updated dimensional information. If the dimensions of the IBuffer object
+     *     are not fully specified, then the dimensions must be fully specified here. If the
+     *     dimensions of the IBuffer object are fully specified, then the dimensions may be empty
+     *     here. If dimensions.size() > 0, then all dimensions must be specified here, and any
+     *     dimension that was specified in the IBuffer object must have the same value here.
+     * @return Nothing on success, otherwise GeneralError.
+     */
+    virtual GeneralResult<void> copyFrom(const Memory& src, const Dimensions& dimensions) const = 0;
+
+    // Public virtual destructor to allow objects to be stored (and destroyed) as smart pointers.
+    // E.g., std::unique_ptr<IBuffer>.
+    virtual ~IBuffer() = default;
+
+   protected:
+    // Protect the non-destructor special member functions to prevent object slicing.
+    IBuffer() = default;
+    IBuffer(const IBuffer&) = default;
+    IBuffer(IBuffer&&) noexcept = default;
+    IBuffer& operator=(const IBuffer&) = default;
+    IBuffer& operator=(IBuffer&&) noexcept = default;
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IBUFFER_H
diff --git a/nn/common/include/nnapi/IDevice.h b/nn/common/include/nnapi/IDevice.h
new file mode 100644
index 0000000..9a2e516
--- /dev/null
+++ b/nn/common/include/nnapi/IDevice.h
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IDEVICE_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IDEVICE_H
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "nnapi/Result.h"
+#include "nnapi/Types.h"
+
+namespace android::nn {
+
+// Forward declarations
+class IBuffer;
+class IPreparedModel;
+
+/**
+ * This interface represents a device driver.
+ *
+ * This interface is thread-safe, and any class that implements this interface must be thread-safe.
+ */
+class IDevice {
+   public:
+    /**
+     * Returns the name of the driver.
+     *
+     * @return Name of the driver.
+     */
+    virtual const std::string& getName() const = 0;
+
+    /**
+     * Get the version string of the driver implementation.
+     *
+     * The version string must be a unique token among the set of version strings of drivers of a
+     * specific device. The token identifies the device driver's implementation. The token must not
+     * be confused with the feature level which is solely defined by the interface version. This API
+     * is opaque to the Android framework, but the Android framework may use the information for
+     * debugging or to pass on to NNAPI applications.
+     *
+     * Application developers sometimes have specific requirements to ensure good user experiences,
+     * and they need more information to make intelligent decisions when the Android framework
+     * cannot. For example, combined with the device name and other information, the token can help
+     * NNAPI applications filter devices based on their needs:
+     * - An application demands a certain level of performance, but a specific version of the driver
+     *   cannot meet that requirement because of a performance regression. The application can
+     *   disallow the driver based on the version provided.
+     * - An application has a minimum precision requirement, but certain versions of the driver
+     *   cannot meet that requirement because of bugs or certain optimizations. The application can
+     *   filter out versions of these drivers.
+     *
+     * @return version The version string of the device implementation. Must have nonzero length.
+     */
+    virtual const std::string& getVersionString() const = 0;
+
+    /**
+     * Returns the feature level of a driver.
+     *
+     * @return featureLevel The API level of the most advanced feature this driver implements.
+     */
+    virtual Version getFeatureLevel() const = 0;
+
+    /**
+     * Returns the device type of a driver.
+     *
+     * The device type can be used to help application developers to distribute Machine Learning
+     * workloads and other workloads such as graphical rendering. E.g., for an app which renders AR
+     * scenes based on real time object detection results, the developer could choose an ACCELERATOR
+     * type device for ML workloads, and reserve GPU for graphical rendering.
+     *
+     * @return type The DeviceType of the device. Please note, this is not a bitfield of
+     *     DeviceTypes. Each device must only be of a single DeviceType.
+     */
+    virtual DeviceType getType() const = 0;
+
+    /**
+     * Gets information about extensions supported by the driver implementation.
+     *
+     * Extensions of category ExtensionCategory::BASE must not appear in the list.
+     *
+     * All extension operations and operands must be fully supported for the extension to appear in
+     * the list of supported extensions.
+     *
+     * @return extensions A list of supported extensions.
+     */
+    virtual const std::vector<Extension>& getSupportedExtensions() const = 0;
+
+    /**
+     * Gets the capabilities of a driver.
+     *
+     * @return capabilities Capabilities of the driver.
+     */
+    virtual const Capabilities& getCapabilities() const = 0;
+
+    /**
+     * Gets the caching requirements of the driver implementation.
+     *
+     * There are two types of cache file descriptors provided to the driver: model cache and data
+     * cache.
+     *
+     * The data cache is for caching constant data, possibly including preprocessed and transformed
+     * tensor buffers. Any modification to the data cache should have no worse effect than
+     * generating bad output values at execution time.
+     *
+     * The model cache is for caching security-sensitive data such as compiled executable machine
+     * code in the device's native binary format. A modification to the model cache may affect the
+     * driver's execution behavior, and a malicious client could make use of this to execute beyond
+     * the granted permission. Thus, the driver must always check whether the model cache is
+     * corrupted before preparing the model from cache.
+     *
+     * IDevice::getNumberOfCacheFilesNeeded returns how many of each type of cache files the driver
+     * implementation needs to cache a single prepared model. Returning 0 for both types indicates
+     * compilation caching is not supported by this driver. The driver may still choose not to cache
+     * certain compiled models even if it reports that caching is supported.
+     *
+     * If the device reports that caching is not supported, the user may avoid calling
+     * IDevice::prepareModelFromCache or providing cache file descriptors to IDevice::prepareModel.
+     *
+     * @return A pair of:
+     *     - numModelCache An unsigned integer indicating how many files for model cache the driver
+     *       needs to cache a single prepared model. It must be less than or equal to
+     *       ::android::nn::kMaxNumberOfCacheFiles.
+     *     - numDataCache An unsigned integer indicating how many files for data cache the driver
+     *       needs to cache a single prepared model. It must be less than or equal to
+     *       ::android::nn::kMaxNumberOfCacheFiles.
+     */
+    virtual std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const = 0;
+
+    /**
+     * Blocks until the device is not in a bad state.
+     *
+     * @return Nothing on success, otherwise GeneralError.
+     */
+    virtual GeneralResult<void> wait() const = 0;
+
+    /**
+     * Gets the supported operations in a model.
+     *
+     * IDevice::getSupportedOperations indicates which operations of the top-level subgraph are
+     * fully supported by the vendor driver. If an operation may not be supported for any reason,
+     * IDevice::getSupportedOperations must return `false` for that operation.
+     *
+     * The {@link OperationType::IF} and {@link OperationType::WHILE} operations may only be fully
+     * supported if the vendor driver fully supports all operations in the referenced subgraphs.
+     *
+     * @param model A Model whose operations--and their corresponding operands--are to be verified
+     *     by the driver.
+     * @return supportedOperations A list of supported operations, where `true` indicates the
+     *     operation is supported and `false` indicates the operation is not supported. The index of
+     *     "supported" corresponds with the index of the operation it is describing.
+     */
+    virtual GeneralResult<std::vector<bool>> getSupportedOperations(const Model& model) const = 0;
+
+    /**
+     * Creates a prepared model for execution.
+     *
+     * IDevice::prepareModel is used to make any necessary transformations or alternative
+     * representations to a model for execution, possibly including transformations on the constant
+     * data, optimization on the model's graph, or compilation into the device's native binary
+     * format. The model itself is not changed.
+     *
+     * Optionally, caching information may be provided for the driver to save the prepared model to
+     * cache files for faster model compilation time when the same model preparation is requested in
+     * the future. There are two types of cache file handles provided to the driver: model cache and
+     * data cache. For more information on the two types of cache handles, refer to
+     * IDevice::getNumberOfCacheFilesNeeded.
+     *
+     * The file descriptors must be opened with read and write permission. A file may have any size,
+     * and the corresponding file descriptor may have any offset. The driver must truncate a file to
+     * zero size before writing to that file. The file descriptors may be closed by the client once
+     * the preparation has finished. The driver must dup a file descriptor if it wants to get access
+     * to the cache file later.
+     *
+     * IDevice::prepareModel must verify its inputs related to preparing the model (as opposed to
+     * saving the prepared model to cache) are correct. If there is an error, IDevice::prepareModel
+     * must immediately return {@link ErrorStatus::INVALID_ARGUMENT} as a GeneralError. If the
+     * inputs to IDevice::prepareModel are valid and there is no error, IDevice::prepareModel must
+     * prepare the model.
+     *
+     * The model is prepared with a priority. This priority is relative to other prepared models
+     * owned by the same client. Higher priority executions may use more compute resources than
+     * lower priority executions, and may preempt or starve lower priority executions.
+     *
+     * IDevice::prepareModel can be called with an optional deadline. If the model is not able to be
+     * prepared before the provided deadline, the model preparation may be aborted, and either
+     * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+     * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned as a GeneralError.
+     *
+     * Optionally, the driver may save the prepared model to cache during the preparation. Any error
+     * that occurs when saving to cache must not affect the status of preparing the model. Even if
+     * the input arguments related to the cache may be invalid, or the driver may fail to save to
+     * cache, IDevice::prepareModel must finish preparing the model. The driver may choose not to
+     * save to cache even if the caching information is provided and valid.
+     *
+     * The only information that may be unknown to the model at this stage is the shape of the
+     * tensors, which may only be known at execution time. As such, some driver services may return
+     * partially prepared models, where the prepared model may only be finished when it is paired
+     * with a set of inputs to the model. Note that the same prepared model object may be used with
+     * different shapes of inputs on different (possibly concurrent) executions.
+     *
+     * @param model The model to be prepared for execution.
+     * @param preference Indicates the intended execution behavior of a prepared model.
+     * @param priority Priority of the prepared model relative to other prepared models owned by an
+     *     application.
+     * @param deadline Optional time point. If provided, prepareModel is expected to complete by
+     *     this time point. If it is not able to be completed by the deadline, the execution may be
+     *     aborted.
+     * @param modelCache A vector of handles with each entry holding exactly one cache file
+     *     descriptor for the security-sensitive cache. The length of the vector must either be 0
+     *     indicating that caching information is not provided, or match numModelCache returned from
+     *     IDevice::getNumberOfCacheFilesNeeded. The cache handles will be provided in the same
+     *     order when retrieving the preparedModel from cache files with
+     *     IDevice::prepareModelFromCache.
+     * @param dataCache A vector of handles with each entry holding exactly one cache file
+     *     descriptor for the constants' cache. The length of the vector must either be 0 indicating
+     *     that caching information is not provided, or match numDataCache returned from
+     *     IDevice::getNumberOfCacheFilesNeeded. The cache handles will be provided in the same
+     *     order when retrieving the preparedModel from cache files with
+     *     IDevice::prepareModelFromCache.
+     * @param token An caching token of length ::android::nn::kByteSizeOfCacheToken identifying the
+     *     prepared model. The same token will be provided when retrieving the prepared model from
+     *     the cache files with IDevice::prepareModelFromCache. Tokens should be chosen to have a
+     *     low rate of collision for a particular application. The driver cannot detect a collision;
+     *     a collision will result in a failed execution or in a successful execution that produces
+     *     incorrect output values. If both modelCache and dataCache are empty indicating that
+     *     caching information is not provided, this token must be ignored.
+     * @return preparedModel An IPreparedModel object representing a model that has been prepared
+     *     for execution, otherwise GeneralError.
+     */
+    virtual GeneralResult<SharedPreparedModel> prepareModel(
+            const Model& model, ExecutionPreference preference, Priority priority,
+            OptionalTimePoint deadline, const std::vector<NativeHandle>& modelCache,
+            const std::vector<NativeHandle>& dataCache, const CacheToken& token) const = 0;
+
+    /**
+     * Creates a prepared model from cache files for execution.
+     *
+     * IDevice::prepareModelFromCache is used to retrieve a prepared model directly from cache files
+     * to avoid slow model compilation time. There are two types of cache file handles provided to
+     * the driver: model cache and data cache. For more information on the two types of cache
+     * handles, refer to IDevice::getNumberOfCacheFilesNeeded.
+     *
+     * The file descriptors must be opened with read and write permission. A file may have any size,
+     * and the corresponding file descriptor may have any offset. The driver must truncate a file to
+     * zero size before writing to that file. The file descriptors may be closed by the client once
+     * the preparation has finished. The driver must dup a file descriptor if it wants to get access
+     * to the cache file later.
+     *
+     * IDevice::prepareModelFromCache must verify its inputs are correct, and that the
+     * security-sensitive cache has not been modified since it was last written by the driver. If
+     * there is an error, or if compilation caching is not supported, or if the security-sensitive
+     * cache has been modified, IDevice::prepareModelFromCache must immediately return {@link
+     * ErrorStatus::INVALID_ARGUMENT} as a GeneralError. If the inputs to
+     * IDevice::prepareModelFromCache are valid, the security-sensitive cache is not modified, and
+     * there is no error, IDevice::prepareModelFromCache must prepare the model
+     *
+     * IDevice::prepareModelFromCache can be called with an optional deadline. If the model is not
+     * able to prepared before the provided deadline, the model preparation may be aborted, and
+     * either {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+     * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned as a GeneralError.
+     *
+     * The only information that may be unknown to the model at this stage is the shape of the
+     * tensors, which may only be known at execution time. As such, some driver services may return
+     * partially prepared models, where the prepared model may only be finished when it is paired
+     * with a set of inputs to the model. Note that the same prepared model object may be used with
+     * different shapes of inputs on different (possibly concurrent) executions.
+     *
+     * @param deadline Optional time point. If provided, prepareModel is expected to complete by
+     *     this time point. If it is not able to be completed by the deadline, the execution may be
+     *     aborted.
+     * @param modelCache A vector of handles with each entry holding exactly one cache file
+     *     descriptor for the security-sensitive cache. The length of the vector must match the
+     *     numModelCache returned from IDevice::getNumberOfCacheFilesNeeded. The cache handles will
+     *     be provided in the same order as with IDevice::prepareModel.
+     * @param dataCache A vector of handles with each entry holding exactly one cache file
+     *     descriptor for the constants' cache. The length of the vector must match the numDataCache
+     *     returned from IDevice::getNumberOfCacheFilesNeeded. The cache handles will be provided in
+     *     the same order as with IDevice::prepareModel.
+     * @param token A caching token of length ::android::nn::kByteSizeOfCacheToken identifying the
+     *     prepared model. It is the same token provided when saving the cache files with
+     *     IDevice::prepareModel. Tokens should be chosen to have a low rate of collision for a
+     *     particular application. The driver cannot detect a collision; a collision will result in
+     *     a failed execution or in a successful execution that produces incorrect output values.
+     * @return preparedModel An IPreparedModel object representing a model that has been prepared
+     *     for execution, otherwise GeneralError.
+     */
+    virtual GeneralResult<SharedPreparedModel> prepareModelFromCache(
+            OptionalTimePoint deadline, const std::vector<NativeHandle>& modelCache,
+            const std::vector<NativeHandle>& dataCache, const CacheToken& token) const = 0;
+
+    /**
+     * Allocates a driver-managed buffer with the properties specified by the descriptor as well as
+     * the input and output roles of prepared models.
+     *
+     * IDevice::allocate must verify its inputs are correct. If there is an error, or if a certain
+     * role or property is not supported by the driver, IDevice::allocate must return with {@link
+     * ErrorStatus::INVALID_ARGUMENT} as a GeneralError. If the allocation is successful, this
+     * method must return the produced IBuffer. A successful allocation must accommodate all of the
+     * specified roles and buffer properties.
+     *
+     * The buffer is allocated as an uninitialized state. An uninitialized buffer may only be used
+     * in ways that are specified by outputRoles. A buffer is initialized after it is used as an
+     * output in a successful execution, or after a successful invocation of IBuffer::copyFrom on
+     * the buffer. An initialized buffer may be used according to all roles specified in inputRoles
+     * and outputRoles. A buffer will return to the uninitialized state if it is used as an output
+     * in a failed execution, or after a failed invocation of IBuffer::copyFrom on the buffer.
+     *
+     * The driver may deduce the dimensions of the buffer according to the buffer descriptor as well
+     * as the input and output roles. The dimensions or rank of the buffer may be unknown at this
+     * stage. As such, some driver services may only create a placeholder and defer the actual
+     * allocation until execution time. Note that the same buffer may be used for different shapes
+     * of outputs on different executions. When the buffer is used as an input, the input shape must
+     * be the same as the output shape from the last execution using this buffer as an output.
+     *
+     * The driver must apply proper validatation upon every usage of the buffer, and fail the
+     * execution immediately if the usage is illegal.
+     *
+     * @param desc A buffer descriptor specifying the properties of the buffer to allocate.
+     * @param preparedModels A vector of IPreparedModel objects. Must only contain IPreparedModel
+     *     objects from the same IDevice as this method invoked on.
+     * @param inputRoles A vector of roles with each specifying an input to a prepared model.
+     * @param outputRoles A vector of roles with each specifying an output to a prepared model.
+     *     Each role specified in inputRoles and outputRoles must be unique. The corresponding model
+     *     operands of the roles must have the same OperandType, scale, zero point, and ExtraParams.
+     *     The dimensions of the operands and the dimensions specified in the buffer descriptor must
+     *     be compatible with each other. Two dimensions are incompatible if there is at least one
+     *     axis that is fully specified in both but has different values.
+     * @return The allocated IBuffer object. If the buffer was unable to be allocated due to an
+     *     error, a GeneralError is returned instead.
+     */
+    virtual GeneralResult<SharedBuffer> allocate(
+            const BufferDesc& desc, const std::vector<SharedPreparedModel>& preparedModels,
+            const std::vector<BufferRole>& inputRoles,
+            const std::vector<BufferRole>& outputRoles) const = 0;
+
+    // Public virtual destructor to allow objects to be stored (and destroyed) as smart pointers.
+    // E.g., std::unique_ptr<IDevice>.
+    virtual ~IDevice() = default;
+
+   protected:
+    // Protect the non-destructor special member functions to prevent object slicing.
+    IDevice() = default;
+    IDevice(const IDevice&) = default;
+    IDevice(IDevice&&) noexcept = default;
+    IDevice& operator=(const IDevice&) = default;
+    IDevice& operator=(IDevice&&) noexcept = default;
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IDEVICE_H
diff --git a/nn/common/include/nnapi/IPreparedModel.h b/nn/common/include/nnapi/IPreparedModel.h
new file mode 100644
index 0000000..07476e2
--- /dev/null
+++ b/nn/common/include/nnapi/IPreparedModel.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IPREPARED_MODEL_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IPREPARED_MODEL_H
+
+#include <any>
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "nnapi/Types.h"
+
+namespace android::nn {
+
+// Returns status, timingLaunched, timingFenced
+using ExecuteFencedInfoCallback = std::function<GeneralResult<std::pair<Timing, Timing>>()>;
+
+/**
+ * IPreparedModel describes a model that has been prepared for execution and is used to launch
+ * executions.
+ *
+ * This interface is thread-safe, and any class that implements this interface must be thread-safe.
+ */
+class IPreparedModel {
+   public:
+    /**
+     * Performs a synchronous execution on a prepared model.
+     *
+     * The execution is performed synchronously with respect to the caller. IPreparedModel::execute
+     * must verify the inputs to the function are correct. If there is an error,
+     * IPreparedModel::execute must immediately return {@link ErrorStatus::INVALID_ARGUMENT} as a
+     * ExecutionError. If the inputs to the function are valid and there is no error,
+     * IPreparedModel::execute must perform the execution, and must not return until the execution
+     * is complete.
+     *
+     * The caller must not change the content of any data object referenced by request (described by
+     * the {@link DataLocation} of a {@link RequestArgument}) until IPreparedModel::execute returns.
+     * IPreparedModel::execute must not change the content of any of the data objects corresponding
+     * to request inputs.
+     *
+     * If the prepared model was prepared from a model wherein all tensor operands have fully
+     * specified dimensions, and the inputs to the function are valid, and at execution time every
+     * operation's input operands have legal values, then the execution should complete
+     * successfully. There must be no failure unless the device itself is in a bad state.
+     *
+     * IPreparedModel::execute may be called with an optional deadline. If the execution is not
+     * able to be completed before the provided deadline, the execution may be aborted, and either
+     * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+     * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned as a ExecutionError.
+     *
+     * @param request The input and output information on which the prepared model is to be
+     *     executed.
+     * @param measure Specifies whether or not to measure duration of the execution.
+     * @param deadline Optional time point. If provided, execute is expected to complete by this
+     *     time point. If it is not able to be completed by the deadline, the execution may be
+     *     aborted.
+     * @param loopTimeoutDuration The maximum amount of time that should be spent executing a {@link
+     *     OperationType::WHILE} operation. If a loop condition model does not output `false` within
+     *     this duration, the execution must be aborted. If no loop timeout duration is provided,
+     *     the maximum amount of time is {@link LoopTimeoutDurationNs::DEFAULT}. When provided, the
+     *     duration must not exceed {@link LoopTimeoutDurationNs::MAXIMUM}.
+     * @return A pair consisting of:
+     *     - A list of shape information of model output operands. The index into "outputShapes"
+     *       corresponds to the index of the output operand in the Request outputs vector.
+     *       outputShapes must be empty unless the execution is successful or the ExecutionResult is
+     *       {@link ErrorStatus::OUTPUT_INSUFFICIENT_SIZE}. outputShapes may be empty if the
+     *       execution is successful and all model output operands are fully-specified at execution
+     *       time. outputShapes must have the same number of elements as the number of model output
+     *       operands if the ExecutionResult is {@link ErrorStatus::OUTPUT_INSUFFICIENT_SIZE}, or if
+     *       the execution is successful and the model has at least one output operand that is not
+     *       fully-specified.
+     *     - Duration of execution. Unless measure is YES and the execution is successful, all times
+     *       must be reported as UINT64_MAX. A driver may choose to report any time as UINT64_MAX,
+     *       indicating that measurement is not available.
+     */
+    virtual ExecutionResult<std::pair<std::vector<OutputShape>, Timing>> execute(
+            const Request& request, MeasureTiming measure, const OptionalTimePoint& deadline,
+            const OptionalTimeoutDuration& loopTimeoutDuration) const = 0;
+
+    /**
+     * Launch a fenced asynchronous execution on a prepared model.
+     *
+     * The execution is performed asynchronously with respect to the caller.
+     * IPreparedModel::executeFenced must verify its inputs are correct, and the usages of memory
+     * pools allocated by IDevice::allocate are valid. If there is an error,
+     * IPreparedModel::executeFenced must immediately return {@link ErrorStatus::INVALID_ARGUMENT}
+     * as a GeneralError. If the inputs to the function are valid and there is no error,
+     * IPreparedModel::executeFenced must dispatch an asynchronous task to perform the execution in
+     * the background, and immediately return with a sync fence that will be signaled once the
+     * execution is completed and a callback that can be used by the client to query the duration
+     * and runtime error status. If the task has finished before the call returns, an empty handle
+     * may be returned for syncFence. The execution must wait for all the sync fences (if any) in
+     * waitFor to be signaled before starting the actual execution.
+     *
+     * When the asynchronous task has finished its execution, it must immediately signal the
+     * syncFence returned from the IPreparedModel::executeFenced call. After the syncFence is
+     * signaled, the task must not modify the content of any data object referenced by request
+     * (described by the {@link DataLocation} of a {@link Request::Argument}).
+     *
+     * IPreparedModel::executeFenced may be called with an optional deadline and an optional
+     * duration. If the execution is not able to be completed before the provided deadline or within
+     * the timeout duration (measured from when all sync fences in waitFor are signaled), whichever
+     * comes earlier, the execution may be aborted, and either {@link
+     * ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link ErrorStatus::MISSED_DEADLINE_PERSISTENT}
+     * may be returned as an GeneralError. The error due to an abort must be sent the same way as
+     * other errors, described above.
+     *
+     * If any of the sync fences in waitFor changes to error status after the
+     * IPreparedModel::executeFenced call succeeds, or the execution is aborted because it cannot
+     * finish before the deadline has been reached or the duration has elapsed, the driver must
+     * immediately set the returned syncFence to error status.
+     *
+     * @param request The input and output information on which the prepared model is to be
+     *     executed.
+     * @param waitFor A vector of sync fence file descriptors. The execution must wait for all sync
+     *     fence to be signaled before starting the task.
+     * @param measure Specifies whether or not to measure duration of the execution.
+     * @param deadline The time by which execution is expected to complete. If the execution cannot
+     *     be finished by the deadline, the execution may be aborted.
+     * @param loopTimeoutDuration The maximum amount of time that should be spent executing a {@link
+     *     OperationType::WHILE} operation. If a loop condition model does not output `false` within
+     *     this duration, the execution must be aborted. If no loop timeout duration is provided,
+     *     the maximum amount of time is {@link LoopTimeoutDurationNs::DEFAULT}. When provided, the
+     *     duration must not exceed {@link LoopTimeoutDurationNs::MAXIMUM}.
+     * @param timeoutDurationAfterFence The timeout duration within which the execution is expected
+     *     to complete after all sync fences in waitFor are signaled.
+     * @return A pair consisting of:
+     *     - A syncFence that will be triggered when the task is completed. The syncFence will be
+     *       set to error if critical error occurs when doing actual evaluation.
+     *     - A callback can be used to query information like duration and detailed runtime error
+     *       status when the task is completed.
+     */
+    virtual GeneralResult<std::pair<SyncFence, ExecuteFencedInfoCallback>> executeFenced(
+            const Request& request, const std::vector<SyncFence>& waitFor, MeasureTiming measure,
+            const OptionalTimePoint& deadline, const OptionalTimeoutDuration& loopTimeoutDuration,
+            const OptionalTimeoutDuration& timeoutDurationAfterFence) const = 0;
+
+    /**
+     * Return the resource that the IPreparedModel wraps, or any empty std::any.
+     *
+     * This method is used for IDevice::allocate.
+     *
+     * @return std::any containing the underlying resource.
+     */
+    virtual std::any getUnderlyingResource() const = 0;
+
+    // Public virtual destructor to allow objects to be stored (and destroyed) as smart pointers.
+    // E.g., std::unique_ptr<IPreparedModel>.
+    virtual ~IPreparedModel() = default;
+
+   protected:
+    // Protect the non-destructor special member functions to prevent object slicing.
+    IPreparedModel() = default;
+    IPreparedModel(const IPreparedModel&) = default;
+    IPreparedModel(IPreparedModel&&) noexcept = default;
+    IPreparedModel& operator=(const IPreparedModel&) = default;
+    IPreparedModel& operator=(IPreparedModel&&) noexcept = default;
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IPREPARED_MODEL_H
diff --git a/nn/common/include/nnapi/Result.h b/nn/common/include/nnapi/Result.h
index e232cef..157eb78 100644
--- a/nn/common/include/nnapi/Result.h
+++ b/nn/common/include/nnapi/Result.h
@@ -22,6 +22,7 @@
 #include <optional>
 #include <sstream>
 #include <string>
+#include <tuple>
 #include <utility>
 
 namespace android::nn {
@@ -38,11 +39,19 @@
 
 namespace detail {
 
+template <typename... Ts>
 class ErrorBuilder {
    public:
+    template <typename... Us>
+    explicit ErrorBuilder(Us&&... args) : mArgs(std::forward<Us>(args)...) {}
+
     template <typename T, typename E>
-    operator base::expected<T, E>() const /* NOLINT(google-explicit-constructor) */ {
-        return base::unexpected<E>(std::move(mStream).str());
+    operator base::expected<T, E>() /* NOLINT(google-explicit-constructor) */ {
+        return std::apply(
+                [this](Ts&&... args) {
+                    return base::unexpected<E>(E{std::move(mStream).str(), std::move(args)...});
+                },
+                std::move(mArgs));
     }
 
     template <typename T>
@@ -52,6 +61,7 @@
     }
 
    private:
+    std::tuple<Ts...> mArgs;
     std::ostringstream mStream;
 };
 
@@ -60,8 +70,9 @@
 /**
  * Creates an error builder for the case where no arguments are provided.
  */
-inline detail::ErrorBuilder error() {
-    return detail::ErrorBuilder();
+template <typename... Types>
+inline detail::ErrorBuilder<std::decay_t<Types>...> error(Types&&... args) {
+    return detail::ErrorBuilder<std::decay_t<Types>...>(std::forward<Types>(args)...);
 }
 
 /**
@@ -80,7 +91,7 @@
  *     return <regular_return_value>;
  */
 #define NN_ERROR(...)                                                     \
-    [] {                                                                  \
+    [&] {                                                                 \
         using ::android::nn::error;                                       \
         return error(__VA_ARGS__) << __FILE__ << ":" << __LINE__ << ": "; \
     }()
diff --git a/nn/common/include/nnapi/SharedMemory.h b/nn/common/include/nnapi/SharedMemory.h
index ee8330f..1c8156b 100644
--- a/nn/common/include/nnapi/SharedMemory.h
+++ b/nn/common/include/nnapi/SharedMemory.h
@@ -43,7 +43,7 @@
     DataLocation append(size_t length);
     bool empty() const;
 
-    Result<Memory> finish();
+    GeneralResult<Memory> finish();
 
    private:
     uint32_t mPoolIndex;
@@ -57,7 +57,7 @@
     DataLocation append(const void* data, size_t length);
     bool empty() const;
 
-    Result<Memory> finish();
+    GeneralResult<Memory> finish();
 
    private:
     struct LazyCopy {
@@ -70,13 +70,13 @@
     std::vector<LazyCopy> mSlices;
 };
 
-Result<Memory> createSharedMemory(size_t size);
+GeneralResult<Memory> createSharedMemory(size_t size);
 
-Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset);
+GeneralResult<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset);
 
-Result<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& memory);
+GeneralResult<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& memory);
 
-Result<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb);
+GeneralResult<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb);
 
 struct Mapping {
     std::variant<void*, const void*> pointer;
@@ -84,7 +84,7 @@
     std::any context;
 };
 
-Result<Mapping> map(const Memory& memory);
+GeneralResult<Mapping> map(const Memory& memory);
 
 bool flush(const Mapping& mapping);
 
diff --git a/nn/common/include/nnapi/TypeUtils.h b/nn/common/include/nnapi/TypeUtils.h
index 56b62f9..6b2af91 100644
--- a/nn/common/include/nnapi/TypeUtils.h
+++ b/nn/common/include/nnapi/TypeUtils.h
@@ -100,6 +100,7 @@
 std::ostream& operator<<(std::ostream& os, const Request::Argument& requestArgument);
 std::ostream& operator<<(std::ostream& os, const Request::MemoryPool& memoryPool);
 std::ostream& operator<<(std::ostream& os, const Request& request);
+std::ostream& operator<<(std::ostream& os, const SyncFence::FenceState& fenceState);
 std::ostream& operator<<(std::ostream& os, const TimePoint& timePoint);
 std::ostream& operator<<(std::ostream& os, const OptionalTimePoint& optionalTimePoint);
 std::ostream& operator<<(std::ostream& os, const TimeoutDuration& timeoutDuration);
diff --git a/nn/common/include/nnapi/Types.h b/nn/common/include/nnapi/Types.h
index 371b4a5..3b9725d 100644
--- a/nn/common/include/nnapi/Types.h
+++ b/nn/common/include/nnapi/Types.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_TYPES_H
 #define ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_TYPES_H
 
+#include <android-base/expected.h>
 #include <utils/NativeHandle.h>
 #include <utils/StrongPointer.h>
 
@@ -57,7 +58,7 @@
 using AlignedData = std::max_align_t;
 using SharedBuffer = std::shared_ptr<const IBuffer>;
 using SharedDevice = std::shared_ptr<const IDevice>;
-using PreparedModel = std::shared_ptr<const IPreparedModel>;
+using SharedPreparedModel = std::shared_ptr<const IPreparedModel>;
 
 // Canonical types
 
@@ -116,6 +117,14 @@
     DEAD_OBJECT = 10000,
 };
 
+struct GeneralError {
+    std::string message;
+    ErrorStatus code = ErrorStatus::GENERAL_FAILURE;
+};
+
+template <typename Type>
+using GeneralResult = base::expected<Type, GeneralError>;
+
 enum class FusedActivationFunc : int32_t {
     NONE = 0,
     RELU = 1,
@@ -133,6 +142,16 @@
     bool isSufficient = false;
 };
 
+struct ExecutionError {
+    std::string message;
+    ErrorStatus code = ErrorStatus::GENERAL_FAILURE;
+    // OutputShapes for code == OUTPUT_INSUFFICIENT_SIZE
+    std::vector<OutputShape> outputShapes = {};
+};
+
+template <typename Type>
+using ExecutionResult = base::expected<Type, ExecutionError>;
+
 struct Timing {
     uint64_t timeOnDevice = kNoTiming;
     uint64_t timeInDriver = kNoTiming;
@@ -290,6 +309,34 @@
     std::vector<MemoryPool> pools;
 };
 
+// Representation of sync_fence.
+class SyncFence {
+   public:
+    static SyncFence createAsSignaled();
+    static Result<SyncFence> create(NativeHandle syncFence);
+
+    // The function syncWait() has the same semantics as the system function
+    // ::sync_wait(), except that the syncWait() return value is semantically
+    // richer.
+    enum class FenceState {
+        ACTIVE,    // fence has not been signaled
+        SIGNALED,  // fence has been signaled
+        ERROR,     // fence has been placed in the error state
+        UNKNOWN,   // either bad argument passed to syncWait(), or internal error
+    };
+    using Timeout = std::chrono::duration<int, std::milli>;
+    using OptionalTimeout = std::optional<Timeout>;
+
+    FenceState syncWait(OptionalTimeout optionalTimeout) const;
+
+    NativeHandle getHandle() const;
+
+   private:
+    explicit SyncFence(NativeHandle syncFence);
+
+    NativeHandle mSyncFence;
+};
+
 using Clock = std::chrono::steady_clock;
 
 using TimePoint = std::chrono::time_point<Clock, std::chrono::nanoseconds>;
diff --git a/nn/common/include/nnapi/Validation.h b/nn/common/include/nnapi/Validation.h
index 0913395..3eda174 100644
--- a/nn/common/include/nnapi/Validation.h
+++ b/nn/common/include/nnapi/Validation.h
@@ -69,10 +69,9 @@
 // IMPORTANT: This function cannot validate dimensions and extraParams with extension operand type.
 // Each driver should do their own validation of extension type dimensions and extraParams.
 Result<Version> validateMemoryDesc(
-        const BufferDesc& desc,
-        const std::vector<std::shared_ptr<const IPreparedModel>>& preparedModels,
+        const BufferDesc& desc, const std::vector<SharedPreparedModel>& preparedModels,
         const std::vector<BufferRole>& inputRoles, const std::vector<BufferRole>& outputRoles,
-        const std::function<const Model*(const std::shared_ptr<const IPreparedModel>&)>& getModel,
+        const std::function<const Model*(const SharedPreparedModel&)>& getModel,
         std::set<PreparedModelRole>* preparedModelRoles, Operand* combinedOperand);
 
 Result<void> validateOperandSymmPerChannelQuantParams(