Add EvsCamera Class and Buffer Manipulation

Test: Build and `atest android.hardware.automotive.evs-aidl-default-service_cam_buffer_test`
Bug: 277861838
Change-Id: I28218f8e22e7daca3d8ce0bbec64c054a4462564
diff --git a/automotive/evs/aidl/impl/default/Android.bp b/automotive/evs/aidl/impl/default/Android.bp
index 79ee956..c56fe2b 100644
--- a/automotive/evs/aidl/impl/default/Android.bp
+++ b/automotive/evs/aidl/impl/default/Android.bp
@@ -21,45 +21,41 @@
     default_applicable_licenses: ["hardware_interfaces_license"],
 }
 
-cc_binary {
-    name: "android.hardware.automotive.evs-aidl-default-service",
+cc_defaults {
+    name: "android.hardware.automotive.evs-aidl-default-service-default",
     defaults: ["EvsHalDefaults"],
-    vintf_fragments: ["manifest_evs-default-service.xml"],
-    init_rc: ["evs-default-service.rc"],
-    vendor: true,
-    relative_install_path: "hw",
-    cflags: [
-        "-DGL_GLEXT_PROTOTYPES",
-        "-DEGL_EGLEXT_PROTOTYPES",
-        "-Wall",
-        "-Wextra",
-        "-Werror",
-        "-Wthread-safety",
-    ],
-    srcs: [
-        ":libgui_frame_event_aidl",
-        "src/*.cpp",
-    ],
     shared_libs: [
         "android.hardware.graphics.bufferqueue@1.0",
         "android.hardware.graphics.bufferqueue@2.0",
         "android.hidl.token@1.0-utils",
         "libEGL",
         "libGLESv2",
-        "libbase",
         "libbinder_ndk",
         "libbufferqueueconverter",
         "libcamera_metadata",
         "libhardware_legacy",
         "libhidlbase",
-        "liblog",
         "libnativewindow",
         "libtinyxml2",
         "libui",
-        "libutils",
         "libyuv",
     ],
-    static_libs: [
+}
+
+cc_library {
+    name: "android.hardware.automotive.evs-aidl-default-service-lib",
+    defaults: ["android.hardware.automotive.evs-aidl-default-service-default"],
+    vendor: true,
+    cflags: [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ],
+    srcs: [
+        ":libgui_frame_event_aidl",
+        "src/*.cpp",
+    ],
+    exclude_srcs: ["src/service.cpp"],
+    whole_static_libs: [
         "android.frameworks.automotive.display-V2-ndk",
         "android.hardware.automotive.evs-V2-ndk",
         "android.hardware.common-V2-ndk",
@@ -71,6 +67,25 @@
     ],
     local_include_dirs: ["include"],
     include_dirs: ["frameworks/native/include/"],
+    export_include_dirs: ["include"],
+}
+
+cc_binary {
+    name: "android.hardware.automotive.evs-aidl-default-service",
+    defaults: ["android.hardware.automotive.evs-aidl-default-service-default"],
+    vintf_fragments: ["manifest_evs-default-service.xml"],
+    init_rc: ["evs-default-service.rc"],
+    vendor: true,
+    relative_install_path: "hw",
+    cflags: [
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ],
+    srcs: ["src/service.cpp"],
+    static_libs: [
+        "android.hardware.automotive.evs-aidl-default-service-lib",
+    ],
+    include_dirs: ["frameworks/native/include/"],
     required: ["evs_mock_hal_configuration.xml"],
 }
 
@@ -80,3 +95,14 @@
     src: "resources/evs_mock_configuration.xml",
     sub_dir: "automotive/evs",
 }
+
+cc_test {
+    name: "android.hardware.automotive.evs-aidl-default-service_cam_buffer_test",
+    defaults: ["android.hardware.automotive.evs-aidl-default-service-default"],
+    vendor: true,
+    srcs: ["tests/EvsCameraBufferTest.cpp"],
+    static_libs: [
+        "android.hardware.automotive.evs-aidl-default-service-lib",
+        "libgmock",
+    ],
+}
diff --git a/automotive/evs/aidl/impl/default/include/EvsCamera.h b/automotive/evs/aidl/impl/default/include/EvsCamera.h
new file mode 100644
index 0000000..3663e3d
--- /dev/null
+++ b/automotive/evs/aidl/impl/default/include/EvsCamera.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "EvsCameraBase.h"
+
+#include <cutils/native_handle.h>
+
+#include <cstddef>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+namespace aidl::android::hardware::automotive::evs::implementation {
+
+class EvsCamera : public EvsCameraBase {
+  private:
+    using Base = EvsCameraBase;
+    using Self = EvsCamera;
+
+  public:
+    using Base::Base;
+
+    ~EvsCamera() override;
+
+    // Methods from ::android::hardware::automotive::evs::IEvsCamera follow.
+    ndk::ScopedAStatus doneWithFrame(const std::vector<evs::BufferDesc>& buffers) override;
+
+    ndk::ScopedAStatus importExternalBuffers(const std::vector<evs::BufferDesc>& buffers,
+                                             int32_t* _aidl_return) override;
+
+    ndk::ScopedAStatus setMaxFramesInFlight(int32_t bufferCount) override;
+
+  protected:
+    virtual ::android::status_t allocateOneFrame(buffer_handle_t* handle) = 0;
+
+    virtual void freeOneFrame(const buffer_handle_t handle);
+
+    void shutdown() override;
+
+    void closeAllBuffers_unsafe();
+
+    // Returns (ID, handle) if succeeds. (static_cast<size_t>(-1), nullptr) otherwise.
+    [[nodiscard]] std::pair<std::size_t, buffer_handle_t> useBuffer_unsafe();
+
+    void returnBuffer_unsafe(const std::size_t id);
+
+    bool increaseAvailableFrames_unsafe(const buffer_handle_t handle);
+
+    bool decreaseAvailableFrames_unsafe();
+
+    bool setAvailableFrames_unsafe(const std::size_t bufferCount);
+
+    void swapBufferFrames_unsafe(const std::size_t pos1, const std::size_t pos2);
+
+    struct BufferRecord {
+        BufferRecord() = default;
+        BufferRecord(const BufferRecord&) = default;
+        BufferRecord(BufferRecord&&) = default;
+        BufferRecord& operator=(const BufferRecord&) = default;
+        BufferRecord& operator=(BufferRecord&&) = default;
+        ~BufferRecord() = default;
+
+        explicit BufferRecord(buffer_handle_t h) : handle(h) {}
+
+        buffer_handle_t handle{nullptr};
+        bool inUse{false};
+    };
+
+    std::mutex mMutex;
+
+    // Graphics buffers to transfer images, always in the order of:
+    // In use buffers ... available buffers ... unavailable (unallocated) buffers.
+    std::vector<BufferRecord> mBuffers;
+
+    // Double-mapping between buffer position and ID.
+    std::vector<std::size_t> mBufferPosToId;
+    std::vector<std::size_t> mBufferIdToPos;
+
+    std::size_t mAvailableFrames{0};
+    std::size_t mFramesInUse{0};
+};
+
+}  // namespace aidl::android::hardware::automotive::evs::implementation
diff --git a/automotive/evs/aidl/impl/default/src/EvsCamera.cpp b/automotive/evs/aidl/impl/default/src/EvsCamera.cpp
new file mode 100644
index 0000000..db3be8f
--- /dev/null
+++ b/automotive/evs/aidl/impl/default/src/EvsCamera.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EvsCamera.h"
+
+#include <aidl/android/hardware/automotive/evs/EvsResult.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android-base/logging.h>
+#include <android/hardware_buffer.h>
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+
+#include <cstddef>
+#include <mutex>
+
+namespace aidl::android::hardware::automotive::evs::implementation {
+
+// Arbitrary limit on number of graphics buffers allowed to be allocated
+// Safeguards against unreasonable resource consumption and provides a testable limit
+constexpr std::size_t kMaxBuffersInFlight = 100;
+
+EvsCamera::~EvsCamera() {
+    shutdown();
+}
+
+ndk::ScopedAStatus EvsCamera::doneWithFrame(const std::vector<evs::BufferDesc>& buffers) {
+    std::lock_guard lck(mMutex);
+    for (const auto& desc : buffers) {
+        returnBuffer_unsafe(desc.bufferId);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus EvsCamera::importExternalBuffers(const std::vector<evs::BufferDesc>& buffers,
+                                                    int32_t* _aidl_return) {
+    if (buffers.empty()) {
+        LOG(DEBUG) << __func__
+                   << ": Ignoring a request to import external buffers with an empty list.";
+        return ndk::ScopedAStatus::ok();
+    }
+    static auto& mapper = ::android::GraphicBufferMapper::get();
+    std::lock_guard lck(mMutex);
+    std::size_t numBuffersToAdd = std::min(buffers.size(), kMaxBuffersInFlight - mAvailableFrames);
+    if (numBuffersToAdd == 0) {
+        LOG(WARNING) << __func__ << ": The number of buffers has hit the upper limit ("
+                     << kMaxBuffersInFlight << "). Stop importing.";
+        return ndk::ScopedAStatus::ok();
+    } else if (numBuffersToAdd < buffers.size()) {
+        LOG(WARNING) << "Exceeds the limit on the number of buffers. Only " << numBuffersToAdd
+                     << " buffers will be imported. " << buffers.size() << " are asked.";
+    }
+    const size_t before = mAvailableFrames;
+    for (std::size_t idx = 0; idx < numBuffersToAdd; ++idx) {
+        auto& buffer = buffers[idx];
+        const AHardwareBuffer_Desc* pDesc =
+                reinterpret_cast<const AHardwareBuffer_Desc*>(&buffer.buffer.description);
+
+        buffer_handle_t handleToImport = ::android::dupFromAidl(buffer.buffer.handle);
+        buffer_handle_t handleToStore = nullptr;
+        if (handleToImport == nullptr) {
+            LOG(WARNING) << "Failed to duplicate a memory handle. Ignoring a buffer "
+                         << buffer.bufferId;
+            continue;
+        }
+
+        ::android::status_t result =
+                mapper.importBuffer(handleToImport, pDesc->width, pDesc->height, pDesc->layers,
+                                    pDesc->format, pDesc->usage, pDesc->stride, &handleToStore);
+        if (result != ::android::NO_ERROR || handleToStore == nullptr ||
+            !increaseAvailableFrames_unsafe(handleToStore)) {
+            LOG(WARNING) << "Failed to import a buffer " << buffer.bufferId;
+        }
+    }
+    *_aidl_return = mAvailableFrames - before;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus EvsCamera::setMaxFramesInFlight(int32_t bufferCount) {
+    std::lock_guard lock(mMutex);
+    if (bufferCount < 1) {
+        LOG(ERROR) << "Ignoring setMaxFramesInFlight with less than one buffer requested.";
+        return ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int>(EvsResult::INVALID_ARG));
+    }
+    if (!setAvailableFrames_unsafe(bufferCount)) {
+        LOG(ERROR) << "Failed to adjust the maximum number of frames in flight.";
+        return ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int>(EvsResult::BUFFER_NOT_AVAILABLE));
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+void EvsCamera::freeOneFrame(const buffer_handle_t handle) {
+    static auto& alloc = ::android::GraphicBufferAllocator::get();
+    alloc.free(handle);
+}
+
+bool EvsCamera::setAvailableFrames_unsafe(const std::size_t bufferCount) {
+    if (bufferCount < 1) {
+        LOG(ERROR) << "Ignoring request to set buffer count to zero.";
+        return false;
+    }
+    if (bufferCount > kMaxBuffersInFlight) {
+        LOG(ERROR) << "Rejecting buffer request in excess of internal limit";
+        return false;
+    }
+
+    if (bufferCount > mAvailableFrames) {
+        bool success = true;
+        const std::size_t numBufferBeforeAlloc = mAvailableFrames;
+        for (int numBufferToAllocate = bufferCount - mAvailableFrames;
+             success && numBufferToAllocate > 0; --numBufferToAllocate) {
+            buffer_handle_t handle = nullptr;
+            const auto result = allocateOneFrame(&handle);
+            if (result != ::android::NO_ERROR || !handle) {
+                LOG(ERROR) << __func__ << ": Failed to allocate a graphics buffer. Error " << result
+                           << ", handle: " << handle;
+                success = false;
+                break;
+            }
+            success &= increaseAvailableFrames_unsafe(handle);
+        }
+        if (!success) {
+            // Rollback when failure.
+            for (int numBufferToRelease = mAvailableFrames - numBufferBeforeAlloc;
+                 numBufferToRelease > 0; --numBufferToRelease) {
+                decreaseAvailableFrames_unsafe();
+            }
+            return false;
+        }
+    } else {
+        for (int numBufferToRelease = mAvailableFrames - std::max(bufferCount, mFramesInUse);
+             numBufferToRelease > 0; --numBufferToRelease) {
+            decreaseAvailableFrames_unsafe();
+        }
+        if (mAvailableFrames > bufferCount) {
+            // This shouldn't happen with a properly behaving client because the client
+            // should only make this call after returning sufficient outstanding buffers
+            // to allow a clean resize.
+            LOG(ERROR) << "Buffer queue shrink failed, asked: " << bufferCount
+                       << ", actual: " << mAvailableFrames
+                       << " -- too many buffers currently in use?";
+        }
+    }
+    return true;
+}
+
+void EvsCamera::shutdown() {
+    std::lock_guard lck(mMutex);
+    closeAllBuffers_unsafe();
+}
+
+void EvsCamera::closeAllBuffers_unsafe() {
+    if (mFramesInUse > 0) {
+        LOG(WARNING) << __func__ << ": Closing while " << mFramesInUse
+                     << " frame(s) are still in use.";
+    }
+    for (auto& buffer : mBuffers) {
+        freeOneFrame(buffer.handle);
+        buffer.handle = nullptr;
+    }
+    mBuffers.clear();
+    mBufferPosToId.clear();
+    mBufferIdToPos.clear();
+}
+
+std::pair<std::size_t, buffer_handle_t> EvsCamera::useBuffer_unsafe() {
+    if (mFramesInUse >= mAvailableFrames) {
+        DCHECK_EQ(mFramesInUse, mAvailableFrames);
+        return {static_cast<std::size_t>(-1), nullptr};
+    }
+    const std::size_t pos = mFramesInUse++;
+    auto& buffer = mBuffers[pos];
+    DCHECK(!buffer.inUse);
+    DCHECK(buffer.handle);
+    buffer.inUse = true;
+    return {mBufferPosToId[pos], buffer.handle};
+}
+
+void EvsCamera::returnBuffer_unsafe(const std::size_t id) {
+    if (id >= mBuffers.size()) {
+        LOG(ERROR) << __func__ << ": ID out-of-bound. id: " << id
+                   << " max: " << mBuffers.size() - 1;
+        return;
+    }
+    const std::size_t pos = mBufferIdToPos[id];
+
+    if (!mBuffers[pos].inUse) {
+        LOG(ERROR) << __func__ << ": Ignoring returning frame " << id << " which is already free.";
+        return;
+    }
+    DCHECK_LT(pos, mFramesInUse);
+    const std::size_t last_in_use_pos = --mFramesInUse;
+    swapBufferFrames_unsafe(pos, last_in_use_pos);
+    mBuffers[last_in_use_pos].inUse = false;
+}
+
+bool EvsCamera::increaseAvailableFrames_unsafe(const buffer_handle_t handle) {
+    if (mAvailableFrames >= kMaxBuffersInFlight) {
+        LOG(WARNING) << __func__ << ": The number of buffers has hit the upper limit ("
+                     << kMaxBuffersInFlight << "). Stop increasing.";
+        return false;
+    }
+    const std::size_t pos = mAvailableFrames++;
+    if (mAvailableFrames > mBuffers.size()) {
+        const std::size_t oldBufferSize = mBuffers.size();
+        mBuffers.resize(mAvailableFrames);
+        mBufferPosToId.resize(mAvailableFrames);
+        mBufferIdToPos.resize(mAvailableFrames);
+        // Build position/ID mapping.
+        for (std::size_t idx = oldBufferSize; idx < mBuffers.size(); ++idx) {
+            mBufferPosToId[idx] = idx;
+            mBufferIdToPos[idx] = idx;
+        }
+    }
+    auto& buffer = mBuffers[pos];
+    DCHECK(!buffer.inUse);
+    DCHECK(!buffer.handle);
+    buffer.handle = handle;
+    return true;
+}
+
+bool EvsCamera::decreaseAvailableFrames_unsafe() {
+    if (mFramesInUse >= mAvailableFrames) {
+        DCHECK_EQ(mFramesInUse, mAvailableFrames);
+        return false;
+    }
+    const std::size_t pos = --mAvailableFrames;
+    auto& buffer = mBuffers[pos];
+    DCHECK(!buffer.inUse);
+    DCHECK(buffer.handle);
+    freeOneFrame(buffer.handle);
+    buffer.handle = nullptr;
+    return true;
+}
+
+void EvsCamera::swapBufferFrames_unsafe(const std::size_t pos1, const std::size_t pos2) {
+    if (pos1 == pos2) {
+        return;
+    }
+    if (pos1 >= mBuffers.size() || pos2 >= mBuffers.size()) {
+        LOG(ERROR) << __func__ << ": Index out-of-bound. pos1: " << pos1 << ", pos2: " << pos2
+                   << ", buffer size: " << mBuffers.size();
+        return;
+    }
+    const std::size_t id1 = mBufferPosToId[pos1];
+    const std::size_t id2 = mBufferPosToId[pos2];
+    std::swap(mBufferPosToId[pos1], mBufferPosToId[pos2]);
+    std::swap(mBufferIdToPos[id1], mBufferIdToPos[id2]);
+    std::swap(mBuffers[pos1], mBuffers[pos2]);
+}
+
+}  // namespace aidl::android::hardware::automotive::evs::implementation
diff --git a/automotive/evs/aidl/impl/default/tests/EvsCameraBufferTest.cpp b/automotive/evs/aidl/impl/default/tests/EvsCameraBufferTest.cpp
new file mode 100644
index 0000000..48f0890
--- /dev/null
+++ b/automotive/evs/aidl/impl/default/tests/EvsCameraBufferTest.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EvsCamera.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <unordered_set>
+#include <vector>
+
+namespace aidl::android::hardware::automotive::evs::implementation {
+
+class EvsCameraForTest : public EvsCamera {
+  public:
+    using EvsCamera::increaseAvailableFrames_unsafe;
+    using EvsCamera::returnBuffer_unsafe;
+    using EvsCamera::useBuffer_unsafe;
+
+    ~EvsCameraForTest() override { shutdown(); }
+
+    ::android::status_t allocateOneFrame(buffer_handle_t* handle) override {
+        static std::intptr_t handle_cnt = 0;
+        *handle = reinterpret_cast<buffer_handle_t>(++handle_cnt);
+        return ::android::OK;
+    }
+
+    void freeOneFrame(const buffer_handle_t /* handle */) override {
+        // Nothing to free because the handles are fake.
+    }
+
+    void checkBufferOrder() {
+        for (std::size_t idx = 0; idx < mBuffers.size(); ++idx) {
+            const auto& buffer = mBuffers[idx];
+            EXPECT_EQ(idx < mFramesInUse, buffer.inUse);
+            EXPECT_EQ(idx < mAvailableFrames, buffer.handle != nullptr);
+            EXPECT_LE(mFramesInUse, mAvailableFrames);
+        }
+    }
+
+    MOCK_METHOD(::ndk::ScopedAStatus, forcePrimaryClient,
+                (const std::shared_ptr<::aidl::android::hardware::automotive::evs::IEvsDisplay>&
+                         in_display),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getCameraInfo,
+                (::aidl::android::hardware::automotive::evs::CameraDesc * _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getExtendedInfo,
+                (int32_t in_opaqueIdentifier, std::vector<uint8_t>* _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getIntParameter,
+                (::aidl::android::hardware::automotive::evs::CameraParam in_id,
+                 std::vector<int32_t>* _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getIntParameterRange,
+                (::aidl::android::hardware::automotive::evs::CameraParam in_id,
+                 ::aidl::android::hardware::automotive::evs::ParameterRange* _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getParameterList,
+                (std::vector<::aidl::android::hardware::automotive::evs::CameraParam> *
+                 _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getPhysicalCameraInfo,
+                (const std::string& in_deviceId,
+                 ::aidl::android::hardware::automotive::evs::CameraDesc* _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, pauseVideoStream, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, resumeVideoStream, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setExtendedInfo,
+                (int32_t in_opaqueIdentifier, const std::vector<uint8_t>& in_opaqueValue),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setIntParameter,
+                (::aidl::android::hardware::automotive::evs::CameraParam in_id, int32_t in_value,
+                 std::vector<int32_t>* _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setPrimaryClient, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, startVideoStream,
+                (const std::shared_ptr<
+                        ::aidl::android::hardware::automotive::evs::IEvsCameraStream>& in_receiver),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, stopVideoStream, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, unsetPrimaryClient, (), (override));
+};
+
+TEST(EvsCameraBufferTest, ChangeBufferPoolSize) {
+    auto evsCam = ndk::SharedRefBase::make<EvsCameraForTest>();
+    EXPECT_TRUE(evsCam->setMaxFramesInFlight(100).isOk());
+    evsCam->checkBufferOrder();
+    EXPECT_TRUE(evsCam->setMaxFramesInFlight(50).isOk());
+    evsCam->checkBufferOrder();
+
+    // 2 buffers in use.
+    const auto [id1, handle1] = evsCam->useBuffer_unsafe();
+    const auto [id2, handle2] = evsCam->useBuffer_unsafe();
+    std::ignore = evsCam->useBuffer_unsafe();
+
+    // It allows you to set the buffer pool size to 1, but it will keep the space for the in use
+    // buffers.
+    EXPECT_TRUE(evsCam->setMaxFramesInFlight(1).isOk());
+    evsCam->checkBufferOrder();
+
+    evsCam->returnBuffer_unsafe(id1);
+    evsCam->checkBufferOrder();
+    evsCam->returnBuffer_unsafe(id2);
+    evsCam->checkBufferOrder();
+}
+
+TEST(EvsCameraForTest, UseAndReturn) {
+    constexpr std::size_t kNumOfHandles = 20;
+    auto evsCam = ndk::SharedRefBase::make<EvsCameraForTest>();
+
+    // Our "fake handles" of this test case is 1 to kNumOfHandles.
+    for (std::size_t i = 1; i <= kNumOfHandles; ++i) {
+        evsCam->increaseAvailableFrames_unsafe(reinterpret_cast<buffer_handle_t>(i));
+    }
+    evsCam->checkBufferOrder();
+
+    {
+        std::vector<std::pair<std::size_t, std::intptr_t>> inUseIDHandlePairs;
+        std::unordered_set<std::size_t> inUseIDs;
+        std::unordered_set<std::intptr_t> inUseHandles;
+        for (std::size_t i = 0; i < kNumOfHandles; ++i) {
+            const auto [id, handle] = evsCam->useBuffer_unsafe();
+            const std::size_t handleInt = reinterpret_cast<std::size_t>(handle);
+            EXPECT_NE(handle, nullptr);
+            EXPECT_LT(id, kNumOfHandles);
+
+            // handleInt must be between [1, kNumOfHandles] as we "allocated" above.
+            EXPECT_LT(0u, handleInt);
+            EXPECT_LE(handleInt, kNumOfHandles);
+
+            inUseIDHandlePairs.push_back({id, handleInt});
+            EXPECT_TRUE(inUseIDs.insert(id).second);
+            EXPECT_TRUE(inUseHandles.insert(handleInt).second);
+            evsCam->checkBufferOrder();
+        }
+        // Return buffers in the order of acquiring.
+        for (const auto [id, handleInt] : inUseIDHandlePairs) {
+            evsCam->returnBuffer_unsafe(id);
+            evsCam->checkBufferOrder();
+        }
+    }
+
+    {
+        std::vector<std::pair<std::size_t, std::intptr_t>> inUseIDHandlePairs;
+        std::unordered_set<std::size_t> inUseIDs;
+        std::unordered_set<std::intptr_t> inUseHandles;
+        for (std::size_t i = 0; i < kNumOfHandles; ++i) {
+            const auto [id, handle] = evsCam->useBuffer_unsafe();
+            const std::size_t handleInt = reinterpret_cast<std::size_t>(handle);
+            EXPECT_NE(handle, nullptr);
+            EXPECT_LT(id, kNumOfHandles);
+
+            // handleInt must be between [1, kNumOfHandles] as we "allocated" above.
+            EXPECT_LT(0u, handleInt);
+            EXPECT_LE(handleInt, kNumOfHandles);
+
+            inUseIDHandlePairs.push_back({id, handleInt});
+            EXPECT_TRUE(inUseIDs.insert(id).second);
+            EXPECT_TRUE(inUseHandles.insert(handleInt).second);
+            evsCam->checkBufferOrder();
+        }
+        // Return buffers in the reverse order of acquiring.
+        std::reverse(inUseIDHandlePairs.begin(), inUseIDHandlePairs.end());
+        for (const auto [id, handleInt] : inUseIDHandlePairs) {
+            evsCam->returnBuffer_unsafe(id);
+            evsCam->checkBufferOrder();
+        }
+    }
+
+    {
+        // Making sure the handles are still in [1, kNumOfHandles] and IDs are still [0,
+        // kNumOfHandles). The mapping may be different, though.
+        std::vector<std::pair<std::size_t, std::intptr_t>> inUseIDHandlePairs;
+        std::unordered_set<std::size_t> inUseIDs;
+        std::unordered_set<std::intptr_t> inUseHandles;
+        for (std::size_t i = 0; i < kNumOfHandles; ++i) {
+            const auto [id, handle] = evsCam->useBuffer_unsafe();
+            const std::size_t handleInt = reinterpret_cast<std::size_t>(handle);
+            EXPECT_NE(handle, nullptr);
+            EXPECT_LT(id, kNumOfHandles);
+
+            // handleInt must be between [1, kNumOfHandles] as we "allocated" above.
+            EXPECT_LT(0u, handleInt);
+            EXPECT_LE(handleInt, kNumOfHandles);
+
+            inUseIDHandlePairs.push_back({id, handleInt});
+            EXPECT_TRUE(inUseIDs.insert(id).second);
+            EXPECT_TRUE(inUseHandles.insert(handleInt).second);
+            evsCam->checkBufferOrder();
+        }
+    }
+}
+
+}  // namespace aidl::android::hardware::automotive::evs::implementation