/*
 * Copyright (C) 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 <gtest/gtest.h>

#include <aidl/Vintf.h>
#include <aidl/android/hardware/camera/common/VendorTagSection.h>
#include <aidl/android/hardware/camera/device/ICameraDevice.h>
#include <aidlcommonsupport/NativeHandle.h>
#include <camera_aidl_test.h>
#include <cutils/properties.h>
#include <device_cb.h>
#include <empty_device_cb.h>
#include <grallocusage/GrallocUsageConversion.h>
#include <gtest/gtest.h>
#include <hardware/gralloc.h>
#include <hardware/gralloc1.h>
#include <hidl/GtestPrinter.h>
#include <hidl/HidlSupport.h>
#include <torch_provider_cb.h>
#include <com_android_internal_camera_flags.h>
#include <list>
#include <nativebase/nativebase.h>

using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
using ::aidl::android::hardware::camera::common::CameraResourceCost;
using ::aidl::android::hardware::camera::common::TorchModeStatus;
using ::aidl::android::hardware::camera::common::VendorTagSection;
using ::aidl::android::hardware::camera::device::ICameraDevice;
using ::aidl::android::hardware::camera::metadata::RequestAvailableColorSpaceProfilesMap;
using ::aidl::android::hardware::camera::metadata::RequestAvailableDynamicRangeProfilesMap;
using ::aidl::android::hardware::camera::metadata::SensorPixelMode;
using ::aidl::android::hardware::camera::provider::CameraIdAndStreamCombination;
using ::aidl::android::hardware::camera::provider::BnCameraProviderCallback;

using ::ndk::ScopedAStatus;

namespace {
const int32_t kBurstFrameCount = 10;
const uint32_t kMaxStillWidth = 2048;
const uint32_t kMaxStillHeight = 1536;

const int64_t kEmptyFlushTimeoutMSec = 200;
namespace flags = com::android::internal::camera::flags;

const static std::vector<int64_t> kMandatoryUseCases = {
        ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT,
        ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW,
        ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE,
        ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD,
        ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL,
        ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL};
}  // namespace

TEST_P(CameraAidlTest, getCameraIdList) {
    std::vector<std::string> idList;
    ScopedAStatus ret = mProvider->getCameraIdList(&idList);
    ASSERT_TRUE(ret.isOk());

    for (size_t i = 0; i < idList.size(); i++) {
        ALOGI("Camera Id[%zu] is %s", i, idList[i].c_str());
    }
}

// Test if ICameraProvider::getVendorTags returns Status::OK
TEST_P(CameraAidlTest, getVendorTags) {
    std::vector<VendorTagSection> vendorTags;
    ScopedAStatus ret = mProvider->getVendorTags(&vendorTags);

    ASSERT_TRUE(ret.isOk());
    for (size_t i = 0; i < vendorTags.size(); i++) {
        ALOGI("Vendor tag section %zu name %s", i, vendorTags[i].sectionName.c_str());
        for (auto& tag : vendorTags[i].tags) {
            ALOGI("Vendor tag id %u name %s type %d", tag.tagId, tag.tagName.c_str(),
                  (int)tag.tagType);
        }
    }
}

// Test if ICameraProvider::setCallback returns Status::OK
TEST_P(CameraAidlTest, setCallback) {
    struct ProviderCb : public BnCameraProviderCallback {
        ScopedAStatus cameraDeviceStatusChange(const std::string& cameraDeviceName,
                                               CameraDeviceStatus newStatus) override {
            ALOGI("camera device status callback name %s, status %d", cameraDeviceName.c_str(),
                  (int)newStatus);
            return ScopedAStatus::ok();
        }
        ScopedAStatus torchModeStatusChange(const std::string& cameraDeviceName,
                                            TorchModeStatus newStatus) override {
            ALOGI("Torch mode status callback name %s, status %d", cameraDeviceName.c_str(),
                  (int)newStatus);
            return ScopedAStatus::ok();
        }
        ScopedAStatus physicalCameraDeviceStatusChange(const std::string& cameraDeviceName,
                                                       const std::string& physicalCameraDeviceName,
                                                       CameraDeviceStatus newStatus) override {
            ALOGI("physical camera device status callback name %s, physical camera name %s,"
                  " status %d",
                  cameraDeviceName.c_str(), physicalCameraDeviceName.c_str(), (int)newStatus);
            return ScopedAStatus::ok();
        }
    };

    std::shared_ptr<ProviderCb> cb = ndk::SharedRefBase::make<ProviderCb>();
    ScopedAStatus ret = mProvider->setCallback(cb);
    ASSERT_TRUE(ret.isOk());
    ret = mProvider->setCallback(nullptr);
    ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), ret.getServiceSpecificError());
}

// Test if ICameraProvider::getCameraDeviceInterface returns Status::OK and non-null device
TEST_P(CameraAidlTest, getCameraDeviceInterface) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    for (const auto& name : cameraDeviceNames) {
        std::shared_ptr<ICameraDevice> cameraDevice;
        ScopedAStatus ret = mProvider->getCameraDeviceInterface(name, &cameraDevice);
        ALOGI("getCameraDeviceInterface returns: %d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(cameraDevice, nullptr);
    }
}

// Verify that the device resource cost can be retrieved and the values are
// correct.
TEST_P(CameraAidlTest, getResourceCost) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    for (const auto& deviceName : cameraDeviceNames) {
        std::shared_ptr<ICameraDevice> cameraDevice;
        ScopedAStatus ret = mProvider->getCameraDeviceInterface(deviceName, &cameraDevice);
        ALOGI("getCameraDeviceInterface returns: %d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(cameraDevice, nullptr);

        CameraResourceCost resourceCost;
        ret = cameraDevice->getResourceCost(&resourceCost);
        ALOGI("getResourceCost returns: %d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());

        ALOGI("    Resource cost is %d", resourceCost.resourceCost);
        ASSERT_LE(resourceCost.resourceCost, 100u);

        for (const auto& name : resourceCost.conflictingDevices) {
            ALOGI("    Conflicting device: %s", name.c_str());
        }
    }
}

// Validate the integrity of manual flash strength control metadata
TEST_P(CameraAidlTest, validateManualFlashStrengthControlKeys) {
    if (flags::camera_manual_flash_strength_control()) {
        std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
        for (const auto& name : cameraDeviceNames) {
            ALOGI("validateManualFlashStrengthControlKeys: Testing camera device %s", name.c_str());
            CameraMetadata meta;
            std::shared_ptr<ICameraDevice> cameraDevice;
            openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                    &cameraDevice /*out*/);
            ndk::ScopedAStatus ret = cameraDevice->getCameraCharacteristics(&meta);
            ASSERT_TRUE(ret.isOk());
            const camera_metadata_t* staticMeta =
                    reinterpret_cast<const camera_metadata_t*>(meta.metadata.data());
            verifyManualFlashStrengthControlCharacteristics(staticMeta);
            ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
        }
    } else {
        ALOGI("validateManualFlashStrengthControlKeys: Test skipped.\n");
        GTEST_SKIP();
    }
}

TEST_P(CameraAidlTest, systemCameraTest) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::map<std::string, std::vector<SystemCameraKind>> hiddenPhysicalIdToLogicalMap;
    for (const auto& name : cameraDeviceNames) {
        std::shared_ptr<ICameraDevice> device;
        ALOGI("systemCameraTest: Testing camera device %s", name.c_str());
        ndk::ScopedAStatus ret = mProvider->getCameraDeviceInterface(name, &device);
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(device, nullptr);

        CameraMetadata cameraCharacteristics;
        ret = device->getCameraCharacteristics(&cameraCharacteristics);
        ASSERT_TRUE(ret.isOk());

        const camera_metadata_t* staticMeta =
                reinterpret_cast<const camera_metadata_t*>(cameraCharacteristics.metadata.data());
        Status rc = isLogicalMultiCamera(staticMeta);
        if (rc == Status::OPERATION_NOT_SUPPORTED) {
            return;
        }

        ASSERT_EQ(rc, Status::OK);
        std::unordered_set<std::string> physicalIds;
        ASSERT_EQ(getPhysicalCameraIds(staticMeta, &physicalIds), Status::OK);
        SystemCameraKind systemCameraKind = SystemCameraKind::PUBLIC;
        Status retStatus = getSystemCameraKind(staticMeta, &systemCameraKind);
        ASSERT_EQ(retStatus, Status::OK);

        for (auto physicalId : physicalIds) {
            bool isPublicId = false;
            for (auto& deviceName : cameraDeviceNames) {
                std::string publicVersion, publicId;
                ASSERT_TRUE(matchDeviceName(deviceName, mProviderType, &publicVersion, &publicId));
                if (physicalId == publicId) {
                    isPublicId = true;
                    break;
                }
            }

            // For hidden physical cameras, collect their associated logical cameras
            // and store the system camera kind.
            if (!isPublicId) {
                auto it = hiddenPhysicalIdToLogicalMap.find(physicalId);
                if (it == hiddenPhysicalIdToLogicalMap.end()) {
                    hiddenPhysicalIdToLogicalMap.insert(std::make_pair(
                            physicalId, std::vector<SystemCameraKind>({systemCameraKind})));
                } else {
                    it->second.push_back(systemCameraKind);
                }
            }
        }
    }

    // Check that the system camera kind of the logical cameras associated with
    // each hidden physical camera is the same.
    for (const auto& it : hiddenPhysicalIdToLogicalMap) {
        SystemCameraKind neededSystemCameraKind = it.second.front();
        for (auto foundSystemCamera : it.second) {
            ASSERT_EQ(neededSystemCameraKind, foundSystemCamera);
        }
    }
}

// Verify that the static camera characteristics can be retrieved
// successfully.
TEST_P(CameraAidlTest, getCameraCharacteristics) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    for (const auto& name : cameraDeviceNames) {
        std::shared_ptr<ICameraDevice> device;
        ALOGI("getCameraCharacteristics: Testing camera device %s", name.c_str());
        ndk::ScopedAStatus ret = mProvider->getCameraDeviceInterface(name, &device);
        ALOGI("getCameraDeviceInterface returns: %d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(device, nullptr);

        CameraMetadata chars;
        ret = device->getCameraCharacteristics(&chars);
        ASSERT_TRUE(ret.isOk());
        verifyCameraCharacteristics(chars);
        verifyMonochromeCharacteristics(chars);
        verifyRecommendedConfigs(chars);
        verifyHighSpeedRecordingCharacteristics(name, chars);
        verifyLogicalOrUltraHighResCameraMetadata(name, device, chars, cameraDeviceNames);

        ASSERT_TRUE(ret.isOk());

        // getPhysicalCameraCharacteristics will fail for publicly
        // advertised camera IDs.
        std::string version, cameraId;
        ASSERT_TRUE(matchDeviceName(name, mProviderType, &version, &cameraId));
        CameraMetadata devChars;
        ret = device->getPhysicalCameraCharacteristics(cameraId, &devChars);
        ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), ret.getServiceSpecificError());
        ASSERT_EQ(0, devChars.metadata.size());
    }
}

TEST_P(CameraAidlTest, getSessionCharacteristics) {
    if (flags::feature_combination_query()) {
        std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

        for (const auto& name : cameraDeviceNames) {
            std::shared_ptr<ICameraDevice> device;
            ALOGI("getSessionCharacteristics: Testing camera device %s", name.c_str());
            ndk::ScopedAStatus ret = mProvider->getCameraDeviceInterface(name, &device);
            ALOGI("getCameraDeviceInterface returns: %d:%d", ret.getExceptionCode(),
                  ret.getServiceSpecificError());
            ASSERT_TRUE(ret.isOk());
            ASSERT_NE(device, nullptr);

            int32_t interfaceVersion = -1;
            ret = device->getInterfaceVersion(&interfaceVersion);
            ASSERT_TRUE(ret.isOk());
            bool supportSessionCharacteristics =
                    (interfaceVersion >= CAMERA_DEVICE_API_MINOR_VERSION_3);
            if (!supportSessionCharacteristics) {
                continue;
            }

            CameraMetadata meta;
            openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                                   &device /*out*/);

            std::vector<AvailableStream> outputStreams;
            camera_metadata_t* staticMeta =
                    reinterpret_cast<camera_metadata_t*>(meta.metadata.data());
            outputStreams.clear();
            ASSERT_EQ(Status::OK, getAvailableOutputStreams(staticMeta, outputStreams));
            ASSERT_NE(0u, outputStreams.size());

            AvailableStream sampleStream = outputStreams[0];

            int32_t streamId = 0;
            Stream stream = {streamId,
                             StreamType::OUTPUT,
                             sampleStream.width,
                             sampleStream.height,
                             static_cast<PixelFormat>(sampleStream.format),
                             static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                     GRALLOC1_CONSUMER_USAGE_VIDEO_ENCODER),
                             Dataspace::UNKNOWN,
                             StreamRotation::ROTATION_0,
                             std::string(),
                             /*bufferSize*/ 0,
                             /*groupId*/ -1,
                             {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                             RequestAvailableDynamicRangeProfilesMap::
                                     ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

            std::vector<Stream> streams = {stream};
            StreamConfiguration config;
            createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config);

            CameraMetadata camera_chars;
            ret = device->getCameraCharacteristics(&camera_chars);
            ASSERT_TRUE(ret.isOk());

            CameraMetadata session_chars;
            ret = device->getSessionCharacteristics(config, &session_chars);
            ASSERT_TRUE(ret.isOk());
            verifySessionCharacteristics(session_chars, camera_chars);

            ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
        }
    } else {
        ALOGI("getSessionCharacteristics: Test skipped.\n");
        GTEST_SKIP();
    }
}

