IMapper 5 - the Stable C approach

Test: make VtsHalGraphicsMapperStableC_TargetTest VtsHalGraphicsAllocatorAidl_TargetTest

Change-Id: I910b27f388e3fb7261425dd4b2133885c05edd37
diff --git a/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp b/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp
new file mode 100644
index 0000000..6ab11a3
--- /dev/null
+++ b/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp
@@ -0,0 +1,1565 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "VtsHalGraphicsMapperStableC_TargetTest"
+
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/graphics/allocator/AllocationError.h>
+#include <aidl/android/hardware/graphics/allocator/AllocationResult.h>
+#include <aidl/android/hardware/graphics/allocator/IAllocator.h>
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_manager.h>
+#include <android/dlext.h>
+#include <android/hardware/graphics/mapper/IMapper.h>
+#include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
+#include <gralloctypes/Gralloc4.h>
+#include <hidl/GtestPrinter.h>
+#include <system/graphics.h>
+
+#include <dlfcn.h>
+#include <drm/drm_fourcc.h>
+#include <gtest/gtest.h>
+#include <vndksupport/linker.h>
+#include <initializer_list>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace aidl::android::hardware::graphics::allocator;
+using namespace aidl::android::hardware::graphics::common;
+using namespace android;
+using namespace android::hardware;
+using namespace ::android::hardware::graphics::mapper;
+
+typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper* _Nullable* _Nonnull outImplementation);
+
+inline constexpr BufferUsage operator|(BufferUsage lhs, BufferUsage rhs) {
+    using T = std::underlying_type_t<BufferUsage>;
+    return static_cast<BufferUsage>(static_cast<T>(lhs) | static_cast<T>(rhs));
+}
+
+inline BufferUsage& operator|=(BufferUsage& lhs, BufferUsage rhs) {
+    lhs = lhs | rhs;
+    return lhs;
+}
+
+struct YCbCr {
+    android_ycbcr yCbCr;
+    int64_t horizontalSubSampling;
+    int64_t verticalSubSampling;
+};
+
+class BufferHandle {
+    AIMapper* mIMapper;
+    buffer_handle_t mHandle = nullptr;
+
+  public:
+    explicit BufferHandle(AIMapper* mapper, native_handle_t* rawHandle) : mIMapper(mapper) {
+        EXPECT_EQ(AIMAPPER_ERROR_NONE, mIMapper->v5.importBuffer(rawHandle, &mHandle));
+    }
+
+    explicit BufferHandle(BufferHandle&& other) { *this = std::move(other); }
+
+    BufferHandle& operator=(BufferHandle&& other) noexcept {
+        reset();
+        mIMapper = other.mIMapper;
+        mHandle = other.mHandle;
+        other.mHandle = nullptr;
+        return *this;
+    }
+
+    ~BufferHandle() { reset(); }
+
+    constexpr explicit operator bool() const noexcept { return mHandle != nullptr; }
+
+    buffer_handle_t operator*() const noexcept { return mHandle; }
+
+    void reset() {
+        if (mHandle != nullptr) {
+            EXPECT_EQ(AIMAPPER_ERROR_NONE, mIMapper->v5.freeBuffer(mHandle));
+            mHandle = nullptr;
+        }
+    }
+};
+
+class BufferAllocation {
+    AIMapper* mIMapper;
+    native_handle_t* mRawHandle;
+    uint32_t mStride;
+    const BufferDescriptorInfo mInfo;
+
+  public:
+    BufferAllocation(const BufferAllocation&) = delete;
+    void operator=(const BufferAllocation&) = delete;
+
+    BufferAllocation(AIMapper* mapper, native_handle_t* handle, uint32_t stride,
+                     const BufferDescriptorInfo& info)
+        : mIMapper(mapper), mRawHandle(handle), mStride(stride), mInfo(info) {}
+
+    ~BufferAllocation() {
+        if (mRawHandle == nullptr) return;
+
+        native_handle_close(mRawHandle);
+        native_handle_delete(mRawHandle);
+    }
+
+    uint32_t stride() const { return mStride; }
+    const BufferDescriptorInfo& info() const { return mInfo; }
+
+    BufferHandle import() { return BufferHandle{mIMapper, mRawHandle}; }
+
+    const native_handle_t* rawHandle() const { return mRawHandle; }
+};
+
+class GraphicsTestsBase {
+  private:
+    friend class BufferAllocation;
+    int32_t mIAllocatorVersion = 1;
+    std::shared_ptr<IAllocator> mAllocator;
+    AIMapper* mIMapper = nullptr;
+    AIMapper_loadIMapperFn mIMapperLoader;
+
+  protected:
+    void Initialize(std::shared_ptr<IAllocator> allocator) {
+        mAllocator = allocator;
+        ASSERT_NE(nullptr, mAllocator.get()) << "failed to get allocator service";
+        ASSERT_TRUE(mAllocator->getInterfaceVersion(&mIAllocatorVersion).isOk());
+        ASSERT_GE(mIAllocatorVersion, 2);
+        std::string mapperSuffix;
+        auto status = mAllocator->getIMapperLibrarySuffix(&mapperSuffix);
+        ASSERT_TRUE(status.isOk()) << "Failed to get IMapper library suffix";
+        std::string lib_name = "mapper." + mapperSuffix + ".so";
+        void* so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW);
+        ASSERT_NE(nullptr, so) << "Failed to load " << lib_name;
+        mIMapperLoader = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper");
+        ASSERT_NE(nullptr, mIMapperLoader) << "AIMapper_locaIMapper missing from " << lib_name;
+        ASSERT_EQ(AIMAPPER_ERROR_NONE, mIMapperLoader(&mIMapper));
+        ASSERT_NE(mIMapper, nullptr);
+    }
+
+  public:
+    AIMapper_loadIMapperFn getIMapperLoader() const { return mIMapperLoader; }
+
+    std::unique_ptr<BufferAllocation> allocate(const BufferDescriptorInfo& descriptorInfo) {
+        AllocationResult result;
+        ::ndk::ScopedAStatus status = mAllocator->allocate2(descriptorInfo, 1, &result);
+        if (!status.isOk()) {
+            status_t error = status.getExceptionCode();
+            if (error == EX_SERVICE_SPECIFIC) {
+                error = status.getServiceSpecificError();
+                EXPECT_NE(OK, error) << "Failed to set error properly";
+            } else {
+                EXPECT_EQ(OK, error) << "Allocation transport failure";
+            }
+            return nullptr;
+        } else {
+            return std::make_unique<BufferAllocation>(mIMapper, dupFromAidl(result.buffers[0]),
+                                                      result.stride, descriptorInfo);
+        }
+    }
+
+    std::unique_ptr<BufferAllocation> allocateGeneric() {
+        return allocate({
+                .name = {"VTS_TEMP"},
+                .width = 64,
+                .height = 64,
+                .layerCount = 1,
+                .format = PixelFormat::RGBA_8888,
+                .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+                .reservedSize = 0,
+        });
+    }
+
+    bool isSupported(const BufferDescriptorInfo& descriptorInfo) {
+        bool ret = false;
+        EXPECT_TRUE(mAllocator->isSupported(descriptorInfo, &ret).isOk());
+        return ret;
+    }
+
+    AIMapper* mapper() const { return mIMapper; }
+
+    template <StandardMetadataType T>
+    auto getStandardMetadata(buffer_handle_t bufferHandle)
+            -> decltype(StandardMetadata<T>::value::decode(nullptr, 0)) {
+        using Value = typename StandardMetadata<T>::value;
+        std::vector<uint8_t> buffer;
+        // Initial guess
+        buffer.resize(512);
+        int32_t sizeRequired = mapper()->v5.getStandardMetadata(
+                bufferHandle, static_cast<int64_t>(T), buffer.data(), buffer.size());
+        if (sizeRequired < 0) {
+            EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, sizeRequired)
+                    << "Received something other than UNSUPPORTED from valid getStandardMetadata "
+                       "call";
+            return std::nullopt;
+        }
+        if (sizeRequired > buffer.size()) {
+            buffer.resize(sizeRequired);
+            sizeRequired = mapper()->v5.getStandardMetadata(bufferHandle, static_cast<int64_t>(T),
+                                                            buffer.data(), buffer.size());
+        }
+        if (sizeRequired < 0 || sizeRequired >= buffer.size()) {
+            ADD_FAILURE() << "getStandardMetadata failed, received " << sizeRequired
+                          << " with buffer size " << buffer.size();
+            // Generate a fail type
+            return std::nullopt;
+        }
+        return Value::decode(buffer.data(), sizeRequired);
+    }
+
+    template <StandardMetadataType T>
+    AIMapper_Error setStandardMetadata(buffer_handle_t bufferHandle,
+                                       const typename StandardMetadata<T>::value_type& value) {
+        using Value = typename StandardMetadata<T>::value;
+        int32_t sizeRequired = Value::encode(value, nullptr, 0);
+        if (sizeRequired < 0) {
+            EXPECT_GE(sizeRequired, 0) << "Failed to calculate required size";
+            return static_cast<AIMapper_Error>(-sizeRequired);
+        }
+        std::vector<uint8_t> buffer;
+        buffer.resize(sizeRequired);
+        sizeRequired = Value::encode(value, buffer.data(), buffer.size());
+        if (sizeRequired < 0 || sizeRequired > buffer.size()) {
+            ADD_FAILURE() << "Failed to encode with calculated size " << sizeRequired
+                          << "; buffer size" << buffer.size();
+            return static_cast<AIMapper_Error>(-sizeRequired);
+        }
+        return mapper()->v5.setStandardMetadata(bufferHandle, static_cast<int64_t>(T),
+                                                buffer.data(), sizeRequired);
+    }
+
+    void verifyRGBA8888PlaneLayouts(const std::vector<PlaneLayout>& planeLayouts) {
+        ASSERT_EQ(1, planeLayouts.size());
+
+        const auto& planeLayout = planeLayouts.front();
+
+        ASSERT_EQ(4, planeLayout.components.size());
+
+        int64_t offsetInBitsR = -1;
+        int64_t offsetInBitsG = -1;
+        int64_t offsetInBitsB = -1;
+        int64_t offsetInBitsA = -1;
+
+        for (const auto& component : planeLayout.components) {
+            if (!gralloc4::isStandardPlaneLayoutComponentType(component.type)) {
+                continue;
+            }
+            EXPECT_EQ(8, component.sizeInBits);
+            if (component.type.value == gralloc4::PlaneLayoutComponentType_R.value) {
+                offsetInBitsR = component.offsetInBits;
+            }
+            if (component.type.value == gralloc4::PlaneLayoutComponentType_G.value) {
+                offsetInBitsG = component.offsetInBits;
+            }
+            if (component.type.value == gralloc4::PlaneLayoutComponentType_B.value) {
+                offsetInBitsB = component.offsetInBits;
+            }
+            if (component.type.value == gralloc4::PlaneLayoutComponentType_A.value) {
+                offsetInBitsA = component.offsetInBits;
+            }
+        }
+
+        EXPECT_EQ(0, offsetInBitsR);
+        EXPECT_EQ(8, offsetInBitsG);
+        EXPECT_EQ(16, offsetInBitsB);
+        EXPECT_EQ(24, offsetInBitsA);
+
+        EXPECT_EQ(0, planeLayout.offsetInBytes);
+        EXPECT_EQ(32, planeLayout.sampleIncrementInBits);
+        // Skip testing stride because any stride is valid
+        EXPECT_LE(planeLayout.widthInSamples * planeLayout.heightInSamples * 4,
+                  planeLayout.totalSizeInBytes);
+        EXPECT_EQ(1, planeLayout.horizontalSubsampling);
+        EXPECT_EQ(1, planeLayout.verticalSubsampling);
+    }
+
+    void fillRGBA8888(uint8_t* data, uint32_t height, size_t strideInBytes, size_t widthInBytes) {
+        for (uint32_t y = 0; y < height; y++) {
+            memset(data, y, widthInBytes);
+            data += strideInBytes;
+        }
+    }
+
+    void verifyRGBA8888(const buffer_handle_t bufferHandle, const uint8_t* data, uint32_t height,
+                        size_t strideInBytes, size_t widthInBytes) {
+        auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(bufferHandle);
+        ASSERT_TRUE(decodeResult.has_value());
+        const auto& planeLayouts = *decodeResult;
+        ASSERT_TRUE(planeLayouts.size() > 0);
+
+        verifyRGBA8888PlaneLayouts(planeLayouts);
+
+        for (uint32_t y = 0; y < height; y++) {
+            for (size_t i = 0; i < widthInBytes; i++) {
+                EXPECT_EQ(static_cast<uint8_t>(y), data[i]);
+            }
+            data += strideInBytes;
+        }
+    }
+
+    void traverseYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height,
+                           int64_t hSubsampling, int64_t vSubsampling,
+                           std::function<void(uint8_t*, uint8_t)> traverseFuncion) {
+        auto yData = static_cast<uint8_t*>(yCbCr.y);
+        auto cbData = static_cast<uint8_t*>(yCbCr.cb);
+        auto crData = static_cast<uint8_t*>(yCbCr.cr);
+        auto yStride = yCbCr.ystride;
+        auto cStride = yCbCr.cstride;
+        auto chromaStep = yCbCr.chroma_step;
+
+        for (uint32_t y = 0; y < height; y++) {
+            for (uint32_t x = 0; x < width; x++) {
+                auto val = static_cast<uint8_t>(height * y + x);
+
+                traverseFuncion(yData + yStride * y + x, val);
+
+                if (y % vSubsampling == 0 && x % hSubsampling == 0) {
+                    uint32_t subSampleX = x / hSubsampling;
+                    uint32_t subSampleY = y / vSubsampling;
+                    const auto subSampleOffset = cStride * subSampleY + chromaStep * subSampleX;
+                    const auto subSampleVal =
+                            static_cast<uint8_t>(height * subSampleY + subSampleX);
+
+                    traverseFuncion(cbData + subSampleOffset, subSampleVal);
+                    traverseFuncion(crData + subSampleOffset, subSampleVal + 1);
+                }
+            }
+        }
+    }
+
+    void fillYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height,
+                       int64_t hSubsampling, int64_t vSubsampling) {
+        traverseYCbCrData(yCbCr, width, height, hSubsampling, vSubsampling,
+                          [](auto address, auto fillingData) { *address = fillingData; });
+    }
+
+    void verifyYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height,
+                         int64_t hSubsampling, int64_t vSubsampling) {
+        traverseYCbCrData(
+                yCbCr, width, height, hSubsampling, vSubsampling,
+                [](auto address, auto expectedData) { EXPECT_EQ(*address, expectedData); });
+    }
+
+    constexpr uint64_t bitsToBytes(int64_t bits) { return bits / 8; }
+    constexpr uint64_t bytesToBits(int64_t bytes) { return bytes * 8; }
+
+    void getAndroidYCbCr(buffer_handle_t bufferHandle, uint8_t* data, android_ycbcr* outYCbCr,
+                         int64_t* hSubsampling, int64_t* vSubsampling) {
+        auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(bufferHandle);
+        ASSERT_TRUE(decodeResult.has_value());
+        const auto& planeLayouts = *decodeResult;
+        ASSERT_TRUE(planeLayouts.size() > 0);
+
+        outYCbCr->y = nullptr;
+        outYCbCr->cb = nullptr;
+        outYCbCr->cr = nullptr;
+        outYCbCr->ystride = 0;
+        outYCbCr->cstride = 0;
+        outYCbCr->chroma_step = 0;
+
+        for (const auto& planeLayout : planeLayouts) {
+            for (const auto& planeLayoutComponent : planeLayout.components) {
+                if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) {
+                    continue;
+                }
+                ASSERT_EQ(0, planeLayoutComponent.offsetInBits % 8);
+
+                uint8_t* tmpData = data + planeLayout.offsetInBytes +
+                                   bitsToBytes(planeLayoutComponent.offsetInBits);
+                uint64_t sampleIncrementInBytes;
+
+                auto type = static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value);
+                switch (type) {
+                    case PlaneLayoutComponentType::Y:
+                        ASSERT_EQ(nullptr, outYCbCr->y);
+                        ASSERT_EQ(8, planeLayoutComponent.sizeInBits);
+                        ASSERT_EQ(8, planeLayout.sampleIncrementInBits);
+                        outYCbCr->y = tmpData;
+                        outYCbCr->ystride = planeLayout.strideInBytes;
+                        break;
+
+                    case PlaneLayoutComponentType::CB:
+                    case PlaneLayoutComponentType::CR:
+                        ASSERT_EQ(0, planeLayout.sampleIncrementInBits % 8);
+
+                        sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8;
+                        ASSERT_TRUE(sampleIncrementInBytes == 1 || sampleIncrementInBytes == 2);
+
+                        if (outYCbCr->cstride == 0 && outYCbCr->chroma_step == 0) {
+                            outYCbCr->cstride = planeLayout.strideInBytes;
+                            outYCbCr->chroma_step = sampleIncrementInBytes;
+                        } else {
+                            ASSERT_EQ(outYCbCr->cstride, planeLayout.strideInBytes);
+                            ASSERT_EQ(outYCbCr->chroma_step, sampleIncrementInBytes);
+                        }
+
+                        if (*hSubsampling == 0 && *vSubsampling == 0) {
+                            *hSubsampling = planeLayout.horizontalSubsampling;
+                            *vSubsampling = planeLayout.verticalSubsampling;
+                        } else {
+                            ASSERT_EQ(*hSubsampling, planeLayout.horizontalSubsampling);
+                            ASSERT_EQ(*vSubsampling, planeLayout.verticalSubsampling);
+                        }
+
+                        if (type == PlaneLayoutComponentType::CB) {
+                            ASSERT_EQ(nullptr, outYCbCr->cb);
+                            outYCbCr->cb = tmpData;
+                        } else {
+                            ASSERT_EQ(nullptr, outYCbCr->cr);
+                            outYCbCr->cr = tmpData;
+                        }
+                        break;
+                    default:
+                        break;
+                };
+            }
+        }
+
+        ASSERT_NE(nullptr, outYCbCr->y);
+        ASSERT_NE(nullptr, outYCbCr->cb);
+        ASSERT_NE(nullptr, outYCbCr->cr);
+    }
+
+    YCbCr getAndroidYCbCr_P010(const native_handle_t* bufferHandle, uint8_t* data) {
+        YCbCr yCbCr_P010;
+        auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(bufferHandle);
+        if (!decodeResult.has_value()) {
+            ADD_FAILURE() << "failed to get plane layout";
+            return YCbCr{};
+        }
+        const auto& planeLayouts = *decodeResult;
+        EXPECT_EQ(2, planeLayouts.size());
+        EXPECT_EQ(1, planeLayouts[0].components.size());
+        EXPECT_EQ(2, planeLayouts[1].components.size());
+
+        yCbCr_P010.yCbCr.y = nullptr;
+        yCbCr_P010.yCbCr.cb = nullptr;
+        yCbCr_P010.yCbCr.cr = nullptr;
+        yCbCr_P010.yCbCr.ystride = 0;
+        yCbCr_P010.yCbCr.cstride = 0;
+        yCbCr_P010.yCbCr.chroma_step = 0;
+        int64_t cb_offset = 0;
+        int64_t cr_offset = 0;
+
+        for (const auto& planeLayout : planeLayouts) {
+            for (const auto& planeLayoutComponent : planeLayout.components) {
+                if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) {
+                    continue;
+                }
+
+                uint8_t* tmpData = data + planeLayout.offsetInBytes +
+                                   bitsToBytes(planeLayoutComponent.offsetInBits);
+                uint64_t sampleIncrementInBytes = 0;
+                auto type = static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value);
+                switch (type) {
+                    case PlaneLayoutComponentType::Y:
+                        // For specs refer:
+                        // https://docs.microsoft.com/en-us/windows/win32/medfound/10-bit-and-16-bit-yuv-video-formats
+                        EXPECT_EQ(6, planeLayoutComponent.offsetInBits);
+                        EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.y);
+                        EXPECT_EQ(10, planeLayoutComponent.sizeInBits);
+                        EXPECT_EQ(16, planeLayout.sampleIncrementInBits);
+
+                        yCbCr_P010.yCbCr.y = tmpData;
+                        yCbCr_P010.yCbCr.ystride = planeLayout.strideInBytes;
+                        break;
+
+                    case PlaneLayoutComponentType::CB:
+                    case PlaneLayoutComponentType::CR:
+                        sampleIncrementInBytes = bitsToBytes(planeLayout.sampleIncrementInBits);
+                        EXPECT_EQ(4, sampleIncrementInBytes);
+
+                        if (yCbCr_P010.yCbCr.cstride == 0 && yCbCr_P010.yCbCr.chroma_step == 0) {
+                            yCbCr_P010.yCbCr.cstride = planeLayout.strideInBytes;
+                            yCbCr_P010.yCbCr.chroma_step = sampleIncrementInBytes;
+                        } else {
+                            EXPECT_EQ(yCbCr_P010.yCbCr.cstride, planeLayout.strideInBytes);
+                            EXPECT_EQ(yCbCr_P010.yCbCr.chroma_step, sampleIncrementInBytes);
+                        }
+
+                        if (yCbCr_P010.horizontalSubSampling == 0 &&
+                            yCbCr_P010.verticalSubSampling == 0) {
+                            yCbCr_P010.horizontalSubSampling = planeLayout.horizontalSubsampling;
+                            yCbCr_P010.verticalSubSampling = planeLayout.verticalSubsampling;
+                        } else {
+                            EXPECT_EQ(yCbCr_P010.horizontalSubSampling,
+                                      planeLayout.horizontalSubsampling);
+                            EXPECT_EQ(yCbCr_P010.verticalSubSampling,
+                                      planeLayout.verticalSubsampling);
+                        }
+
+                        if (type == PlaneLayoutComponentType::CB) {
+                            EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.cb);
+                            yCbCr_P010.yCbCr.cb = tmpData;
+                            cb_offset = planeLayoutComponent.offsetInBits;
+                        } else {
+                            EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.cr);
+                            yCbCr_P010.yCbCr.cr = tmpData;
+                            cr_offset = planeLayoutComponent.offsetInBits;
+                        }
+                        break;
+                    default:
+                        break;
+                };
+            }
+        }
+
+        EXPECT_EQ(cb_offset + bytesToBits(2), cr_offset);
+        EXPECT_NE(nullptr, yCbCr_P010.yCbCr.y);
+        EXPECT_NE(nullptr, yCbCr_P010.yCbCr.cb);
+        EXPECT_NE(nullptr, yCbCr_P010.yCbCr.cr);
+        return yCbCr_P010;
+    }
+};
+
+class GraphicsMapperStableCTests
+    : public GraphicsTestsBase,
+      public ::testing::TestWithParam<std::tuple<std::string, std::shared_ptr<IAllocator>>> {
+  public:
+    void SetUp() override { Initialize(std::get<1>(GetParam())); }
+
+    void TearDown() override {}
+};
+
+TEST_P(GraphicsMapperStableCTests, AllV5CallbacksDefined) {
+    ASSERT_GE(mapper()->version, AIMAPPER_VERSION_5);
+
+    EXPECT_TRUE(mapper()->v5.importBuffer);
+    EXPECT_TRUE(mapper()->v5.freeBuffer);
+    EXPECT_TRUE(mapper()->v5.getTransportSize);
+    EXPECT_TRUE(mapper()->v5.lock);
+    EXPECT_TRUE(mapper()->v5.unlock);
+    EXPECT_TRUE(mapper()->v5.flushLockedBuffer);
+    EXPECT_TRUE(mapper()->v5.rereadLockedBuffer);
+    EXPECT_TRUE(mapper()->v5.getMetadata);
+    EXPECT_TRUE(mapper()->v5.getStandardMetadata);
+    EXPECT_TRUE(mapper()->v5.setMetadata);
+    EXPECT_TRUE(mapper()->v5.setStandardMetadata);
+    EXPECT_TRUE(mapper()->v5.listSupportedMetadataTypes);
+    EXPECT_TRUE(mapper()->v5.dumpBuffer);
+    EXPECT_TRUE(mapper()->v5.getReservedRegion);
+}
+
+TEST_P(GraphicsMapperStableCTests, DualLoadIsIdentical) {
+    ASSERT_GE(mapper()->version, AIMAPPER_VERSION_5);
+    AIMapper* secondMapper;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, getIMapperLoader()(&secondMapper));
+
+    EXPECT_EQ(secondMapper->v5.importBuffer, mapper()->v5.importBuffer);
+    EXPECT_EQ(secondMapper->v5.freeBuffer, mapper()->v5.freeBuffer);
+    EXPECT_EQ(secondMapper->v5.getTransportSize, mapper()->v5.getTransportSize);
+    EXPECT_EQ(secondMapper->v5.lock, mapper()->v5.lock);
+    EXPECT_EQ(secondMapper->v5.unlock, mapper()->v5.unlock);
+    EXPECT_EQ(secondMapper->v5.flushLockedBuffer, mapper()->v5.flushLockedBuffer);
+    EXPECT_EQ(secondMapper->v5.rereadLockedBuffer, mapper()->v5.rereadLockedBuffer);
+    EXPECT_EQ(secondMapper->v5.getMetadata, mapper()->v5.getMetadata);
+    EXPECT_EQ(secondMapper->v5.getStandardMetadata, mapper()->v5.getStandardMetadata);
+    EXPECT_EQ(secondMapper->v5.setMetadata, mapper()->v5.setMetadata);
+    EXPECT_EQ(secondMapper->v5.setStandardMetadata, mapper()->v5.setStandardMetadata);
+    EXPECT_EQ(secondMapper->v5.listSupportedMetadataTypes, mapper()->v5.listSupportedMetadataTypes);
+    EXPECT_EQ(secondMapper->v5.dumpBuffer, mapper()->v5.dumpBuffer);
+    EXPECT_EQ(secondMapper->v5.getReservedRegion, mapper()->v5.getReservedRegion);
+}
+
+TEST_P(GraphicsMapperStableCTests, CanAllocate) {
+    auto buffer = allocate({
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    });
+    ASSERT_NE(nullptr, buffer.get());
+    EXPECT_GE(buffer->stride(), 64);
+}
+
+TEST_P(GraphicsMapperStableCTests, ImportFreeBuffer) {
+    auto buffer = allocate({
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    });
+    ASSERT_NE(nullptr, buffer.get());
+    EXPECT_GE(buffer->stride(), 64);
+
+    {
+        auto import1 = buffer->import();
+        auto import2 = buffer->import();
+        EXPECT_TRUE(import1);
+        EXPECT_TRUE(import2);
+        EXPECT_NE(*import1, *import2);
+    }
+}
+
+/**
+ * Test IMapper::importBuffer and IMapper::freeBuffer cross mapper instances.
+ */
+TEST_P(GraphicsMapperStableCTests, ImportFreeBufferSingleton) {
+    auto buffer = allocate({
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    });
+    ASSERT_NE(nullptr, buffer.get());
+    EXPECT_GE(buffer->stride(), 64);
+
+    buffer_handle_t bufferHandle = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.importBuffer(buffer->rawHandle(), &bufferHandle));
+    ASSERT_NE(nullptr, bufferHandle);
+
+    AIMapper* secondMapper;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, getIMapperLoader()(&secondMapper));
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, secondMapper->v5.freeBuffer(bufferHandle));
+}
+
+/**
+ * Test IMapper::importBuffer with invalid buffers.
+ */
+TEST_P(GraphicsMapperStableCTests, ImportBufferNegative) {
+    native_handle_t* invalidHandle = nullptr;
+    buffer_handle_t bufferHandle = nullptr;
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.importBuffer(invalidHandle, &bufferHandle))
+            << "importBuffer with nullptr did not fail with BAD_BUFFER";
+
+    invalidHandle = native_handle_create(0, 0);
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.importBuffer(invalidHandle, &bufferHandle))
+            << "importBuffer with invalid handle did not fail with BAD_BUFFER";
+    native_handle_delete(invalidHandle);
+}
+
+/**
+ * Test IMapper::freeBuffer with invalid buffers.
+ */
+TEST_P(GraphicsMapperStableCTests, FreeBufferNegative) {
+    native_handle_t* bufferHandle = nullptr;
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(bufferHandle))
+            << "freeBuffer with nullptr did not fail with BAD_BUFFER";
+
+    bufferHandle = native_handle_create(0, 0);
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(bufferHandle))
+            << "freeBuffer with invalid handle did not fail with BAD_BUFFER";
+    native_handle_delete(bufferHandle);
+
+    auto buffer = allocateGeneric();
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(buffer->rawHandle()))
+            << "freeBuffer with un-imported handle did not fail with BAD_BUFFER";
+}
+
+/**
+ * Test IMapper::lock and IMapper::unlock.
+ */
+TEST_P(GraphicsMapperStableCTests, LockUnlockBasic) {
+    constexpr auto usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN;
+    auto buffer = allocate({
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = usage,
+            .reservedSize = 0,
+    });
+    ASSERT_NE(nullptr, buffer.get());
+
+    // lock buffer for writing
+    const auto& info = buffer->info();
+    const auto stride = buffer->stride();
+    const ARect region{0, 0, info.width, info.height};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE,
+              mapper()->v5.lock(*handle, static_cast<int64_t>(usage), region, -1, (void**)&data));
+
+    // RGBA_8888
+    fillRGBA8888(data, info.height, stride * 4, info.width * 4);
+
+    int releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+
+    // lock again for reading
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(usage), region,
+                                                     releaseFence, (void**)&data));
+    releaseFence = -1;
+
+    ASSERT_NO_FATAL_FAILURE(verifyRGBA8888(*handle, data, info.height, stride * 4, info.width * 4));
+
+    releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+    }
+}
+
+/**
+ *  Test multiple operations associated with different color formats
+ */
+TEST_P(GraphicsMapperStableCTests, Lock_YCRCB_420_SP) {
+    BufferDescriptorInfo info{
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::YCRCB_420_SP,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    };
+    auto buffer = allocate(info);
+    if (!buffer) {
+        ASSERT_FALSE(isSupported(info));
+        GTEST_SUCCEED() << "YCRCB_420_SP format is unsupported";
+        return;
+    }
+
+    // lock buffer for writing
+    const ARect region{0, 0, info.width, info.height};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, -1, (void**)&data));
+
+    android_ycbcr yCbCr;
+    int64_t hSubsampling = 0;
+    int64_t vSubsampling = 0;
+    ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+    constexpr uint32_t kCbCrSubSampleFactor = 2;
+    ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling);
+    ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling);
+
+    auto cbData = static_cast<uint8_t*>(yCbCr.cb);
+    auto crData = static_cast<uint8_t*>(yCbCr.cr);
+    ASSERT_EQ(crData + 1, cbData);
+    ASSERT_EQ(2, yCbCr.chroma_step);
+
+    fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+    int releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+
+    // lock again for reading
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, releaseFence, (void**)&data));
+    releaseFence = -1;
+
+    ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+    verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+    releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, YV12SubsampleMetadata) {
+    BufferDescriptorInfo info{
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::YV12,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    };
+    auto buffer = allocate(info);
+    ASSERT_NE(nullptr, buffer.get());
+
+    // lock buffer for writing
+    const ARect region{0, 0, info.width, info.height};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, -1, (void**)&data));
+
+    auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(*handle);
+    ASSERT_TRUE(decodeResult.has_value());
+    const auto& planeLayouts = *decodeResult;
+
+    ASSERT_EQ(3, planeLayouts.size());
+
+    auto yPlane = planeLayouts[0];
+    auto crPlane = planeLayouts[1];
+    auto cbPlane = planeLayouts[2];
+
+    constexpr uint32_t kCbCrSubSampleFactor = 2;
+    EXPECT_EQ(kCbCrSubSampleFactor, crPlane.horizontalSubsampling);
+    EXPECT_EQ(kCbCrSubSampleFactor, crPlane.verticalSubsampling);
+
+    EXPECT_EQ(kCbCrSubSampleFactor, cbPlane.horizontalSubsampling);
+    EXPECT_EQ(kCbCrSubSampleFactor, cbPlane.verticalSubsampling);
+
+    const long chromaSampleWidth = info.width / kCbCrSubSampleFactor;
+    const long chromaSampleHeight = info.height / kCbCrSubSampleFactor;
+
+    EXPECT_EQ(info.width, yPlane.widthInSamples);
+    EXPECT_EQ(info.height, yPlane.heightInSamples);
+
+    EXPECT_EQ(chromaSampleWidth, crPlane.widthInSamples);
+    EXPECT_EQ(chromaSampleHeight, crPlane.heightInSamples);
+
+    EXPECT_EQ(chromaSampleWidth, cbPlane.widthInSamples);
+    EXPECT_EQ(chromaSampleHeight, cbPlane.heightInSamples);
+
+    EXPECT_LE(crPlane.widthInSamples, crPlane.strideInBytes);
+    EXPECT_LE(cbPlane.widthInSamples, cbPlane.strideInBytes);
+
+    int releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_YV12) {
+    BufferDescriptorInfo info{
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::YV12,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    };
+    auto buffer = allocate(info);
+    ASSERT_NE(nullptr, buffer.get());
+
+    // lock buffer for writing
+    const ARect region{0, 0, info.width, info.height};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, -1, (void**)&data));
+
+    android_ycbcr yCbCr;
+    int64_t hSubsampling = 0;
+    int64_t vSubsampling = 0;
+    ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+    constexpr uint32_t kCbCrSubSampleFactor = 2;
+    ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling);
+    ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling);
+
+    auto cbData = static_cast<uint8_t*>(yCbCr.cb);
+    auto crData = static_cast<uint8_t*>(yCbCr.cr);
+    ASSERT_EQ(crData + yCbCr.cstride * info.height / vSubsampling, cbData);
+    ASSERT_EQ(1, yCbCr.chroma_step);
+
+    fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+    int releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+
+    // lock again for reading
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, releaseFence, (void**)&data));
+    releaseFence = -1;
+
+    ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+    verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_YCBCR_420_888) {
+    BufferDescriptorInfo info{
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::YCBCR_420_888,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    };
+    auto buffer = allocate(info);
+    ASSERT_NE(nullptr, buffer.get());
+
+    // lock buffer for writing
+    const ARect region{0, 0, info.width, info.height};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, -1, (void**)&data));
+
+    android_ycbcr yCbCr;
+    int64_t hSubsampling = 0;
+    int64_t vSubsampling = 0;
+    ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+    constexpr uint32_t kCbCrSubSampleFactor = 2;
+    ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling);
+    ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling);
+
+    fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+    int releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+
+    // lock again for reading
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, releaseFence, (void**)&data));
+    releaseFence = -1;
+
+    ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+    verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_RAW10) {
+    BufferDescriptorInfo info{
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RAW10,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    };
+    auto buffer = allocate(info);
+    if (!buffer) {
+        ASSERT_FALSE(isSupported(info));
+        GTEST_SUCCEED() << "RAW10 format is unsupported";
+        return;
+    }
+
+    // lock buffer for writing
+    const ARect region{0, 0, info.width, info.height};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, -1, (void**)&data));
+
+    auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(*handle);
+    ASSERT_TRUE(decodeResult.has_value());
+    const auto& planeLayouts = *decodeResult;
+
+    ASSERT_EQ(1, planeLayouts.size());
+    auto planeLayout = planeLayouts[0];
+
+    EXPECT_EQ(0, planeLayout.sampleIncrementInBits);
+    EXPECT_EQ(1, planeLayout.horizontalSubsampling);
+    EXPECT_EQ(1, planeLayout.verticalSubsampling);
+
+    ASSERT_EQ(1, planeLayout.components.size());
+    auto planeLayoutComponent = planeLayout.components[0];
+
+    EXPECT_EQ(PlaneLayoutComponentType::RAW,
+              static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value));
+    EXPECT_EQ(0, planeLayoutComponent.offsetInBits % 8);
+    EXPECT_EQ(-1, planeLayoutComponent.sizeInBits);
+
+    int releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_RAW12) {
+    BufferDescriptorInfo info{
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RAW12,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    };
+    auto buffer = allocate(info);
+    if (!buffer) {
+        ASSERT_FALSE(isSupported(info));
+        GTEST_SUCCEED() << "RAW12 format is unsupported";
+        return;
+    }
+
+    // lock buffer for writing
+    const ARect region{0, 0, info.width, info.height};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, -1, (void**)&data));
+
+    auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(*handle);
+    ASSERT_TRUE(decodeResult.has_value());
+    const auto& planeLayouts = *decodeResult;
+
+    ASSERT_EQ(1, planeLayouts.size());
+    auto planeLayout = planeLayouts[0];
+
+    EXPECT_EQ(0, planeLayout.sampleIncrementInBits);
+    EXPECT_EQ(1, planeLayout.horizontalSubsampling);
+    EXPECT_EQ(1, planeLayout.verticalSubsampling);
+
+    ASSERT_EQ(1, planeLayout.components.size());
+    auto planeLayoutComponent = planeLayout.components[0];
+
+    EXPECT_EQ(PlaneLayoutComponentType::RAW,
+              static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value));
+    EXPECT_EQ(0, planeLayoutComponent.offsetInBits % 8);
+    EXPECT_EQ(-1, planeLayoutComponent.sizeInBits);
+
+    int releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_YCBCR_P010) {
+    BufferDescriptorInfo info{
+            .name = {"VTS_TEMP"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::YCBCR_P010,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    };
+    auto buffer = allocate(info);
+    if (!buffer) {
+        ASSERT_FALSE(isSupported(info));
+        GTEST_SUCCEED() << "YCBCR_P010 format is unsupported";
+        return;
+    }
+
+    // lock buffer for writing
+    const ARect region{0, 0, info.width, info.height};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                     region, -1, (void**)&data));
+
+    YCbCr yCbCr;
+    ASSERT_NO_FATAL_FAILURE(yCbCr = getAndroidYCbCr_P010(*handle, data));
+
+    constexpr uint32_t kCbCrSubSampleFactor = 2;
+    ASSERT_EQ(kCbCrSubSampleFactor, yCbCr.horizontalSubSampling);
+    ASSERT_EQ(kCbCrSubSampleFactor, yCbCr.verticalSubSampling);
+
+    ASSERT_EQ(0, info.height % 2);
+
+    // fill the data
+    fillYCbCrData(yCbCr.yCbCr, info.width, info.height, yCbCr.horizontalSubSampling,
+                  yCbCr.verticalSubSampling);
+    // verify the YCbCr data
+    verifyYCbCrData(yCbCr.yCbCr, info.width, info.height, yCbCr.horizontalSubSampling,
+                    yCbCr.verticalSubSampling);
+
+    int releaseFence = -1;
+    ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, LockBadAccessRegion) {
+    auto buffer = allocateGeneric();
+    ASSERT_NE(nullptr, buffer);
+    const auto& info = buffer->info();
+
+    // lock buffer for writing
+    const ARect region{0, 0, info.width * 2, info.height * 2};
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_VALUE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+                                                          region, -1, (void**)&data));
+}
+
+TEST_P(GraphicsMapperStableCTests, UnlockNegative) {
+    native_handle_t* invalidHandle = nullptr;
+    int releaseFence = -1;
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(invalidHandle, &releaseFence))
+            << "unlock with nullptr did not fail with BAD_BUFFER";
+
+    invalidHandle = native_handle_create(0, 0);
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(invalidHandle, &releaseFence))
+            << "unlock with invalid handle did not fail with BAD_BUFFER";
+    native_handle_delete(invalidHandle);
+
+    auto buffer = allocateGeneric();
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(buffer->rawHandle(), &releaseFence))
+            << "unlock with un-imported handle did not fail with BAD_BUFFER";
+}
+
+TEST_P(GraphicsMapperStableCTests, UnlockNotImported) {
+    int releaseFence = -1;
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(buffer->rawHandle(), &releaseFence))
+            << "unlock with un-imported handle did not fail with BAD_BUFFER";
+}
+
+TEST_P(GraphicsMapperStableCTests, UnlockNotLocked) {
+    int releaseFence = -1;
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(*bufferHandle, &releaseFence))
+            << "unlock with unlocked handle did not fail with BAD_BUFFER";
+}
+
+TEST_P(GraphicsMapperStableCTests, LockUnlockNested) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    const ARect region{0, 0, buffer->info().width, buffer->info().height};
+    auto usage = static_cast<int64_t>(buffer->info().usage);
+    auto handle = buffer->import();
+    uint8_t* data = nullptr;
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, usage, region, -1, (void**)&data));
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, usage, region, -1, (void**)&data))
+            << "Second lock failed";
+    int releaseFence = -1;
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+        releaseFence = -1;
+    }
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence))
+            << "Second unlock failed";
+    if (releaseFence != -1) {
+        close(releaseFence);
+        releaseFence = -1;
+    }
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(*handle, &releaseFence))
+            << "Third, unmatched, unlock should have failed with BAD_BUFFER";
+}
+
+TEST_P(GraphicsMapperStableCTests, FlushRereadBasic) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    const auto& info = buffer->info();
+    const auto stride = buffer->stride();
+    const ARect region{0, 0, buffer->info().width, buffer->info().height};
+
+    auto writeHandle = buffer->import();
+    auto readHandle = buffer->import();
+    ASSERT_TRUE(writeHandle && readHandle);
+
+    // lock buffer for writing
+
+    uint8_t* writeData;
+    EXPECT_EQ(AIMAPPER_ERROR_NONE,
+              mapper()->v5.lock(*writeHandle, static_cast<uint64_t>(BufferUsage::CPU_WRITE_OFTEN),
+                                region, -1, (void**)&writeData));
+
+    uint8_t* readData;
+    EXPECT_EQ(AIMAPPER_ERROR_NONE,
+              mapper()->v5.lock(*readHandle, static_cast<uint64_t>(BufferUsage::CPU_READ_OFTEN),
+                                region, -1, (void**)&readData));
+
+    fillRGBA8888(writeData, info.height, stride * 4, info.width * 4);
+
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.flushLockedBuffer(*writeHandle));
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.rereadLockedBuffer(*readHandle));
+
+    ASSERT_NO_FATAL_FAILURE(
+            verifyRGBA8888(*readHandle, readData, info.height, stride * 4, info.width * 4));
+
+    int releaseFence = -1;
+
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*readHandle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+        releaseFence = -1;
+    }
+
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*writeHandle, &releaseFence));
+    if (releaseFence != -1) {
+        close(releaseFence);
+        releaseFence = -1;
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, FlushLockedBufferBadBuffer) {
+    // Amazingly this is enough to make the compiler happy even though flushLockedBuffer
+    // is _Nonnull :shrug:
+    buffer_handle_t badBuffer = nullptr;
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.flushLockedBuffer(badBuffer));
+}
+
+TEST_P(GraphicsMapperStableCTests, RereadLockedBufferBadBuffer) {
+    buffer_handle_t badBuffer = nullptr;
+    EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.rereadLockedBuffer(badBuffer));
+}
+
+TEST_P(GraphicsMapperStableCTests, GetBufferId) {
+    auto buffer = allocateGeneric();
+    auto bufferHandle = buffer->import();
+    auto bufferId = getStandardMetadata<StandardMetadataType::BUFFER_ID>(*bufferHandle);
+    ASSERT_TRUE(bufferId.has_value());
+
+    auto buffer2 = allocateGeneric();
+    auto bufferHandle2 = buffer2->import();
+    auto bufferId2 = getStandardMetadata<StandardMetadataType::BUFFER_ID>(*bufferHandle2);
+    ASSERT_TRUE(bufferId2.has_value());
+
+    EXPECT_NE(*bufferId, *bufferId2);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetName) {
+    auto buffer = allocate({
+            .name = {"Hello, World!"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    });
+    auto bufferHandle = buffer->import();
+    auto name = getStandardMetadata<StandardMetadataType::NAME>(*bufferHandle);
+    ASSERT_TRUE(name.has_value());
+    EXPECT_EQ(*name, "Hello, World!");
+}
+
+TEST_P(GraphicsMapperStableCTests, GetWidthHeight) {
+    auto buffer = allocate({
+            .name = {"Hello, World!"},
+            .width = 64,
+            .height = 128,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    });
+    auto bufferHandle = buffer->import();
+    auto value = getStandardMetadata<StandardMetadataType::WIDTH>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(*value, 64);
+    value = getStandardMetadata<StandardMetadataType::HEIGHT>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(*value, 128);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetLayerCount) {
+    auto buffer = allocateGeneric();
+    auto bufferHandle = buffer->import();
+    auto value = getStandardMetadata<StandardMetadataType::LAYER_COUNT>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(*value, buffer->info().layerCount);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetPixelFormatRequested) {
+    auto buffer = allocateGeneric();
+    auto bufferHandle = buffer->import();
+    auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(*value, buffer->info().format);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetPixelFormatFourCC) {
+    auto buffer = allocate({
+            .name = {"Hello, World!"},
+            .width = 64,
+            .height = 128,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    });
+    {
+        auto bufferHandle = buffer->import();
+        auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_FOURCC>(*bufferHandle);
+        ASSERT_TRUE(value.has_value());
+        EXPECT_EQ(*value, DRM_FORMAT_ABGR8888);
+    }
+
+    buffer = allocate({
+            .name = {"yv12"},
+            .width = 64,
+            .height = 128,
+            .layerCount = 1,
+            .format = PixelFormat::YV12,
+            .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+            .reservedSize = 0,
+    });
+    {
+        auto bufferHandle = buffer->import();
+        auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_FOURCC>(*bufferHandle);
+        ASSERT_TRUE(value.has_value());
+        EXPECT_EQ(*value, DRM_FORMAT_YVU420);
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, GetPixelFormatModifier) {
+    auto buffer = allocateGeneric();
+    auto bufferHandle = buffer->import();
+    auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_MODIFIER>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    // Only the upper 8-bits are defined and is just the vendor ID, the lower 56 bits are
+    // then vendor specific. So there's not anything useful to assert here beyond just that
+    // we successfully queried a value
+}
+
+TEST_P(GraphicsMapperStableCTests, GetUsage) {
+    auto buffer = allocateGeneric();
+    auto bufferHandle = buffer->import();
+    auto value = getStandardMetadata<StandardMetadataType::USAGE>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(buffer->info().usage, *value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetAllocationSize) {
+    auto buffer = allocateGeneric();
+    auto bufferHandle = buffer->import();
+    auto value = getStandardMetadata<StandardMetadataType::ALLOCATION_SIZE>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    const auto estimatedSize = buffer->stride() * buffer->info().height * 4;
+    // This buffer has CPU usage, so we expect at least stride * height * 4 since it should be
+    // generally linear uncompressed.
+    EXPECT_GE(*value, estimatedSize)
+            << "Expected allocation size to be at least stride * height * 4bpp";
+    // Might need refining, but hopefully this a generous-enough upper-bound?
+    EXPECT_LT(*value, estimatedSize * 2)
+            << "Expected allocation size to less than double stride * height * 4bpp";
+}
+
+TEST_P(GraphicsMapperStableCTests, GetProtectedContent) {
+    const BufferDescriptorInfo info{
+            .name = {"prot8888"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::PROTECTED | BufferUsage::COMPOSER_OVERLAY,
+            .reservedSize = 0,
+    };
+    auto buffer = allocate(info);
+    if (!buffer) {
+        ASSERT_FALSE(isSupported(info))
+                << "Allocation of trivial sized buffer failed, so isSupported() must be false";
+        GTEST_SUCCEED() << "PROTECTED RGBA_8888 is unsupported";
+        return;
+    }
+    auto bufferHandle = buffer->import();
+    auto value = getStandardMetadata<StandardMetadataType::PROTECTED_CONTENT>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(*value, 1);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetCompression) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::COMPRESSION>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(gralloc4::Compression_None.name, value->name);
+    EXPECT_EQ(gralloc4::Compression_None.value, value->value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetInterlaced) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::INTERLACED>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(gralloc4::Interlaced_None.name, value->name);
+    EXPECT_EQ(gralloc4::Interlaced_None.value, value->value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetChromaSiting) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::CHROMA_SITING>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(gralloc4::ChromaSiting_None.name, value->name);
+    EXPECT_EQ(gralloc4::ChromaSiting_None.value, value->value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetPlaneLayouts) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    ASSERT_NO_FATAL_FAILURE(verifyRGBA8888PlaneLayouts(*value));
+}
+
+TEST_P(GraphicsMapperStableCTests, GetCrop) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::CROP>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(1, value->size());
+    const Rect expected{0, 0, buffer->info().width, buffer->info().height};
+    EXPECT_EQ(expected, value->at(0));
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSetDataspace) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::DATASPACE>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(Dataspace::UNKNOWN, *value);
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, setStandardMetadata<StandardMetadataType::DATASPACE>(
+                                           *bufferHandle, Dataspace::DISPLAY_P3));
+    value = getStandardMetadata<StandardMetadataType::DATASPACE>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(Dataspace::DISPLAY_P3, *value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSetBlendMode) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::BLEND_MODE>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(BlendMode::INVALID, *value);
+    EXPECT_EQ(AIMAPPER_ERROR_NONE, setStandardMetadata<StandardMetadataType::BLEND_MODE>(
+                                           *bufferHandle, BlendMode::COVERAGE));
+    value = getStandardMetadata<StandardMetadataType::BLEND_MODE>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(BlendMode::COVERAGE, *value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSetSmpte2086) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_FALSE(value->has_value());
+
+    // TODO: Maybe use something resembling real values, but validation isn't supposed to happen
+    // here anyway so :shrug:
+    const Smpte2086 awesomeHdr{
+            XyColor{1.f, 1.f},      XyColor{2.f, 2.f}, XyColor{3.f, 3.f},
+            XyColor{400.f, 1000.f}, 100000.0f,         0.0001f,
+    };
+    EXPECT_EQ(AIMAPPER_ERROR_NONE,
+              setStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle, awesomeHdr));
+    value = getStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    ASSERT_TRUE(value->has_value());
+    EXPECT_EQ(awesomeHdr, *value);
+
+    EXPECT_EQ(AIMAPPER_ERROR_NONE,
+              setStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle, std::nullopt));
+    value = getStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_FALSE(value->has_value());
+}
+
+TEST_P(GraphicsMapperStableCTests, GetCta861_3) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_FALSE(value->has_value());
+
+    const Cta861_3 genericHlgish{1000.f, 140.f};
+    EXPECT_EQ(AIMAPPER_ERROR_NONE,
+              setStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle, genericHlgish));
+    value = getStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    ASSERT_TRUE(value->has_value());
+    EXPECT_EQ(genericHlgish, *value);
+
+    EXPECT_EQ(AIMAPPER_ERROR_NONE,
+              setStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle, std::nullopt));
+    value = getStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_FALSE(value->has_value());
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSmpte2094_10) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_10>(*bufferHandle);
+    if (value.has_value()) {
+        EXPECT_FALSE(value->has_value());
+    }
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSmpte2094_40) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_40>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_FALSE(value->has_value());
+}
+
+std::vector<std::tuple<std::string, std::shared_ptr<IAllocator>>> getIAllocatorsAtLeastVersion(
+        int32_t minVersion) {
+    auto instanceNames = getAidlHalInstanceNames(IAllocator::descriptor);
+    std::vector<std::tuple<std::string, std::shared_ptr<IAllocator>>> filteredInstances;
+    filteredInstances.reserve(instanceNames.size());
+    for (const auto& name : instanceNames) {
+        auto allocator =
+                IAllocator::fromBinder(ndk::SpAIBinder(AServiceManager_checkService(name.c_str())));
+        int32_t version = 0;
+        if (allocator->getInterfaceVersion(&version).isOk()) {
+            if (version >= minVersion) {
+                filteredInstances.emplace_back(name, std::move(allocator));
+            }
+        }
+    }
+    return filteredInstances;
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsMapperStableCTests);
+INSTANTIATE_TEST_CASE_P(PerInstance, GraphicsMapperStableCTests,
+                        testing::ValuesIn(getIAllocatorsAtLeastVersion(2)),
+                        [](auto info) -> std::string {
+                            std::string name =
+                                    std::to_string(info.index) + "/" + std::get<0>(info.param);
+                            return Sanitize(name);
+                        });
\ No newline at end of file