diff --git a/automotive/evs/aidl/impl/default/Android.bp b/automotive/evs/aidl/impl/default/Android.bp index 79ee956641..c56fe2bfc7 100644 --- a/automotive/evs/aidl/impl/default/Android.bp +++ b/automotive/evs/aidl/impl/default/Android.bp @@ -21,45 +21,41 @@ package { 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 @@ cc_binary { ], 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 @@ prebuilt_etc { 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 0000000000..3663e3d05a --- /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 + +#include +#include +#include +#include + +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& buffers) override; + + ndk::ScopedAStatus importExternalBuffers(const std::vector& 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(-1), nullptr) otherwise. + [[nodiscard]] std::pair 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 mBuffers; + + // Double-mapping between buffer position and ID. + std::vector mBufferPosToId; + std::vector 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 0000000000..db3be8fb36 --- /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 +#include +#include +#include +#include +#include + +#include +#include + +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& 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& 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(&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(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(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 EvsCamera::useBuffer_unsafe() { + if (mFramesInUse >= mAvailableFrames) { + DCHECK_EQ(mFramesInUse, mAvailableFrames); + return {static_cast(-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 0000000000..48f0890e69 --- /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 +#include + +#include +#include +#include + +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(++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* _aidl_return), (override)); + MOCK_METHOD(::ndk::ScopedAStatus, getIntParameter, + (::aidl::android::hardware::automotive::evs::CameraParam in_id, + std::vector* _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& in_opaqueValue), + (override)); + MOCK_METHOD(::ndk::ScopedAStatus, setIntParameter, + (::aidl::android::hardware::automotive::evs::CameraParam in_id, int32_t in_value, + std::vector* _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(); + 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(); + + // 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(i)); + } + evsCam->checkBufferOrder(); + + { + std::vector> inUseIDHandlePairs; + std::unordered_set inUseIDs; + std::unordered_set inUseHandles; + for (std::size_t i = 0; i < kNumOfHandles; ++i) { + const auto [id, handle] = evsCam->useBuffer_unsafe(); + const std::size_t handleInt = reinterpret_cast(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> inUseIDHandlePairs; + std::unordered_set inUseIDs; + std::unordered_set inUseHandles; + for (std::size_t i = 0; i < kNumOfHandles; ++i) { + const auto [id, handle] = evsCam->useBuffer_unsafe(); + const std::size_t handleInt = reinterpret_cast(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> inUseIDHandlePairs; + std::unordered_set inUseIDs; + std::unordered_set inUseHandles; + for (std::size_t i = 0; i < kNumOfHandles; ++i) { + const auto [id, handle] = evsCam->useBuffer_unsafe(); + const std::size_t handleInt = reinterpret_cast(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