// Verify that the torch strength level can be set and retrieved successfully.
TEST_P(CameraAidlTest, turnOnTorchWithStrengthLevel) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    std::shared_ptr<TorchProviderCb> cb = ndk::SharedRefBase::make<TorchProviderCb>(this);
    ndk::ScopedAStatus ret = mProvider->setCallback(cb);
    ASSERT_TRUE(ret.isOk());

    for (const auto& name : cameraDeviceNames) {
        int32_t defaultLevel;
        std::shared_ptr<ICameraDevice> device;
        ALOGI("%s: Testing camera device %s", __FUNCTION__, name.c_str());

        ret = mProvider->getCameraDeviceInterface(name, &device);
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(device, nullptr);

        CameraMetadata chars;
        ret = device->getCameraCharacteristics(&chars);
        ASSERT_TRUE(ret.isOk());

        const camera_metadata_t* staticMeta =
                reinterpret_cast<const camera_metadata_t*>(chars.metadata.data());
        bool torchStrengthControlSupported = isTorchStrengthControlSupported(staticMeta);
        camera_metadata_ro_entry entry;
        int rc = find_camera_metadata_ro_entry(staticMeta,
                                               ANDROID_FLASH_INFO_STRENGTH_DEFAULT_LEVEL, &entry);
        if (torchStrengthControlSupported) {
            ASSERT_EQ(rc, 0);
            ASSERT_GT(entry.count, 0);
            defaultLevel = *entry.data.i32;
            ALOGI("Default level is:%d", defaultLevel);
        }

        mTorchStatus = TorchModeStatus::NOT_AVAILABLE;
        ret = device->turnOnTorchWithStrengthLevel(2);
        ALOGI("turnOnTorchWithStrengthLevel returns status: %d", ret.getServiceSpecificError());
        // OPERATION_NOT_SUPPORTED check
        if (!torchStrengthControlSupported) {
            ALOGI("Torch strength control not supported.");
            ASSERT_EQ(static_cast<int32_t>(Status::OPERATION_NOT_SUPPORTED),
                      ret.getServiceSpecificError());
        } else {
            {
                ASSERT_TRUE(ret.isOk());
                std::unique_lock<std::mutex> l(mTorchLock);
                while (TorchModeStatus::NOT_AVAILABLE == mTorchStatus) {
                    auto timeout = std::chrono::system_clock::now() +
                                   std::chrono::seconds(kTorchTimeoutSec);
                    ASSERT_NE(std::cv_status::timeout, mTorchCond.wait_until(l, timeout));
                }
                ASSERT_EQ(TorchModeStatus::AVAILABLE_ON, mTorchStatus);
                mTorchStatus = TorchModeStatus::NOT_AVAILABLE;
            }
            ALOGI("getTorchStrengthLevel: Testing");
            int32_t strengthLevel;
            ret = device->getTorchStrengthLevel(&strengthLevel);
            ASSERT_TRUE(ret.isOk());
            ALOGI("Torch strength level is : %d", strengthLevel);
            ASSERT_EQ(strengthLevel, 2);

            // Turn OFF the torch and verify torch strength level is reset to default level.
            ALOGI("Testing torch strength level reset after turning the torch OFF.");
            ret = device->setTorchMode(false);
            ASSERT_TRUE(ret.isOk());
            {
                std::unique_lock<std::mutex> l(mTorchLock);
                while (TorchModeStatus::NOT_AVAILABLE == mTorchStatus) {
                    auto timeout = std::chrono::system_clock::now() +
                                   std::chrono::seconds(kTorchTimeoutSec);
                    ASSERT_NE(std::cv_status::timeout, mTorchCond.wait_until(l, timeout));
                }
                ASSERT_EQ(TorchModeStatus::AVAILABLE_OFF, mTorchStatus);
            }

            ret = device->getTorchStrengthLevel(&strengthLevel);
            ASSERT_TRUE(ret.isOk());
            ALOGI("Torch strength level after turning OFF torch is : %d", strengthLevel);
            ASSERT_EQ(strengthLevel, defaultLevel);
        }
    }
}

// In case it is supported verify that torch can be enabled.
// Check for corresponding torch callbacks as well.
TEST_P(CameraAidlTest, setTorchMode) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    std::shared_ptr<TorchProviderCb> cb = ndk::SharedRefBase::make<TorchProviderCb>(this);
    ndk::ScopedAStatus ret = mProvider->setCallback(cb);
    ALOGI("setCallback returns status: %d", ret.getServiceSpecificError());
    ASSERT_TRUE(ret.isOk());
    ASSERT_NE(cb, nullptr);

    for (const auto& name : cameraDeviceNames) {
        std::shared_ptr<ICameraDevice> device;
        ALOGI("setTorchMode: Testing camera device %s", name.c_str());
        ret = mProvider->getCameraDeviceInterface(name, &device);
        ALOGI("getCameraDeviceInterface returns status:%d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(device, nullptr);

        CameraMetadata metadata;
        ret = device->getCameraCharacteristics(&metadata);
        ALOGI("getCameraCharacteristics returns status:%d", ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        camera_metadata_t* staticMeta =
                reinterpret_cast<camera_metadata_t*>(metadata.metadata.data());
        bool torchSupported = isTorchSupported(staticMeta);

        mTorchStatus = TorchModeStatus::NOT_AVAILABLE;
        ret = device->setTorchMode(true);
        ALOGI("setTorchMode returns status: %d", ret.getServiceSpecificError());
        if (!torchSupported) {
            ASSERT_EQ(static_cast<int32_t>(Status::OPERATION_NOT_SUPPORTED),
                      ret.getServiceSpecificError());
        } else {
            ASSERT_TRUE(ret.isOk());
            {
                std::unique_lock<std::mutex> l(mTorchLock);
                while (TorchModeStatus::NOT_AVAILABLE == mTorchStatus) {
                    auto timeout = std::chrono::system_clock::now() +
                                   std::chrono::seconds(kTorchTimeoutSec);
                    ASSERT_NE(std::cv_status::timeout, mTorchCond.wait_until(l, timeout));
                }
                ASSERT_EQ(TorchModeStatus::AVAILABLE_ON, mTorchStatus);
                mTorchStatus = TorchModeStatus::NOT_AVAILABLE;
            }

            ret = device->setTorchMode(false);
            ASSERT_TRUE(ret.isOk());
            {
                std::unique_lock<std::mutex> l(mTorchLock);
                while (TorchModeStatus::NOT_AVAILABLE == mTorchStatus) {
                    auto timeout = std::chrono::system_clock::now() +
                                   std::chrono::seconds(kTorchTimeoutSec);
                    ASSERT_NE(std::cv_status::timeout, mTorchCond.wait_until(l, timeout));
                }
                ASSERT_EQ(TorchModeStatus::AVAILABLE_OFF, mTorchStatus);
            }
        }
    }
}

// Check dump functionality.
TEST_P(CameraAidlTest, dump) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    for (const auto& name : cameraDeviceNames) {
        std::shared_ptr<ICameraDevice> device;
        ALOGI("dump: Testing camera device %s", name.c_str());

        ndk::ScopedAStatus ret = mProvider->getCameraDeviceInterface(name, &device);
        ALOGI("getCameraDeviceInterface returns status:%d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(device, nullptr);

        int raw_handle = open(kDumpOutput, O_RDWR);
        ASSERT_GE(raw_handle, 0);

        auto retStatus = device->dump(raw_handle, nullptr, 0);
        ASSERT_EQ(retStatus, ::android::OK);
        close(raw_handle);
    }
}

// Open, dump, then close
TEST_P(CameraAidlTest, openClose) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    for (const auto& name : cameraDeviceNames) {
        std::shared_ptr<ICameraDevice> device;
        ALOGI("openClose: Testing camera device %s", name.c_str());
        ndk::ScopedAStatus ret = mProvider->getCameraDeviceInterface(name, &device);
        ALOGI("getCameraDeviceInterface returns status:%d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(device, nullptr);

        std::shared_ptr<EmptyDeviceCb> cb = ndk::SharedRefBase::make<EmptyDeviceCb>();

        ret = device->open(cb, &mSession);
        ASSERT_TRUE(ret.isOk());
        ALOGI("device::open returns status:%d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_NE(mSession, nullptr);
        int raw_handle = open(kDumpOutput, O_RDWR);
        ASSERT_GE(raw_handle, 0);

        auto retStatus = device->dump(raw_handle, nullptr, 0);
        ASSERT_EQ(retStatus, ::android::OK);
        close(raw_handle);

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
        // TODO: test all session API calls return INTERNAL_ERROR after close
        // TODO: keep a wp copy here and verify session cannot be promoted out of this scope
    }
}

// Check whether all common default request settings can be successfully
// constructed.
TEST_P(CameraAidlTest, constructDefaultRequestSettings) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    for (const auto& name : cameraDeviceNames) {
        std::shared_ptr<ICameraDevice> device;
        ALOGI("constructDefaultRequestSettings: Testing camera device %s", name.c_str());
        ndk::ScopedAStatus ret = mProvider->getCameraDeviceInterface(name, &device);
        ALOGI("getCameraDeviceInterface returns status:%d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(device, nullptr);

        int32_t interfaceVersion;
        ret = device->getInterfaceVersion(&interfaceVersion);
        ASSERT_TRUE(ret.isOk());
        bool supportFeatureCombinationQuery =
                (interfaceVersion >= CAMERA_DEVICE_API_MINOR_VERSION_3);

        std::shared_ptr<EmptyDeviceCb> cb = ndk::SharedRefBase::make<EmptyDeviceCb>();
        ret = device->open(cb, &mSession);
        ALOGI("device::open returns status:%d:%d", ret.getExceptionCode(),
              ret.getServiceSpecificError());
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(mSession, nullptr);

        for (int32_t t = (int32_t)RequestTemplate::PREVIEW; t <= (int32_t)RequestTemplate::MANUAL;
             t++) {
            RequestTemplate reqTemplate = (RequestTemplate)t;
            CameraMetadata rawMetadata;
            ret = mSession->constructDefaultRequestSettings(reqTemplate, &rawMetadata);
            ALOGI("constructDefaultRequestSettings returns status:%d:%d", ret.getExceptionCode(),
                  ret.getServiceSpecificError());

            if (reqTemplate == RequestTemplate::ZERO_SHUTTER_LAG ||
                reqTemplate == RequestTemplate::MANUAL) {
                // optional templates
                ASSERT_TRUE(ret.isOk() || static_cast<int32_t>(Status::ILLEGAL_ARGUMENT) ==
                                                  ret.getServiceSpecificError());
            } else {
                ASSERT_TRUE(ret.isOk());
            }

            if (ret.isOk()) {
                validateDefaultRequestMetadata(reqTemplate, rawMetadata);
            } else {
                ASSERT_EQ(0u, rawMetadata.metadata.size());
            }

            if (flags::feature_combination_query()) {
                if (supportFeatureCombinationQuery) {
                    CameraMetadata rawMetadata2;
                    ndk::ScopedAStatus ret2 =
                            device->constructDefaultRequestSettings(reqTemplate, &rawMetadata2);

                    ASSERT_EQ(ret.isOk(), ret2.isOk());
                    ASSERT_EQ(ret.getStatus(), ret2.getStatus());

                    ASSERT_EQ(rawMetadata.metadata.size(), rawMetadata2.metadata.size());
                    if (ret2.isOk()) {
                        validateDefaultRequestMetadata(reqTemplate, rawMetadata2);
                    }
                }
            }
        }
        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Verify that all supported stream formats and sizes can be configured
// successfully.
TEST_P(CameraAidlTest, configureStreamsAvailableOutputs) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputStreams;

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;
        std::shared_ptr<ICameraDevice> device;

        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/, &device /*out*/);

        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());
        outputStreams.clear();
        ASSERT_EQ(Status::OK, getAvailableOutputStreams(staticMeta, outputStreams));
        ASSERT_NE(0u, outputStreams.size());

        int32_t jpegBufferSize = 0;
        ASSERT_EQ(Status::OK, getJpegBufferSize(staticMeta, &jpegBufferSize));
        ASSERT_NE(0u, jpegBufferSize);

        int32_t streamId = 0;
        int32_t streamConfigCounter = 0;
        for (auto& it : outputStreams) {
            Stream stream;
            Dataspace dataspace = getDataspace(static_cast<PixelFormat>(it.format));
            stream.id = streamId;
            stream.streamType = StreamType::OUTPUT;
            stream.width = it.width;
            stream.height = it.height;
            stream.format = static_cast<PixelFormat>(it.format);
            stream.dataSpace = dataspace;
            stream.usage = static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                    GRALLOC1_CONSUMER_USAGE_HWCOMPOSER);
            stream.rotation = StreamRotation::ROTATION_0;
            stream.dynamicRangeProfile = RequestAvailableDynamicRangeProfilesMap::
                    ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD;
            stream.useCase = ScalerAvailableStreamUseCases::
                    ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
            stream.colorSpace = static_cast<int>(
                    RequestAvailableColorSpaceProfilesMap::
                            ANDROID_REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED);

            std::vector<Stream> streams = {stream};
            StreamConfiguration config;
            createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                      jpegBufferSize);

            verifyStreamCombination(device, config, /*expectedStatus*/ true);

            config.streamConfigCounter = streamConfigCounter++;
            std::vector<HalStream> halConfigs;
            ndk::ScopedAStatus ret = mSession->configureStreams(config, &halConfigs);
            ASSERT_TRUE(ret.isOk());
            ASSERT_EQ(halConfigs.size(), 1);
            ASSERT_EQ(halConfigs[0].id, streamId);

            streamId++;
        }

        ndk::ScopedAStatus ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Verify that mandatory concurrent streams and outputs are supported.
TEST_P(CameraAidlTest, configureConcurrentStreamsAvailableOutputs) {
    struct CameraTestInfo {
        CameraMetadata staticMeta;
        std::shared_ptr<ICameraDeviceSession> session;
        std::shared_ptr<ICameraDevice> cameraDevice;
        StreamConfiguration config;
    };

    std::map<std::string, std::string> idToNameMap = getCameraDeviceIdToNameMap(mProvider);
    std::vector<ConcurrentCameraIdCombination> concurrentDeviceCombinations =
            getConcurrentDeviceCombinations(mProvider);
    std::vector<AvailableStream> outputStreams;
    for (const auto& cameraDeviceIds : concurrentDeviceCombinations) {
        std::vector<CameraIdAndStreamCombination> cameraIdsAndStreamCombinations;
        std::vector<CameraTestInfo> cameraTestInfos;
        for (const auto& id : cameraDeviceIds.combination) {
            CameraTestInfo cti;
            auto it = idToNameMap.find(id);
            ASSERT_TRUE(idToNameMap.end() != it);
            std::string name = it->second;

            openEmptyDeviceSession(name, mProvider, &cti.session /*out*/, &cti.staticMeta /*out*/,
                                   &cti.cameraDevice /*out*/);

            outputStreams.clear();
            camera_metadata_t* staticMeta =
                    reinterpret_cast<camera_metadata_t*>(cti.staticMeta.metadata.data());
            ASSERT_EQ(Status::OK, getMandatoryConcurrentStreams(staticMeta, &outputStreams));
            ASSERT_NE(0u, outputStreams.size());

            int32_t jpegBufferSize = 0;
            ASSERT_EQ(Status::OK, getJpegBufferSize(staticMeta, &jpegBufferSize));
            ASSERT_NE(0u, jpegBufferSize);

            int32_t streamId = 0;
            std::vector<Stream> streams(outputStreams.size());
            size_t j = 0;
            for (const auto& s : outputStreams) {
                Stream stream;
                Dataspace dataspace = getDataspace(static_cast<PixelFormat>(s.format));
                stream.id = streamId++;
                stream.streamType = StreamType::OUTPUT;
                stream.width = s.width;
                stream.height = s.height;
                stream.format = static_cast<PixelFormat>(s.format);
                stream.usage = static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                        GRALLOC1_CONSUMER_USAGE_HWCOMPOSER);
                stream.dataSpace = dataspace;
                stream.rotation = StreamRotation::ROTATION_0;
                stream.sensorPixelModesUsed = {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT};
                stream.dynamicRangeProfile = RequestAvailableDynamicRangeProfilesMap::
                        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD;
                streams[j] = stream;
                j++;
            }

            // Add the created stream configs to cameraIdsAndStreamCombinations
            createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &cti.config,
                                      jpegBufferSize);

            cti.config.streamConfigCounter = outputStreams.size();
            CameraIdAndStreamCombination cameraIdAndStreamCombination;
            cameraIdAndStreamCombination.cameraId = id;
            cameraIdAndStreamCombination.streamConfiguration = cti.config;
            cameraIdsAndStreamCombinations.push_back(cameraIdAndStreamCombination);
            cameraTestInfos.push_back(cti);
        }
        // Now verify that concurrent streams are supported
        bool combinationSupported;
        ndk::ScopedAStatus ret = mProvider->isConcurrentStreamCombinationSupported(
                cameraIdsAndStreamCombinations, &combinationSupported);
        ASSERT_TRUE(ret.isOk());
        ASSERT_EQ(combinationSupported, true);

        // Test the stream can actually be configured
        for (auto& cti : cameraTestInfos) {
            if (cti.session != nullptr) {
                verifyStreamCombination(cti.cameraDevice, cti.config, /*expectedStatus*/ true);
            }

            if (cti.session != nullptr) {
                std::vector<HalStream> streamConfigs;
                ret = cti.session->configureStreams(cti.config, &streamConfigs);
                ASSERT_TRUE(ret.isOk());
                ASSERT_EQ(cti.config.streams.size(), streamConfigs.size());
            }
        }

        for (auto& cti : cameraTestInfos) {
            ret = cti.session->close();
            ASSERT_TRUE(ret.isOk());
        }
    }
}

