/* * 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. */ #include "MockEvsHal.h" #include "Constants.h" #include #include #include #include #include #include #include #include #include #include #include namespace { using ::aidl::android::hardware::automotive::evs::BufferDesc; using ::aidl::android::hardware::automotive::evs::CameraDesc; using ::aidl::android::hardware::automotive::evs::CameraParam; using ::aidl::android::hardware::automotive::evs::DeviceStatus; using ::aidl::android::hardware::automotive::evs::DeviceStatusType; using ::aidl::android::hardware::automotive::evs::DisplayDesc; using ::aidl::android::hardware::automotive::evs::DisplayState; using ::aidl::android::hardware::automotive::evs::EvsEventDesc; using ::aidl::android::hardware::automotive::evs::EvsEventType; using ::aidl::android::hardware::automotive::evs::EvsResult; using ::aidl::android::hardware::automotive::evs::IEvsCamera; using ::aidl::android::hardware::automotive::evs::IEvsCameraStream; using ::aidl::android::hardware::automotive::evs::IEvsDisplay; using ::aidl::android::hardware::automotive::evs::IEvsEnumerator; using ::aidl::android::hardware::automotive::evs::IEvsEnumeratorStatusCallback; using ::aidl::android::hardware::automotive::evs::IEvsUltrasonicsArray; using ::aidl::android::hardware::automotive::evs::ParameterRange; using ::aidl::android::hardware::automotive::evs::Rotation; using ::aidl::android::hardware::automotive::evs::Stream; using ::aidl::android::hardware::automotive::evs::StreamType; using ::aidl::android::hardware::common::NativeHandle; using ::aidl::android::hardware::graphics::common::BufferUsage; using ::aidl::android::hardware::graphics::common::HardwareBuffer; using ::aidl::android::hardware::graphics::common::PixelFormat; using ::std::chrono_literals::operator""ms; using ::std::chrono_literals::operator""s; inline constexpr char kMockCameraDeviceNamePrefix[] = "/dev/mockcamera"; inline constexpr int32_t kCameraParamDefaultMinValue = -255; inline constexpr int32_t kCameraParamDefaultMaxValue = 255; inline constexpr int32_t kCameraParamDefaultStepValue = 3; inline constexpr size_t kMinimumNumBuffers = 2; inline constexpr size_t kMaximumNumBuffers = 10; NativeHandle copyNativeHandle(const NativeHandle& handle, bool doDup) { NativeHandle dup; dup.fds = std::vector(handle.fds.size()); if (!doDup) { for (auto i = 0; i < handle.fds.size(); ++i) { dup.fds.at(i).set(handle.fds[i].get()); } } else { for (auto i = 0; i < handle.fds.size(); ++i) { dup.fds[i] = std::move(handle.fds[i].dup()); } } dup.ints = handle.ints; return std::move(dup); } HardwareBuffer copyHardwareBuffer(const HardwareBuffer& buffer, bool doDup) { HardwareBuffer copied = { .description = buffer.description, .handle = copyNativeHandle(buffer.handle, doDup), }; return std::move(copied); } BufferDesc copyBufferDesc(const BufferDesc& src, bool doDup) { BufferDesc copied = { .buffer = copyHardwareBuffer(src.buffer, doDup), .pixelSizeBytes = src.pixelSizeBytes, .bufferId = src.bufferId, .deviceId = src.deviceId, .timestamp = src.timestamp, .metadata = src.metadata, }; return std::move(copied); } } // namespace namespace aidl::android::automotive::evs::implementation { MockEvsHal::~MockEvsHal() { std::unordered_map threads; { std::lock_guard lock(mLock); for (auto& [id, t] : mCameraFrameThread) { auto it = mStreamState.find(id); if (it != mStreamState.end() && it->second == StreamState::kRunning) { it->second = StreamState::kStopping; } threads.insert_or_assign(id, std::move(t)); } mCameraFrameThread.clear(); } // Join all frame-forwarding threads. for (auto& [_, t] : threads) { if (!t.joinable()) { continue; } t.join(); } { std::lock_guard lock(mLock); deinitializeBufferPoolLocked(); mStreamState.clear(); mCameraClient.clear(); } } std::shared_ptr MockEvsHal::getEnumerator() { if (!mMockEvsEnumerator) { LOG(ERROR) << "MockEvsHal has not initialized yet."; return nullptr; } return IEvsEnumerator::fromBinder(mMockEvsEnumerator->asBinder()); } void MockEvsHal::initialize() { initializeBufferPool(kMaximumNumBuffers); configureCameras(mNumCameras); configureDisplays(mNumDisplays); configureEnumerator(); } bool MockEvsHal::buildCameraMetadata(int32_t width, int32_t height, int32_t format, std::vector* out) { ::android::CameraMetadata metadata; const std::vector availableStreamConfigurations = {format, width, height, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}; metadata.update(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, availableStreamConfigurations.data(), availableStreamConfigurations.size()); camera_metadata_t* p = metadata.release(); if (validate_camera_metadata_structure(p, /* expected_size= */ nullptr) != ::android::OK) { LOG(ERROR) << "Failed to build a camera metadata."; return false; } size_t n = get_camera_metadata_size(p); out->resize(n); memcpy(out->data(), p, n); return true; } void MockEvsHal::forwardFrames(size_t numberOfFramesToForward, const std::string& deviceId) { std::unique_lock l(mLock); ::android::base::ScopedLockAssertion lock_assertion(mLock); auto it = mStreamState.find(deviceId); if (it != mStreamState.end() && it->second != StreamState::kStopped) { LOG(WARNING) << "A mock video stream is already active."; return; } mStreamState.insert_or_assign(deviceId, StreamState::kRunning); for (size_t count = 0; mStreamState[deviceId] == StreamState::kRunning && count < numberOfFramesToForward; ++count) { if (mBufferPool.empty()) { if (!mBufferAvailableSignal.wait_for(l, /* rel_time= */ 10s, [this]() REQUIRES(mLock) { // Waiting for a buffer to use. return !mBufferPool.empty(); })) { LOG(ERROR) << "Buffer timeout; " << count << "/" << numberOfFramesToForward << " are sent."; break; } } auto it = mCameraClient.find(deviceId); if (it == mCameraClient.end() || it->second == nullptr) { LOG(ERROR) << "Failed to forward a frame as no active recipient exists; " << count << "/" << numberOfFramesToForward << " are sent."; break; } // Duplicate a buffer. BufferDesc bufferToUse = std::move(mBufferPool.back()); mBufferPool.pop_back(); BufferDesc bufferToForward = copyBufferDesc(bufferToUse, /* doDup= */ true); bufferToForward.timestamp = static_cast(::android::elapsedRealtimeNano() * 1e+3); bufferToForward.deviceId = deviceId; // Mark a buffer in-use. mBuffersInUse.push_back(std::move(bufferToUse)); // Store a recipient before releasing a lock. auto client = it->second; l.unlock(); // Forward a duplicated buffer. This must be done without a lock // because a shared data will be modified in doneWithFrame(). std::vector packet; packet.push_back(std::move(bufferToForward)); client->deliverFrame(packet); LOG(DEBUG) << deviceId << ": " << (count + 1) << "/" << numberOfFramesToForward << " frames are sent"; std::this_thread::sleep_for(33ms); // 30 frames per seconds l.lock(); } if (mStreamState.find(deviceId) != mStreamState.end()) { mStreamState[deviceId] = StreamState::kStopped; } } size_t MockEvsHal::initializeBufferPool(size_t requested) { std::lock_guard lock(mLock); for (auto count = 0; count < requested; ++count) { AHardwareBuffer_Desc desc = { .width = 64, .height = 32, .layers = 1, .format = HAL_PIXEL_FORMAT_RGBA_8888, .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, }; AHardwareBuffer* ahwb; if (AHardwareBuffer_allocate(&desc, &ahwb) != ::android::NO_ERROR) { LOG(ERROR) << "Failed to allocate AHardwareBuffer"; return count; } buffer_handle_t memHandle = AHardwareBuffer_getNativeHandle(ahwb); BufferDesc aBuffer = { .buffer = { .description = { .width = 64, .height = 32, .layers = 1, .format = PixelFormat::RGBA_8888, .usage = BufferUsage::CPU_READ_OFTEN, .stride = 64, }, .handle = ::android::dupToAidl(memHandle), }, .pixelSizeBytes = 1, .bufferId = count, .deviceId = "Mock EvsCamera", }; mBufferRecord.insert_or_assign(count, ahwb); mBufferPool.push_back(std::move(aBuffer)); } return mBufferPool.size(); } void MockEvsHal::deinitializeBufferPoolLocked() { for (auto&& descriptor : mBuffersInUse) { auto it = mBufferRecord.find(descriptor.bufferId); if (it == mBufferRecord.end()) { LOG(WARNING) << "Ignoring unknown buffer id, " << descriptor.bufferId; } else { LOG(WARNING) << "Releasing buffer in use, id = " << descriptor.bufferId; AHardwareBuffer_release(it->second); mBufferRecord.erase(it); } } for (auto&& descriptor : mBufferPool) { auto it = mBufferRecord.find(descriptor.bufferId); if (it == mBufferRecord.end()) { LOG(WARNING) << "Ignoring unknown buffer id, " << descriptor.bufferId; } else { AHardwareBuffer_release(it->second); mBufferRecord.erase(it); } } mBuffersInUse.clear(); mBufferPool.clear(); } void MockEvsHal::configureCameras(size_t n) { // Initializes a list of the camera parameters each mock camera // supports with their default values. mCameraParams = {{CameraParam::BRIGHTNESS, 80}, {CameraParam::CONTRAST, 60}, {CameraParam::AUTOGAIN, 3}, {CameraParam::AUTO_EXPOSURE, 1}}; for (auto i = 0; i < n; ++i) { (void)addMockCameraDevice(kMockCameraDeviceNamePrefix + std::to_string(i)); } } bool MockEvsHal::addMockCameraDevice(const std::string& deviceId) { std::shared_ptr mockCamera = ndk::SharedRefBase::make(deviceId); // For the testing purpose, this method will return // EvsResult::INVALID_ARG if the client returns any buffer with // unknown identifier. ON_CALL(*mockCamera, doneWithFrame) .WillByDefault([this](const std::vector& buffers) { size_t returned = 0; std::lock_guard lock(mLock); for (auto& b : buffers) { auto it = std::find_if(mBuffersInUse.begin(), mBuffersInUse.end(), [id = b.bufferId](const BufferDesc& desc) { return id == desc.bufferId; }); if (it == mBuffersInUse.end()) { continue; } ++returned; mBufferPool.push_back(std::move(*it)); mBuffersInUse.erase(it); } if (returned > 0) { mBufferAvailableSignal.notify_all(); return ndk::ScopedAStatus::ok(); } else { return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } }); // EVS HAL accepts only a single client; therefore, this method // returns a success always. ON_CALL(*mockCamera, forcePrimaryClient).WillByDefault([](const std::shared_ptr&) { return ndk::ScopedAStatus::ok(); }); // We return a mock camera descriptor with the metadata but empty vendor // flag. ON_CALL(*mockCamera, getCameraInfo).WillByDefault([deviceId, this](CameraDesc* desc) { CameraDesc mockDesc = { .id = deviceId, .vendorFlags = 0x0, }; if (!buildCameraMetadata(/* width= */ 640, /* height= */ 480, /* format= */ HAL_PIXEL_FORMAT_RGBA_8888, &mockDesc.metadata)) { return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::UNDERLYING_SERVICE_ERROR)); } *desc = std::move(mockDesc); return ndk::ScopedAStatus::ok(); }); // This method will return a value associated with a given // identifier if exists. ON_CALL(*mockCamera, getExtendedInfo) .WillByDefault([this](int32_t id, std::vector* v) { auto it = mCameraExtendedInfo.find(id); if (it == mCameraExtendedInfo.end()) { // A requested information does not exist. return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } *v = it->second; return ndk::ScopedAStatus::ok(); }); // This method will return a value of a requested camera parameter // if it is supported by a mock EVS camera. ON_CALL(*mockCamera, getIntParameter) .WillByDefault([this](CameraParam id, std::vector* v) { auto it = mCameraParams.find(id); if (it == mCameraParams.end()) { LOG(ERROR) << "Ignore a request to read an unsupported parameter, " << (int)id; return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } // EVS HAL always returns a single integer value. v->push_back(it->second); return ndk::ScopedAStatus::ok(); }); // This method returns the same range values if a requested camera // parameter is supported by a mock EVS camera. ON_CALL(*mockCamera, getIntParameterRange) .WillByDefault([this](CameraParam id, ParameterRange* range) { auto it = mCameraParams.find(id); if (it == mCameraParams.end()) { return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } // For the testing purpose, this mock EVS HAL always returns the // same values. range->min = kCameraParamDefaultMinValue; range->max = kCameraParamDefaultMaxValue; range->step = kCameraParamDefaultStepValue; return ndk::ScopedAStatus::ok(); }); // This method returns a list of camera parameters supported by a // mock EVS camera. ON_CALL(*mockCamera, getParameterList).WillByDefault([this](std::vector* list) { for (auto& [k, _] : mCameraParams) { list->push_back(k); } return ndk::ScopedAStatus::ok(); }); // This method behaves exactly the same as getCameraInfo() because // the EVS HAL does not support a concept of the group (or logical) // camera. ON_CALL(*mockCamera, getPhysicalCameraInfo) .WillByDefault([deviceId](const std::string&, CameraDesc* desc) { CameraDesc mockDesc = { .id = deviceId, .vendorFlags = 0x0, .metadata = {}, }; *desc = std::move(mockDesc); return ndk::ScopedAStatus::ok(); }); // This method adds given buffer descriptors to the internal buffer // pool if their identifiers do not conflict to existing ones. ON_CALL(*mockCamera, importExternalBuffers) .WillByDefault([this](const std::vector& buffers, int32_t* num) { std::lock_guard l(mLock); size_t count = 0; for (auto i = 0; i < buffers.size(); ++i) { auto it = std::find_if(mBufferPool.begin(), mBufferPool.end(), [&](const BufferDesc& b) { return b.bufferId == buffers[i].bufferId; }); if (it != mBufferPool.end()) { // Ignores external buffers with a conflicting // identifier. continue; } // TODO(b/235110887): Explicitly copies external buffers // stores them in mBufferPool. // // Temporarily, we count the number of buffers that // identifiers do not conflict with existing buffers. ++count; } *num = count; return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockCamera, pauseVideoStream).WillByDefault([]() { return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockCamera, resumeVideoStream).WillByDefault([]() { return ndk::ScopedAStatus::ok(); }); // This method stores a given vector with id. ON_CALL(*mockCamera, setExtendedInfo) .WillByDefault([this](int32_t id, const std::vector& v) { mCameraExtendedInfo.insert_or_assign(id, v); return ndk::ScopedAStatus::ok(); }); // This method updates a parameter value if exists. ON_CALL(*mockCamera, setIntParameter) .WillByDefault([this](CameraParam id, int32_t in, std::vector* out) { auto it = mCameraParams.find(id); if (it == mCameraParams.end()) { LOG(ERROR) << "Ignore a request to program an unsupported parameter, " << (int)id; return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } in = in > kCameraParamDefaultMaxValue ? kCameraParamDefaultMaxValue : in < kCameraParamDefaultMinValue ? kCameraParamDefaultMinValue : in; mCameraParams.insert_or_assign(id, in); out->push_back(in); return ndk::ScopedAStatus::ok(); }); // We always return a success because EVS HAL does not allow // multiple camera clients exist. ON_CALL(*mockCamera, setPrimaryClient).WillByDefault([]() { return ndk::ScopedAStatus::ok(); }); // Because EVS HAL does allow multiple camera clients exist, we simply // set the size of the buffer pool. ON_CALL(*mockCamera, setMaxFramesInFlight) .WillByDefault([this, id = mockCamera->getId()](int32_t bufferCount) { std::lock_guard l(mLock); if (bufferCount < kMinimumNumBuffers) { LOG(WARNING) << "Requested buffer pool size is too small to run a camera; " "adjusting the pool size to " << kMinimumNumBuffers; bufferCount = kMinimumNumBuffers; } int64_t delta = bufferCount; auto it = mCameraBufferPoolSize.find(id); if (it != mCameraBufferPoolSize.end()) { delta -= it->second; } if (!delta) { // No further action required. return ndk::ScopedAStatus::ok(); } size_t totalSize = mBufferPoolSize + delta; if (totalSize > kMaximumNumBuffers) { LOG(ERROR) << "Requested size, " << totalSize << ", exceeds the limitation."; return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } mBufferPoolSize = totalSize; mCameraBufferPoolSize.insert_or_assign(id, bufferCount); return ndk::ScopedAStatus::ok(); }); // We manage the camera ownership on recency-basis; therefore we simply // replace the client in this method. ON_CALL(*mockCamera, startVideoStream) .WillByDefault( [this, id = mockCamera->getId()](const std::shared_ptr& cb) { // TODO(b/235110887): Notifies a camera loss to the current // client. size_t n = 0; { std::lock_guard l(mLock); mCameraClient.insert_or_assign(id, cb); n = mNumberOfFramesToSend; } std::lock_guard l(mLock); std::packaged_task task( &MockEvsHal::forwardFrames); std::thread t(std::move(task), this, /* numberOfFramesForward= */ n, id); mCameraFrameThread.insert_or_assign(id, std::move(t)); return ndk::ScopedAStatus::ok(); }); // We simply drop a current client. ON_CALL(*mockCamera, stopVideoStream).WillByDefault([this, id = mockCamera->getId()]() { std::shared_ptr cb; std::thread threadToJoin; { std::lock_guard l(mLock); auto state = mStreamState.find(id); if (state == mStreamState.end() || state->second != StreamState::kRunning) { return ndk::ScopedAStatus::ok(); } auto callback = mCameraClient.find(id); if (callback == mCameraClient.end()) { return ndk::ScopedAStatus::ok(); } cb = callback->second; callback->second = nullptr; state->second = StreamState::kStopping; auto it = mCameraFrameThread.find(id); if (it == mCameraFrameThread.end() || !it->second.joinable()) { return ndk::ScopedAStatus::ok(); } threadToJoin = std::move(it->second); mCameraFrameThread.erase(it); } if (cb) { EvsEventDesc e = { .aType = EvsEventType::STREAM_STOPPED, .deviceId = id, }; cb->notify(e); } // Join a frame-forward thread threadToJoin.join(); return ndk::ScopedAStatus::ok(); }); // We don't take any action because EVS HAL allows only a single camera // client exists at a time. ON_CALL(*mockCamera, unsetPrimaryClient).WillByDefault([]() { return ndk::ScopedAStatus::ok(); }); std::lock_guard l(mLock); mMockEvsCameras.push_back(std::move(mockCamera)); mMockDeviceStatus.insert_or_assign(deviceId, DeviceStatusType::CAMERA_AVAILABLE); std::vector msg(1); msg[0] = { .id = deviceId, .status = DeviceStatusType::CAMERA_AVAILABLE, }; for (auto callback : mDeviceStatusCallbacks) { callback->deviceStatusChanged(msg); } return true; } void MockEvsHal::removeMockCameraDevice(const std::string& deviceId) { std::lock_guard l(mLock); auto it = mMockDeviceStatus.find(deviceId); if (it == mMockDeviceStatus.end()) { // Nothing to do. return; } mMockDeviceStatus[deviceId] = DeviceStatusType::CAMERA_NOT_AVAILABLE; std::vector msg(1); msg[0] = { .id = deviceId, .status = DeviceStatusType::CAMERA_NOT_AVAILABLE, }; for (auto callback : mDeviceStatusCallbacks) { callback->deviceStatusChanged(msg); } } void MockEvsHal::configureDisplays(size_t n) { // Build mock IEvsDisplcy instances std::vector> displays(n); for (auto i = 0; i < n; ++i) { (void)addMockDisplayDevice(i); } } bool MockEvsHal::addMockDisplayDevice(int id) { std::shared_ptr mockDisplay = ndk::SharedRefBase::make(); ON_CALL(*mockDisplay, getDisplayInfo).WillByDefault([id](DisplayDesc* out) { DisplayDesc desc = { .width = 1920, .id = "MockDisplay" + std::to_string(id), .height = 1080, .orientation = Rotation::ROTATION_0, .vendorFlags = id, // For the testing purpose, we put a display id in the vendor // flag field. }; *out = std::move(desc); return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockDisplay, getDisplayState).WillByDefault([this](DisplayState* out) { *out = mCurrentDisplayState; return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockDisplay, getTargetBuffer).WillByDefault([](BufferDesc* out) { (void)out; return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockDisplay, returnTargetBufferForDisplay).WillByDefault([](const BufferDesc& in) { (void)in; return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockDisplay, setDisplayState).WillByDefault([this](DisplayState in) { mCurrentDisplayState = in; return ndk::ScopedAStatus::ok(); }); std::lock_guard l(mLock); mMockEvsDisplays.push_back(std::move(mockDisplay)); mMockDeviceStatus.insert_or_assign(std::to_string(id), DeviceStatusType::DISPLAY_AVAILABLE); std::vector msg(1); msg[0] = { .id = std::to_string(id), .status = DeviceStatusType::DISPLAY_AVAILABLE, }; for (auto callback : mDeviceStatusCallbacks) { callback->deviceStatusChanged(msg); } return true; } void MockEvsHal::removeMockDisplayDevice(int id) { std::lock_guard l(mLock); auto key = std::to_string(id); auto it = mMockDeviceStatus.find(key); if (it == mMockDeviceStatus.end()) { // Nothing to do. return; } mMockDeviceStatus[key] = DeviceStatusType::DISPLAY_NOT_AVAILABLE; std::vector msg(1); msg[0] = { .id = key, .status = DeviceStatusType::DISPLAY_NOT_AVAILABLE, }; for (auto callback : mDeviceStatusCallbacks) { callback->deviceStatusChanged(msg); } } size_t MockEvsHal::setNumberOfFramesToSend(size_t n) { std::lock_guard l(mLock); return mNumberOfFramesToSend = n; } void MockEvsHal::configureEnumerator() { std::shared_ptr mockEnumerator = ndk::SharedRefBase::make(); ON_CALL(*mockEnumerator, closeCamera) .WillByDefault([this](const std::shared_ptr& c) { CameraDesc desc; if (!c->getCameraInfo(&desc).isOk()) { // Safely ignore a request to close a camera if we fail to read a // camera descriptor. return ndk::ScopedAStatus::ok(); } std::lock_guard l(mLock); auto it = mCameraBufferPoolSize.find(desc.id); if (it == mCameraBufferPoolSize.end()) { // Safely ignore a request if we fail to find a corresponding mock // camera. return ndk::ScopedAStatus::ok(); } mBufferPoolSize -= it->second; if (mBufferPoolSize < 0) { LOG(WARNING) << "mBuffeRPoolSize should not have a negative value, " << mBufferPoolSize; mBufferPoolSize = 0; } mCameraBufferPoolSize.insert_or_assign(desc.id, 0); return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, closeDisplay) .WillByDefault([this]([[maybe_unused]] const std::shared_ptr& displayObj) { auto pActiveDisplay = mActiveDisplay.lock(); if (!pActiveDisplay) { return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::OWNERSHIP_LOST)); } // Nothing else to do. return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, closeUltrasonicsArray) .WillByDefault([](const std::shared_ptr&) { // Mock EVS HAL does not support IEvsUltrasonicsArray. return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, getCameraList).WillByDefault([this](std::vector* out) { out->resize(mMockEvsCameras.size()); for (auto i = 0; i < mMockEvsCameras.size(); ++i) { CameraDesc desc; if (!mMockEvsCameras[i]->getCameraInfo(&desc).isOk()) { LOG(ERROR) << "Failed to retrieve a camera desc"; continue; } // Inserts a camera record if it does not exist. if (mCameraList.find(desc.id) == mCameraList.end()) { mCameraList.insert_or_assign(desc.id, desc); } (*out)[i] = std::move(desc); } return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, getDisplayIdList).WillByDefault([this](std::vector* out) { out->resize(mMockEvsDisplays.size()); for (auto i = 0; i < mMockEvsDisplays.size(); ++i) { DisplayDesc desc; if (!mMockEvsDisplays[i]->getDisplayInfo(&desc).isOk()) { continue; } // MockEvsDisplay contains a display ID in its vendor flags. (*out)[i] = static_cast(desc.vendorFlags); } return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, getDisplayState).WillByDefault([this](DisplayState* out) { *out = mCurrentDisplayState; return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, getStreamList) .WillByDefault([](const CameraDesc& desc, std::vector* out) { if (desc.metadata.empty()) { return ndk::ScopedAStatus::ok(); } camera_metadata_t* p = const_cast( reinterpret_cast(desc.metadata.data())); camera_metadata_entry_t entry; if (find_camera_metadata_entry(p, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry)) { return ndk::ScopedAStatus::ok(); } const auto n = calculate_camera_metadata_entry_data_size( get_camera_metadata_tag_type( ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS), entry.count); out->resize(n); for (auto i = 0; i < n; ++i) { // ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS is a set of 5 // int32_t words. Stream s = { .id = i, .streamType = entry.data.i32[3] == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS ? StreamType::OUTPUT : StreamType::INPUT, .width = entry.data.i32[1], .height = entry.data.i32[2], .format = static_cast(entry.data.i32[0]), .usage = BufferUsage::CAMERA_INPUT, .rotation = Rotation::ROTATION_0, }; (*out)[i] = s; } return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, getUltrasonicsArrayList) .WillByDefault([](std::vector*) { // Mock EVS HAL does not support IEvsUltrasonicsArray yet. return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, isHardware).WillByDefault([](bool* flag) { *flag = false; return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, openCamera) .WillByDefault([this](const std::string& id, [[maybe_unused]] const Stream& config, std::shared_ptr* out) { auto it = std::find_if(mMockEvsCameras.begin(), mMockEvsCameras.end(), [id](const std::shared_ptr& c) { CameraDesc desc; return c->getCameraInfo(&desc).isOk() && desc.id == id; }); if (it == mMockEvsCameras.end()) { return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } auto instance = mCameraList.find(id); // Guaranteed to exist always. instance->second.activeInstance = *it; *out = IEvsCamera::fromBinder((*it)->asBinder()); return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, openDisplay) .WillByDefault([this](int32_t id, std::shared_ptr* out) { if (id == kExclusiveDisplayId) { if (mDisplayOwnedExclusively && !mActiveDisplay.expired()) { return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::RESOURCE_BUSY)); } DisplayDesc desc; if (!mMockEvsDisplays[0]->getDisplayInfo(&desc).isOk()) { return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::UNDERLYING_SERVICE_ERROR)); } id = desc.vendorFlags; // the first display in the list is // the main display. mDisplayOwnedExclusively = true; } auto it = std::find_if(mMockEvsDisplays.begin(), mMockEvsDisplays.end(), [id](const std::shared_ptr& d) { DisplayDesc desc; return d->getDisplayInfo(&desc).isOk() && desc.vendorFlags == id; }); if (it == mMockEvsDisplays.end()) { return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } mActiveDisplay = *it; mCurrentDisplayState = DisplayState::NOT_VISIBLE; *out = IEvsDisplay::fromBinder((*it)->asBinder()); return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, openUltrasonicsArray) .WillByDefault([](const std::string&, std::shared_ptr*) { // Mock EVS HAL does not support IEvsUltrasonicsArray yet. return ndk::ScopedAStatus::ok(); }); ON_CALL(*mockEnumerator, registerStatusCallback) .WillByDefault([this](const std::shared_ptr& cb) { if (!cb) { return ndk::ScopedAStatus::ok(); } std::lock_guard l(mLock); mDeviceStatusCallbacks.insert(cb); return ndk::ScopedAStatus::ok(); }); mMockEvsEnumerator = std::move(mockEnumerator); } } // namespace aidl::android::automotive::evs::implementation