// Check for correct handling of invalid/incorrect configuration parameters.
TEST_P(CameraAidlTest, configureStreamsInvalidOutputs) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputStreams;

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;
        std::shared_ptr<ICameraDevice> cameraDevice;

        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                               &cameraDevice /*out*/);
        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());
        outputStreams.clear();

        ASSERT_EQ(Status::OK, getAvailableOutputStreams(staticMeta, outputStreams));
        ASSERT_NE(0u, outputStreams.size());

        int32_t jpegBufferSize = 0;
        ASSERT_EQ(Status::OK, getJpegBufferSize(staticMeta, &jpegBufferSize));
        ASSERT_NE(0u, jpegBufferSize);

        int32_t streamId = 0;
        Stream stream = {streamId++,
                         StreamType::OUTPUT,
                         static_cast<uint32_t>(0),
                         static_cast<uint32_t>(0),
                         static_cast<PixelFormat>(outputStreams[0].format),
                         static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                 GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                         Dataspace::UNKNOWN,
                         StreamRotation::ROTATION_0,
                         std::string(),
                         jpegBufferSize,
                         -1,
                         {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                         RequestAvailableDynamicRangeProfilesMap::
                                 ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
        int32_t streamConfigCounter = 0;
        std::vector<Stream> streams = {stream};
        StreamConfiguration config;
        createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                  jpegBufferSize);

        verifyStreamCombination(cameraDevice, config, /*expectedStatus*/ false);

        config.streamConfigCounter = streamConfigCounter++;
        std::vector<HalStream> halConfigs;
        ndk::ScopedAStatus ret = mSession->configureStreams(config, &halConfigs);
        ASSERT_TRUE(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT) ==
                            ret.getServiceSpecificError() ||
                    static_cast<int32_t>(Status::INTERNAL_ERROR) == ret.getServiceSpecificError());

        stream = {streamId++,
                  StreamType::OUTPUT,
                  /*width*/ INT32_MAX,
                  /*height*/ INT32_MAX,
                  static_cast<PixelFormat>(outputStreams[0].format),
                  static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                          GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                  Dataspace::UNKNOWN,
                  StreamRotation::ROTATION_0,
                  std::string(),
                  jpegBufferSize,
                  -1,
                  {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                  RequestAvailableDynamicRangeProfilesMap::
                          ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

        streams[0] = stream;
        createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                  jpegBufferSize);

        config.streamConfigCounter = streamConfigCounter++;
        halConfigs.clear();
        ret = mSession->configureStreams(config, &halConfigs);
        ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), ret.getServiceSpecificError());

        for (auto& it : outputStreams) {
            stream = {streamId++,
                      StreamType::OUTPUT,
                      it.width,
                      it.height,
                      static_cast<PixelFormat>(UINT32_MAX),
                      static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                              GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                      Dataspace::UNKNOWN,
                      StreamRotation::ROTATION_0,
                      std::string(),
                      jpegBufferSize,
                      -1,
                      {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                      RequestAvailableDynamicRangeProfilesMap::
                              ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

            streams[0] = stream;
            createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                      jpegBufferSize);
            config.streamConfigCounter = streamConfigCounter++;
            halConfigs.clear();
            ret = mSession->configureStreams(config, &halConfigs);
            ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT),
                      ret.getServiceSpecificError());

            stream = {streamId++,
                      StreamType::OUTPUT,
                      it.width,
                      it.height,
                      static_cast<PixelFormat>(it.format),
                      static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                              GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                      Dataspace::UNKNOWN,
                      static_cast<StreamRotation>(UINT32_MAX),
                      std::string(),
                      jpegBufferSize,
                      -1,
                      {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                      RequestAvailableDynamicRangeProfilesMap::
                              ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

            streams[0] = stream;
            createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                      jpegBufferSize);

            config.streamConfigCounter = streamConfigCounter++;
            halConfigs.clear();
            ret = mSession->configureStreams(config, &halConfigs);
            ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT),
                      ret.getServiceSpecificError());
        }

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Check whether all supported ZSL output stream combinations can be
// configured successfully.
TEST_P(CameraAidlTest, configureStreamsZSLInputOutputs) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> inputStreams;
    std::vector<AvailableZSLInputOutput> inputOutputMap;

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;
        std::shared_ptr<ICameraDevice> cameraDevice;

        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                               &cameraDevice /*out*/);
        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());

        Status rc = isZSLModeAvailable(staticMeta);
        if (Status::OPERATION_NOT_SUPPORTED == rc) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }
        ASSERT_EQ(Status::OK, rc);

        inputStreams.clear();
        ASSERT_EQ(Status::OK, getAvailableOutputStreams(staticMeta, inputStreams));
        ASSERT_NE(0u, inputStreams.size());

        inputOutputMap.clear();
        ASSERT_EQ(Status::OK, getZSLInputOutputMap(staticMeta, inputOutputMap));
        ASSERT_NE(0u, inputOutputMap.size());

        bool supportMonoY8 = false;
        if (Status::OK == isMonochromeCamera(staticMeta)) {
            for (auto& it : inputStreams) {
                if (it.format == static_cast<uint32_t>(PixelFormat::Y8)) {
                    supportMonoY8 = true;
                    break;
                }
            }
        }

        int32_t jpegBufferSize = 0;
        ASSERT_EQ(Status::OK, getJpegBufferSize(staticMeta, &jpegBufferSize));
        ASSERT_NE(0u, jpegBufferSize);

        int32_t streamId = 0;
        bool hasPrivToY8 = false, hasY8ToY8 = false, hasY8ToBlob = false;
        uint32_t streamConfigCounter = 0;
        for (auto& inputIter : inputOutputMap) {
            AvailableStream input;
            ASSERT_EQ(Status::OK, findLargestSize(inputStreams, inputIter.inputFormat, input));
            ASSERT_NE(0u, inputStreams.size());

            if (inputIter.inputFormat ==
                        static_cast<uint32_t>(PixelFormat::IMPLEMENTATION_DEFINED) &&
                inputIter.outputFormat == static_cast<uint32_t>(PixelFormat::Y8)) {
                hasPrivToY8 = true;
            } else if (inputIter.inputFormat == static_cast<uint32_t>(PixelFormat::Y8)) {
                if (inputIter.outputFormat == static_cast<uint32_t>(PixelFormat::BLOB)) {
                    hasY8ToBlob = true;
                } else if (inputIter.outputFormat == static_cast<uint32_t>(PixelFormat::Y8)) {
                    hasY8ToY8 = true;
                }
            }
            AvailableStream outputThreshold = {INT32_MAX, INT32_MAX, inputIter.outputFormat};
            std::vector<AvailableStream> outputStreams;
            ASSERT_EQ(Status::OK,
                      getAvailableOutputStreams(staticMeta, outputStreams, &outputThreshold));
            for (auto& outputIter : outputStreams) {
                Dataspace outputDataSpace =
                        getDataspace(static_cast<PixelFormat>(outputIter.format));
                Stream zslStream = {
                        streamId++,
                        StreamType::OUTPUT,
                        input.width,
                        input.height,
                        static_cast<PixelFormat>(input.format),
                        static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                GRALLOC_USAGE_HW_CAMERA_ZSL),
                        Dataspace::UNKNOWN,
                        StreamRotation::ROTATION_0,
                        std::string(),
                        jpegBufferSize,
                        -1,
                        {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                        RequestAvailableDynamicRangeProfilesMap::
                                ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
                Stream inputStream = {
                        streamId++,
                        StreamType::INPUT,
                        input.width,
                        input.height,
                        static_cast<PixelFormat>(input.format),
                        static_cast<aidl::android::hardware::graphics::common::BufferUsage>(0),
                        Dataspace::UNKNOWN,
                        StreamRotation::ROTATION_0,
                        std::string(),
                        jpegBufferSize,
                        -1,
                        {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                        RequestAvailableDynamicRangeProfilesMap::
                                ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
                Stream outputStream = {
                        streamId++,
                        StreamType::OUTPUT,
                        outputIter.width,
                        outputIter.height,
                        static_cast<PixelFormat>(outputIter.format),
                        static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                        outputDataSpace,
                        StreamRotation::ROTATION_0,
                        std::string(),
                        jpegBufferSize,
                        -1,
                        {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                        RequestAvailableDynamicRangeProfilesMap::
                                ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

                std::vector<Stream> streams = {inputStream, zslStream, outputStream};

                StreamConfiguration config;
                createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                          jpegBufferSize);

                verifyStreamCombination(cameraDevice, config, /*expectedStatus*/ true);

                config.streamConfigCounter = streamConfigCounter++;
                std::vector<HalStream> halConfigs;
                ndk::ScopedAStatus ret = mSession->configureStreams(config, &halConfigs);
                ASSERT_TRUE(ret.isOk());
                ASSERT_EQ(3u, halConfigs.size());
            }
        }

        if (supportMonoY8) {
            if (Status::OK == isZSLModeAvailable(staticMeta, PRIV_REPROCESS)) {
                ASSERT_TRUE(hasPrivToY8);
            }
            if (Status::OK == isZSLModeAvailable(staticMeta, YUV_REPROCESS)) {
                ASSERT_TRUE(hasY8ToY8);
                ASSERT_TRUE(hasY8ToBlob);
            }
        }

        ndk::ScopedAStatus ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Check whether session parameters are supported. If Hal support for them
// exist, then try to configure a preview stream using them.
TEST_P(CameraAidlTest, configureStreamsWithSessionParameters) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputPreviewStreams;
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;

        std::shared_ptr<ICameraDevice> unusedCameraDevice;
        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                               &unusedCameraDevice /*out*/);
        camera_metadata_t* staticMetaBuffer =
                reinterpret_cast<camera_metadata_t*>(meta.metadata.data());

        std::unordered_set<int32_t> availableSessionKeys;
        auto rc = getSupportedKeys(staticMetaBuffer, ANDROID_REQUEST_AVAILABLE_SESSION_KEYS,
                                   &availableSessionKeys);
        ASSERT_TRUE(Status::OK == rc);
        if (availableSessionKeys.empty()) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        android::hardware::camera::common::V1_0::helper::CameraMetadata previewRequestSettings;
        android::hardware::camera::common::V1_0::helper::CameraMetadata sessionParams,
                modifiedSessionParams;
        constructFilteredSettings(mSession, availableSessionKeys, RequestTemplate::PREVIEW,
                                  &previewRequestSettings, &sessionParams);
        if (sessionParams.isEmpty()) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        outputPreviewStreams.clear();

        ASSERT_EQ(Status::OK, getAvailableOutputStreams(staticMetaBuffer, outputPreviewStreams,
                                                        &previewThreshold));
        ASSERT_NE(0u, outputPreviewStreams.size());

        Stream previewStream = {
                0,
                StreamType::OUTPUT,
                outputPreviewStreams[0].width,
                outputPreviewStreams[0].height,
                static_cast<PixelFormat>(outputPreviewStreams[0].format),
                static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                        GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                Dataspace::UNKNOWN,
                StreamRotation::ROTATION_0,
                std::string(),
                /*bufferSize*/ 0,
                /*groupId*/ -1,
                {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                RequestAvailableDynamicRangeProfilesMap::
                        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

        std::vector<Stream> streams = {previewStream};
        StreamConfiguration config;

        config.streams = streams;
        config.operationMode = StreamConfigurationMode::NORMAL_MODE;
        modifiedSessionParams = sessionParams;
        auto sessionParamsBuffer = sessionParams.release();
        std::vector<uint8_t> rawSessionParam =
                std::vector(reinterpret_cast<uint8_t*>(sessionParamsBuffer),
                            reinterpret_cast<uint8_t*>(sessionParamsBuffer) +
                                    get_camera_metadata_size(sessionParamsBuffer));

        config.sessionParams.metadata = rawSessionParam;
        config.streamConfigCounter = 0;
        config.streams = {previewStream};
        config.streamConfigCounter = 0;
        config.multiResolutionInputImage = false;

        bool newSessionParamsAvailable = false;
        for (const auto& it : availableSessionKeys) {
            if (modifiedSessionParams.exists(it)) {
                modifiedSessionParams.erase(it);
                newSessionParamsAvailable = true;
                break;
            }
        }
        if (newSessionParamsAvailable) {
            auto modifiedSessionParamsBuffer = modifiedSessionParams.release();
            verifySessionReconfigurationQuery(mSession, sessionParamsBuffer,
                                              modifiedSessionParamsBuffer);
            modifiedSessionParams.acquire(modifiedSessionParamsBuffer);
        }

        std::vector<HalStream> halConfigs;
        ndk::ScopedAStatus ret = mSession->configureStreams(config, &halConfigs);
        ASSERT_TRUE(ret.isOk());
        ASSERT_EQ(1u, halConfigs.size());

        sessionParams.acquire(sessionParamsBuffer);
        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Verify that all supported preview + still capture stream combinations
// can be configured successfully.
TEST_P(CameraAidlTest, configureStreamsPreviewStillOutputs) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputBlobStreams;
    std::vector<AvailableStream> outputPreviewStreams;
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};
    AvailableStream blobThreshold = {INT32_MAX, INT32_MAX, static_cast<int32_t>(PixelFormat::BLOB)};

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;

        std::shared_ptr<ICameraDevice> cameraDevice;
        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                               &cameraDevice /*out*/);

        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());

        // Check if camera support depth only
        if (isDepthOnly(staticMeta)) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        outputBlobStreams.clear();
        ASSERT_EQ(Status::OK,
                  getAvailableOutputStreams(staticMeta, outputBlobStreams, &blobThreshold));
        ASSERT_NE(0u, outputBlobStreams.size());

        outputPreviewStreams.clear();
        ASSERT_EQ(Status::OK,
                  getAvailableOutputStreams(staticMeta, outputPreviewStreams, &previewThreshold));
        ASSERT_NE(0u, outputPreviewStreams.size());

        int32_t jpegBufferSize = 0;
        ASSERT_EQ(Status::OK, getJpegBufferSize(staticMeta, &jpegBufferSize));
        ASSERT_NE(0u, jpegBufferSize);

        int32_t streamId = 0;
        uint32_t streamConfigCounter = 0;

        for (auto& blobIter : outputBlobStreams) {
            for (auto& previewIter : outputPreviewStreams) {
                Stream previewStream = {
                        streamId++,
                        StreamType::OUTPUT,
                        previewIter.width,
                        previewIter.height,
                        static_cast<PixelFormat>(previewIter.format),
                        static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                        Dataspace::UNKNOWN,
                        StreamRotation::ROTATION_0,
                        std::string(),
                        /*bufferSize*/ 0,
                        /*groupId*/ -1,
                        {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                        RequestAvailableDynamicRangeProfilesMap::
                                ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
                Stream blobStream = {
                        streamId++,
                        StreamType::OUTPUT,
                        blobIter.width,
                        blobIter.height,
                        static_cast<PixelFormat>(blobIter.format),
                        static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                GRALLOC1_CONSUMER_USAGE_CPU_READ),
                        Dataspace::JFIF,
                        StreamRotation::ROTATION_0,
                        std::string(),
                        /*bufferSize*/ 0,
                        /*groupId*/ -1,
                        {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                        RequestAvailableDynamicRangeProfilesMap::
                                ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
                std::vector<Stream> streams = {previewStream, blobStream};
                StreamConfiguration config;

                createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                          jpegBufferSize);
                config.streamConfigCounter = streamConfigCounter++;
                verifyStreamCombination(cameraDevice, config, /*expectedStatus*/ true);

                std::vector<HalStream> halConfigs;
                ndk::ScopedAStatus ret = mSession->configureStreams(config, &halConfigs);
                ASSERT_TRUE(ret.isOk());
                ASSERT_EQ(2u, halConfigs.size());
            }
        }

        ndk::ScopedAStatus ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// In case constrained mode is supported, test whether it can be
// configured. Additionally check for common invalid inputs when
// using this mode.
TEST_P(CameraAidlTest, configureStreamsConstrainedOutputs) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;
        std::shared_ptr<ICameraDevice> cameraDevice;

        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                               &cameraDevice /*out*/);
        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());

        Status rc = isConstrainedModeAvailable(staticMeta);
        if (Status::OPERATION_NOT_SUPPORTED == rc) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }
        ASSERT_EQ(Status::OK, rc);

        AvailableStream hfrStream;
        rc = pickConstrainedModeSize(staticMeta, hfrStream);
        ASSERT_EQ(Status::OK, rc);

        int32_t streamId = 0;
        uint32_t streamConfigCounter = 0;
        Stream stream = {streamId,
                         StreamType::OUTPUT,
                         hfrStream.width,
                         hfrStream.height,
                         static_cast<PixelFormat>(hfrStream.format),
                         static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                 GRALLOC1_CONSUMER_USAGE_VIDEO_ENCODER),
                         Dataspace::UNKNOWN,
                         StreamRotation::ROTATION_0,
                         std::string(),
                         /*bufferSize*/ 0,
                         /*groupId*/ -1,
                         {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                         RequestAvailableDynamicRangeProfilesMap::
                                 ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
        std::vector<Stream> streams = {stream};
        StreamConfiguration config;
        createStreamConfiguration(streams, StreamConfigurationMode::CONSTRAINED_HIGH_SPEED_MODE,
                                  &config);

        verifyStreamCombination(cameraDevice, config, /*expectedStatus*/ true);

        config.streamConfigCounter = streamConfigCounter++;
        std::vector<HalStream> halConfigs;
        ndk::ScopedAStatus ret = mSession->configureStreams(config, &halConfigs);
        ASSERT_TRUE(ret.isOk());
        ASSERT_EQ(1u, halConfigs.size());
        ASSERT_EQ(halConfigs[0].id, streamId);

        stream = {streamId++,
                  StreamType::OUTPUT,
                  static_cast<uint32_t>(0),
                  static_cast<uint32_t>(0),
                  static_cast<PixelFormat>(hfrStream.format),
                  static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                          GRALLOC1_CONSUMER_USAGE_VIDEO_ENCODER),
                  Dataspace::UNKNOWN,
                  StreamRotation::ROTATION_0,
                  std::string(),
                  /*bufferSize*/ 0,
                  /*groupId*/ -1,
                  {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                  RequestAvailableDynamicRangeProfilesMap::
                          ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
        streams[0] = stream;
        createStreamConfiguration(streams, StreamConfigurationMode::CONSTRAINED_HIGH_SPEED_MODE,
                                  &config);

        config.streamConfigCounter = streamConfigCounter++;
        std::vector<HalStream> halConfig;
        ret = mSession->configureStreams(config, &halConfig);
        ASSERT_TRUE(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT) ==
                            ret.getServiceSpecificError() ||
                    static_cast<int32_t>(Status::INTERNAL_ERROR) == ret.getServiceSpecificError());

        stream = {streamId++,
                  StreamType::OUTPUT,
                  INT32_MAX,
                  INT32_MAX,
                  static_cast<PixelFormat>(hfrStream.format),
                  static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                          GRALLOC1_CONSUMER_USAGE_VIDEO_ENCODER),
                  Dataspace::UNKNOWN,
                  StreamRotation::ROTATION_0,
                  std::string(),
                  /*bufferSize*/ 0,
                  /*groupId*/ -1,
                  {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                  RequestAvailableDynamicRangeProfilesMap::
                          ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
        streams[0] = stream;
        createStreamConfiguration(streams, StreamConfigurationMode::CONSTRAINED_HIGH_SPEED_MODE,
                                  &config);

        config.streamConfigCounter = streamConfigCounter++;
        halConfigs.clear();
        ret = mSession->configureStreams(config, &halConfigs);
        ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), ret.getServiceSpecificError());

        stream = {streamId++,
                  StreamType::OUTPUT,
                  hfrStream.width,
                  hfrStream.height,
                  static_cast<PixelFormat>(UINT32_MAX),
                  static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                          GRALLOC1_CONSUMER_USAGE_VIDEO_ENCODER),
                  Dataspace::UNKNOWN,
                  StreamRotation::ROTATION_0,
                  std::string(),
                  /*bufferSize*/ 0,
                  /*groupId*/ -1,
                  {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                  RequestAvailableDynamicRangeProfilesMap::
                          ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
        streams[0] = stream;
        createStreamConfiguration(streams, StreamConfigurationMode::CONSTRAINED_HIGH_SPEED_MODE,
                                  &config);

        config.streamConfigCounter = streamConfigCounter++;
        halConfigs.clear();
        ret = mSession->configureStreams(config, &halConfigs);
        ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), ret.getServiceSpecificError());

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Verify that all supported video + snapshot stream combinations can
// be configured successfully.
TEST_P(CameraAidlTest, configureStreamsVideoStillOutputs) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputBlobStreams;
    std::vector<AvailableStream> outputVideoStreams;
    AvailableStream videoThreshold = {kMaxVideoWidth, kMaxVideoHeight,
                                      static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};
    AvailableStream blobThreshold = {kMaxVideoWidth, kMaxVideoHeight,
                                     static_cast<int32_t>(PixelFormat::BLOB)};

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;
        std::shared_ptr<ICameraDevice> cameraDevice;

        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                               &cameraDevice /*out*/);

        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());

        // Check if camera support depth only
        if (isDepthOnly(staticMeta)) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        outputBlobStreams.clear();
        ASSERT_EQ(Status::OK,
                  getAvailableOutputStreams(staticMeta, outputBlobStreams, &blobThreshold));
        ASSERT_NE(0u, outputBlobStreams.size());

        outputVideoStreams.clear();
        ASSERT_EQ(Status::OK,
                  getAvailableOutputStreams(staticMeta, outputVideoStreams, &videoThreshold));
        ASSERT_NE(0u, outputVideoStreams.size());

        int32_t jpegBufferSize = 0;
        ASSERT_EQ(Status::OK, getJpegBufferSize(staticMeta, &jpegBufferSize));
        ASSERT_NE(0u, jpegBufferSize);

        int32_t streamId = 0;
        uint32_t streamConfigCounter = 0;
        for (auto& blobIter : outputBlobStreams) {
            for (auto& videoIter : outputVideoStreams) {
                Stream videoStream = {
                        streamId++,
                        StreamType::OUTPUT,
                        videoIter.width,
                        videoIter.height,
                        static_cast<PixelFormat>(videoIter.format),
                        static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                GRALLOC1_CONSUMER_USAGE_VIDEO_ENCODER),
                        Dataspace::UNKNOWN,
                        StreamRotation::ROTATION_0,
                        std::string(),
                        jpegBufferSize,
                        /*groupId*/ -1,
                        {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                        RequestAvailableDynamicRangeProfilesMap::
                                ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
                Stream blobStream = {
                        streamId++,
                        StreamType::OUTPUT,
                        blobIter.width,
                        blobIter.height,
                        static_cast<PixelFormat>(blobIter.format),
                        static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                GRALLOC1_CONSUMER_USAGE_CPU_READ),
                        Dataspace::JFIF,
                        StreamRotation::ROTATION_0,
                        std::string(),
                        jpegBufferSize,
                        /*groupId*/ -1,
                        {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                        RequestAvailableDynamicRangeProfilesMap::
                                ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
                std::vector<Stream> streams = {videoStream, blobStream};
                StreamConfiguration config;

                createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                          jpegBufferSize);
                verifyStreamCombination(cameraDevice, config, /*expectedStatus*/ true);

                config.streamConfigCounter = streamConfigCounter++;
                std::vector<HalStream> halConfigs;
                ndk::ScopedAStatus ret = mSession->configureStreams(config, &halConfigs);
                ASSERT_TRUE(ret.isOk());
                ASSERT_EQ(2u, halConfigs.size());
            }
        }

        ndk::ScopedAStatus ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Generate and verify a camera capture request
TEST_P(CameraAidlTest, processCaptureRequestPreview) {
    // TODO(b/220897574): Failing with BUFFER_ERROR
    processCaptureRequestInternal(GRALLOC1_CONSUMER_USAGE_HWCOMPOSER, RequestTemplate::PREVIEW,
                                  false /*secureOnlyCameras*/);
}

// Generate and verify a secure camera capture request
TEST_P(CameraAidlTest, processSecureCaptureRequest) {
    processCaptureRequestInternal(GRALLOC1_PRODUCER_USAGE_PROTECTED, RequestTemplate::STILL_CAPTURE,
                                  true /*secureOnlyCameras*/);
}

TEST_P(CameraAidlTest, processCaptureRequestPreviewStabilization) {
    std::unordered_map<std::string, nsecs_t> cameraDeviceToTimeLag;
    processPreviewStabilizationCaptureRequestInternal(/*previewStabilizationOn*/ false,
                                                      cameraDeviceToTimeLag);
    processPreviewStabilizationCaptureRequestInternal(/*previewStabilizationOn*/ true,
                                                      cameraDeviceToTimeLag);
}

// Generate and verify a multi-camera capture request
TEST_P(CameraAidlTest, processMultiCaptureRequestPreview) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::YCBCR_420_888)};
    int64_t bufferId = 1;
    uint32_t frameNumber = 1;
    std::vector<uint8_t> settings;
    std::vector<uint8_t> emptySettings;
    std::string invalidPhysicalId = "-1";

    for (const auto& name : cameraDeviceNames) {
        std::string version, deviceId;
        ALOGI("processMultiCaptureRequestPreview: Test device %s", name.c_str());
        ASSERT_TRUE(matchDeviceName(name, mProviderType, &version, &deviceId));
        CameraMetadata metadata;

        std::shared_ptr<ICameraDevice> unusedDevice;
        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &metadata /*out*/,
                               &unusedDevice /*out*/);

        camera_metadata_t* staticMeta =
                reinterpret_cast<camera_metadata_t*>(metadata.metadata.data());
        Status rc = isLogicalMultiCamera(staticMeta);
        if (Status::OPERATION_NOT_SUPPORTED == rc) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }
        ASSERT_EQ(Status::OK, rc);

        std::unordered_set<std::string> physicalIds;
        rc = getPhysicalCameraIds(staticMeta, &physicalIds);
        ASSERT_TRUE(Status::OK == rc);
        ASSERT_TRUE(physicalIds.size() > 1);

        std::unordered_set<int32_t> physicalRequestKeyIDs;
        rc = getSupportedKeys(staticMeta, ANDROID_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS,
                              &physicalRequestKeyIDs);
        ASSERT_TRUE(Status::OK == rc);
        if (physicalRequestKeyIDs.empty()) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            // The logical camera doesn't support any individual physical requests.
            continue;
        }

        android::hardware::camera::common::V1_0::helper::CameraMetadata defaultPreviewSettings;
        android::hardware::camera::common::V1_0::helper::CameraMetadata filteredSettings;
        constructFilteredSettings(mSession, physicalRequestKeyIDs, RequestTemplate::PREVIEW,
                                  &defaultPreviewSettings, &filteredSettings);
        if (filteredSettings.isEmpty()) {
            // No physical device settings in default request.
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        const camera_metadata_t* settingsBuffer = defaultPreviewSettings.getAndLock();
        uint8_t* rawSettingsBuffer = (uint8_t*)settingsBuffer;
        settings.assign(rawSettingsBuffer,
                        rawSettingsBuffer + get_camera_metadata_size(settingsBuffer));
        CameraMetadata settingsMetadata = {settings};
        overrideRotateAndCrop(&settingsMetadata);

        ndk::ScopedAStatus ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());

        // Leave only 2 physical devices in the id set.
        auto it = physicalIds.begin();
        std::string physicalDeviceId = *it;
        it++;
        physicalIds.erase(++it, physicalIds.end());
        ASSERT_EQ(physicalIds.size(), 2u);

        std::vector<HalStream> halStreams;
        bool supportsPartialResults = false;
        std::set<int32_t> halBufManagedStreamIds;
        int32_t partialResultCount = 0;
        Stream previewStream;
        std::shared_ptr<DeviceCb> cb;

        configurePreviewStreams(
                name, mProvider, &previewThreshold, physicalIds, &mSession, &previewStream,
                &halStreams /*out*/, &supportsPartialResults /*out*/, &partialResultCount /*out*/,
                &halBufManagedStreamIds /*out*/, &cb /*out*/, 0 /*streamConfigCounter*/, true);
        if (mSession == nullptr) {
            // stream combination not supported by HAL, skip test for device
            continue;
        }

        ::aidl::android::hardware::common::fmq::MQDescriptor<
                int8_t, aidl::android::hardware::common::fmq::SynchronizedReadWrite>
                descriptor;
        auto resultQueueRet = mSession->getCaptureResultMetadataQueue(&descriptor);
        ASSERT_TRUE(resultQueueRet.isOk());
        std::shared_ptr<ResultMetadataQueue> resultQueue =
                std::make_shared<ResultMetadataQueue>(descriptor);
        if (!resultQueue->isValid() || resultQueue->availableToWrite() <= 0) {
            ALOGE("%s: HAL returns empty result metadata fmq, not use it", __func__);
            resultQueue = nullptr;
            // Don't use the queue onwards.
        }

        std::shared_ptr<InFlightRequest> inflightReq = std::make_shared<InFlightRequest>(
                static_cast<ssize_t>(halStreams.size()), false, supportsPartialResults,
                partialResultCount, physicalIds, resultQueue);

        std::vector<CaptureRequest> requests(1);
        CaptureRequest& request = requests[0];
        request.frameNumber = frameNumber;
        request.fmqSettingsSize = 0;
        request.settings = settingsMetadata;

        std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;

        std::vector<buffer_handle_t> graphicBuffers;
        graphicBuffers.reserve(halStreams.size());
        outputBuffers.resize(halStreams.size());
        size_t k = 0;
        for (const auto& halStream : halStreams) {
            buffer_handle_t buffer_handle;
            bool useHalBufManagerForStream =
                    halBufManagedStreamIds.find(halStream.id) != halBufManagedStreamIds.end();
            if (useHalBufManagerForStream) {
                outputBuffers[k] = {halStream.id,     /*bufferId*/ 0, NativeHandle(),
                                    BufferStatus::OK, NativeHandle(), NativeHandle()};
            } else {
                allocateGraphicBuffer(previewStream.width, previewStream.height,
                                      ANDROID_NATIVE_UNSIGNED_CAST(android_convertGralloc1To0Usage(
                                              static_cast<uint64_t>(halStream.producerUsage),
                                              static_cast<uint64_t>(halStream.consumerUsage))),
                                      halStream.overrideFormat, &buffer_handle);
                graphicBuffers.push_back(buffer_handle);
                outputBuffers[k] = {
                        halStream.id,     bufferId,       ::android::makeToAidl(buffer_handle),
                        BufferStatus::OK, NativeHandle(), NativeHandle()};
                bufferId++;
            }
            k++;
        }

        std::vector<PhysicalCameraSetting> camSettings(1);
        const camera_metadata_t* filteredSettingsBuffer = filteredSettings.getAndLock();
        uint8_t* rawFilteredSettingsBuffer = (uint8_t*)filteredSettingsBuffer;
        camSettings[0].settings = {std::vector(
                rawFilteredSettingsBuffer,
                rawFilteredSettingsBuffer + get_camera_metadata_size(filteredSettingsBuffer))};
        overrideRotateAndCrop(&camSettings[0].settings);
        camSettings[0].fmqSettingsSize = 0;
        camSettings[0].physicalCameraId = physicalDeviceId;

        request.inputBuffer = {
                -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};
        request.physicalCameraSettings = camSettings;

        {
            std::unique_lock<std::mutex> l(mLock);
            mInflightMap.clear();
            mInflightMap[frameNumber] = inflightReq;
        }

        int32_t numRequestProcessed = 0;
        std::vector<BufferCache> cachesToRemove;
        ndk::ScopedAStatus returnStatus =
                mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
        ASSERT_TRUE(returnStatus.isOk());
        ASSERT_EQ(numRequestProcessed, 1u);

        {
            std::unique_lock<std::mutex> l(mLock);
            while (!inflightReq->errorCodeValid &&
                   ((0 < inflightReq->numBuffersLeft) || (!inflightReq->haveResultMetadata))) {
                auto timeout = std::chrono::system_clock::now() +
                               std::chrono::seconds(kStreamBufferTimeoutSec);
                ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout));
            }

            ASSERT_FALSE(inflightReq->errorCodeValid);
            ASSERT_NE(inflightReq->resultOutputBuffers.size(), 0u);

            request.frameNumber++;
            // Empty settings should be supported after the first call
            // for repeating requests.
            request.settings.metadata.clear();
            request.physicalCameraSettings[0].settings.metadata.clear();
            // The buffer has been registered to HAL by bufferId, so per
            // API contract we should send a null handle for this buffer
            request.outputBuffers[0].buffer = NativeHandle();
            mInflightMap.clear();
            inflightReq = std::make_shared<InFlightRequest>(
                    static_cast<ssize_t>(physicalIds.size()), false, supportsPartialResults,
                    partialResultCount, physicalIds, resultQueue);
            mInflightMap[request.frameNumber] = inflightReq;
        }

        returnStatus =
                mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
        ASSERT_TRUE(returnStatus.isOk());
        ASSERT_EQ(numRequestProcessed, 1u);

        {
            std::unique_lock<std::mutex> l(mLock);
            while (!inflightReq->errorCodeValid &&
                   ((0 < inflightReq->numBuffersLeft) || (!inflightReq->haveResultMetadata))) {
                auto timeout = std::chrono::system_clock::now() +
                               std::chrono::seconds(kStreamBufferTimeoutSec);
                ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout));
            }

            ASSERT_FALSE(inflightReq->errorCodeValid);
            ASSERT_NE(inflightReq->resultOutputBuffers.size(), 0u);
        }

        // Invalid physical camera id should fail process requests
        frameNumber++;
        camSettings[0].physicalCameraId = invalidPhysicalId;
        camSettings[0].settings.metadata = settings;

        request.physicalCameraSettings = camSettings;  // Invalid camera settings
        returnStatus =
                mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
        ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT),
                  returnStatus.getServiceSpecificError());

        defaultPreviewSettings.unlock(settingsBuffer);
        filteredSettings.unlock(filteredSettingsBuffer);

        if (halBufManagedStreamIds.size() != 0) {
            std::vector<int32_t> streamIds;
            for (size_t i = 0; i < halStreams.size(); i++) {
                int32_t streamId = halStreams[i].id;
                if (halBufManagedStreamIds.find(streamId) != halBufManagedStreamIds.end()) {
                    streamIds.emplace_back(streamId);
                }
            }
            verifyBuffersReturned(mSession, streamIds, cb);
        }

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Generate and verify an ultra high resolution capture request
TEST_P(CameraAidlTest, processUltraHighResolutionRequest) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    int64_t bufferId = 1;
    int32_t frameNumber = 1;
    CameraMetadata settings;

    for (const auto& name : cameraDeviceNames) {
        std::string version, deviceId;
        ASSERT_TRUE(matchDeviceName(name, mProviderType, &version, &deviceId));
        CameraMetadata meta;

        std::shared_ptr<ICameraDevice> unusedDevice;
        openEmptyDeviceSession(name, mProvider, &mSession, &meta, &unusedDevice);
        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());
        if (!isUltraHighResolution(staticMeta)) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }
        CameraMetadata req;
        android::hardware::camera::common::V1_0::helper::CameraMetadata defaultSettings;
        ndk::ScopedAStatus ret =
                mSession->constructDefaultRequestSettings(RequestTemplate::STILL_CAPTURE, &req);
        ASSERT_TRUE(ret.isOk());

        const camera_metadata_t* metadata =
                reinterpret_cast<const camera_metadata_t*>(req.metadata.data());
        size_t expectedSize = req.metadata.size();
        int result = validate_camera_metadata_structure(metadata, &expectedSize);
        ASSERT_TRUE((result == 0) || (result == CAMERA_METADATA_VALIDATION_SHIFTED));

        size_t entryCount = get_camera_metadata_entry_count(metadata);
        ASSERT_GT(entryCount, 0u);
        defaultSettings = metadata;
        uint8_t sensorPixelMode =
                static_cast<uint8_t>(ANDROID_SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION);
        ASSERT_EQ(::android::OK,
                  defaultSettings.update(ANDROID_SENSOR_PIXEL_MODE, &sensorPixelMode, 1));

        const camera_metadata_t* settingsBuffer = defaultSettings.getAndLock();
        uint8_t* rawSettingsBuffer = (uint8_t*)settingsBuffer;
        settings.metadata = std::vector(
                rawSettingsBuffer, rawSettingsBuffer + get_camera_metadata_size(settingsBuffer));
        overrideRotateAndCrop(&settings);

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());

        std::vector<HalStream> halStreams;
        bool supportsPartialResults = false;
        std::set<int32_t> halBufManagedStreamIds;
        int32_t partialResultCount = 0;
        Stream previewStream;
        std::shared_ptr<DeviceCb> cb;

        std::list<PixelFormat> pixelFormats = {PixelFormat::YCBCR_420_888, PixelFormat::RAW16};
        for (PixelFormat format : pixelFormats) {
            previewStream.usage =
                static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                        GRALLOC1_CONSUMER_USAGE_CPU_READ);
            previewStream.dataSpace = Dataspace::UNKNOWN;
            configureStreams(name, mProvider, format, &mSession, &previewStream, &halStreams,
                             &supportsPartialResults, &partialResultCount, &halBufManagedStreamIds,
                             &cb, 0, /*maxResolution*/ true);
            ASSERT_NE(mSession, nullptr);

            ::aidl::android::hardware::common::fmq::MQDescriptor<
                    int8_t, aidl::android::hardware::common::fmq::SynchronizedReadWrite>
                    descriptor;
            auto resultQueueRet = mSession->getCaptureResultMetadataQueue(&descriptor);
            ASSERT_TRUE(resultQueueRet.isOk());

            std::shared_ptr<ResultMetadataQueue> resultQueue =
                    std::make_shared<ResultMetadataQueue>(descriptor);
            if (!resultQueue->isValid() || resultQueue->availableToWrite() <= 0) {
                ALOGE("%s: HAL returns empty result metadata fmq, not use it", __func__);
                resultQueue = nullptr;
                // Don't use the queue onwards.
            }

            std::vector<buffer_handle_t> graphicBuffers;
            graphicBuffers.reserve(halStreams.size());
            std::shared_ptr<InFlightRequest> inflightReq = std::make_shared<InFlightRequest>(
                    static_cast<ssize_t>(halStreams.size()), false, supportsPartialResults,
                    partialResultCount, std::unordered_set<std::string>(), resultQueue);

            std::vector<CaptureRequest> requests(1);
            CaptureRequest& request = requests[0];
            std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
            outputBuffers.resize(halStreams.size());

            size_t k = 0;
            for (const auto& halStream : halStreams) {
                buffer_handle_t buffer_handle;
                bool halBufManagerUsed =
                        halBufManagedStreamIds.find(halStream.id) != halBufManagedStreamIds.end();
                if (halBufManagerUsed) {
                    outputBuffers[k] = {halStream.id,   0,
                                        NativeHandle(), BufferStatus::OK,
                                        NativeHandle(), NativeHandle()};
                } else {
                    allocateGraphicBuffer(previewStream.width, previewStream.height,
                                          ANDROID_NATIVE_UNSIGNED_CAST(android_convertGralloc1To0Usage(
                                                  static_cast<uint64_t>(halStream.producerUsage),
                                                  static_cast<uint64_t>(halStream.consumerUsage))),
                                          halStream.overrideFormat, &buffer_handle);
                    graphicBuffers.push_back(buffer_handle);
                    outputBuffers[k] = {
                            halStream.id,     bufferId,       ::android::makeToAidl(buffer_handle),
                            BufferStatus::OK, NativeHandle(), NativeHandle()};
                    bufferId++;
                }
                k++;
            }

            request.inputBuffer = {
                    -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};
            request.frameNumber = frameNumber;
            request.fmqSettingsSize = 0;
            request.settings = settings;
            request.inputWidth = 0;
            request.inputHeight = 0;

            {
                std::unique_lock<std::mutex> l(mLock);
                mInflightMap.clear();
                mInflightMap[frameNumber] = inflightReq;
            }

            int32_t numRequestProcessed = 0;
            std::vector<BufferCache> cachesToRemove;
            ndk::ScopedAStatus returnStatus =
                    mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
            ASSERT_TRUE(returnStatus.isOk());
            ASSERT_EQ(numRequestProcessed, 1u);

            {
                std::unique_lock<std::mutex> l(mLock);
                while (!inflightReq->errorCodeValid &&
                       ((0 < inflightReq->numBuffersLeft) || (!inflightReq->haveResultMetadata))) {
                    auto timeout = std::chrono::system_clock::now() +
                                   std::chrono::seconds(kStreamBufferTimeoutSec);
                    ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout));
                }

                ASSERT_FALSE(inflightReq->errorCodeValid);
                ASSERT_NE(inflightReq->resultOutputBuffers.size(), 0u);
            }
            if (halBufManagedStreamIds.size()) {
                std::vector<int32_t> streamIds;
                for (size_t i = 0; i < halStreams.size(); i++) {
                    if (contains(halBufManagedStreamIds, halStreams[i].id)) {
                        streamIds.emplace_back(halStreams[i].id);
                    }
                }
                verifyBuffersReturned(mSession, streamIds, cb);
            }

            ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
        }
    }
}

// Generate and verify 10-bit dynamic range request
TEST_P(CameraAidlTest, process10BitDynamicRangeRequest) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    CameraMetadata settings;

    for (const auto& name : cameraDeviceNames) {
        std::string version, deviceId;
        ASSERT_TRUE(matchDeviceName(name, mProviderType, &version, &deviceId));
        CameraMetadata meta;
        std::shared_ptr<ICameraDevice> device;
        openEmptyDeviceSession(name, mProvider, &mSession, &meta, &device);
        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());
        if (!is10BitDynamicRangeCapable(staticMeta)) {
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }
        std::vector<RequestAvailableDynamicRangeProfilesMap> profileList;
        get10BitDynamicRangeProfiles(staticMeta, &profileList);
        ASSERT_FALSE(profileList.empty());

        CameraMetadata req;
        android::hardware::camera::common::V1_0::helper::CameraMetadata defaultSettings;
        ndk::ScopedAStatus ret =
                mSession->constructDefaultRequestSettings(RequestTemplate::PREVIEW, &req);
        ASSERT_TRUE(ret.isOk());

        const camera_metadata_t* metadata =
                reinterpret_cast<const camera_metadata_t*>(req.metadata.data());
        size_t expectedSize = req.metadata.size();
        int result = validate_camera_metadata_structure(metadata, &expectedSize);
        ASSERT_TRUE((result == 0) || (result == CAMERA_METADATA_VALIDATION_SHIFTED));

        size_t entryCount = get_camera_metadata_entry_count(metadata);
        ASSERT_GT(entryCount, 0u);
        defaultSettings = metadata;

        const camera_metadata_t* settingsBuffer = defaultSettings.getAndLock();
        uint8_t* rawSettingsBuffer = (uint8_t*)settingsBuffer;
        settings.metadata = std::vector(
                rawSettingsBuffer, rawSettingsBuffer + get_camera_metadata_size(settingsBuffer));
        overrideRotateAndCrop(&settings);

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());

        std::vector<HalStream> halStreams;
        bool supportsPartialResults = false;
        std::set<int32_t> halBufManagedStreamIds;
        int32_t partialResultCount = 0;
        Stream previewStream;
        std::shared_ptr<DeviceCb> cb;
        for (const auto& profile : profileList) {
            previewStream.usage =
                static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                        GRALLOC1_CONSUMER_USAGE_HWCOMPOSER);
            previewStream.dataSpace = getDataspace(PixelFormat::IMPLEMENTATION_DEFINED);
            configureStreams(name, mProvider, PixelFormat::IMPLEMENTATION_DEFINED, &mSession,
                             &previewStream, &halStreams, &supportsPartialResults,
                             &partialResultCount, &halBufManagedStreamIds, &cb, 0,
                             /*maxResolution*/ false, profile);
            ASSERT_NE(mSession, nullptr);

            ::aidl::android::hardware::common::fmq::MQDescriptor<
                    int8_t, aidl::android::hardware::common::fmq::SynchronizedReadWrite>
                    descriptor;
            auto resultQueueRet = mSession->getCaptureResultMetadataQueue(&descriptor);
            ASSERT_TRUE(resultQueueRet.isOk());

            std::shared_ptr<ResultMetadataQueue> resultQueue =
                    std::make_shared<ResultMetadataQueue>(descriptor);
            if (!resultQueue->isValid() || resultQueue->availableToWrite() <= 0) {
                ALOGE("%s: HAL returns empty result metadata fmq, not use it", __func__);
                resultQueue = nullptr;
                // Don't use the queue onwards.
            }

            mInflightMap.clear();
            // Stream as long as needed to fill the Hal inflight queue
            std::vector<CaptureRequest> requests(halStreams[0].maxBuffers);

            for (int32_t requestId = 0; requestId < requests.size(); requestId++) {
                std::shared_ptr<InFlightRequest> inflightReq = std::make_shared<InFlightRequest>(
                        static_cast<ssize_t>(halStreams.size()), false, supportsPartialResults,
                        partialResultCount, std::unordered_set<std::string>(), resultQueue);

                CaptureRequest& request = requests[requestId];
                std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
                outputBuffers.resize(halStreams.size());

                size_t k = 0;
                inflightReq->mOutstandingBufferIds.resize(halStreams.size());
                std::vector<buffer_handle_t> graphicBuffers;
                graphicBuffers.reserve(halStreams.size());

                auto bufferId = requestId + 1; // Buffer id value 0 is not valid
                for (const auto& halStream : halStreams) {
                    buffer_handle_t buffer_handle;
                    if (contains(halBufManagedStreamIds, halStream.id)) {
                        outputBuffers[k] = {halStream.id,   0,
                                            NativeHandle(), BufferStatus::OK,
                                            NativeHandle(), NativeHandle()};
                    } else {
                        auto usage = ANDROID_NATIVE_UNSIGNED_CAST(android_convertGralloc1To0Usage(
                                static_cast<uint64_t>(halStream.producerUsage),
                                static_cast<uint64_t>(halStream.consumerUsage)));
                        allocateGraphicBuffer(previewStream.width, previewStream.height, usage,
                                              halStream.overrideFormat, &buffer_handle);

                        inflightReq->mOutstandingBufferIds[halStream.id][bufferId] = buffer_handle;
                        graphicBuffers.push_back(buffer_handle);
                        outputBuffers[k] = {halStream.id, bufferId,
                            android::makeToAidl(buffer_handle), BufferStatus::OK, NativeHandle(),
                            NativeHandle()};
                    }
                    k++;
                }

                request.inputBuffer = {
                        -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};
                request.frameNumber = bufferId;
                request.fmqSettingsSize = 0;
                request.settings = settings;
                request.inputWidth = 0;
                request.inputHeight = 0;

                {
                    std::unique_lock<std::mutex> l(mLock);
                    mInflightMap[bufferId] = inflightReq;
                }

            }

            int32_t numRequestProcessed = 0;
            std::vector<BufferCache> cachesToRemove;
            ndk::ScopedAStatus returnStatus =
                mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
            ASSERT_TRUE(returnStatus.isOk());
            ASSERT_EQ(numRequestProcessed, requests.size());

            returnStatus = mSession->repeatingRequestEnd(requests.size() - 1,
                    std::vector<int32_t> {halStreams[0].id});
            ASSERT_TRUE(returnStatus.isOk());

            // We are keeping frame numbers and buffer ids consistent. Buffer id value of 0
            // is used to indicate a buffer that is not present/available so buffer ids as well
            // as frame numbers begin with 1.
            for (int32_t frameNumber = 1; frameNumber <= requests.size(); frameNumber++) {
                const auto& inflightReq = mInflightMap[frameNumber];
                std::unique_lock<std::mutex> l(mLock);
                while (!inflightReq->errorCodeValid &&
                       ((0 < inflightReq->numBuffersLeft) || (!inflightReq->haveResultMetadata))) {
                    auto timeout = std::chrono::system_clock::now() +
                                   std::chrono::seconds(kStreamBufferTimeoutSec);
                    ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout));
                }

                waitForReleaseFence(inflightReq->resultOutputBuffers);

                ASSERT_FALSE(inflightReq->errorCodeValid);
                ASSERT_NE(inflightReq->resultOutputBuffers.size(), 0u);
                verify10BitMetadata(mHandleImporter, *inflightReq, profile);
            }

            if (halBufManagedStreamIds.size() != 0) {
                std::vector<int32_t> streamIds;
                for (size_t i = 0; i < halStreams.size(); i++) {
                    if (contains(halBufManagedStreamIds, halStreams[i].id)) {
                        streamIds.emplace_back(halStreams[i].id);
                    }
                }
                mSession->signalStreamFlush(streamIds, /*streamConfigCounter*/ 0);
                cb->waitForBuffersReturned();
            }

            ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
        }
    }
}

TEST_P(CameraAidlTest, process8BitColorSpaceRequests) {
    static int profiles[] = {ColorSpaceNamed::DISPLAY_P3, ColorSpaceNamed::SRGB};

    for (int32_t i = 0; i < sizeof(profiles) / sizeof(profiles[0]); i++) {
        processColorSpaceRequest(static_cast<RequestAvailableColorSpaceProfilesMap>(profiles[i]),
                static_cast<RequestAvailableDynamicRangeProfilesMap>(
                ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD));
    }
}

TEST_P(CameraAidlTest, process10BitColorSpaceRequests) {
    static const camera_metadata_enum_android_request_available_dynamic_range_profiles_map
            dynamicRangeProfiles[] = {
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM,
        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO
    };

    // Process all dynamic range profiles with BT2020_HLG
    for (int32_t i = 0; i < sizeof(dynamicRangeProfiles) / sizeof(dynamicRangeProfiles[0]); i++) {
        processColorSpaceRequest(
                static_cast<RequestAvailableColorSpaceProfilesMap>(ColorSpaceNamed::BT2020_HLG),
                static_cast<RequestAvailableDynamicRangeProfilesMap>(dynamicRangeProfiles[i]));
    }
}

TEST_P(CameraAidlTest, processZoomSettingsOverrideRequests) {
    const int32_t kFrameCount = 5;
    const int32_t kTestCases = 2;
    const bool kOverrideSequence[kTestCases][kFrameCount] = {// ZOOM, ZOOM, ZOOM, ZOOM, ZOOM;
                                                             {true, true, true, true, true},
                                                             // OFF, ZOOM, ZOOM, ZOOM, OFF;
                                                             {false, true, true, true, false}};
    const bool kExpectedOverrideResults[kTestCases][kFrameCount] = {
            // All resuls should be overridden except the last one. The last result's
            // zoom doesn't have speed-up.
            {true, true, true, true, false},
            // Because we require at least 1 frame speed-up, request #1, #2 and #3
            // will be overridden.
            {true, true, true, false, false}};

    for (int i = 0; i < kTestCases; i++) {
        processZoomSettingsOverrideRequests(kFrameCount, kOverrideSequence[i],
                kExpectedOverrideResults[i]);
    }
}

// Generate and verify a burst containing alternating sensor sensitivity values
TEST_P(CameraAidlTest, processCaptureRequestBurstISO) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};
    int64_t bufferId = 1;
    int32_t frameNumber = 1;
    float isoTol = .03f;
    CameraMetadata settings;

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;
        settings.metadata.clear();
        std::shared_ptr<ICameraDevice> unusedDevice;
        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                               &unusedDevice /*out*/);
        camera_metadata_t* staticMetaBuffer =
                clone_camera_metadata(reinterpret_cast<camera_metadata_t*>(meta.metadata.data()));
        ::android::hardware::camera::common::V1_0::helper::CameraMetadata staticMeta(
                staticMetaBuffer);

        camera_metadata_entry_t hwLevel = staticMeta.find(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL);
        ASSERT_TRUE(0 < hwLevel.count);
        if (ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED == hwLevel.data.u8[0] ||
            ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL == hwLevel.data.u8[0]) {
            // Limited/External devices can skip this test
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        camera_metadata_entry_t isoRange = staticMeta.find(ANDROID_SENSOR_INFO_SENSITIVITY_RANGE);
        ASSERT_EQ(isoRange.count, 2u);

        ndk::ScopedAStatus ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());

        bool supportsPartialResults = false;
        bool useHalBufManager = false;
        int32_t partialResultCount = 0;
        Stream previewStream;
        std::vector<HalStream> halStreams;
        std::shared_ptr<DeviceCb> cb;
        configurePreviewStream(name, mProvider, &previewThreshold, &mSession /*out*/,
                               &previewStream /*out*/, &halStreams /*out*/,
                               &supportsPartialResults /*out*/, &partialResultCount /*out*/,
                               &useHalBufManager /*out*/, &cb /*out*/);

        ::aidl::android::hardware::common::fmq::MQDescriptor<
                int8_t, aidl::android::hardware::common::fmq::SynchronizedReadWrite>
                descriptor;
        auto resultQueueRet = mSession->getCaptureResultMetadataQueue(&descriptor);
        std::shared_ptr<ResultMetadataQueue> resultQueue =
                std::make_shared<ResultMetadataQueue>(descriptor);
        ASSERT_TRUE(resultQueueRet.isOk());
        if (!resultQueue->isValid() || resultQueue->availableToWrite() <= 0) {
            ALOGE("%s: HAL returns empty result metadata fmq, not use it", __func__);
            resultQueue = nullptr;
            // Don't use the queue onwards.
        }

        ret = mSession->constructDefaultRequestSettings(RequestTemplate::PREVIEW, &settings);
        ASSERT_TRUE(ret.isOk());

        ::android::hardware::camera::common::V1_0::helper::CameraMetadata requestMeta;
        std::vector<CaptureRequest> requests(kBurstFrameCount);
        std::vector<buffer_handle_t> buffers(kBurstFrameCount);
        std::vector<std::shared_ptr<InFlightRequest>> inflightReqs(kBurstFrameCount);
        std::vector<int32_t> isoValues(kBurstFrameCount);
        std::vector<CameraMetadata> requestSettings(kBurstFrameCount);

        for (int32_t i = 0; i < kBurstFrameCount; i++) {
            std::unique_lock<std::mutex> l(mLock);
            CaptureRequest& request = requests[i];
            std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
            outputBuffers.resize(1);
            StreamBuffer& outputBuffer = outputBuffers[0];

            isoValues[i] = ((i % 2) == 0) ? isoRange.data.i32[0] : isoRange.data.i32[1];
            if (useHalBufManager) {
                outputBuffer = {halStreams[0].id, 0,
                                NativeHandle(),   BufferStatus::OK,
                                NativeHandle(),   NativeHandle()};
            } else {
                allocateGraphicBuffer(previewStream.width, previewStream.height,
                                      ANDROID_NATIVE_UNSIGNED_CAST(android_convertGralloc1To0Usage(
                                              static_cast<uint64_t>(halStreams[0].producerUsage),
                                              static_cast<uint64_t>(halStreams[0].consumerUsage))),
                                      halStreams[0].overrideFormat, &buffers[i]);
                outputBuffer = {halStreams[0].id, bufferId + i,   ::android::makeToAidl(buffers[i]),
                                BufferStatus::OK, NativeHandle(), NativeHandle()};
            }

            requestMeta.append(reinterpret_cast<camera_metadata_t*>(settings.metadata.data()));

            // Disable all 3A routines
            uint8_t mode = static_cast<uint8_t>(ANDROID_CONTROL_MODE_OFF);
            ASSERT_EQ(::android::OK, requestMeta.update(ANDROID_CONTROL_MODE, &mode, 1));
            ASSERT_EQ(::android::OK,
                      requestMeta.update(ANDROID_SENSOR_SENSITIVITY, &isoValues[i], 1));
            camera_metadata_t* metaBuffer = requestMeta.release();
            uint8_t* rawMetaBuffer = reinterpret_cast<uint8_t*>(metaBuffer);
            requestSettings[i].metadata = std::vector(
                    rawMetaBuffer, rawMetaBuffer + get_camera_metadata_size(metaBuffer));
            overrideRotateAndCrop(&(requestSettings[i]));

            request.frameNumber = frameNumber + i;
            request.fmqSettingsSize = 0;
            request.settings = requestSettings[i];
            request.inputBuffer = {
                    -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};

            inflightReqs[i] = std::make_shared<InFlightRequest>(1, false, supportsPartialResults,
                                                                partialResultCount, resultQueue);
            mInflightMap[frameNumber + i] = inflightReqs[i];
        }

        int32_t numRequestProcessed = 0;
        std::vector<BufferCache> cachesToRemove;

        ndk::ScopedAStatus returnStatus =
                mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
        ASSERT_TRUE(returnStatus.isOk());
        ASSERT_EQ(numRequestProcessed, kBurstFrameCount);

        for (size_t i = 0; i < kBurstFrameCount; i++) {
            std::unique_lock<std::mutex> l(mLock);
            while (!inflightReqs[i]->errorCodeValid && ((0 < inflightReqs[i]->numBuffersLeft) ||
                                                        (!inflightReqs[i]->haveResultMetadata))) {
                auto timeout = std::chrono::system_clock::now() +
                               std::chrono::seconds(kStreamBufferTimeoutSec);
                ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout));
            }

            ASSERT_FALSE(inflightReqs[i]->errorCodeValid);
            ASSERT_NE(inflightReqs[i]->resultOutputBuffers.size(), 0u);
            ASSERT_EQ(previewStream.id, inflightReqs[i]->resultOutputBuffers[0].buffer.streamId);
            ASSERT_FALSE(inflightReqs[i]->collectedResult.isEmpty());
            ASSERT_TRUE(inflightReqs[i]->collectedResult.exists(ANDROID_SENSOR_SENSITIVITY));
            camera_metadata_entry_t isoResult =
                    inflightReqs[i]->collectedResult.find(ANDROID_SENSOR_SENSITIVITY);
            ASSERT_TRUE(std::abs(isoResult.data.i32[0] - isoValues[i]) <=
                        std::round(isoValues[i] * isoTol));
        }

        if (useHalBufManager) {
            verifyBuffersReturned(mSession, previewStream.id, cb);
        }
        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Test whether an incorrect capture request with missing settings will
// be reported correctly.
TEST_P(CameraAidlTest, processCaptureRequestInvalidSinglePreview) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputPreviewStreams;
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};
    int64_t bufferId = 1;
    int32_t frameNumber = 1;
    CameraMetadata settings;

    for (const auto& name : cameraDeviceNames) {
        Stream previewStream;
        std::vector<HalStream> halStreams;
        std::shared_ptr<DeviceCb> cb;
        bool supportsPartialResults = false;
        bool useHalBufManager = false;
        int32_t partialResultCount = 0;
        configurePreviewStream(name, mProvider, &previewThreshold, &mSession /*out*/,
                               &previewStream /*out*/, &halStreams /*out*/,
                               &supportsPartialResults /*out*/, &partialResultCount /*out*/,
                               &useHalBufManager /*out*/, &cb /*out*/);
        ASSERT_NE(mSession, nullptr);
        ASSERT_FALSE(halStreams.empty());

        buffer_handle_t buffer_handle = nullptr;

        if (useHalBufManager) {
            bufferId = 0;
        } else {
            allocateGraphicBuffer(previewStream.width, previewStream.height,
                                  ANDROID_NATIVE_UNSIGNED_CAST(android_convertGralloc1To0Usage(
                                          static_cast<uint64_t>(halStreams[0].producerUsage),
                                          static_cast<uint64_t>(halStreams[0].consumerUsage))),
                                  halStreams[0].overrideFormat, &buffer_handle);
        }

        std::vector<CaptureRequest> requests(1);
        CaptureRequest& request = requests[0];
        std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
        outputBuffers.resize(1);
        StreamBuffer& outputBuffer = outputBuffers[0];

        outputBuffer = {
                halStreams[0].id,
                bufferId,
                buffer_handle == nullptr ? NativeHandle() : ::android::makeToAidl(buffer_handle),
                BufferStatus::OK,
                NativeHandle(),
                NativeHandle()};

        request.inputBuffer = {
                -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};
        request.frameNumber = frameNumber;
        request.fmqSettingsSize = 0;
        request.settings = settings;

        // Settings were not correctly initialized, we should fail here
        int32_t numRequestProcessed = 0;
        std::vector<BufferCache> cachesToRemove;
        ndk::ScopedAStatus ret =
                mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
        ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), ret.getServiceSpecificError());
        ASSERT_EQ(numRequestProcessed, 0u);

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Verify camera offline session behavior
TEST_P(CameraAidlTest, switchToOffline) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    AvailableStream threshold = {kMaxStillWidth, kMaxStillHeight,
                                 static_cast<int32_t>(PixelFormat::BLOB)};
    int64_t bufferId = 1;
    int32_t frameNumber = 1;
    CameraMetadata settings;

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;
        {
            std::shared_ptr<ICameraDevice> unusedDevice;
            openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                                   &unusedDevice);
            camera_metadata_t* staticMetaBuffer = clone_camera_metadata(
                    reinterpret_cast<camera_metadata_t*>(meta.metadata.data()));
            ::android::hardware::camera::common::V1_0::helper::CameraMetadata staticMeta(
                    staticMetaBuffer);

            if (isOfflineSessionSupported(staticMetaBuffer) != Status::OK) {
                ndk::ScopedAStatus ret = mSession->close();
                mSession = nullptr;
                ASSERT_TRUE(ret.isOk());
                continue;
            }
            ndk::ScopedAStatus ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
        }

        bool supportsPartialResults = false;
        int32_t partialResultCount = 0;
        Stream stream;
        std::vector<HalStream> halStreams;
        std::shared_ptr<DeviceCb> cb;
        int32_t jpegBufferSize;
        std::set<int32_t> halBufManagedStreamIds;
        configureOfflineStillStream(name, mProvider, &threshold, &mSession /*out*/, &stream /*out*/,
                                    &halStreams /*out*/, &supportsPartialResults /*out*/,
                                    &partialResultCount /*out*/, &cb /*out*/,
                                    &jpegBufferSize /*out*/, &halBufManagedStreamIds /*out*/);

        auto ret = mSession->constructDefaultRequestSettings(RequestTemplate::STILL_CAPTURE,
                                                             &settings);
        ASSERT_TRUE(ret.isOk());

        ::aidl::android::hardware::common::fmq::MQDescriptor<
                int8_t, aidl::android::hardware::common::fmq::SynchronizedReadWrite>
                descriptor;

        ndk::ScopedAStatus resultQueueRet = mSession->getCaptureResultMetadataQueue(&descriptor);
        ASSERT_TRUE(resultQueueRet.isOk());
        std::shared_ptr<ResultMetadataQueue> resultQueue =
                std::make_shared<ResultMetadataQueue>(descriptor);
        if (!resultQueue->isValid() || resultQueue->availableToWrite() <= 0) {
            ALOGE("%s: HAL returns empty result metadata fmq, not use it", __func__);
            resultQueue = nullptr;
            // Don't use the queue onwards.
        }

        ::android::hardware::camera::common::V1_0::helper::CameraMetadata requestMeta;

        std::vector<buffer_handle_t> buffers(kBurstFrameCount);
        std::vector<std::shared_ptr<InFlightRequest>> inflightReqs(kBurstFrameCount);
        std::vector<CameraMetadata> requestSettings(kBurstFrameCount);

        std::vector<CaptureRequest> requests(kBurstFrameCount);

        HalStream halStream = halStreams[0];
        for (uint32_t i = 0; i < kBurstFrameCount; i++) {
            CaptureRequest& request = requests[i];
            std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
            outputBuffers.resize(1);
            StreamBuffer& outputBuffer = outputBuffers[0];

            std::unique_lock<std::mutex> l(mLock);
            if (contains(halBufManagedStreamIds, halStream.id)) {
                outputBuffer = {halStream.id,  0, NativeHandle(), BufferStatus::OK, NativeHandle(),
                                NativeHandle()};
            } else {
                // jpeg buffer (w,h) = (blobLen, 1)
                allocateGraphicBuffer(jpegBufferSize, /*height*/ 1,
                                      ANDROID_NATIVE_UNSIGNED_CAST(android_convertGralloc1To0Usage(
                                              static_cast<uint64_t>(halStream.producerUsage),
                                              static_cast<uint64_t>(halStream.consumerUsage))),
                                      halStream.overrideFormat, &buffers[i]);
                outputBuffer = {halStream.id,     bufferId + i,   ::android::makeToAidl(buffers[i]),
                                BufferStatus::OK, NativeHandle(), NativeHandle()};
            }

            requestMeta.clear();
            requestMeta.append(reinterpret_cast<camera_metadata_t*>(settings.metadata.data()));

            camera_metadata_t* metaBuffer = requestMeta.release();
            uint8_t* rawMetaBuffer = reinterpret_cast<uint8_t*>(metaBuffer);
            requestSettings[i].metadata = std::vector(
                    rawMetaBuffer, rawMetaBuffer + get_camera_metadata_size(metaBuffer));
            overrideRotateAndCrop(&requestSettings[i]);

            request.frameNumber = frameNumber + i;
            request.fmqSettingsSize = 0;
            request.settings = requestSettings[i];
            request.inputBuffer = {/*streamId*/ -1,
                                   /*bufferId*/ 0,      NativeHandle(),
                                   BufferStatus::ERROR, NativeHandle(),
                                   NativeHandle()};

            inflightReqs[i] = std::make_shared<InFlightRequest>(1, false, supportsPartialResults,
                                                                partialResultCount, resultQueue);
            mInflightMap[frameNumber + i] = inflightReqs[i];
        }

        int32_t numRequestProcessed = 0;
        std::vector<BufferCache> cachesToRemove;

        ndk::ScopedAStatus returnStatus =
                mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
        ASSERT_TRUE(returnStatus.isOk());
        ASSERT_EQ(numRequestProcessed, kBurstFrameCount);

        std::vector<int32_t> offlineStreamIds = {halStream.id};
        CameraOfflineSessionInfo offlineSessionInfo;
        std::shared_ptr<ICameraOfflineSession> offlineSession;
        returnStatus =
                mSession->switchToOffline(offlineStreamIds, &offlineSessionInfo, &offlineSession);

        if (!halStreams[0].supportOffline) {
            ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT),
                      returnStatus.getServiceSpecificError());
            ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        ASSERT_TRUE(returnStatus.isOk());
        // Hal might be unable to find any requests qualified for offline mode.
        if (offlineSession == nullptr) {
            ret = mSession->close();
            mSession = nullptr;
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        ASSERT_EQ(offlineSessionInfo.offlineStreams.size(), 1u);
        ASSERT_EQ(offlineSessionInfo.offlineStreams[0].id, halStream.id);
        ASSERT_NE(offlineSessionInfo.offlineRequests.size(), 0u);

        // close device session to make sure offline session does not rely on it
        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());

        ::aidl::android::hardware::common::fmq::MQDescriptor<
                int8_t, aidl::android::hardware::common::fmq::SynchronizedReadWrite>
                offlineResultDescriptor;

        auto offlineResultQueueRet =
                offlineSession->getCaptureResultMetadataQueue(&offlineResultDescriptor);
        std::shared_ptr<ResultMetadataQueue> offlineResultQueue =
                std::make_shared<ResultMetadataQueue>(descriptor);
        if (!offlineResultQueue->isValid() || offlineResultQueue->availableToWrite() <= 0) {
            ALOGE("%s: offline session returns empty result metadata fmq, not use it", __func__);
            offlineResultQueue = nullptr;
            // Don't use the queue onwards.
        }
        ASSERT_TRUE(offlineResultQueueRet.isOk());

        updateInflightResultQueue(offlineResultQueue);

        ret = offlineSession->setCallback(cb);
        ASSERT_TRUE(ret.isOk());

        for (size_t i = 0; i < kBurstFrameCount; i++) {
            std::unique_lock<std::mutex> l(mLock);
            while (!inflightReqs[i]->errorCodeValid && ((0 < inflightReqs[i]->numBuffersLeft) ||
                                                        (!inflightReqs[i]->haveResultMetadata))) {
                auto timeout = std::chrono::system_clock::now() +
                               std::chrono::seconds(kStreamBufferTimeoutSec);
                ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout));
            }

            ASSERT_FALSE(inflightReqs[i]->errorCodeValid);
            ASSERT_NE(inflightReqs[i]->resultOutputBuffers.size(), 0u);
            ASSERT_EQ(stream.id, inflightReqs[i]->resultOutputBuffers[0].buffer.streamId);
            ASSERT_FALSE(inflightReqs[i]->collectedResult.isEmpty());
        }

        ret = offlineSession->close();
        ASSERT_TRUE(ret.isOk());
    }
}

// Check whether an invalid capture request with missing output buffers
// will be reported correctly.
TEST_P(CameraAidlTest, processCaptureRequestInvalidBuffer) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputBlobStreams;
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};
    int32_t frameNumber = 1;
    CameraMetadata settings;

    for (const auto& name : cameraDeviceNames) {
        Stream previewStream;
        std::vector<HalStream> halStreams;
        std::shared_ptr<DeviceCb> cb;
        bool supportsPartialResults = false;
        bool useHalBufManager = false;
        int32_t partialResultCount = 0;
        configurePreviewStream(name, mProvider, &previewThreshold, &mSession /*out*/,
                               &previewStream /*out*/, &halStreams /*out*/,
                               &supportsPartialResults /*out*/, &partialResultCount /*out*/,
                               &useHalBufManager /*out*/, &cb /*out*/);

        RequestTemplate reqTemplate = RequestTemplate::PREVIEW;
        ndk::ScopedAStatus ret = mSession->constructDefaultRequestSettings(reqTemplate, &settings);
        ASSERT_TRUE(ret.isOk());
        overrideRotateAndCrop(&settings);

        std::vector<CaptureRequest> requests(1);
        CaptureRequest& request = requests[0];
        std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
        outputBuffers.resize(1);
        // Empty output buffer
        outputBuffers[0] = {
                -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};

        request.inputBuffer = {
                -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};
        request.frameNumber = frameNumber;
        request.fmqSettingsSize = 0;
        request.settings = settings;

        // Output buffers are missing, we should fail here
        int32_t numRequestProcessed = 0;
        std::vector<BufferCache> cachesToRemove;
        ret = mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
        ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), ret.getServiceSpecificError());
        ASSERT_EQ(numRequestProcessed, 0u);

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Generate, trigger and flush a preview request
TEST_P(CameraAidlTest, flushPreviewRequest) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputPreviewStreams;
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};
    int64_t bufferId = 1;
    int32_t frameNumber = 1;
    CameraMetadata settings;

    for (const auto& name : cameraDeviceNames) {
        Stream previewStream;
        std::vector<HalStream> halStreams;
        std::shared_ptr<DeviceCb> cb;
        bool supportsPartialResults = false;
        bool useHalBufManager = false;
        int32_t partialResultCount = 0;

        configurePreviewStream(name, mProvider, &previewThreshold, &mSession /*out*/,
                               &previewStream /*out*/, &halStreams /*out*/,
                               &supportsPartialResults /*out*/, &partialResultCount /*out*/,
                               &useHalBufManager /*out*/, &cb /*out*/);

        ASSERT_NE(mSession, nullptr);
        ASSERT_NE(cb, nullptr);
        ASSERT_FALSE(halStreams.empty());

        ::aidl::android::hardware::common::fmq::MQDescriptor<
                int8_t, aidl::android::hardware::common::fmq::SynchronizedReadWrite>
                descriptor;

        auto resultQueueRet = mSession->getCaptureResultMetadataQueue(&descriptor);
        std::shared_ptr<ResultMetadataQueue> resultQueue =
                std::make_shared<ResultMetadataQueue>(descriptor);
        ASSERT_TRUE(resultQueueRet.isOk());
        if (!resultQueue->isValid() || resultQueue->availableToWrite() <= 0) {
            ALOGE("%s: HAL returns empty result metadata fmq, not use it", __func__);
            resultQueue = nullptr;
            // Don't use the queue onwards.
        }

        std::shared_ptr<InFlightRequest> inflightReq = std::make_shared<InFlightRequest>(
                1, false, supportsPartialResults, partialResultCount, resultQueue);
        RequestTemplate reqTemplate = RequestTemplate::PREVIEW;

        ndk::ScopedAStatus ret = mSession->constructDefaultRequestSettings(reqTemplate, &settings);
        ASSERT_TRUE(ret.isOk());
        overrideRotateAndCrop(&settings);

        buffer_handle_t buffer_handle;
        std::vector<CaptureRequest> requests(1);
        CaptureRequest& request = requests[0];
        std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
        outputBuffers.resize(1);
        StreamBuffer& outputBuffer = outputBuffers[0];
        if (useHalBufManager) {
            bufferId = 0;
            outputBuffer = {halStreams[0].id, bufferId,       NativeHandle(),
                            BufferStatus::OK, NativeHandle(), NativeHandle()};
        } else {
            allocateGraphicBuffer(previewStream.width, previewStream.height,
                                  ANDROID_NATIVE_UNSIGNED_CAST(android_convertGralloc1To0Usage(
                                          static_cast<uint64_t>(halStreams[0].producerUsage),
                                          static_cast<uint64_t>(halStreams[0].consumerUsage))),
                                  halStreams[0].overrideFormat, &buffer_handle);
            outputBuffer = {halStreams[0].id, bufferId,       ::android::makeToAidl(buffer_handle),
                            BufferStatus::OK, NativeHandle(), NativeHandle()};
        }

        request.frameNumber = frameNumber;
        request.fmqSettingsSize = 0;
        request.settings = settings;
        request.inputBuffer = {
                -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};

        {
            std::unique_lock<std::mutex> l(mLock);
            mInflightMap.clear();
            mInflightMap[frameNumber] = inflightReq;
        }

        int32_t numRequestProcessed = 0;
        std::vector<BufferCache> cachesToRemove;
        ret = mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
        ASSERT_TRUE(ret.isOk());
        ASSERT_EQ(numRequestProcessed, 1u);

        // Flush before waiting for request to complete.
        ndk::ScopedAStatus returnStatus = mSession->flush();
        ASSERT_TRUE(returnStatus.isOk());

        {
            std::unique_lock<std::mutex> l(mLock);
            while (!inflightReq->errorCodeValid &&
                   ((0 < inflightReq->numBuffersLeft) || (!inflightReq->haveResultMetadata))) {
                auto timeout = std::chrono::system_clock::now() +
                               std::chrono::seconds(kStreamBufferTimeoutSec);
                ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout));
            }

            if (!inflightReq->errorCodeValid) {
                ASSERT_NE(inflightReq->resultOutputBuffers.size(), 0u);
                ASSERT_EQ(previewStream.id, inflightReq->resultOutputBuffers[0].buffer.streamId);
            } else {
                switch (inflightReq->errorCode) {
                    case ErrorCode::ERROR_REQUEST:
                    case ErrorCode::ERROR_RESULT:
                    case ErrorCode::ERROR_BUFFER:
                        // Expected
                        break;
                    case ErrorCode::ERROR_DEVICE:
                    default:
                        FAIL() << "Unexpected error:"
                               << static_cast<uint32_t>(inflightReq->errorCode);
                }
            }
        }

        if (useHalBufManager) {
            verifyBuffersReturned(mSession, previewStream.id, cb);
        }

        ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Verify that camera flushes correctly without any pending requests.
TEST_P(CameraAidlTest, flushEmpty) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputPreviewStreams;
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};

    for (const auto& name : cameraDeviceNames) {
        Stream previewStream;
        std::vector<HalStream> halStreams;
        std::shared_ptr<DeviceCb> cb;
        bool supportsPartialResults = false;
        bool useHalBufManager = false;

        int32_t partialResultCount = 0;
        configurePreviewStream(name, mProvider, &previewThreshold, &mSession /*out*/,
                               &previewStream /*out*/, &halStreams /*out*/,
                               &supportsPartialResults /*out*/, &partialResultCount /*out*/,
                               &useHalBufManager /*out*/, &cb /*out*/);

        ndk::ScopedAStatus returnStatus = mSession->flush();
        ASSERT_TRUE(returnStatus.isOk());

        {
            std::unique_lock<std::mutex> l(mLock);
            auto timeout = std::chrono::system_clock::now() +
                           std::chrono::milliseconds(kEmptyFlushTimeoutMSec);
            ASSERT_EQ(std::cv_status::timeout, mResultCondition.wait_until(l, timeout));
        }

        ndk::ScopedAStatus ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

// Test camera provider notify method
TEST_P(CameraAidlTest, providerDeviceStateNotification) {
    notifyDeviceState(ICameraProvider::DEVICE_STATE_BACK_COVERED);
    notifyDeviceState(ICameraProvider::DEVICE_STATE_NORMAL);
}

// Verify that all supported stream formats and sizes can be configured
// successfully for injection camera.
TEST_P(CameraAidlTest, configureInjectionStreamsAvailableOutputs) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputStreams;

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata metadata;

        std::shared_ptr<ICameraInjectionSession> injectionSession;
        std::shared_ptr<ICameraDevice> unusedDevice;
        openEmptyInjectionSession(name, mProvider, &injectionSession /*out*/, &metadata /*out*/,
                                  &unusedDevice /*out*/);
        if (injectionSession == nullptr) {
            continue;
        }

        camera_metadata_t* staticMetaBuffer =
                reinterpret_cast<camera_metadata_t*>(metadata.metadata.data());
        CameraMetadata chars;
        chars.metadata = metadata.metadata;

        outputStreams.clear();
        ASSERT_EQ(Status::OK, getAvailableOutputStreams(staticMetaBuffer, outputStreams));
        ASSERT_NE(0u, outputStreams.size());

        int32_t jpegBufferSize = 0;
        ASSERT_EQ(Status::OK, getJpegBufferSize(staticMetaBuffer, &jpegBufferSize));
        ASSERT_NE(0u, jpegBufferSize);

        int32_t streamId = 0;
        int32_t streamConfigCounter = 0;
        for (auto& it : outputStreams) {
            Dataspace dataspace = getDataspace(static_cast<PixelFormat>(it.format));
            Stream stream = {streamId,
                             StreamType::OUTPUT,
                             it.width,
                             it.height,
                             static_cast<PixelFormat>(it.format),
                             static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                     GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                             dataspace,
                             StreamRotation::ROTATION_0,
                             std::string(),
                             jpegBufferSize,
                             0,
                             {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                             RequestAvailableDynamicRangeProfilesMap::
                                     ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

            std::vector<Stream> streams = {stream};
            StreamConfiguration config;
            createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                      jpegBufferSize);

            config.streamConfigCounter = streamConfigCounter++;
            ndk::ScopedAStatus s = injectionSession->configureInjectionStreams(config, chars);
            ASSERT_TRUE(s.isOk());
            streamId++;
        }

        std::shared_ptr<ICameraDeviceSession> session;
        ndk::ScopedAStatus ret = injectionSession->getCameraDeviceSession(&session);
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(session, nullptr);
        ret = session->close();
        ASSERT_TRUE(ret.isOk());
    }
}

// Check for correct handling of invalid/incorrect configuration parameters for injection camera.
TEST_P(CameraAidlTest, configureInjectionStreamsInvalidOutputs) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputStreams;

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata metadata;
        std::shared_ptr<ICameraInjectionSession> injectionSession;
        std::shared_ptr<ICameraDevice> unusedDevice;
        openEmptyInjectionSession(name, mProvider, &injectionSession /*out*/, &metadata /*out*/,
                                  &unusedDevice);
        if (injectionSession == nullptr) {
            continue;
        }

        camera_metadata_t* staticMetaBuffer =
                reinterpret_cast<camera_metadata_t*>(metadata.metadata.data());
        std::shared_ptr<ICameraDeviceSession> session;
        ndk::ScopedAStatus ret = injectionSession->getCameraDeviceSession(&session);
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(session, nullptr);

        CameraMetadata chars;
        chars.metadata = metadata.metadata;

        outputStreams.clear();
        ASSERT_EQ(Status::OK, getAvailableOutputStreams(staticMetaBuffer, outputStreams));
        ASSERT_NE(0u, outputStreams.size());

        int32_t jpegBufferSize = 0;
        ASSERT_EQ(Status::OK, getJpegBufferSize(staticMetaBuffer, &jpegBufferSize));
        ASSERT_NE(0u, jpegBufferSize);

        int32_t streamId = 0;
        Stream stream = {streamId++,
                         StreamType::OUTPUT,
                         0,
                         0,
                         static_cast<PixelFormat>(outputStreams[0].format),
                         static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                                 GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                         Dataspace::UNKNOWN,
                         StreamRotation::ROTATION_0,
                         std::string(),
                         jpegBufferSize,
                         0,
                         {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                         RequestAvailableDynamicRangeProfilesMap::
                                 ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

        int32_t streamConfigCounter = 0;
        std::vector<Stream> streams = {stream};
        StreamConfiguration config;
        createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                  jpegBufferSize);

        config.streamConfigCounter = streamConfigCounter++;
        ndk::ScopedAStatus s = injectionSession->configureInjectionStreams(config, chars);
        ASSERT_TRUE(
                (static_cast<int32_t>(Status::ILLEGAL_ARGUMENT) == s.getServiceSpecificError()) ||
                (static_cast<int32_t>(Status::INTERNAL_ERROR) == s.getServiceSpecificError()));

        stream = {streamId++,
                  StreamType::OUTPUT,
                  INT32_MAX,
                  INT32_MAX,
                  static_cast<PixelFormat>(outputStreams[0].format),
                  static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                          GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                  Dataspace::UNKNOWN,
                  StreamRotation::ROTATION_0,
                  std::string(),
                  jpegBufferSize,
                  0,
                  {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                  RequestAvailableDynamicRangeProfilesMap::
                          ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};

        streams[0] = stream;
        createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                  jpegBufferSize);
        config.streamConfigCounter = streamConfigCounter++;
        s = injectionSession->configureInjectionStreams(config, chars);
        ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), s.getServiceSpecificError());

        for (auto& it : outputStreams) {
            stream = {streamId++,
                      StreamType::OUTPUT,
                      it.width,
                      it.height,
                      static_cast<PixelFormat>(INT32_MAX),
                      static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                              GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                      Dataspace::UNKNOWN,
                      StreamRotation::ROTATION_0,
                      std::string(),
                      jpegBufferSize,
                      0,
                      {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                      RequestAvailableDynamicRangeProfilesMap::
                              ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
            streams[0] = stream;
            createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                      jpegBufferSize);
            config.streamConfigCounter = streamConfigCounter++;
            s = injectionSession->configureInjectionStreams(config, chars);
            ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), s.getServiceSpecificError());

            stream = {streamId++,
                      StreamType::OUTPUT,
                      it.width,
                      it.height,
                      static_cast<PixelFormat>(it.format),
                      static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
                              GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                      Dataspace::UNKNOWN,
                      static_cast<StreamRotation>(INT32_MAX),
                      std::string(),
                      jpegBufferSize,
                      0,
                      {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                      RequestAvailableDynamicRangeProfilesMap::
                              ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
            streams[0] = stream;
            createStreamConfiguration(streams, StreamConfigurationMode::NORMAL_MODE, &config,
                                      jpegBufferSize);
            config.streamConfigCounter = streamConfigCounter++;
            s = injectionSession->configureInjectionStreams(config, chars);
            ASSERT_EQ(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT), s.getServiceSpecificError());
        }

        ret = session->close();
        ASSERT_TRUE(ret.isOk());
    }
}

// Check whether session parameters are supported for injection camera. If Hal support for them
// exist, then try to configure a preview stream using them.
TEST_P(CameraAidlTest, configureInjectionStreamsWithSessionParameters) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputPreviewStreams;
    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata metadata;
        std::shared_ptr<ICameraInjectionSession> injectionSession;
        std::shared_ptr<ICameraDevice> unusedDevice;
        openEmptyInjectionSession(name, mProvider, &injectionSession /*out*/, &metadata /*out*/,
                                  &unusedDevice /*out*/);
        if (injectionSession == nullptr) {
            continue;
        }

        std::shared_ptr<ICameraDeviceSession> session;
        ndk::ScopedAStatus ret = injectionSession->getCameraDeviceSession(&session);
        ASSERT_TRUE(ret.isOk());
        ASSERT_NE(session, nullptr);

        camera_metadata_t* staticMetaBuffer =
                reinterpret_cast<camera_metadata_t*>(metadata.metadata.data());
        CameraMetadata chars;
        chars.metadata = metadata.metadata;

        std::unordered_set<int32_t> availableSessionKeys;
        Status rc = getSupportedKeys(staticMetaBuffer, ANDROID_REQUEST_AVAILABLE_SESSION_KEYS,
                                     &availableSessionKeys);
        ASSERT_EQ(Status::OK, rc);
        if (availableSessionKeys.empty()) {
            ret = session->close();
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        android::hardware::camera::common::V1_0::helper::CameraMetadata previewRequestSettings;
        android::hardware::camera::common::V1_0::helper::CameraMetadata sessionParams,
                modifiedSessionParams;
        constructFilteredSettings(session, availableSessionKeys, RequestTemplate::PREVIEW,
                                  &previewRequestSettings, &sessionParams);
        if (sessionParams.isEmpty()) {
            ret = session->close();
            ASSERT_TRUE(ret.isOk());
            continue;
        }

        outputPreviewStreams.clear();

        ASSERT_EQ(Status::OK, getAvailableOutputStreams(staticMetaBuffer, outputPreviewStreams,
                                                        &previewThreshold));
        ASSERT_NE(0u, outputPreviewStreams.size());

        Stream previewStream = {
                0,
                StreamType::OUTPUT,
                outputPreviewStreams[0].width,
                outputPreviewStreams[0].height,
                static_cast<PixelFormat>(outputPreviewStreams[0].format),
                static_cast<::aidl::android::hardware::graphics::common::BufferUsage>(
                        GRALLOC1_CONSUMER_USAGE_HWCOMPOSER),
                Dataspace::UNKNOWN,
                StreamRotation::ROTATION_0,
                std::string(),
                0,
                -1,
                {SensorPixelMode::ANDROID_SENSOR_PIXEL_MODE_DEFAULT},
                RequestAvailableDynamicRangeProfilesMap::
                        ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD};
        std::vector<Stream> streams = {previewStream};
        StreamConfiguration config;
        config.streams = streams;
        config.operationMode = StreamConfigurationMode::NORMAL_MODE;

        modifiedSessionParams = sessionParams;
        camera_metadata_t* sessionParamsBuffer = sessionParams.release();
        uint8_t* rawSessionParamsBuffer = reinterpret_cast<uint8_t*>(sessionParamsBuffer);
        config.sessionParams.metadata =
                std::vector(rawSessionParamsBuffer,
                            rawSessionParamsBuffer + get_camera_metadata_size(sessionParamsBuffer));

        config.streamConfigCounter = 0;
        config.streamConfigCounter = 0;
        config.multiResolutionInputImage = false;

        ndk::ScopedAStatus s = injectionSession->configureInjectionStreams(config, chars);
        ASSERT_TRUE(s.isOk());

        sessionParams.acquire(sessionParamsBuffer);
        free_camera_metadata(staticMetaBuffer);
        ret = session->close();
        ASSERT_TRUE(ret.isOk());
    }
}

TEST_P(CameraAidlTest, configureStreamsUseCasesCroppedRaw) {
    AvailableStream rawStreamThreshold =
            {INT_MAX, INT_MAX, static_cast<int32_t>(PixelFormat::RAW16)};
    configureStreamUseCaseInternal(rawStreamThreshold);
}

// Verify that  valid stream use cases can be configured successfully, and invalid use cases
// fail stream configuration.
TEST_P(CameraAidlTest, configureStreamsUseCases) {
    AvailableStream previewStreamThreshold =
            {kMaxPreviewWidth, kMaxPreviewHeight, static_cast<int32_t>(PixelFormat::YCBCR_420_888)};
    configureStreamUseCaseInternal(previewStreamThreshold);
}

// Validate the integrity of stream configuration metadata
TEST_P(CameraAidlTest, validateStreamConfigurations) {
    std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
    std::vector<AvailableStream> outputStreams;

    const int32_t scalerSizesTag = ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS;
    const int32_t scalerMinFrameDurationsTag = ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS;
    const int32_t scalerStallDurationsTag = ANDROID_SCALER_AVAILABLE_STALL_DURATIONS;

    for (const auto& name : cameraDeviceNames) {
        CameraMetadata meta;
        std::shared_ptr<ICameraDevice> cameraDevice;

        openEmptyDeviceSession(name, mProvider, &mSession /*out*/, &meta /*out*/,
                               &cameraDevice /*out*/);
        camera_metadata_t* staticMeta = reinterpret_cast<camera_metadata_t*>(meta.metadata.data());

        if (is10BitDynamicRangeCapable(staticMeta)) {
            std::vector<std::tuple<size_t, size_t>> supportedP010Sizes, supportedBlobSizes;

            getSupportedSizes(staticMeta, scalerSizesTag, HAL_PIXEL_FORMAT_BLOB,
                              &supportedBlobSizes);
            getSupportedSizes(staticMeta, scalerSizesTag, HAL_PIXEL_FORMAT_YCBCR_P010,
                              &supportedP010Sizes);
            ASSERT_FALSE(supportedP010Sizes.empty());

            std::vector<int64_t> blobMinDurations, blobStallDurations;
            getSupportedDurations(staticMeta, scalerMinFrameDurationsTag, HAL_PIXEL_FORMAT_BLOB,
                                  supportedP010Sizes, &blobMinDurations);
            getSupportedDurations(staticMeta, scalerStallDurationsTag, HAL_PIXEL_FORMAT_BLOB,
                                  supportedP010Sizes, &blobStallDurations);
            ASSERT_FALSE(blobStallDurations.empty());
            ASSERT_FALSE(blobMinDurations.empty());
            ASSERT_EQ(supportedP010Sizes.size(), blobMinDurations.size());
            ASSERT_EQ(blobMinDurations.size(), blobStallDurations.size());
        }

        // TODO (b/280887191): Validate other aspects of stream configuration metadata...

        ndk::ScopedAStatus ret = mSession->close();
        mSession = nullptr;
        ASSERT_TRUE(ret.isOk());
    }
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CameraAidlTest);
INSTANTIATE_TEST_SUITE_P(
        PerInstance, CameraAidlTest,
        testing::ValuesIn(android::getAidlHalInstanceNames(ICameraProvider::descriptor)),
        android::hardware::PrintInstanceNameToString);
