/*
 * Copyright 2018 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "Codec2Client"
#define ATRACE_TAG  ATRACE_TAG_VIDEO
#include <android-base/logging.h>
#include <utils/Trace.h>

#include <android_media_codec.h>

#include <codec2/aidl/GraphicBufferAllocator.h>
#include <codec2/common/HalSelection.h>
#include <codec2/hidl/client.h>

#include <C2Debug.h>
#include <C2BufferPriv.h>
#include <C2Config.h> // for C2StreamUsageTuning
#include <C2PlatformSupport.h>

#include <android/hardware/media/bufferpool/2.0/IClientManager.h>
#include <android/hardware/media/c2/1.0/IComponent.h>
#include <android/hardware/media/c2/1.0/IComponentInterface.h>
#include <android/hardware/media/c2/1.0/IComponentListener.h>
#include <android/hardware/media/c2/1.0/IComponentStore.h>
#include <android/hardware/media/c2/1.0/IConfigurable.h>
#include <android/hidl/manager/1.2/IServiceManager.h>

#include <aidl/android/hardware/media/bufferpool2/IClientManager.h>
#include <aidl/android/hardware/media/c2/BnComponentListener.h>
#include <aidl/android/hardware/media/c2/FieldSupportedValues.h>
#include <aidl/android/hardware/media/c2/FieldSupportedValuesQuery.h>
#include <aidl/android/hardware/media/c2/FieldSupportedValuesQueryResult.h>
#include <aidl/android/hardware/media/c2/IComponent.h>
#include <aidl/android/hardware/media/c2/IComponentInterface.h>
#include <aidl/android/hardware/media/c2/IComponentStore.h>
#include <aidl/android/hardware/media/c2/IConfigurable.h>
#include <aidl/android/hardware/media/c2/ParamDescriptor.h>
#include <aidl/android/hardware/media/c2/StructDescriptor.h>

#include <aidlcommonsupport/NativeHandle.h>
#include <android/api-level.h>
#include <android/binder_auto_utils.h>
#include <android/binder_ibinder.h>
#include <android/binder_manager.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <apex/ApexCodecs.h>
#include <bufferpool/ClientManager.h>
#include <bufferpool2/ClientManager.h>
#include <codec2/aidl/BufferTypes.h>
#include <codec2/aidl/ParamTypes.h>
#include <codec2/hidl/1.0/types.h>
#include <codec2/hidl/1.1/types.h>
#include <codec2/hidl/1.2/types.h>
#include <codec2/hidl/output.h>
#include <cutils/native_handle.h>
#include <gui/bufferqueue/2.0/B2HGraphicBufferProducer.h>
#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
#include <hardware/gralloc.h> // for GRALLOC_USAGE_*
#include <hidl/HidlSupport.h>
#include <media/stagefright/foundation/ADebug.h> // for asString(status_t)
#include <private/android/AHardwareBufferHelpers.h>
#include <system/window.h> // for NATIVE_WINDOW_QUERY_*

#include <deque>
#include <iterator>
#include <limits>
#include <map>
#include <mutex>
#include <optional>
#include <sstream>
#include <thread>
#include <type_traits>
#include <vector>

namespace android {

using ::android::hardware::hidl_vec;
using ::android::hardware::hidl_string;
using ::android::hardware::Return;
using ::android::hardware::Void;

using HGraphicBufferProducer1 = ::android::hardware::graphics::bufferqueue::
        V1_0::IGraphicBufferProducer;
using HGraphicBufferProducer2 = ::android::hardware::graphics::bufferqueue::
        V2_0::IGraphicBufferProducer;
using B2HGraphicBufferProducer2 = ::android::hardware::graphics::bufferqueue::
        V2_0::utils::B2HGraphicBufferProducer;
using H2BGraphicBufferProducer2 = ::android::hardware::graphics::bufferqueue::
        V2_0::utils::H2BGraphicBufferProducer;
using ::android::hardware::media::c2::V1_2::SurfaceSyncObj;

using AidlGraphicBufferAllocator = ::aidl::android::hardware::media::c2::
        implementation::GraphicBufferAllocator;

namespace bufferpool2_aidl = ::aidl::android::hardware::media::bufferpool2;
namespace bufferpool_hidl = ::android::hardware::media::bufferpool::V2_0;
namespace c2_aidl = ::aidl::android::hardware::media::c2;
namespace c2_hidl_base = ::android::hardware::media::c2;
namespace c2_hidl = ::android::hardware::media::c2::V1_2;

using c2_hidl::utils::operator<<;

namespace /* unnamed */ {

// c2_status_t value that corresponds to hwbinder transaction failure.
constexpr c2_status_t C2_TRANSACTION_FAILED = C2_CORRUPTED;

// By default prepare buffer to be displayed on any of the common surfaces
constexpr uint64_t kDefaultConsumerUsage =
    (GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER);

// Searches for a name in GetServiceNames() and returns the index found. If the
// name is not found, the returned index will be equal to
// GetServiceNames().size().
size_t getServiceIndex(char const* name) {
    std::vector<std::string> const& names = Codec2Client::GetServiceNames();
    size_t i = 0;
    for (; i < names.size(); ++i) {
        if (name == names[i]) {
            break;
        }
    }
    return i;
}

class Client2Store : public C2ComponentStore {
    std::shared_ptr<Codec2Client> mClient;

public:
    Client2Store(std::shared_ptr<Codec2Client> const& client)
        : mClient(client) { }

    virtual ~Client2Store() = default;

    virtual c2_status_t config_sm(
            std::vector<C2Param*> const &params,
            std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
        return mClient->config(params, C2_MAY_BLOCK, failures);
    };

    virtual c2_status_t copyBuffer(
            std::shared_ptr<C2GraphicBuffer>,
            std::shared_ptr<C2GraphicBuffer>) {
        return C2_OMITTED;
    }

    virtual c2_status_t createComponent(
            C2String, std::shared_ptr<C2Component>* const component) {
        component->reset();
        return C2_OMITTED;
    }

    virtual c2_status_t createInterface(
            C2String, std::shared_ptr<C2ComponentInterface>* const interface) {
        interface->reset();
        return C2_OMITTED;
    }

    virtual c2_status_t query_sm(
            std::vector<C2Param*> const& stackParams,
            std::vector<C2Param::Index> const& heapParamIndices,
            std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
        return mClient->query(stackParams, heapParamIndices, C2_MAY_BLOCK, heapParams);
    }

    virtual c2_status_t querySupportedParams_nb(
            std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const {
        return mClient->querySupportedParams(params);
    }

    virtual c2_status_t querySupportedValues_sm(
            std::vector<C2FieldSupportedValuesQuery>& fields) const {
        return mClient->querySupportedValues(fields, C2_MAY_BLOCK);
    }

    virtual C2String getName() const {
        return mClient->getName();
    }

    virtual std::shared_ptr<C2ParamReflector> getParamReflector() const {
        return mClient->getParamReflector();
    }

    virtual std::vector<std::shared_ptr<C2Component::Traits const>> listComponents() {
        return std::vector<std::shared_ptr<C2Component::Traits const>>();
    }
};

c2_status_t GetC2Status(const ::ndk::ScopedAStatus &transStatus, const char *method) {
    if (!transStatus.isOk()) {
        if (transStatus.getExceptionCode() == EX_SERVICE_SPECIFIC) {
            c2_status_t status = static_cast<c2_status_t>(transStatus.getServiceSpecificError());
            LOG(DEBUG) << method << " -- call failed: " << status << ".";
            return status;
        } else {
            LOG(ERROR) << method << " -- transaction failed.";
            return C2_TRANSACTION_FAILED;
        }
    }
    return C2_OK;
}

}  // unnamed namespace

// This class caches a Codec2Client object and its component traits. The client
// will be created the first time it is needed, and it can be refreshed if the
// service dies (by calling invalidate()). The first time listComponents() is
// called from the client, the result will be cached.
class Codec2Client::Cache {
    // Cached client
    std::shared_ptr<Codec2Client> mClient;
    mutable std::mutex mClientMutex;

    // Cached component traits
    std::vector<C2Component::Traits> mTraits;
    std::once_flag mTraitsInitializationFlag;

    // The index of the service. This is based on GetServiceNames().
    size_t mIndex;
    // Called by s() exactly once to initialize the cache. The index must be a
    // valid index into the vector returned by GetServiceNames(). Calling
    // init(index) will associate the cache to the service with name
    // GetServiceNames()[index].
    void init(size_t index) {
        mIndex = index;
    }

public:
    Cache() = default;

    // Initializes mClient if needed, then returns mClient.
    // If the service is unavailable but listed in the manifest, this function
    // will block indefinitely.
    std::shared_ptr<Codec2Client> getClient() {
        std::scoped_lock lock{mClientMutex};
        if (!mClient) {
            mClient = Codec2Client::_CreateFromIndex(mIndex);
        }
        CHECK(mClient) << "Failed to create Codec2Client to service \""
                       << GetServiceNames()[mIndex] << "\". (Index = "
                       << mIndex << ").";
        return mClient;
    }

    // Causes a subsequent call to getClient() to create a new client. This
    // function should be called after the service dies.
    //
    // Note: This function is called only by ForAllServices().
    void invalidate() {
        std::scoped_lock lock{mClientMutex};
        mClient = nullptr;
    }

    // Returns a list of traits for components supported by the service. This
    // list is cached.
    std::vector<C2Component::Traits> const& getTraits() {
        std::call_once(mTraitsInitializationFlag, [this]() {
            bool success{false};
            // Spin until _listComponents() is successful.
            while (true) {
                std::shared_ptr<Codec2Client> client = getClient();
                mTraits = client->_listComponents(&success);
                if (success) {
                    break;
                }
                invalidate();
                using namespace std::chrono_literals;
                static constexpr auto kServiceRetryPeriod = 5s;
                LOG(INFO) << "Failed to retrieve component traits from service "
                             "\"" << GetServiceNames()[mIndex] << "\". "
                             "Retrying...";
                std::this_thread::sleep_for(kServiceRetryPeriod);
            }
        });
        return mTraits;
    }

    // List() returns the list of all caches.
    static std::vector<Cache>& List() {
        static std::vector<Cache> sCaches{[]() {
            size_t numServices = GetServiceNames().size();
            std::vector<Cache> caches(numServices);
            for (size_t i = 0; i < numServices; ++i) {
                caches[i].init(i);
            }
            return caches;
        }()};
        return sCaches;
    }
};
// Codec2ConfigurableClient::HidlImpl

struct Codec2ConfigurableClient::HidlImpl : public Codec2ConfigurableClient::ImplBase {
    typedef c2_hidl::IConfigurable Base;

    // base cannot be null.
    explicit HidlImpl(const sp<Base>& base);

    const C2String& getName() const override {
        return mName;
    }

    c2_status_t query(
            const std::vector<C2Param*>& stackParams,
            const std::vector<C2Param::Index> &heapParamIndices,
            c2_blocking_t mayBlock,
            std::vector<std::unique_ptr<C2Param>>* const heapParams) const override;

    c2_status_t config(
            const std::vector<C2Param*> &params,
            c2_blocking_t mayBlock,
            std::vector<std::unique_ptr<C2SettingResult>>* const failures) override;

    c2_status_t querySupportedParams(
            std::vector<std::shared_ptr<C2ParamDescriptor>>* const params
            ) const override;

    c2_status_t querySupportedValues(
            std::vector<C2FieldSupportedValuesQuery>& fields,
            c2_blocking_t mayBlock) const override;

private:
    sp<Base> mBase;
    const C2String mName;
};

Codec2ConfigurableClient::HidlImpl::HidlImpl(const sp<Base>& base)
      : mBase{base},
        mName{[base]() -> C2String {
                C2String outName;
                Return<void> transStatus = base->getName(
                        [&outName](const hidl_string& name) {
                            outName = name.c_str();
                        });
                return transStatus.isOk() ? outName : "";
            }()} {
}

c2_status_t Codec2ConfigurableClient::HidlImpl::query(
        const std::vector<C2Param*> &stackParams,
        const std::vector<C2Param::Index> &heapParamIndices,
        c2_blocking_t mayBlock,
        std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
    hidl_vec<c2_hidl::ParamIndex> indices(
            stackParams.size() + heapParamIndices.size());
    size_t numIndices = 0;
    for (C2Param* const& stackParam : stackParams) {
        if (!stackParam) {
            LOG(WARNING) << "query -- null stack param encountered.";
            continue;
        }
        indices[numIndices++] = static_cast<c2_hidl::ParamIndex>(stackParam->index());
    }
    size_t numStackIndices = numIndices;
    for (const C2Param::Index& index : heapParamIndices) {
        indices[numIndices++] =
                static_cast<c2_hidl::ParamIndex>(static_cast<uint32_t>(index));
    }
    indices.resize(numIndices);
    if (heapParams) {
        heapParams->reserve(heapParams->size() + numIndices);
    }
    c2_status_t status;
    Return<void> transStatus = mBase->query(
            indices,
            mayBlock == C2_MAY_BLOCK,
            [&status, &numStackIndices, &stackParams, heapParams](
                    c2_hidl::Status s, const c2_hidl::Params& p) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK && status != C2_BAD_INDEX) {
                    LOG(DEBUG) << "query -- call failed: "
                               << status << ".";
                    return;
                }
                std::vector<C2Param*> paramPointers;
                if (!c2_hidl::utils::parseParamsBlob(&paramPointers, p)) {
                    LOG(ERROR) << "query -- error while parsing params.";
                    status = C2_CORRUPTED;
                    return;
                }
                size_t i = 0;
                for (auto it = paramPointers.begin();
                        it != paramPointers.end(); ) {
                    C2Param* paramPointer = *it;
                    if (numStackIndices > 0) {
                        --numStackIndices;
                        if (!paramPointer) {
                            LOG(WARNING) << "query -- null stack param.";
                            ++it;
                            continue;
                        }
                        for (; i < stackParams.size() && !stackParams[i]; ) {
                            ++i;
                        }
                        if (i >= stackParams.size()) {
                            LOG(ERROR) << "query -- unexpected error.";
                            status = C2_CORRUPTED;
                            return;
                        }
                        if (stackParams[i]->index() != paramPointer->index()) {
                            LOG(WARNING) << "query -- param skipped: "
                                            "index = "
                                         << stackParams[i]->index() << ".";
                            stackParams[i++]->invalidate();
                            continue;
                        }
                        if (!stackParams[i++]->updateFrom(*paramPointer)) {
                            LOG(WARNING) << "query -- param update failed: "
                                            "index = "
                                         << paramPointer->index() << ".";
                        }
                    } else {
                        if (!paramPointer) {
                            LOG(WARNING) << "query -- null heap param.";
                            ++it;
                            continue;
                        }
                        if (!heapParams) {
                            LOG(WARNING) << "query -- "
                                            "unexpected extra stack param.";
                        } else {
                            heapParams->emplace_back(
                                    C2Param::Copy(*paramPointer));
                        }
                    }
                    ++it;
                }
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "query -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    return status;
}

c2_status_t Codec2ConfigurableClient::HidlImpl::config(
        const std::vector<C2Param*> &params,
        c2_blocking_t mayBlock,
        std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
    c2_hidl::Params hidlParams;
    if (!c2_hidl::utils::createParamsBlob(&hidlParams, params)) {
        LOG(ERROR) << "config -- bad input.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status;
    Return<void> transStatus = mBase->config(
            hidlParams,
            mayBlock == C2_MAY_BLOCK,
            [&status, &params, failures](
                    c2_hidl::Status s,
                    const hidl_vec<c2_hidl::SettingResult> f,
                    const c2_hidl::Params& o) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK && status != C2_BAD_INDEX) {
                    LOG(DEBUG) << "config -- call failed: "
                               << status << ".";
                }
                size_t i = failures->size();
                failures->resize(i + f.size());
                for (const c2_hidl::SettingResult& sf : f) {
                    if (!c2_hidl::utils::objcpy(&(*failures)[i++], sf)) {
                        LOG(ERROR) << "config -- "
                                   << "invalid SettingResult returned.";
                        return;
                    }
                }
                if (!c2_hidl::utils::updateParamsFromBlob(params, o)) {
                    LOG(ERROR) << "config -- "
                               << "failed to parse returned params.";
                    status = C2_CORRUPTED;
                }
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "config -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    return status;
}

c2_status_t Codec2ConfigurableClient::HidlImpl::querySupportedParams(
        std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const {
    // TODO: Cache and query properly!
    c2_status_t status;
    Return<void> transStatus = mBase->querySupportedParams(
            std::numeric_limits<uint32_t>::min(),
            std::numeric_limits<uint32_t>::max(),
            [&status, params](
                    c2_hidl::Status s,
                    const hidl_vec<c2_hidl::ParamDescriptor>& p) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    LOG(DEBUG) << "querySupportedParams -- call failed: "
                               << status << ".";
                    return;
                }
                size_t i = params->size();
                params->resize(i + p.size());
                for (const c2_hidl::ParamDescriptor& sp : p) {
                    if (!c2_hidl::utils::objcpy(&(*params)[i++], sp)) {
                        LOG(ERROR) << "querySupportedParams -- "
                                   << "invalid returned ParamDescriptor.";
                        return;
                    }
                }
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "querySupportedParams -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    return status;
}

c2_status_t Codec2ConfigurableClient::HidlImpl::querySupportedValues(
        std::vector<C2FieldSupportedValuesQuery>& fields,
        c2_blocking_t mayBlock) const {
    hidl_vec<c2_hidl::FieldSupportedValuesQuery> inFields(fields.size());
    for (size_t i = 0; i < fields.size(); ++i) {
        if (!c2_hidl::utils::objcpy(&inFields[i], fields[i])) {
            LOG(ERROR) << "querySupportedValues -- bad input";
            return C2_TRANSACTION_FAILED;
        }
    }

    c2_status_t status;
    Return<void> transStatus = mBase->querySupportedValues(
            inFields,
            mayBlock == C2_MAY_BLOCK,
            [&status, &inFields, &fields](
                    c2_hidl::Status s,
                    const hidl_vec<c2_hidl::FieldSupportedValuesQueryResult>& r) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    LOG(DEBUG) << "querySupportedValues -- call failed: "
                               << status << ".";
                    return;
                }
                if (r.size() != fields.size()) {
                    LOG(ERROR) << "querySupportedValues -- "
                                  "input and output lists "
                                  "have different sizes.";
                    status = C2_CORRUPTED;
                    return;
                }
                for (size_t i = 0; i < fields.size(); ++i) {
                    if (!c2_hidl::utils::objcpy(&fields[i], inFields[i], r[i])) {
                        LOG(ERROR) << "querySupportedValues -- "
                                      "invalid returned value.";
                        status = C2_CORRUPTED;
                        return;
                    }
                }
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "querySupportedValues -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    return status;
}

// Codec2ConfigurableClient::AidlImpl

struct Codec2ConfigurableClient::AidlImpl : public Codec2ConfigurableClient::ImplBase {
    typedef c2_aidl::IConfigurable Base;

    // base cannot be null.
    explicit AidlImpl(const std::shared_ptr<Base>& base);

    const C2String& getName() const override {
        return mName;
    }

    c2_status_t query(
            const std::vector<C2Param*>& stackParams,
            const std::vector<C2Param::Index> &heapParamIndices,
            c2_blocking_t mayBlock,
            std::vector<std::unique_ptr<C2Param>>* const heapParams) const override;

    c2_status_t config(
            const std::vector<C2Param*> &params,
            c2_blocking_t mayBlock,
            std::vector<std::unique_ptr<C2SettingResult>>* const failures) override;

    c2_status_t querySupportedParams(
            std::vector<std::shared_ptr<C2ParamDescriptor>>* const params
            ) const override;

    c2_status_t querySupportedValues(
            std::vector<C2FieldSupportedValuesQuery>& fields,
            c2_blocking_t mayBlock) const override;

private:
    std::shared_ptr<Base> mBase;
    const C2String mName;
};

Codec2ConfigurableClient::AidlImpl::AidlImpl(const std::shared_ptr<Base>& base)
      : mBase{base},
        mName{[base]() -> C2String {
                std::string outName;
                ndk::ScopedAStatus status = base->getName(&outName);
                return status.isOk() ? outName : "";
            }()} {
}

c2_status_t Codec2ConfigurableClient::AidlImpl::query(
        const std::vector<C2Param*> &stackParams,
        const std::vector<C2Param::Index> &heapParamIndices,
        c2_blocking_t mayBlock,
        std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
    std::vector<int> indices(
            stackParams.size() + heapParamIndices.size());
    size_t numIndices = 0;
    for (C2Param* const& stackParam : stackParams) {
        if (!stackParam) {
            LOG(WARNING) << "query -- null stack param encountered.";
            continue;
        }
        indices[numIndices++] = int(stackParam->index());
    }
    size_t numStackIndices = numIndices;
    for (const C2Param::Index& index : heapParamIndices) {
        indices[numIndices++] = int(static_cast<uint32_t>(index));
    }
    indices.resize(numIndices);
    if (heapParams) {
        heapParams->reserve(heapParams->size() + numIndices);
    }
    c2_aidl::IConfigurable::QueryResult result;
    ndk::ScopedAStatus transStatus = mBase->query(indices, (mayBlock == C2_MAY_BLOCK), &result);
    c2_status_t status = GetC2Status(transStatus, "query");
    if (status != C2_OK) {
        return status;
    }
    status = static_cast<c2_status_t>(result.status.status);

    std::vector<C2Param*> paramPointers;
    if (!c2_aidl::utils::ParseParamsBlob(&paramPointers, result.params)) {
        LOG(ERROR) << "query -- error while parsing params.";
        return C2_CORRUPTED;
    }
    size_t i = 0;
    size_t numQueried = 0;
    for (auto it = paramPointers.begin(); it != paramPointers.end(); ) {
        C2Param* paramPointer = *it;
        if (numStackIndices > 0) {
            --numStackIndices;
            if (!paramPointer) {
                LOG(DEBUG) << "query -- null stack param.";
                ++it;
                continue;
            }
            for (; i < stackParams.size() && !stackParams[i]; ) {
                ++i;
            }
            if (i >= stackParams.size()) {
                LOG(ERROR) << "query -- unexpected error.";
                status = C2_CORRUPTED;
                break;
            }
            if (stackParams[i]->index() != paramPointer->index()) {
                LOG(DEBUG) << "query -- param skipped: "
                              "index = "
                           << stackParams[i]->index() << ".";
                stackParams[i++]->invalidate();
                // this means that the param could not be queried.
                // signalling C2_BAD_INDEX to the client.
                status = C2_BAD_INDEX;
                continue;
            }
            if (stackParams[i++]->updateFrom(*paramPointer)) {
                ++numQueried;
            } else {
                LOG(WARNING) << "query -- param update failed: "
                                "index = "
                             << paramPointer->index() << ".";
            }
        } else {
            if (!paramPointer) {
                LOG(DEBUG) << "query -- null heap param.";
                ++it;
                continue;
            }
            if (!heapParams) {
                LOG(WARNING) << "query -- "
                                "unexpected extra stack param.";
            } else {
                heapParams->emplace_back(C2Param::Copy(*paramPointer));
                ++numQueried;
            }
        }
        ++it;
    }
    if (status == C2_OK && indices.size() != numQueried) {
        status = C2_BAD_INDEX;
    }
    return status;
}

c2_status_t Codec2ConfigurableClient::AidlImpl::config(
        const std::vector<C2Param*> &params,
        c2_blocking_t mayBlock,
        std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
    c2_aidl::Params aidlParams;
    if (!c2_aidl::utils::CreateParamsBlob(&aidlParams, params)) {
        LOG(ERROR) << "config -- bad input.";
        return C2_TRANSACTION_FAILED;
    }
    c2_aidl::IConfigurable::ConfigResult result;
    ndk::ScopedAStatus transStatus = mBase->config(aidlParams, (mayBlock == C2_MAY_BLOCK), &result);
    c2_status_t status = GetC2Status(transStatus, "config");
    if (status != C2_OK) {
        return status;
    }
    status = static_cast<c2_status_t>(result.status.status);
    size_t i = failures->size();
    failures->resize(i + result.failures.size());
    for (const c2_aidl::SettingResult& sf : result.failures) {
        if (!c2_aidl::utils::FromAidl(&(*failures)[i++], sf)) {
            LOG(ERROR) << "config -- invalid SettingResult returned.";
            return C2_CORRUPTED;
        }
    }
    if (!c2_aidl::utils::UpdateParamsFromBlob(params, result.params)) {
        LOG(ERROR) << "config -- "
                   << "failed to parse returned params.";
        status = C2_CORRUPTED;
    }
    return status;
}

c2_status_t Codec2ConfigurableClient::AidlImpl::querySupportedParams(
        std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const {
    // TODO: Cache and query properly!
    std::vector<c2_aidl::ParamDescriptor> result;
    ndk::ScopedAStatus transStatus = mBase->querySupportedParams(
            std::numeric_limits<uint32_t>::min(),
            std::numeric_limits<uint32_t>::max(),
            &result);
    c2_status_t status = GetC2Status(transStatus, "querySupportedParams");
    if (status != C2_OK) {
        return status;
    }
    size_t i = params->size();
    params->resize(i + result.size());
    for (const c2_aidl::ParamDescriptor& sp : result) {
        if (!c2_aidl::utils::FromAidl(&(*params)[i++], sp)) {
            LOG(ERROR) << "querySupportedParams -- invalid returned ParamDescriptor.";
            return C2_CORRUPTED;
        }
    }
    return status;
}

c2_status_t Codec2ConfigurableClient::AidlImpl::querySupportedValues(
        std::vector<C2FieldSupportedValuesQuery>& fields,
        c2_blocking_t mayBlock) const {
    std::vector<c2_aidl::FieldSupportedValuesQuery> inFields(fields.size());
    for (size_t i = 0; i < fields.size(); ++i) {
        if (!c2_aidl::utils::ToAidl(&inFields[i], fields[i])) {
            LOG(ERROR) << "querySupportedValues -- bad input";
            return C2_TRANSACTION_FAILED;
        }
    }

    c2_aidl::IConfigurable::QuerySupportedValuesResult result;

    ndk::ScopedAStatus transStatus = mBase->querySupportedValues(
            inFields, (mayBlock == C2_MAY_BLOCK), &result);
    c2_status_t status = GetC2Status(transStatus, "querySupportedValues");
    if (status != C2_OK) {
        return status;
    }
    status = static_cast<c2_status_t>(result.status.status);
    if (result.values.size() != fields.size()) {
        LOG(ERROR) << "querySupportedValues -- "
                      "input and output lists "
                      "have different sizes.";
        return C2_CORRUPTED;
    }
    for (size_t i = 0; i < fields.size(); ++i) {
        if (!c2_aidl::utils::FromAidl(&fields[i], inFields[i], result.values[i])) {
            LOG(ERROR) << "querySupportedValues -- "
                          "invalid returned value.";
            return C2_CORRUPTED;
        }
    }
    return status;
}

// Codec2ConfigurableClient::ApexImpl

struct Codec2ConfigurableClient::ApexImpl : public Codec2ConfigurableClient::ImplBase {
    ApexImpl(ApexCodec_Configurable *base, const C2String &name);

    const C2String& getName() const override {
        return mName;
    }

    c2_status_t query(
            const std::vector<C2Param*>& stackParams,
            const std::vector<C2Param::Index> &heapParamIndices,
            c2_blocking_t mayBlock,
            std::vector<std::unique_ptr<C2Param>>* const heapParams) const override;

    c2_status_t config(
            const std::vector<C2Param*> &params,
            c2_blocking_t mayBlock,
            std::vector<std::unique_ptr<C2SettingResult>>* const failures) override;

    c2_status_t querySupportedParams(
            std::vector<std::shared_ptr<C2ParamDescriptor>>* const params
            ) const override;

    c2_status_t querySupportedValues(
            std::vector<C2FieldSupportedValuesQuery>& fields,
            c2_blocking_t mayBlock) const override;

private:
    ApexCodec_Configurable* mBase;
    const C2String mName;
};

Codec2ConfigurableClient::ApexImpl::ApexImpl(ApexCodec_Configurable *base, const C2String &name)
      : mBase{base},
        mName{name} {
}

c2_status_t Codec2ConfigurableClient::ApexImpl::query(
        const std::vector<C2Param*> &stackParams,
        const std::vector<C2Param::Index> &heapParamIndices,
        [[maybe_unused]] c2_blocking_t mayBlock,
        std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
    if (mBase == nullptr) {
        return C2_OMITTED;
    }

    if (__builtin_available(android 36, *)) {
        std::vector<uint32_t> indices(
                stackParams.size() + heapParamIndices.size());
        size_t numIndices = 0;
        for (C2Param* const& stackParam : stackParams) {
            if (!stackParam) {
                LOG(WARNING) << "query -- null stack param encountered.";
                continue;
            }
            indices[numIndices++] = uint32_t(stackParam->index());
        }
        size_t numStackIndices = numIndices;
        for (const C2Param::Index& index : heapParamIndices) {
            indices[numIndices++] = uint32_t(index);
        }
        indices.resize(numIndices);
        if (heapParams) {
            heapParams->reserve(heapParams->size() + numIndices);
        }
        if (numIndices == 0) {
            return C2_OK;
        }
        thread_local std::vector<uint8_t> configBuffer(1024);
        if (configBuffer.capacity() < numIndices * 16u) {
            configBuffer.resize(numIndices * 16u);
        }
        ApexCodec_LinearBuffer config{configBuffer.data(), configBuffer.capacity()};
        size_t writtenOrRequested = 0;
        ApexCodec_Status status = ApexCodec_Configurable_query(
                mBase, indices.data(), indices.size(), &config, &writtenOrRequested);
        if (status == APEXCODEC_STATUS_NO_MEMORY) {
            size_t requested = writtenOrRequested;
            configBuffer.resize(align(requested, 1024));
            config.data = configBuffer.data();
            config.size = configBuffer.capacity();
            status = ApexCodec_Configurable_query(
                    mBase, indices.data(), indices.size(), &config, &writtenOrRequested);
        }
        size_t written = writtenOrRequested;
        if (status != APEXCODEC_STATUS_OK && status != APEXCODEC_STATUS_BAD_INDEX) {
            written = 0;
        }
        configBuffer.resize(written);
        std::vector<C2Param*> paramPointers;
        if (!::android::parseParamsBlob(&paramPointers, configBuffer)) {
            LOG(ERROR) << "query -- error while parsing params.";
            return C2_CORRUPTED;
        }
        size_t i = 0;
        size_t numQueried = 0;
        for (auto it = paramPointers.begin(); it != paramPointers.end(); ) {
            C2Param* paramPointer = *it;
            if (numStackIndices > 0) {
                --numStackIndices;
                if (!paramPointer) {
                    LOG(DEBUG) << "query -- null stack param.";
                    ++it;
                    continue;
                }
                for (; i < stackParams.size() && !stackParams[i]; ) {
                    ++i;
                }
                if (i >= stackParams.size()) {
                    LOG(ERROR) << "query -- unexpected error.";
                    status = APEXCODEC_STATUS_CORRUPTED;
                    break;
                }
                if (stackParams[i]->index() != paramPointer->index()) {
                    LOG(DEBUG) << "query -- param skipped: "
                                "index = "
                            << stackParams[i]->index() << ".";
                    stackParams[i++]->invalidate();
                    // this means that the param could not be queried.
                    // signalling C2_BAD_INDEX to the client.
                    status = APEXCODEC_STATUS_BAD_INDEX;
                    continue;
                }
                if (stackParams[i++]->updateFrom(*paramPointer)) {
                    ++numQueried;
                } else {
                    LOG(WARNING) << "query -- param update failed: "
                                    "index = "
                                << paramPointer->index() << ".";
                }
            } else {
                if (!paramPointer) {
                    LOG(DEBUG) << "query -- null heap param.";
                    ++it;
                    continue;
                }
                if (!heapParams) {
                    LOG(WARNING) << "query -- "
                                    "unexpected extra stack param.";
                } else {
                    heapParams->emplace_back(C2Param::Copy(*paramPointer));
                    ++numQueried;
                }
            }
            ++it;
        }
        if (status == APEXCODEC_STATUS_OK && indices.size() != numQueried) {
            status = APEXCODEC_STATUS_BAD_INDEX;
        }
        return (c2_status_t)status;
    } else {
        return C2_OMITTED;
    }
}

namespace {
struct ParamOrField : public C2ParamField {
    explicit ParamOrField(const ApexCodec_ParamFieldValues& field)
            : C2ParamField(field.index, field.offset, field.size) {}
};

static bool FromApex(
        ApexCodec_SupportedValues *apexValues,
        C2FieldSupportedValues* c2Values) {
    if (__builtin_available(android 36, *)) {
        if (apexValues == nullptr) {
            c2Values->type = C2FieldSupportedValues::EMPTY;
            return true;
        }
        ApexCodec_SupportedValuesType type = APEXCODEC_SUPPORTED_VALUES_EMPTY;
        ApexCodec_SupportedValuesNumberType numberType = APEXCODEC_SUPPORTED_VALUES_TYPE_NONE;
        ApexCodec_Value* values = nullptr;
        uint32_t numValues = 0;
        ApexCodec_SupportedValues_getTypeAndValues(
                apexValues, &type, &numberType, &values, &numValues);
        c2Values->type = (C2FieldSupportedValues::type_t)type;
        std::function<C2Value::Primitive(const ApexCodec_Value &)> getPrimitive;
        switch (numberType) {
            case APEXCODEC_SUPPORTED_VALUES_TYPE_NONE:
                getPrimitive = [](const ApexCodec_Value &) -> C2Value::Primitive {
                    return C2Value::Primitive();
                };
                break;
            case APEXCODEC_SUPPORTED_VALUES_TYPE_INT32:
                getPrimitive = [](const ApexCodec_Value &value) -> C2Value::Primitive {
                    return C2Value::Primitive(value.i32);
                };
                break;
            case APEXCODEC_SUPPORTED_VALUES_TYPE_UINT32:
                getPrimitive = [](const ApexCodec_Value &value) -> C2Value::Primitive {
                    return C2Value::Primitive(value.u32);
                };
                break;
            case APEXCODEC_SUPPORTED_VALUES_TYPE_INT64:
                getPrimitive = [](const ApexCodec_Value &value) -> C2Value::Primitive {
                    return C2Value::Primitive(value.i64);
                };
                break;
            case APEXCODEC_SUPPORTED_VALUES_TYPE_UINT64:
                getPrimitive = [](const ApexCodec_Value &value) -> C2Value::Primitive {
                    return C2Value::Primitive(value.u64);
                };
                break;
            case APEXCODEC_SUPPORTED_VALUES_TYPE_FLOAT:
                getPrimitive = [](const ApexCodec_Value &value) -> C2Value::Primitive {
                    return C2Value::Primitive(value.f);
                };
                break;
            default:
                LOG(ERROR) << "Unsupported number type: " << numberType;
                return false;
        }
        switch (type) {
            case APEXCODEC_SUPPORTED_VALUES_EMPTY:
                break;
            case APEXCODEC_SUPPORTED_VALUES_RANGE:
                c2Values->range.min   = getPrimitive(values[0]);
                c2Values->range.max   = getPrimitive(values[1]);
                c2Values->range.step  = getPrimitive(values[2]);
                c2Values->range.num   = getPrimitive(values[3]);
                c2Values->range.denom = getPrimitive(values[4]);
                break;
            case APEXCODEC_SUPPORTED_VALUES_VALUES:
            case APEXCODEC_SUPPORTED_VALUES_FLAGS:
                c2Values->values.clear();
                for (uint32_t i = 0; i < numValues; ++i) {
                    c2Values->values.push_back(getPrimitive(values[i]));
                }
                break;
            default:
                LOG(ERROR) << "Unsupported supported values type: " << type;
                return false;
        }
        return true;
    } else {
        return false;
    }
}

}  // anonymous namespace

c2_status_t Codec2ConfigurableClient::ApexImpl::config(
        const std::vector<C2Param*> &params,
        c2_blocking_t mayBlock,
        std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
    (void)mayBlock;
    if (mBase == nullptr) {
        return C2_OMITTED;
    }

    if (__builtin_available(android 36, *)) {
        std::vector<uint8_t> configBuffer;
        if (!::android::_createParamsBlob(&configBuffer, params)) {
            LOG(ERROR) << "config -- bad input.";
            return C2_TRANSACTION_FAILED;
        }
        ApexCodec_SettingResults* result = nullptr;
        ApexCodec_LinearBuffer config{configBuffer.data(), configBuffer.size()};
        ApexCodec_Status status = ApexCodec_Configurable_config(
                mBase, &config, &result);
        base::ScopeGuard guard([result] {
            if (result) {
                ApexCodec_SettingResults_release(result);
            }
        });
        size_t index = 0;
        ApexCodec_SettingResultFailure failure;
        ApexCodec_ParamFieldValues field;
        ApexCodec_ParamFieldValues* conflicts = nullptr;
        size_t numConflicts = 0;
        ApexCodec_Status getResultStatus = ApexCodec_SettingResults_getResultAtIndex(
            result, 0, &failure, &field, &conflicts, &numConflicts);
        while (getResultStatus == APEXCODEC_STATUS_OK) {
            std::unique_ptr<C2SettingResult> settingResult;
            settingResult.reset(new C2SettingResult{
                C2SettingResult::Failure(failure), C2ParamFieldValues(ParamOrField(field)), {}
            });
            // TODO: settingResult->field.values = ?
            for (size_t i = 0; i < numConflicts; ++i) {
                settingResult->conflicts.emplace_back(ParamOrField(conflicts[i]));
                C2ParamFieldValues& conflict = settingResult->conflicts.back();
                conflict.values = std::make_unique<C2FieldSupportedValues>();
                FromApex(conflicts[i].values, conflict.values.get());
            }
            failures->push_back(std::move(settingResult));
            getResultStatus = ApexCodec_SettingResults_getResultAtIndex(
                    result, ++index, &failure, &field, &conflicts, &numConflicts);
        }
        if (!::android::updateParamsFromBlob(params, configBuffer)) {
            LOG(ERROR) << "config -- "
                    << "failed to parse returned params.";
            status = APEXCODEC_STATUS_CORRUPTED;
        }
        return (c2_status_t)status;
    } else {
        return C2_OMITTED;
    }
}

c2_status_t Codec2ConfigurableClient::ApexImpl::querySupportedParams(
        std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const {
    if (mBase == nullptr) {
        return C2_OMITTED;
    }

    if (__builtin_available(android 36, *)) {
        // TODO: Cache and query properly!
        ApexCodec_ParamDescriptors* paramDescs = nullptr;
        ApexCodec_Configurable_querySupportedParams(mBase, &paramDescs);
        base::ScopeGuard guard([paramDescs] {
            if (paramDescs) {
                ApexCodec_ParamDescriptors_release(paramDescs);
            }
        });
        uint32_t *indices = nullptr;
        size_t numIndices = 0;
        ApexCodec_Status status = ApexCodec_ParamDescriptors_getIndices(
                paramDescs, &indices, &numIndices);
        if (status != APEXCODEC_STATUS_OK) {
            return (c2_status_t)status;
        }
        if (numIndices > 0) {
            for (int i = 0; i < numIndices; ++i) {
                uint32_t index = indices[i];
                ApexCodec_ParamAttribute attr = (ApexCodec_ParamAttribute)0;
                const char* name = nullptr;
                uint32_t* dependencies = nullptr;
                size_t numDependencies = 0;
                ApexCodec_Status status = ApexCodec_ParamDescriptors_getDescriptor(
                        paramDescs, index, &attr, &name, &dependencies, &numDependencies);
                if (status != APEXCODEC_STATUS_OK) {
                    LOG(WARNING) << "querySupportedParams -- "
                                << "failed to get descriptor for index "
                                << std::hex << index << std::dec << " with status " << status;
                    continue;
                }
                params->push_back(std::make_shared<C2ParamDescriptor>(
                        C2Param::Index(index), C2ParamDescriptor::attrib_t(attr), name,
                        std::vector<C2Param::Index>(dependencies, dependencies + numDependencies)));
            }
        }
        return (c2_status_t)status;
    } else {
        return C2_OMITTED;
    }
}

c2_status_t Codec2ConfigurableClient::ApexImpl::querySupportedValues(
        std::vector<C2FieldSupportedValuesQuery>& fields,
        [[maybe_unused]] c2_blocking_t mayBlock) const {
    if (mBase == nullptr) {
        return C2_OMITTED;
    }

    if (__builtin_available(android 36, *)) {
        std::vector<ApexCodec_SupportedValuesQuery> queries(fields.size());
        for (size_t i = 0; i < fields.size(); ++i) {
            queries[i].index  = _C2ParamInspector::GetIndex(fields[i].field());
            queries[i].offset = _C2ParamInspector::GetOffset(fields[i].field());
            queries[i].type   = (ApexCodec_SupportedValuesQueryType)fields[i].type();
            queries[i].status = APEXCODEC_STATUS_OK;
            queries[i].values = nullptr;
        }
        ApexCodec_Status status = ApexCodec_Configurable_querySupportedValues(
                mBase, queries.data(), queries.size());
        for (size_t i = 0; i < fields.size(); ++i) {
            fields[i].status = (c2_status_t)queries[i].status;
            FromApex(queries[i].values, &fields[i].values);
            if (queries[i].values) {
                ApexCodec_SupportedValues_release(queries[i].values);
                queries[i].values = nullptr;
            }
        }
        return (c2_status_t)status;
    } else {
        return C2_OMITTED;
    }
}

// Codec2ConfigurableClient

Codec2ConfigurableClient::Codec2ConfigurableClient(const sp<HidlBase> &hidlBase)
    : mImpl(new Codec2ConfigurableClient::HidlImpl(hidlBase)) {
}

Codec2ConfigurableClient::Codec2ConfigurableClient(
        const std::shared_ptr<AidlBase> &aidlBase)
    : mImpl(new Codec2ConfigurableClient::AidlImpl(aidlBase)) {
}

Codec2ConfigurableClient::Codec2ConfigurableClient(
        ApexCodec_Configurable *apexBase, const C2String &name)
    : mImpl(new Codec2ConfigurableClient::ApexImpl(apexBase, name)) {
}

const C2String& Codec2ConfigurableClient::getName() const {
    return mImpl->getName();
}

c2_status_t Codec2ConfigurableClient::query(
        const std::vector<C2Param*>& stackParams,
        const std::vector<C2Param::Index> &heapParamIndices,
        c2_blocking_t mayBlock,
        std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
    return mImpl->query(stackParams, heapParamIndices, mayBlock, heapParams);
}

c2_status_t Codec2ConfigurableClient::config(
        const std::vector<C2Param*> &params,
        c2_blocking_t mayBlock,
        std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
    return mImpl->config(params, mayBlock, failures);
}

c2_status_t Codec2ConfigurableClient::querySupportedParams(
        std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const {
    return mImpl->querySupportedParams(params);
}

c2_status_t Codec2ConfigurableClient::querySupportedValues(
        std::vector<C2FieldSupportedValuesQuery>& fields,
        c2_blocking_t mayBlock) const {
    return mImpl->querySupportedValues(fields, mayBlock);
}


// Codec2Client::Component::HidlListener
struct Codec2Client::Component::HidlListener : public c2_hidl::IComponentListener {
    std::weak_ptr<Component> component;
    std::weak_ptr<Listener> base;

    virtual Return<void> onWorkDone(const c2_hidl::WorkBundle& workBundle) override {
        std::list<std::unique_ptr<C2Work>> workItems;
        if (!c2_hidl::utils::objcpy(&workItems, workBundle)) {
            LOG(DEBUG) << "onWorkDone -- received corrupted WorkBundle.";
            return Void();
        }
        // release input buffers potentially held by the component from queue
        std::shared_ptr<Codec2Client::Component> strongComponent =
                component.lock();
        if (strongComponent) {
            strongComponent->handleOnWorkDone(workItems);
        }
        if (std::shared_ptr<Codec2Client::Listener> listener = base.lock()) {
            listener->onWorkDone(component, workItems);
        } else {
            LOG(DEBUG) << "onWorkDone -- listener died.";
        }
        return Void();
    }

    virtual Return<void> onTripped(
            const hidl_vec<c2_hidl::SettingResult>& settingResults) override {
        std::vector<std::shared_ptr<C2SettingResult>> c2SettingResults(
                settingResults.size());
        for (size_t i = 0; i < settingResults.size(); ++i) {
            std::unique_ptr<C2SettingResult> c2SettingResult;
            if (!c2_hidl::utils::objcpy(&c2SettingResult, settingResults[i])) {
                LOG(DEBUG) << "onTripped -- received corrupted SettingResult.";
                return Void();
            }
            c2SettingResults[i] = std::move(c2SettingResult);
        }
        if (std::shared_ptr<Codec2Client::Listener> listener = base.lock()) {
            listener->onTripped(component, c2SettingResults);
        } else {
            LOG(DEBUG) << "onTripped -- listener died.";
        }
        return Void();
    }

    virtual Return<void> onError(c2_hidl::Status s, uint32_t errorCode) override {
        LOG(DEBUG) << "onError --"
                   << " status = " << s
                   << ", errorCode = " << errorCode
                   << ".";
        if (std::shared_ptr<Listener> listener = base.lock()) {
            listener->onError(component, s == c2_hidl::Status::OK ?
                    errorCode : static_cast<c2_status_t>(s));
        } else {
            LOG(DEBUG) << "onError -- listener died.";
        }
        return Void();
    }

    virtual Return<void> onFramesRendered(
            const hidl_vec<RenderedFrame>& renderedFrames) override {
        std::shared_ptr<Listener> listener = base.lock();
        if (!listener) {
            LOG(DEBUG) << "onFramesRendered -- listener died.";
            return Void();
        }
        for (const RenderedFrame& renderedFrame : renderedFrames) {
            listener->onFrameRendered(
                    renderedFrame.bufferQueueId,
                    renderedFrame.slotId,
                    renderedFrame.timestampNs);
        }
        return Void();
    }

    virtual Return<void> onInputBuffersReleased(
            const hidl_vec<InputBuffer>& inputBuffers) override {
        std::shared_ptr<Listener> listener = base.lock();
        if (!listener) {
            LOG(DEBUG) << "onInputBuffersReleased -- listener died.";
            return Void();
        }
        for (const InputBuffer& inputBuffer : inputBuffers) {
            LOG(VERBOSE) << "onInputBuffersReleased --"
                            " received death notification of"
                            " input buffer:"
                            " frameIndex = " << inputBuffer.frameIndex
                         << ", bufferIndex = " << inputBuffer.arrayIndex
                         << ".";
            listener->onInputBufferDone(
                    inputBuffer.frameIndex, inputBuffer.arrayIndex);
        }
        return Void();
    }

};

// Codec2Client::Component::AidlListener
struct Codec2Client::Component::AidlListener : public c2_aidl::BnComponentListener {
    std::weak_ptr<Component> component;
    std::weak_ptr<Listener> base;

    virtual ::ndk::ScopedAStatus onWorkDone(const c2_aidl::WorkBundle& workBundle) override {
        std::list<std::unique_ptr<C2Work>> workItems;
        if (!c2_aidl::utils::FromAidl(&workItems, workBundle)) {
            LOG(DEBUG) << "onWorkDone -- received corrupted WorkBundle.";
            return ::ndk::ScopedAStatus::ok();
        }
        // release input buffers potentially held by the component from queue
        std::shared_ptr<Codec2Client::Component> strongComponent =
                component.lock();
        if (strongComponent) {
            strongComponent->handleOnWorkDone(workItems);
        }
        if (std::shared_ptr<Codec2Client::Listener> listener = base.lock()) {
            listener->onWorkDone(component, workItems);
        } else {
            LOG(DEBUG) << "onWorkDone -- listener died.";
        }
        return ::ndk::ScopedAStatus::ok();
    }

    virtual ::ndk::ScopedAStatus onTripped(
            const std::vector<c2_aidl::SettingResult>& settingResults) override {
        std::vector<std::shared_ptr<C2SettingResult>> c2SettingResults(
                settingResults.size());
        for (size_t i = 0; i < settingResults.size(); ++i) {
            std::unique_ptr<C2SettingResult> c2SettingResult;
            if (!c2_aidl::utils::FromAidl(&c2SettingResult, settingResults[i])) {
                LOG(DEBUG) << "onTripped -- received corrupted SettingResult.";
                return ::ndk::ScopedAStatus::ok();
            }
            c2SettingResults[i] = std::move(c2SettingResult);
        }
        if (std::shared_ptr<Codec2Client::Listener> listener = base.lock()) {
            listener->onTripped(component, c2SettingResults);
        } else {
            LOG(DEBUG) << "onTripped -- listener died.";
        }
        return ::ndk::ScopedAStatus::ok();
    }

    virtual ::ndk::ScopedAStatus onError(const c2_aidl::Status &s, int32_t errorCode) override {
        LOG(DEBUG) << "onError --"
                   << " status = " << s.status
                   << ", errorCode = " << errorCode
                   << ".";
        if (std::shared_ptr<Listener> listener = base.lock()) {
            listener->onError(component, s.status == c2_aidl::Status::OK ?
                    errorCode : static_cast<c2_status_t>(s.status));
        } else {
            LOG(DEBUG) << "onError -- listener died.";
        }
        return ::ndk::ScopedAStatus::ok();
    }

    virtual ::ndk::ScopedAStatus onFramesRendered(
            const std::vector<RenderedFrame>& renderedFrames) override {
        std::shared_ptr<Listener> listener = base.lock();
        if (!listener) {
            LOG(DEBUG) << "onFramesRendered -- listener died.";
            return ::ndk::ScopedAStatus::ok();
        }
        for (const RenderedFrame& renderedFrame : renderedFrames) {
            listener->onFrameRendered(
                    renderedFrame.bufferQueueId,
                    renderedFrame.slotId,
                    renderedFrame.timestampNs);
        }
        return ::ndk::ScopedAStatus::ok();
    }

    virtual ::ndk::ScopedAStatus onInputBuffersReleased(
            const std::vector<InputBuffer>& inputBuffers) override {
        std::shared_ptr<Listener> listener = base.lock();
        if (!listener) {
            LOG(DEBUG) << "onInputBuffersReleased -- listener died.";
            return ::ndk::ScopedAStatus::ok();
        }
        for (const InputBuffer& inputBuffer : inputBuffers) {
            LOG(VERBOSE) << "onInputBuffersReleased --"
                            " received death notification of"
                            " input buffer:"
                            " frameIndex = " << inputBuffer.frameIndex
                         << ", bufferIndex = " << inputBuffer.arrayIndex
                         << ".";
            listener->onInputBufferDone(
                    inputBuffer.frameIndex, inputBuffer.arrayIndex);
        }
        return ::ndk::ScopedAStatus::ok();
    }

};

// Codec2Client::Component::ApexHandler
class Codec2Client::Component::ApexHandler {
public:
    ApexHandler(ApexCodec_Component *apexComponent,
                const std::shared_ptr<Listener> &listener,
                const std::shared_ptr<Component> &comp)
          : mApexComponent(apexComponent),
            mListener(listener),
            mComponent(comp),
            mStopped(false),
            mOutputBufferType(APEXCODEC_BUFFER_TYPE_INVALID) {
    }

    void start() {
        std::shared_ptr<Component> comp = mComponent.lock();
        if (!comp) {
            LOG(ERROR) << "ApexHandler::start -- component died.";
            return;
        }
        C2ComponentDomainSetting domain;
        C2ComponentKindSetting kind;
        c2_status_t status = comp->query({&domain, &kind}, {}, C2_MAY_BLOCK, {});
        if (status != C2_OK) {
            LOG(ERROR) << "ApexHandler::start -- failed to query component domain and kind";
            return;
        }
        if (kind.value != C2Component::KIND_DECODER
                && kind.value != C2Component::KIND_ENCODER) {
            LOG(ERROR) << "ApexHandler::start -- unrecognized component kind " << kind.value;
            return;
        }
        ApexCodec_BufferType outputBufferType = APEXCODEC_BUFFER_TYPE_INVALID;
        if (domain.value == C2Component::DOMAIN_AUDIO) {
            // For both encoders and decoders the output buffer type is linear.
            outputBufferType = APEXCODEC_BUFFER_TYPE_LINEAR;
        } else if (domain.value == C2Component::DOMAIN_VIDEO
                    || domain.value == C2Component::DOMAIN_IMAGE) {
            // For video / image domain the decoder outputs a graphic buffer, and the encoder
            // outputs a linear buffer.
            outputBufferType = (kind.value == C2Component::KIND_DECODER)
                    ? APEXCODEC_BUFFER_TYPE_GRAPHIC : APEXCODEC_BUFFER_TYPE_LINEAR;
        } else {
            LOG(ERROR) << "ApexHandler::start -- unrecognized component domain " << domain.value;
            return;
        }
        {
            std::unique_lock<std::mutex> l(mMutex);
            mStopped = false;
            mOutputBufferType = outputBufferType;
        }
        mThread = std::thread([this]() {
            run();
        });
    }

    void queue(std::list<std::unique_ptr<C2Work>>& workItems) {
        std::unique_lock<std::mutex> l(mMutex);
        mWorkQueue.splice(mWorkQueue.end(), workItems);
        mCondition.notify_all();
    }

    void stop() {
        std::unique_lock<std::mutex> l(mMutex);
        mStopped = true;
        mCondition.notify_all();
        l.unlock();
        mThread.join();
    }

private:
    void run() {
        while (true) {
            std::unique_lock<std::mutex> l(mMutex);
            mCondition.wait(l, [this]() {
                return !mWorkQueue.empty() || mStopped;
            });
            if (mStopped) {
                break;
            }
            if (mWorkQueue.empty()) {
                continue;
            }
            std::list<std::unique_ptr<C2Work>> workItems;
            mWorkQueue.swap(workItems);
            for (std::unique_ptr<C2Work>& workItem : workItems) {
                if (mStopped) {
                    break;
                }
                l.unlock();
                handleWork(std::move(workItem));
                l.lock();
            }
        }
        mWorkQueue.clear();
        mWorkMap.clear();
    }

    void handleWork(std::unique_ptr<C2Work> &&workItem) {
        if (__builtin_available(android 36, *)) {
            std::shared_ptr<Listener> listener = mListener.lock();
            if (!listener) {
                LOG(DEBUG) << "handleWork -- listener died.";
                return;
            }
            ApexCodec_Buffer input;
            input.flags = (ApexCodec_BufferFlags)workItem->input.flags;
            input.frameIndex = workItem->input.ordinal.frameIndex.peekll();
            input.timestampUs = workItem->input.ordinal.timestamp.peekll();

            if (workItem->input.buffers.size() > 1) {
                LOG(ERROR) << "handleWork -- input buffer size is "
                           << workItem->input.buffers.size();
                return;
            }
            std::shared_ptr<C2Buffer> buffer;
            std::optional<C2ReadView> linearView;
            if (!workItem->input.buffers.empty()) {
                buffer = workItem->input.buffers[0];
            }
            if (!FillMemory(buffer, &input, &linearView)) {
                LOG(ERROR) << "handleWork -- failed to map input";
                return;
            }

            std::vector<uint8_t> configUpdatesVector;
            if (!_createParamsBlob(&configUpdatesVector, workItem->input.configUpdate)) {
                listener->onError(mComponent, C2_CORRUPTED);
                return;
            }
            input.configUpdates.data = configUpdatesVector.data();
            input.configUpdates.size = configUpdatesVector.size();
            mWorkMap.insert_or_assign(
                    workItem->input.ordinal.frameIndex.peekll(), std::move(workItem));

            std::list<std::unique_ptr<C2Work>> workItems;
            bool inputDrained = false;
            while (!inputDrained) {
                ApexCodec_Buffer output;
                std::shared_ptr<C2LinearBlock> linearBlock;
                std::optional<C2WriteView> linearView;
                std::shared_ptr<C2GraphicBlock> graphicBlock;
                allocOutputBuffer(&output, &linearBlock, &linearView, &graphicBlock);
                size_t consumed = 0;
                size_t produced = 0;
                ApexCodec_Status status = ApexCodec_Component_process(
                        mApexComponent, &input, &output, &consumed, &produced);
                if (status == APEXCODEC_STATUS_NO_MEMORY) {
                    continue;
                }
                if (produced > 0) {
                    auto it = mWorkMap.find(output.frameIndex);
                    std::unique_ptr<C2Work> outputWorkItem;
                    if (it != mWorkMap.end()) {
                        if (output.flags & APEXCODEC_FLAG_INCOMPLETE) {
                            outputWorkItem = std::make_unique<C2Work>();
                            outputWorkItem->input.ordinal = it->second->input.ordinal;
                            outputWorkItem->input.flags = it->second->input.flags;
                        } else {
                            outputWorkItem = std::move(it->second);
                            mWorkMap.erase(it);
                        }
                    } else {
                        LOG(WARNING) << "handleWork -- no work item found for output frame index "
                                    << output.frameIndex;
                        outputWorkItem = std::make_unique<C2Work>();
                        outputWorkItem->input.ordinal.frameIndex = output.frameIndex;
                        outputWorkItem->input.ordinal.timestamp = output.timestampUs;
                    }
                    outputWorkItem->worklets.emplace_back(new C2Worklet);
                    const std::unique_ptr<C2Worklet> &worklet = outputWorkItem->worklets.front();
                    if (worklet == nullptr) {
                        LOG(ERROR) << "handleWork -- output work item has null worklet";
                        return;
                    }
                    worklet->output.ordinal.frameIndex = output.frameIndex;
                    worklet->output.ordinal.timestamp = output.timestampUs;
                    // non-owning hidl_vec<> to wrap around the output config updates
                    hidl_vec<uint8_t> outputConfigUpdates;
                    outputConfigUpdates.setToExternal(
                            output.configUpdates.data, output.configUpdates.size);
                    std::vector<C2Param*> outputConfigUpdatePtrs;
                    parseParamsBlob(&outputConfigUpdatePtrs, outputConfigUpdates);
                    worklet->output.configUpdate.clear();
                    std::ranges::transform(
                            outputConfigUpdatePtrs,
                            std::back_inserter(worklet->output.configUpdate),
                            [](C2Param* param) { return C2Param::Copy(*param); });
                    worklet->output.flags = (C2FrameData::flags_t)output.flags;

                    workItems.push_back(std::move(outputWorkItem));
                }

                // determine whether the input buffer is drained
                if (input.type == APEXCODEC_BUFFER_TYPE_LINEAR) {
                    if (input.memory.linear.size < consumed) {
                        LOG(WARNING) << "handleWork -- component consumed more bytes "
                                    << "than the input buffer size";
                        inputDrained = true;
                    } else {
                        input.memory.linear.data += consumed;
                        input.memory.linear.size -= consumed;
                    }
                } else if (input.type == APEXCODEC_BUFFER_TYPE_GRAPHIC) {
                    inputDrained = (consumed > 0);
                }
            }

            if (!workItems.empty()) {
                listener->onWorkDone(mComponent, workItems);
            }
        }
    }

    bool ensureBlockPool() {
        std::shared_ptr<Component> comp = mComponent.lock();
        if (!comp) {
            return false;
        }
        std::vector<std::unique_ptr<C2Param>> heapParams;
        comp->query({}, {C2PortBlockPoolsTuning::output::PARAM_TYPE}, C2_MAY_BLOCK, &heapParams);
        if (heapParams.size() != 1) {
            return false;
        }
        const C2Param* param = heapParams[0].get();
        if (param->type() != C2PortBlockPoolsTuning::output::PARAM_TYPE) {
            return false;
        }
        const C2PortBlockPoolsTuning::output *blockPools =
                static_cast<const C2PortBlockPoolsTuning::output *>(param);
        if (blockPools->flexCount() == 0) {
            return false;
        }
        C2BlockPool::local_id_t blockPoolId = blockPools->m.values[0];
        if (mBlockPool && mBlockPool->getLocalId() == blockPoolId) {
            // no need to update
            return true;
        }
        return C2_OK == GetCodec2BlockPool(blockPoolId, nullptr, &mBlockPool);
    }

    void allocOutputBuffer(
            ApexCodec_Buffer* output,
            std::shared_ptr<C2LinearBlock> *linearBlock,
            std::optional<C2WriteView> *linearView,
            std::shared_ptr<C2GraphicBlock> *graphicBlock) {
        if (mOutputBufferType == APEXCODEC_BUFFER_TYPE_LINEAR) {
            if (!ensureBlockPool()) {
                return;
            }
            {
                std::shared_ptr<Component> comp = mComponent.lock();
                if (!comp) {
                    return;
                }
                C2StreamMaxBufferSizeInfo::output maxBufferSize(0u /* stream */);
                comp->query({&maxBufferSize}, {}, C2_MAY_BLOCK, {});
                mLinearBlockCapacity = maxBufferSize ? maxBufferSize.value : 1024 * 1024;
            }
            output->type = APEXCODEC_BUFFER_TYPE_LINEAR;
            c2_status_t status = mBlockPool->fetchLinearBlock(
                    mLinearBlockCapacity,
                    C2MemoryUsage(C2MemoryUsage::CPU_READ | C2MemoryUsage::CPU_WRITE),
                    linearBlock);
            if (!(*linearBlock)) {
                return;
            }
            linearView->emplace((*linearBlock)->map().get());
            if ((*linearView)->error() != C2_OK) {
                return;
            }
            output->memory.linear.data = (*linearView)->data();
            output->memory.linear.size = (*linearView)->capacity();
        } else if (mOutputBufferType == APEXCODEC_BUFFER_TYPE_GRAPHIC) {
            if (!ensureBlockPool()) {
                return;
            }
            {
                std::shared_ptr<Component> comp = mComponent.lock();
                if (!comp) {
                    return;
                }
                C2StreamMaxPictureSizeTuning::output maxPictureSize(0u /* stream */);
                C2StreamPictureSizeInfo::output pictureSize(0u /* stream */);
                C2StreamPixelFormatInfo::output pixelFormat(0u /* stream */);
                comp->query({&maxPictureSize, &pictureSize, &pixelFormat}, {}, C2_MAY_BLOCK, {});
                mWidth = maxPictureSize ? maxPictureSize.width : pictureSize.width;
                mHeight = maxPictureSize ? maxPictureSize.height : pictureSize.height;
                mFormat = pixelFormat ? pixelFormat.value : HAL_PIXEL_FORMAT_YCBCR_420_888;
            }
            output->type = APEXCODEC_BUFFER_TYPE_GRAPHIC;
            c2_status_t status = mBlockPool->fetchGraphicBlock(
                    mWidth, mHeight, mFormat,
                    C2MemoryUsage(C2MemoryUsage::CPU_READ | C2MemoryUsage::CPU_WRITE),
                    graphicBlock);
            if (!(*graphicBlock)) {
                return;
            }
            const C2Handle *handle = (*graphicBlock)->handle();
            uint32_t width, height, format, stride, igbp_slot, generation;
            uint64_t usage, igbp_id;
            _UnwrapNativeCodec2GrallocMetadata(
                    handle, &width, &height, &format, &usage, &stride, &generation,
                    &igbp_id, &igbp_slot);
            native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(handle);
            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
                    grallocHandle, GraphicBuffer::CLONE_HANDLE,
                    width, height, format, 1, usage, stride);
            native_handle_delete(grallocHandle);
            AHardwareBuffer *hardwareBuffer =
                AHardwareBuffer_from_GraphicBuffer(graphicBuffer.get());
            AHardwareBuffer_acquire(hardwareBuffer);
            output->memory.graphic = hardwareBuffer;
        } else {
            LOG(ERROR) << "allocOutputBuffer -- unsupported output buffer type: "
                       << mOutputBufferType;
            return;
        }
    }

    static bool FillMemory(
            const std::shared_ptr<C2Buffer>& buffer,
            ApexCodec_Buffer* apexBuffer,
            std::optional<C2ReadView>* linearView) {
        if (buffer->data().type() == C2BufferData::LINEAR) {
            apexBuffer->type = APEXCODEC_BUFFER_TYPE_LINEAR;
            if (buffer->data().linearBlocks().empty()) {
                apexBuffer->memory.linear.data = nullptr;
                apexBuffer->memory.linear.size = 0;
                return true;
            } else if (buffer->data().linearBlocks().size() > 1) {
                return false;
            }
            linearView->emplace(buffer->data().linearBlocks().front().map().get());
            if ((*linearView)->error() != C2_OK) {
                return false;
            }
            apexBuffer->memory.linear.data = const_cast<uint8_t*>((*linearView)->data());
            apexBuffer->memory.linear.size = (*linearView)->capacity();
            return true;
        } else if (buffer->data().type() == C2BufferData::GRAPHIC) {
            apexBuffer->type = APEXCODEC_BUFFER_TYPE_GRAPHIC;
            if (buffer->data().graphicBlocks().empty()) {
                apexBuffer->memory.graphic = nullptr;
                return true;
            } else if (buffer->data().graphicBlocks().size() > 1) {
                return false;
            }
            const C2Handle *handle = buffer->data().graphicBlocks().front().handle();
            uint32_t width, height, format, stride, igbp_slot, generation;
            uint64_t usage, igbp_id;
            _UnwrapNativeCodec2GrallocMetadata(
                    handle, &width, &height, &format, &usage, &stride, &generation,
                    &igbp_id, &igbp_slot);
            native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(handle);
            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
                    grallocHandle, GraphicBuffer::CLONE_HANDLE,
                    width, height, format, 1, usage, stride);
            native_handle_delete(grallocHandle);
            AHardwareBuffer *hardwareBuffer =
                AHardwareBuffer_from_GraphicBuffer(graphicBuffer.get());
            AHardwareBuffer_acquire(hardwareBuffer);
            apexBuffer->memory.graphic = hardwareBuffer;
            return true;
        }
        return false;
    }

    ApexCodec_Component *mApexComponent;
    std::weak_ptr<Listener> mListener;
    std::weak_ptr<Component> mComponent;

    std::thread mThread;
    std::mutex mMutex;
    std::condition_variable mCondition;
    bool mStopped;
    ApexCodec_BufferType mOutputBufferType;

    size_t mLinearBlockCapacity;
    uint32_t mWidth;
    uint32_t mHeight;
    uint32_t mFormat;

    std::shared_ptr<C2BlockPool> mBlockPool;
    std::list<std::unique_ptr<C2Work>> mWorkQueue;
    std::map<uint64_t, std::unique_ptr<C2Work>> mWorkMap;
};

// Codec2Client::Component::HidlBufferPoolSender
struct Codec2Client::Component::HidlBufferPoolSender :
        hardware::media::c2::V1_1::utils::DefaultBufferPoolSender {
    HidlBufferPoolSender()
          : hardware::media::c2::V1_1::utils::DefaultBufferPoolSender() {
    }
};

// Codec2Client::Component::AidlBufferPoolSender
struct Codec2Client::Component::AidlBufferPoolSender :
        c2_aidl::utils::DefaultBufferPoolSender {
    AidlBufferPoolSender()
          : c2_aidl::utils::DefaultBufferPoolSender() {
    }
};

// Codec2Client::Component::OutputBufferQueue
struct Codec2Client::Component::OutputBufferQueue :
        hardware::media::c2::OutputBufferQueue {
    OutputBufferQueue()
          : hardware::media::c2::OutputBufferQueue() {
    }
};

// The class holds GraphicBufferAllocator and the associated id of
// HAL side BlockPool.
// This is tightly coupled with BlockPool creation and destruction.
// The life cycle inside class will be as follows.
//
// On createBlockPool client request.
//    1. this::create() creates a GraphicBufferAllocator and set it as
//        the current.
//    2. C2AIDL_HAL::createBlockPool() creates a C2BlockPool using
//        the GraphicBufferAllocator created in #1.
//    3. this::setCurrentId() associates the id returned in #2 to the current
//
// On destroyBlockPool cliet request
//    1. C2AIDL_HAL::destroyBlockPool() destroys the block pool
//       from HAL process.
//    2. this::remove() destroys GraphicBufferAllocator which is associatted
//       with the C2BlockPool in #1.
//
struct Codec2Client::Component::GraphicBufferAllocators {
private:
    std::optional<C2BlockPool::local_id_t> mCurrentId;
    std::shared_ptr<AidlGraphicBufferAllocator> mCurrent;

    // A new BlockPool is created before the old BlockPool is destroyed.
    // This holds the reference of the old BlockPool when a new BlockPool is
    // created until the old BlockPool is explicitly requested for destruction.
    std::map<C2BlockPool::local_id_t, std::shared_ptr<AidlGraphicBufferAllocator>> mOlds;
    std::mutex mMutex;

public:
    // Creates a GraphicBufferAllocator which will be passed to HAL
    // for creating C2BlockPool. And the created GraphicBufferAllocator
    // will be used afterwards by current().
    std::shared_ptr<AidlGraphicBufferAllocator> create() {
        std::unique_lock<std::mutex> l(mMutex);
        if (mCurrent) {
            // If this is not stopped.
            mCurrent->reset();
            if (mCurrentId.has_value()) {
                mOlds.emplace(mCurrentId.value(), mCurrent);
            }
            mCurrentId.reset();
            mCurrent.reset();
        }
        // TODO: integrate initial value with CCodec/CCodecBufferChannel
        mCurrent =
                AidlGraphicBufferAllocator::CreateGraphicBufferAllocator(3 /* maxDequeueCount */);
        ALOGD("GraphicBufferAllocator created");
        return mCurrent;
    }

    // Associates the blockpool Id returned from HAL to the
    // current GraphicBufferAllocator.
    void setCurrentId(C2BlockPool::local_id_t id) {
        std::unique_lock<std::mutex> l(mMutex);
        CHECK(!mCurrentId.has_value());
        mCurrentId = id;
    }

    // Returns the current GraphicBufferAllocator.
    std::shared_ptr<AidlGraphicBufferAllocator> current() {
        std::unique_lock<std::mutex> l(mMutex);
        return mCurrent;
    }

    // Removes the GraphicBufferAllocator associated with given \p id.
    void remove(C2BlockPool::local_id_t id) {
        std::unique_lock<std::mutex> l(mMutex);
        mOlds.erase(id);
        if (mCurrentId == id) {
            if (mCurrent) {
                mCurrent->reset();
                mCurrent.reset();
            }
            mCurrentId.reset();
        }
    }
};

// Codec2Client
Codec2Client::Codec2Client(sp<HidlBase> const& base,
                           sp<c2_hidl::IConfigurable> const& configurable,
                           size_t serviceIndex)
      : Configurable{configurable},
        mHidlBase1_0{base},
        mHidlBase1_1{HidlBase1_1::castFrom(base)},
        mHidlBase1_2{HidlBase1_2::castFrom(base)},
        mServiceIndex{serviceIndex} {
    Return<sp<bufferpool_hidl::IClientManager>> transResult = base->getPoolClientManager();
    if (!transResult.isOk()) {
        LOG(ERROR) << "getPoolClientManager -- transaction failed.";
    } else {
        mHidlHostPoolManager = static_cast<sp<bufferpool_hidl::IClientManager>>(transResult);
    }
}

Codec2Client::Codec2Client(std::shared_ptr<AidlBase> const& base,
                           std::shared_ptr<c2_aidl::IConfigurable> const& configurable,
                           size_t serviceIndex)
      : Configurable{configurable},
        mAidlBase{base},
        mServiceIndex{serviceIndex} {
    ::ndk::ScopedAStatus transStatus = base->getPoolClientManager(&mAidlHostPoolManager);
    if (!transStatus.isOk()) {
        LOG(ERROR) << "getPoolClientManager -- transaction failed.";
        mAidlHostPoolManager.reset();
    }
}

Codec2Client::Codec2Client(ApexCodec_ComponentStore *base,
                           size_t serviceIndex)
      : Configurable{nullptr, "android.componentStore.apexCodecs"},
        mApexBase{base},
        mServiceIndex{serviceIndex} {
}

sp<Codec2Client::HidlBase> const& Codec2Client::getHidlBase() const {
    return mHidlBase1_0;
}

sp<Codec2Client::HidlBase1_0> const& Codec2Client::getHidlBase1_0() const {
    return mHidlBase1_0;
}

sp<Codec2Client::HidlBase1_1> const& Codec2Client::getHidlBase1_1() const {
    return mHidlBase1_1;
}

sp<Codec2Client::HidlBase1_2> const& Codec2Client::getHidlBase1_2() const {
    return mHidlBase1_2;
}

::ndk::SpAIBinder Codec2Client::getAidlBase() const {
    return mAidlBase ? mAidlBase->asBinder() : nullptr;
}

std::string const& Codec2Client::getServiceName() const {
    return GetServiceNames()[mServiceIndex];
}

c2_status_t Codec2Client::createComponent(
        const C2String& name,
        const std::shared_ptr<Codec2Client::Listener>& listener,
        std::shared_ptr<Codec2Client::Component>* const component) {
    if (mApexBase) {
        return createComponent_apex(name, listener, component);
    } else if (mAidlBase) {
        return createComponent_aidl(name, listener, component);
    } else {
        return createComponent_hidl(name, listener, component);
    }
}

c2_status_t Codec2Client::createComponent_apex(
        const C2String& name,
        const std::shared_ptr<Codec2Client::Listener>& listener,
        std::shared_ptr<Codec2Client::Component>* const component) {
    if (__builtin_available(android 36, *)) {
        ApexCodec_Component *apexComponent = nullptr;
        ApexCodec_Status status = ApexCodec_Component_create(
                mApexBase, name.c_str(), &apexComponent);
        if (status != APEXCODEC_STATUS_OK) {
            return (c2_status_t)status;
        }
        *component = std::make_shared<Codec2Client::Component>(apexComponent, name);
        (*component)->initApexHandler(listener, *component);
        return C2_OK;
    } else {
        return C2_OMITTED;
    }
}

c2_status_t Codec2Client::createComponent_aidl(
        const C2String& name,
        const std::shared_ptr<Codec2Client::Listener>& listener,
        std::shared_ptr<Codec2Client::Component>* const component) {
    std::shared_ptr<Component::AidlListener> aidlListener =
            Component::AidlListener::make<Component::AidlListener>();
    aidlListener->base = listener;
    std::shared_ptr<c2_aidl::IComponent> aidlComponent;
    ::ndk::ScopedAStatus transStatus = mAidlBase->createComponent(
            name,
            aidlListener,
            bufferpool2_aidl::implementation::ClientManager::getInstance(),
            &aidlComponent);
    c2_status_t status = GetC2Status(transStatus, "createComponent");
    if (status != C2_OK) {
        return status;
    } else if (!aidlComponent) {
        LOG(ERROR) << "createComponent(" << name.c_str()
                    << ") -- null component.";
        return C2_CORRUPTED;
    }
    *component = std::make_shared<Codec2Client::Component>(aidlComponent);
    status = (*component)->setDeathListener((*component), listener);
    if (status != C2_OK) {
        LOG(ERROR) << "createComponent(" << name.c_str()
                    << ") -- failed to set up death listener: "
                    << status << ".";
    }
    (*component)->mAidlBufferPoolSender->setReceiver(mAidlHostPoolManager);
    aidlListener->component = *component;
    return status;
}

c2_status_t Codec2Client::createComponent_hidl(
        const C2String& name,
        const std::shared_ptr<Codec2Client::Listener>& listener,
        std::shared_ptr<Codec2Client::Component>* const component) {
    c2_status_t status;
    sp<Component::HidlListener> hidlListener = new Component::HidlListener{};
    hidlListener->base = listener;
    Return<void> transStatus;
    if (mHidlBase1_2) {
        transStatus = mHidlBase1_2->createComponent_1_2(
            name,
            hidlListener,
            bufferpool_hidl::implementation::ClientManager::getInstance(),
            [&status, component, hidlListener](
                    c2_hidl::Status s,
                    const sp<c2_hidl::IComponent>& c) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    return;
                }
                *component = std::make_shared<Codec2Client::Component>(c);
                hidlListener->component = *component;
            });
    }
    else if (mHidlBase1_1) {
        transStatus = mHidlBase1_1->createComponent_1_1(
            name,
            hidlListener,
            bufferpool_hidl::implementation::ClientManager::getInstance(),
            [&status, component, hidlListener](
                    c2_hidl::Status s,
                    const sp<c2_hidl_base::V1_1::IComponent>& c) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    return;
                }
                *component = std::make_shared<Codec2Client::Component>(c);
                hidlListener->component = *component;
            });
    } else if (mHidlBase1_0) { // ver1_0
        transStatus = mHidlBase1_0->createComponent(
            name,
            hidlListener,
            bufferpool_hidl::implementation::ClientManager::getInstance(),
            [&status, component, hidlListener](
                    c2_hidl::Status s,
                    const sp<c2_hidl_base::V1_0::IComponent>& c) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    return;
                }
                *component = std::make_shared<Codec2Client::Component>(c);
                hidlListener->component = *component;
            });
    } else {
        status = C2_CORRUPTED;
    }
    if (!transStatus.isOk()) {
        LOG(ERROR) << "createComponent(" << name.c_str()
                   << ") -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    } else if (status != C2_OK) {
        if (status == C2_NOT_FOUND) {
            LOG(VERBOSE) << "createComponent(" << name.c_str()
                         << ") -- component not found.";
        } else {
            LOG(ERROR) << "createComponent(" << name.c_str()
                       << ") -- call failed: " << status << ".";
        }
        return status;
    } else if (!*component) {
        LOG(ERROR) << "createComponent(" << name.c_str()
                   << ") -- null component.";
        return C2_CORRUPTED;
    }

    status = (*component)->setDeathListener(*component, listener);
    if (status != C2_OK) {
        LOG(ERROR) << "createComponent(" << name.c_str()
                   << ") -- failed to set up death listener: "
                   << status << ".";
    }

    (*component)->mHidlBufferPoolSender->setReceiver(mHidlHostPoolManager);
    return status;
}

c2_status_t Codec2Client::createInterface(
        const C2String& name,
        std::shared_ptr<Codec2Client::Interface>* const interface) {
    if (mAidlBase) {
        std::shared_ptr<c2_aidl::IComponentInterface> aidlInterface;
        ::ndk::ScopedAStatus transStatus = mAidlBase->createInterface(
                name,
                &aidlInterface);
        c2_status_t status = GetC2Status(transStatus, "createInterface");
        if (status != C2_OK) {
            return status;
        } else if (!aidlInterface) {
            LOG(ERROR) << "createInterface(" << name.c_str()
                       << ") -- null interface.";
            return C2_CORRUPTED;
        }
        interface->reset(new Codec2Client::Interface(aidlInterface));
        return C2_OK;
    }

    c2_status_t status;
    Return<void> transStatus = mHidlBase1_0->createInterface(
            name,
            [&status, interface](
                    c2_hidl::Status s,
                    const sp<c2_hidl::IComponentInterface>& i) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    return;
                }
                *interface = std::make_shared<Interface>(i);
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "createInterface(" << name.c_str()
                   << ") -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    } else if (status != C2_OK) {
        if (status == C2_NOT_FOUND) {
            LOG(VERBOSE) << "createInterface(" << name.c_str()
                         << ") -- component not found.";
        } else {
            LOG(ERROR) << "createInterface(" << name.c_str()
                       << ") -- call failed: " << status << ".";
        }
        return status;
    }

    return status;
}

c2_status_t Codec2Client::createInputSurface(
        std::shared_ptr<InputSurface>* const inputSurface) {
    if (mAidlBase) {
        // FIXME
        return C2_OMITTED;
    }

    c2_status_t status;
    Return<void> transStatus = mHidlBase1_0->createInputSurface(
            [&status, inputSurface](
                    c2_hidl::Status s,
                    const sp<c2_hidl::IInputSurface>& i) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    return;
                }
                *inputSurface = std::make_shared<InputSurface>(i);
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "createInputSurface -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    } else if (status != C2_OK) {
        LOG(DEBUG) << "createInputSurface -- call failed: "
                   << status << ".";
    }
    return status;
}

std::vector<C2Component::Traits> const& Codec2Client::listComponents() const {
    return Cache::List()[mServiceIndex].getTraits();
}

std::vector<C2Component::Traits> Codec2Client::_listComponents(
        bool* success) const {
    std::vector<C2Component::Traits> traits;
    std::string const& serviceName = getServiceName();

    if (mAidlBase) {
        std::vector<c2_aidl::IComponentStore::ComponentTraits> aidlTraits;
        ::ndk::ScopedAStatus transStatus = mAidlBase->listComponents(&aidlTraits);
        if (!transStatus.isOk()) {
            LOG(ERROR) << "_listComponents -- transaction failed.";
            *success = false;
        } else {
            traits.resize(aidlTraits.size());
            *success = true;
            for (size_t i = 0; i < aidlTraits.size(); ++i) {
                if (!c2_aidl::utils::FromAidl(&traits[i], aidlTraits[i])) {
                    LOG(ERROR) << "_listComponents -- corrupted output.";
                    *success = false;
                    traits.clear();
                    break;
                }
                traits[i].owner = serviceName;
            }
        }
        return traits;
    }
    Return<void> transStatus = mHidlBase1_0->listComponents(
            [&traits, &serviceName](c2_hidl::Status s,
                   const hidl_vec<c2_hidl::IComponentStore::ComponentTraits>& t) {
                if (s != c2_hidl::Status::OK) {
                    LOG(DEBUG) << "_listComponents -- call failed: "
                               << static_cast<c2_status_t>(s) << ".";
                    return;
                }
                traits.resize(t.size());
                for (size_t i = 0; i < t.size(); ++i) {
                    if (!c2_hidl::utils::objcpy(&traits[i], t[i])) {
                        LOG(ERROR) << "_listComponents -- corrupted output.";
                        return;
                    }
                    traits[i].owner = serviceName;
                }
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "_listComponents -- transaction failed.";
        *success = false;
    } else {
        *success = true;
    }
    return traits;
}

c2_status_t Codec2Client::copyBuffer(
        const std::shared_ptr<C2Buffer>& src,
        const std::shared_ptr<C2Buffer>& dst) {
    // TODO: Implement?
    (void)src;
    (void)dst;
    LOG(ERROR) << "copyBuffer not implemented";
    return C2_OMITTED;
}

std::shared_ptr<C2ParamReflector> Codec2Client::getParamReflector() {
    // TODO: this is not meant to be exposed as C2ParamReflector on the client side; instead, it
    // should reflect the HAL API.
    struct HidlSimpleParamReflector : public C2ParamReflector {
        std::unique_ptr<C2StructDescriptor> describe(
                C2Param::CoreIndex coreIndex) const override {
            hidl_vec<c2_hidl::ParamIndex> indices(1);
            indices[0] = static_cast<c2_hidl::ParamIndex>(coreIndex.coreIndex());
            std::unique_ptr<C2StructDescriptor> descriptor;
            Return<void> transStatus = mBase->getStructDescriptors(
                    indices,
                    [&descriptor](
                            c2_hidl::Status s,
                            const hidl_vec<c2_hidl::StructDescriptor>& sd) {
                        c2_status_t status = static_cast<c2_status_t>(s);
                        if (status != C2_OK) {
                            LOG(DEBUG) << "SimpleParamReflector -- "
                                          "getStructDescriptors() failed: "
                                       << status << ".";
                            descriptor.reset();
                            return;
                        }
                        if (sd.size() != 1) {
                            LOG(DEBUG) << "SimpleParamReflector -- "
                                          "getStructDescriptors() "
                                          "returned vector of size "
                                       << sd.size() << ". "
                                          "It should be 1.";
                            descriptor.reset();
                            return;
                        }
                        if (!c2_hidl::utils::objcpy(&descriptor, sd[0])) {
                            LOG(DEBUG) << "SimpleParamReflector -- "
                                          "getStructDescriptors() returned "
                                          "corrupted data.";
                            descriptor.reset();
                            return;
                        }
                    });
            if (!transStatus.isOk()) {
                LOG(DEBUG) << "SimpleParamReflector -- transaction failed: "
                           << transStatus.description();
                descriptor.reset();
            }
            return descriptor;
        }

        HidlSimpleParamReflector(sp<HidlBase> base)
            : mBase(base) { }

        sp<HidlBase> mBase;
    };
    struct AidlSimpleParamReflector : public C2ParamReflector {
        std::unique_ptr<C2StructDescriptor> describe(
                C2Param::CoreIndex coreIndex) const override {
            std::vector<c2_aidl::StructDescriptor> aidlDesc;
            std::unique_ptr<C2StructDescriptor> descriptor;
            ::ndk::ScopedAStatus transStatus = mBase->getStructDescriptors(
                    {int32_t(coreIndex.coreIndex())},
                    &aidlDesc);
            c2_status_t status = GetC2Status(transStatus, "describe");
            if (status != C2_OK) {
                descriptor.reset();
            } else if (!c2_aidl::utils::FromAidl(&descriptor, aidlDesc[0])) {
                LOG(ERROR) << "describe -- conversion failed.";
                descriptor.reset();
            }
            return descriptor;
        }

        AidlSimpleParamReflector(const std::shared_ptr<AidlBase> &base)
            : mBase(base) { }

        std::shared_ptr<AidlBase> mBase;
    };

    if (mAidlBase) {
        return std::make_shared<AidlSimpleParamReflector>(mAidlBase);
    }
    return std::make_shared<HidlSimpleParamReflector>(mHidlBase1_0);
};

std::vector<std::string> Codec2Client::CacheServiceNames() {
    std::vector<std::string> names;

    if (c2_aidl::utils::IsSelected()) {
        if (__builtin_available(android __ANDROID_API_S__, *)) {
            // Get AIDL service names
            AServiceManager_forEachDeclaredInstance(
                    AidlBase::descriptor, &names, [](const char *name, void *context) {
                        std::vector<std::string> *names = (std::vector<std::string> *)context;
                        names->emplace_back(name);
                    });
        } else {
            LOG(FATAL) << "C2 AIDL cannot be selected on Android version older than 35";
        }
    } else {
        // Get HIDL service names
        using ::android::hardware::media::c2::V1_0::IComponentStore;
        using ::android::hidl::manager::V1_2::IServiceManager;
        while (true) {
            sp<IServiceManager> serviceManager = IServiceManager::getService();
            CHECK(serviceManager) << "Hardware service manager is not running.";

            Return<void> transResult;
            transResult = serviceManager->listManifestByInterface(
                    IComponentStore::descriptor,
                    [&names](
                            hidl_vec<hidl_string> const& instanceNames) {
                        names.insert(names.end(), instanceNames.begin(), instanceNames.end());
                    });
            if (transResult.isOk()) {
                break;
            }
            LOG(ERROR) << "Could not retrieve the list of service instances of "
                       << IComponentStore::descriptor
                       << ". Retrying...";
        }
    }
    // Sort service names in each category.
    std::stable_sort(
        names.begin(), names.end(),
        [](const std::string &a, const std::string &b) {
            // First compare by prefix: default -> vendor -> {everything else}
            constexpr int DEFAULT = 1;
            constexpr int VENDOR = 2;
            constexpr int OTHER = 3;
            int aPrefix = ((a.compare(0, 7, "default") == 0) ? DEFAULT :
                           (a.compare(0, 6, "vendor") == 0) ? VENDOR :
                           OTHER);
            int bPrefix = ((b.compare(0, 7, "default") == 0) ? DEFAULT :
                           (b.compare(0, 6, "vendor") == 0) ? VENDOR :
                           OTHER);
            if (aPrefix != bPrefix) {
                return aPrefix < bPrefix;
            }
            // If the prefix is the same, compare alphabetically
            return a < b;
        });

    if (__builtin_available(android 36, *)) {
        if (android::media::codec::provider_->in_process_sw_audio_codec_support()
                && nullptr != ApexCodec_GetComponentStore()) {
            names.push_back("__ApexCodecs__");
        }
    }

    // Summarize to logcat.
    if (names.empty()) {
        LOG(INFO) << "No Codec2 services declared in the manifest.";
    } else {
        std::stringstream stringOutput;
        stringOutput << "Available Codec2 services:";
        for (std::string const& name : names) {
            stringOutput << " \"" << name << "\"";
        }
        LOG(INFO) << stringOutput.str();
    }

    return names;
}

std::vector<std::string> const& Codec2Client::GetServiceNames() {
    static std::vector<std::string> sServiceNames = CacheServiceNames();
    return sServiceNames;
}

std::shared_ptr<Codec2Client> Codec2Client::CreateFromService(
        const char* name,
        bool setAsPreferredCodec2ComponentStore) {
    size_t index = getServiceIndex(name);
    if (index == GetServiceNames().size()) {
        if (setAsPreferredCodec2ComponentStore) {
            LOG(WARNING) << "CreateFromService(" << name
                         << ") -- preferred C2ComponentStore not set.";
        }
        return nullptr;
    }
    std::shared_ptr<Codec2Client> client = _CreateFromIndex(index);
    if (setAsPreferredCodec2ComponentStore) {
        SetPreferredCodec2ComponentStore(
                std::make_shared<Client2Store>(client));
        LOG(INFO) << "CreateFromService(" << name
                  << ") -- service set as preferred C2ComponentStore.";
    }
    return client;
}

std::vector<std::shared_ptr<Codec2Client>> Codec2Client::
        CreateFromAllServices() {
    std::vector<std::shared_ptr<Codec2Client>> clients(
            GetServiceNames().size());
    for (size_t i = GetServiceNames().size(); i > 0; ) {
        --i;
        clients[i] = _CreateFromIndex(i);
    }
    return clients;
}

std::shared_ptr<Codec2Client> Codec2Client::_CreateFromIndex(size_t index) {
    std::string const& name = GetServiceNames()[index];
    LOG(VERBOSE) << "Creating a Codec2 client to service \"" << name << "\"";

    if (name == "__ApexCodecs__") {
        if (__builtin_available(android 36, *)) {
            return std::make_shared<Codec2Client>(ApexCodec_GetComponentStore(), index);
        } else {
            LOG(FATAL) << "ApexCodecs not supported on Android version older than 36";
        }
    } else if (c2_aidl::utils::IsSelected()) {
        if (__builtin_available(android __ANDROID_API_S__, *)) {
            std::string instanceName =
                ::android::base::StringPrintf("%s/%s", AidlBase::descriptor, name.c_str());
            if (AServiceManager_isDeclared(instanceName.c_str())) {
                std::shared_ptr<AidlBase> baseStore = AidlBase::fromBinder(
                        ::ndk::SpAIBinder(AServiceManager_waitForService(instanceName.c_str())));
                CHECK(baseStore) << "Codec2 AIDL service \"" << name << "\""
                                    " inaccessible for unknown reasons.";
                LOG(VERBOSE) << "Client to Codec2 AIDL service \"" << name << "\" created";
                std::shared_ptr<c2_aidl::IConfigurable> configurable;
                ::ndk::ScopedAStatus transStatus = baseStore->getConfigurable(&configurable);
                CHECK(transStatus.isOk()) << "Codec2 AIDL service \"" << name << "\""
                                            "does not have IConfigurable.";
                return std::make_shared<Codec2Client>(baseStore, configurable, index);
            } else {
                LOG(ERROR) << "Codec2 AIDL service \"" << name << "\" is not declared";
            }
        } else {
            LOG(FATAL) << "C2 AIDL cannot be selected on Android version older than 35";
        }
    } else {
        std::string instanceName = "android.hardware.media.c2/" + name;
        sp<HidlBase> baseStore = HidlBase::getService(name);
        CHECK(baseStore) << "Codec2 service \"" << name << "\""
                            " inaccessible for unknown reasons.";
        LOG(VERBOSE) << "Client to Codec2 service \"" << name << "\" created";
        Return<sp<c2_hidl::IConfigurable>> transResult = baseStore->getConfigurable();
        CHECK(transResult.isOk()) << "Codec2 service \"" << name << "\""
                                    "does not have IConfigurable.";
        sp<c2_hidl::IConfigurable> configurable =
            static_cast<sp<c2_hidl::IConfigurable>>(transResult);
        return std::make_shared<Codec2Client>(baseStore, configurable, index);
    }
    return nullptr;
}

c2_status_t Codec2Client::ForAllServices(
        const std::string &key,
        size_t numberOfAttempts,
        std::function<c2_status_t(const std::shared_ptr<Codec2Client>&)>
            predicate) {
    c2_status_t status = C2_NO_INIT;  // no IComponentStores present

    // Cache the mapping key -> index of Codec2Client in Cache::List().
    static std::mutex key2IndexMutex;
    static std::map<std::string, size_t> key2Index;

    // By default try all stores. However, try the last known client first. If
    // the last known client fails, retry once. We do this by pushing the last
    // known client in front of the list of all clients.
    std::deque<size_t> indices;
    for (size_t index = Cache::List().size(); index > 0; ) {
        indices.push_front(--index);
    }

    bool wasMapped = false;
    {
        std::scoped_lock lock{key2IndexMutex};
        auto it = key2Index.find(key);
        if (it != key2Index.end()) {
            indices.push_front(it->second);
            wasMapped = true;
        }
    }

    for (size_t index : indices) {
        Cache& cache = Cache::List()[index];
        for (size_t tries = numberOfAttempts; tries > 0; --tries) {
            std::shared_ptr<Codec2Client> client{cache.getClient()};
            status = predicate(client);
            if (status == C2_OK) {
                std::scoped_lock lock{key2IndexMutex};
                key2Index[key] = index; // update last known client index
                return C2_OK;
            } else if (status == C2_NO_MEMORY) {
                return C2_NO_MEMORY;
            } else if (status == C2_TRANSACTION_FAILED) {
                LOG(WARNING) << "\"" << key << "\" failed for service \""
                             << client->getName()
                             << "\" due to transaction failure. "
                             << "(Service may have crashed.)"
                             << (tries > 1 ? " Retrying..." : "");
                cache.invalidate();
                continue;
            }
            if (wasMapped) {
                LOG(INFO) << "\"" << key << "\" became invalid in service \""
                          << client->getName() << "\". Retrying...";
                wasMapped = false;
            }
            break;
        }
    }
    return status; // return the last status from a valid client
}

c2_status_t Codec2Client::CreateComponentByName(
        const char* componentName,
        const std::shared_ptr<Listener>& listener,
        std::shared_ptr<Component>* component,
        std::shared_ptr<Codec2Client>* owner,
        size_t numberOfAttempts) {
    std::string key{"create:"};
    key.append(componentName);
    c2_status_t status = ForAllServices(
            key,
            numberOfAttempts,
            [owner, component, componentName, &listener](
                    const std::shared_ptr<Codec2Client> &client)
                        -> c2_status_t {
                c2_status_t status = client->createComponent(componentName,
                                                             listener,
                                                             component);
                if (status == C2_OK) {
                    if (owner) {
                        *owner = client;
                    }
                } else if (status != C2_NOT_FOUND) {
                    LOG(DEBUG) << "IComponentStore("
                                   << client->getServiceName()
                               << ")::createComponent(\"" << componentName
                               << "\") returned status = "
                               << status << ".";
                }
                return status;
            });
    if (status != C2_OK) {
        LOG(DEBUG) << "Failed to create component \"" << componentName
                   << "\" from all known services. "
                      "Last returned status = " << status << ".";
    }
    return status;
}

std::shared_ptr<Codec2Client::Interface> Codec2Client::CreateInterfaceByName(
        const char* interfaceName,
        std::shared_ptr<Codec2Client>* owner,
        size_t numberOfAttempts) {
    std::string key{"create:"};
    key.append(interfaceName);
    std::shared_ptr<Interface> interface;
    c2_status_t status = ForAllServices(
            key,
            numberOfAttempts,
            [owner, &interface, interfaceName](
                    const std::shared_ptr<Codec2Client> &client)
                        -> c2_status_t {
                c2_status_t status = client->createInterface(interfaceName,
                                                             &interface);
                if (status == C2_OK) {
                    if (owner) {
                        *owner = client;
                    }
                } else if (status != C2_NOT_FOUND) {
                    LOG(DEBUG) << "IComponentStore("
                                   << client->getServiceName()
                               << ")::createInterface(\"" << interfaceName
                               << "\") returned status = "
                               << status << ".";
                }
                return status;
            });
    if (status != C2_OK) {
        LOG(DEBUG) << "Failed to create interface \"" << interfaceName
                   << "\" from all known services. "
                      "Last returned status = " << status << ".";
    }
    return interface;
}

std::vector<C2Component::Traits> const& Codec2Client::ListComponents() {
    static std::vector<C2Component::Traits> sList{[]() {
        std::vector<C2Component::Traits> list;
        for (Cache& cache : Cache::List()) {
            std::vector<C2Component::Traits> const& traits = cache.getTraits();
            list.insert(list.end(), traits.begin(), traits.end());
        }
        return list;
    }()};
    return sList;
}

std::shared_ptr<Codec2Client::InputSurface> Codec2Client::CreateInputSurface(
        char const* serviceName) {
    if (!IsCodec2AidlInputSurfaceSelected()) {
        return nullptr;
    }
    size_t index = GetServiceNames().size();
    if (serviceName) {
        index = getServiceIndex(serviceName);
        if (index == GetServiceNames().size()) {
            LOG(DEBUG) << "CreateInputSurface -- invalid service name: \""
                       << serviceName << "\"";
        }
    }

    std::shared_ptr<Codec2Client::InputSurface> inputSurface;
    if (index != GetServiceNames().size()) {
        std::shared_ptr<Codec2Client> client = Cache::List()[index].getClient();
        if (client->createInputSurface(&inputSurface) == C2_OK) {
            return inputSurface;
        }
    }
    LOG(INFO) << "CreateInputSurface -- attempting to create an input surface "
                 "from all services...";
    for (Cache& cache : Cache::List()) {
        std::shared_ptr<Codec2Client> client = cache.getClient();
        if (client->createInputSurface(&inputSurface) == C2_OK) {
            LOG(INFO) << "CreateInputSurface -- input surface obtained from "
                         "service \"" << client->getServiceName() << "\"";
            return inputSurface;
        }
    }
    LOG(WARNING) << "CreateInputSurface -- failed to create an input surface "
                    "from all services";
    return nullptr;
}

bool Codec2Client::IsAidlSelected() {
    return c2_aidl::utils::IsSelected();
}

// Codec2Client::Interface
Codec2Client::Interface::Interface(const sp<HidlBase>& base)
      : Configurable{
            [base]() -> sp<c2_hidl::IConfigurable> {
                Return<sp<c2_hidl::IConfigurable>> transResult =
                        base->getConfigurable();
                return transResult.isOk() ?
                        static_cast<sp<c2_hidl::IConfigurable>>(transResult) :
                        nullptr;
            }()
        },
        mHidlBase{base} {
}

Codec2Client::Interface::Interface(const std::shared_ptr<AidlBase>& base)
      : Configurable{
            [base]() -> std::shared_ptr<c2_aidl::IConfigurable> {
                std::shared_ptr<c2_aidl::IConfigurable> aidlConfigurable;
                ::ndk::ScopedAStatus transStatus =
                    base->getConfigurable(&aidlConfigurable);
                return transStatus.isOk() ? aidlConfigurable : nullptr;
            }()
        },
        mAidlBase{base} {
}

// Codec2Client::Component

class Codec2Client::Component::AidlDeathManager {
public:
    AidlDeathManager()
        : mSeq(0),
          mDeathRecipient(AIBinder_DeathRecipient_new(OnBinderDied)) {
    }

    ~AidlDeathManager() = default;

    bool linkToDeath(
            const std::shared_ptr<Component> &comp,
            const std::shared_ptr<Listener> &listener,
            size_t *seqPtr) {
        std::unique_lock lock(mMutex);
        size_t seq = mSeq++;
        if (!mMap.try_emplace(seq, comp, listener).second) {
            return false;
        }
        if (STATUS_OK != AIBinder_linkToDeath(
                comp->mAidlBase->asBinder().get(), mDeathRecipient.get(), (void *)seq)) {
            mMap.erase(seq);
            return false;
        }
        *seqPtr = seq;
        return true;
    }

    void unlinkToDeath(size_t seq, const std::shared_ptr<AidlBase> &base) {
        std::unique_lock lock(mMutex);
        AIBinder_unlinkToDeath(base->asBinder().get(), mDeathRecipient.get(), (void *)seq);
        mMap.erase(seq);
    }

private:
    std::mutex mMutex;
    size_t mSeq;
    typedef std::tuple<std::weak_ptr<Component>, std::weak_ptr<Listener>> Context;
    std::map<size_t, Context> mMap;
    ::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;

    bool extractContext(size_t seq, Context *context) {
        std::unique_lock lock(mMutex);
        auto node = mMap.extract(seq);
        if (!node) {
            return false;
        }
        *context = node.mapped();
        return true;
    }

    static void OnBinderDied(void *cookie) {
        size_t seq = size_t(cookie);
        Context context;
        if (!Component::GetAidlDeathManager()->extractContext(seq, &context)) {
            return;
        }
        std::weak_ptr<Component> weakComponent;
        std::weak_ptr<Listener> weakListener;
        std::tie(weakComponent, weakListener) = context;
        if (std::shared_ptr<Listener> listener = weakListener.lock()) {
            listener->onDeath(weakComponent);
        } else {
            LOG(DEBUG) << "onDeath -- listener died.";
        }
    }
};

Codec2Client::Component::Component(const sp<HidlBase>& base)
      : Configurable{
            [base]() -> sp<c2_hidl::IConfigurable> {
                Return<sp<c2_hidl::IComponentInterface>> transResult1 =
                        base->getInterface();
                if (!transResult1.isOk()) {
                    return nullptr;
                }
                Return<sp<c2_hidl::IConfigurable>> transResult2 =
                        static_cast<sp<c2_hidl::IComponentInterface>>(transResult1)->
                        getConfigurable();
                return transResult2.isOk() ?
                        static_cast<sp<c2_hidl::IConfigurable>>(transResult2) :
                        nullptr;
            }()
        },
        mHidlBase1_0{base},
        mHidlBase1_1{HidlBase1_1::castFrom(base)},
        mHidlBase1_2{HidlBase1_2::castFrom(base)},
        mHidlBufferPoolSender{std::make_unique<HidlBufferPoolSender>()},
        mOutputBufferQueue{std::make_unique<OutputBufferQueue>()} {
}

Codec2Client::Component::Component(const sp<HidlBase1_1>& base)
      : Configurable{
            [base]() -> sp<c2_hidl::IConfigurable> {
                Return<sp<c2_hidl::IComponentInterface>> transResult1 =
                        base->getInterface();
                if (!transResult1.isOk()) {
                    return nullptr;
                }
                Return<sp<c2_hidl::IConfigurable>> transResult2 =
                        static_cast<sp<c2_hidl::IComponentInterface>>(transResult1)->
                        getConfigurable();
                return transResult2.isOk() ?
                        static_cast<sp<c2_hidl::IConfigurable>>(transResult2) :
                        nullptr;
            }()
        },
        mHidlBase1_0{base},
        mHidlBase1_1{base},
        mHidlBase1_2{HidlBase1_2::castFrom(base)},
        mHidlBufferPoolSender{std::make_unique<HidlBufferPoolSender>()},
        mOutputBufferQueue{std::make_unique<OutputBufferQueue>()} {
}

Codec2Client::Component::Component(const sp<HidlBase1_2>& base)
      : Configurable{
            [base]() -> sp<c2_hidl::IConfigurable> {
                Return<sp<c2_hidl::IComponentInterface>> transResult1 =
                        base->getInterface();
                if (!transResult1.isOk()) {
                    return nullptr;
                }
                Return<sp<c2_hidl::IConfigurable>> transResult2 =
                        static_cast<sp<c2_hidl::IComponentInterface>>(transResult1)->
                        getConfigurable();
                return transResult2.isOk() ?
                        static_cast<sp<c2_hidl::IConfigurable>>(transResult2) :
                        nullptr;
            }()
        },
        mHidlBase1_0{base},
        mHidlBase1_1{base},
        mHidlBase1_2{base},
        mHidlBufferPoolSender{std::make_unique<HidlBufferPoolSender>()},
        mOutputBufferQueue{std::make_unique<OutputBufferQueue>()} {
}

Codec2Client::Component::Component(const std::shared_ptr<AidlBase> &base)
      : Configurable{
            [base]() -> std::shared_ptr<c2_aidl::IConfigurable> {
                std::shared_ptr<c2_aidl::IComponentInterface> aidlIntf;
                ::ndk::ScopedAStatus transStatus = base->getInterface(&aidlIntf);
                if (!transStatus.isOk()) {
                    return nullptr;
                }
                std::shared_ptr<c2_aidl::IConfigurable> aidlConfigurable;
                transStatus = aidlIntf->getConfigurable(&aidlConfigurable);
                return transStatus.isOk() ? aidlConfigurable : nullptr;
            }()
        },
        mAidlBase{base},
        mAidlBufferPoolSender{std::make_unique<AidlBufferPoolSender>()},
        mGraphicBufferAllocators{std::make_unique<GraphicBufferAllocators>()} {
}

Codec2Client::Component::Component(ApexCodec_Component *base, const C2String &name)
      : Configurable{[base]() -> ApexCodec_Configurable * {
            if (__builtin_available(android 36, *)) {
                return ApexCodec_Component_getConfigurable(base);
            } else {
                return nullptr;
            }
        }(), name},
        mApexBase{base} {
}

Codec2Client::Component::~Component() {
    if (mAidlDeathSeq) {
        GetAidlDeathManager()->unlinkToDeath(*mAidlDeathSeq, mAidlBase);
    }
    if (mApexBase) {
        if (__builtin_available(android 36, *)) {
            ApexCodec_Component_destroy(mApexBase);
        }
        mApexBase = nullptr;
    }
}

c2_status_t Codec2Client::Component::createBlockPool(
        C2Allocator::id_t id,
        C2BlockPool::local_id_t* blockPoolId,
        std::shared_ptr<Codec2Client::Configurable>* configurable) {
    if (mApexBase) {
        std::shared_ptr<C2BlockPool> blockPool;
        CreateCodec2BlockPool(id, nullptr, &blockPool);
        *blockPoolId = blockPool->getLocalId();
        *configurable = nullptr;
        mBlockPools[*blockPoolId] = blockPool;
        return C2_OK;
    }
    if (mAidlBase) {
        c2_aidl::IComponent::BlockPool aidlBlockPool;
        c2_status_t status = C2_OK;

        // TODO: Temporary mapping for the current CCodecBufferChannel.
        // Handle this properly and remove this temporary allocator mapping.
        id = id == C2PlatformAllocatorStore::BUFFERQUEUE ?
                C2PlatformAllocatorStore::IGBA : id;

        c2_aidl::IComponent::BlockPoolAllocator allocator;
        allocator.allocatorId = id;
        if (id == C2PlatformAllocatorStore::IGBA)  {
            std::shared_ptr<AidlGraphicBufferAllocator> gba =
                    mGraphicBufferAllocators->create();
            ::ndk::ScopedFileDescriptor waitableFd;
            ::ndk::ScopedAStatus ret = gba->getWaitableFd(&waitableFd);
            status = GetC2Status(ret, "Gba::getWaitableFd");
            if (status != C2_OK) {
                return status;
            }
            c2_aidl::IComponent::GbAllocator gbAllocator;
            gbAllocator.waitableFd = std::move(waitableFd);
            gbAllocator.igba =
                    c2_aidl::IGraphicBufferAllocator::fromBinder(gba->asBinder());
            allocator.gbAllocator = std::move(gbAllocator);
            ::ndk::ScopedAStatus transStatus = mAidlBase->createBlockPool(
                    allocator, &aidlBlockPool);
            status = GetC2Status(transStatus, "createBlockPool");
            if (status != C2_OK) {
                return status;
            }
            mGraphicBufferAllocators->setCurrentId(aidlBlockPool.blockPoolId);
        } else {
            ::ndk::ScopedAStatus transStatus = mAidlBase->createBlockPool(
                    allocator, &aidlBlockPool);
            status = GetC2Status(transStatus, "createBlockPool");
            if (status != C2_OK) {
                return status;
            }
        }
        *blockPoolId = aidlBlockPool.blockPoolId;
        *configurable = std::make_shared<Configurable>(aidlBlockPool.configurable);
        return C2_OK;
    }
    c2_status_t status;
    Return<void> transStatus = mHidlBase1_0->createBlockPool(
            static_cast<uint32_t>(id),
            [&status, blockPoolId, configurable](
                    c2_hidl::Status s,
                    uint64_t pId,
                    const sp<c2_hidl::IConfigurable>& c) {
                status = static_cast<c2_status_t>(s);
                configurable->reset();
                if (status != C2_OK) {
                    LOG(DEBUG) << "createBlockPool -- call failed: "
                               << status << ".";
                    return;
                }
                *blockPoolId = static_cast<C2BlockPool::local_id_t>(pId);
                *configurable = std::make_shared<Configurable>(c);
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "createBlockPool -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    return status;
}

c2_status_t Codec2Client::Component::destroyBlockPool(
        C2BlockPool::local_id_t localId) {
    if (mApexBase) {
        mBlockPools.erase(localId);
        return C2_OK;
    }
    if (mAidlBase) {
        mGraphicBufferAllocators->remove(localId);
        ::ndk::ScopedAStatus transStatus = mAidlBase->destroyBlockPool(localId);
        return GetC2Status(transStatus, "destroyBlockPool");
    }
    Return<c2_hidl::Status> transResult = mHidlBase1_0->destroyBlockPool(
            static_cast<uint64_t>(localId));
    if (!transResult.isOk()) {
        LOG(ERROR) << "destroyBlockPool -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    return static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transResult));
}

void Codec2Client::Component::handleOnWorkDone(
        const std::list<std::unique_ptr<C2Work>> &workItems) {
    if (mApexBase) {
        // no-op
        return;
    } else if (mAidlBase) {
        holdIgbaBlocks(workItems);
    } else {
        // Output bufferqueue-based blocks' lifetime management
        mOutputBufferQueue->holdBufferQueueBlocks(workItems);
    }
}

c2_status_t Codec2Client::Component::queue(
        std::list<std::unique_ptr<C2Work>>* const items) {
    if (mApexBase) {
        mApexHandler->queue(*items);
        return C2_OK;
    }
    if (mAidlBase) {
        c2_aidl::WorkBundle workBundle;
        if (!c2_aidl::utils::ToAidl(&workBundle, *items, mAidlBufferPoolSender.get())) {
            LOG(ERROR) << "queue -- bad input.";
            return C2_TRANSACTION_FAILED;
        }
        ::ndk::ScopedAStatus transStatus = mAidlBase->queue(workBundle);
        return GetC2Status(transStatus, "queue");
    }
    c2_hidl::WorkBundle workBundle;
    if (!c2_hidl::utils::objcpy(&workBundle, *items, mHidlBufferPoolSender.get())) {
        LOG(ERROR) << "queue -- bad input.";
        return C2_TRANSACTION_FAILED;
    }
    Return<c2_hidl::Status> transStatus = mHidlBase1_0->queue(workBundle);
    if (!transStatus.isOk()) {
        LOG(ERROR) << "queue -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status =
            static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
    if (status != C2_OK) {
        LOG(DEBUG) << "queue -- call failed: " << status << ".";
    }
    return status;
}

c2_status_t Codec2Client::Component::flush(
        C2Component::flush_mode_t mode,
        std::list<std::unique_ptr<C2Work>>* const flushedWork) {
    (void)mode; // Flush mode isn't supported in HIDL/AIDL yet.
    if (mApexBase) {
        if (__builtin_available(android 36, *)) {
            return (c2_status_t)ApexCodec_Component_flush(mApexBase);
        } else {
            return C2_OMITTED;
        }
    }
    c2_status_t status = C2_OK;
    if (mAidlBase) {
        c2_aidl::WorkBundle workBundle;
        ::ndk::ScopedAStatus transStatus = mAidlBase->flush(&workBundle);
        c2_status_t status = GetC2Status(transStatus, "flush");
        if (status != C2_OK) {
            return status;
        }
        if (!c2_aidl::utils::FromAidl(flushedWork, workBundle)) {
            LOG(DEBUG) << "flush -- flushedWork corrupted.";
            return C2_CORRUPTED;
        }
    } else {
        Return<void> transStatus = mHidlBase1_0->flush(
                [&status, flushedWork](
                        c2_hidl::Status s, const c2_hidl::WorkBundle& wb) {
                    status = static_cast<c2_status_t>(s);
                    if (status != C2_OK) {
                        LOG(DEBUG) << "flush -- call failed: " << status << ".";
                        return;
                    }
                    if (!c2_hidl::utils::objcpy(flushedWork, wb)) {
                        status = C2_CORRUPTED;
                    } else {
                        status = C2_OK;
                    }
                });
        if (!transStatus.isOk()) {
            LOG(ERROR) << "flush -- transaction failed.";
            return C2_TRANSACTION_FAILED;
        }
    }

    // Indices of flushed work items.
    std::vector<uint64_t> flushedIndices;
    for (const std::unique_ptr<C2Work> &work : *flushedWork) {
        if (work) {
            if (work->worklets.empty()
                    || !work->worklets.back()
                    || (work->worklets.back()->output.flags &
                        C2FrameData::FLAG_INCOMPLETE) == 0) {
                // input is complete
                flushedIndices.emplace_back(
                        work->input.ordinal.frameIndex.peeku());
            }
        }
    }

    if (mAidlBase) {
        holdIgbaBlocks(*flushedWork);
    } else {
        // Output bufferqueue-based blocks' lifetime management
        mOutputBufferQueue->holdBufferQueueBlocks(*flushedWork);
    }

    return status;
}

c2_status_t Codec2Client::Component::drain(C2Component::drain_mode_t mode) {
    if (mApexBase) {
        return C2_OMITTED;
    }
    if (mAidlBase) {
        ::ndk::ScopedAStatus transStatus = mAidlBase->drain(
                mode == C2Component::DRAIN_COMPONENT_WITH_EOS);
        return GetC2Status(transStatus, "drain");
    }
    Return<c2_hidl::Status> transStatus = mHidlBase1_0->drain(
            mode == C2Component::DRAIN_COMPONENT_WITH_EOS);
    if (!transStatus.isOk()) {
        LOG(ERROR) << "drain -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status =
            static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
    if (status != C2_OK) {
        LOG(DEBUG) << "drain -- call failed: " << status << ".";
    }
    return status;
}

c2_status_t Codec2Client::Component::start() {
    if (mApexBase) {
        // no-op
        return C2_OK;
    }
    if (mAidlBase) {
        ::ndk::ScopedAStatus transStatus = mAidlBase->start();
        return GetC2Status(transStatus, "start");
    }
    Return<c2_hidl::Status> transStatus = mHidlBase1_0->start();
    if (!transStatus.isOk()) {
        LOG(ERROR) << "start -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status =
            static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
    if (status != C2_OK) {
        LOG(DEBUG) << "start -- call failed: " << status << ".";
    }
    return status;
}

c2_status_t Codec2Client::Component::stop() {
    if (mAidlBase) {
        ::ndk::ScopedAStatus transStatus = mAidlBase->stop();
        return GetC2Status(transStatus, "stop");
    }
    Return<c2_hidl::Status> transStatus = mHidlBase1_0->stop();
    if (!transStatus.isOk()) {
        LOG(ERROR) << "stop -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status =
            static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
    if (status != C2_OK) {
        LOG(DEBUG) << "stop -- call failed: " << status << ".";
    }
    return status;
}

c2_status_t Codec2Client::Component::reset() {
    if (mApexBase) {
        if (__builtin_available(android 36, *)) {
            return (c2_status_t)ApexCodec_Component_reset(mApexBase);
        } else {
            return C2_OMITTED;
        }
    }
    if (mAidlBase) {
        ::ndk::ScopedAStatus transStatus = mAidlBase->reset();
        return GetC2Status(transStatus, "reset");
    }
    Return<c2_hidl::Status> transStatus = mHidlBase1_0->reset();
    if (!transStatus.isOk()) {
        LOG(ERROR) << "reset -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status =
            static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
    if (status != C2_OK) {
        LOG(DEBUG) << "reset -- call failed: " << status << ".";
    }
    return status;
}

c2_status_t Codec2Client::Component::release() {
    if (mApexBase) {
        if (__builtin_available(android 36, *)) {
            return (c2_status_t)ApexCodec_Component_reset(mApexBase);
        } else {
            return C2_OMITTED;
        }
    }
    if (mAidlBase) {
        ::ndk::ScopedAStatus transStatus = mAidlBase->release();
        return GetC2Status(transStatus, "release");
    }
    Return<c2_hidl::Status> transStatus = mHidlBase1_0->release();
    if (!transStatus.isOk()) {
        LOG(ERROR) << "release -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status =
            static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
    if (status != C2_OK) {
        LOG(DEBUG) << "release -- call failed: " << status << ".";
    }
    return status;
}

c2_status_t Codec2Client::Component::configureVideoTunnel(
        uint32_t avSyncHwId,
        native_handle_t** sidebandHandle) {
    *sidebandHandle = nullptr;
    if (mApexBase) {
        // tunneling is not supported in APEX
        return C2_OMITTED;
    }
    if (mAidlBase) {
        ::aidl::android::hardware::common::NativeHandle handle;
        ::ndk::ScopedAStatus transStatus = mAidlBase->configureVideoTunnel(avSyncHwId, &handle);
        c2_status_t status = GetC2Status(transStatus, "configureVideoTunnel");
        if (status != C2_OK) {
            return status;
        }
        if (isAidlNativeHandleEmpty(handle)) {
            LOG(DEBUG) << "configureVideoTunnel -- empty handle returned";
        } else {
            *sidebandHandle = dupFromAidl(handle);
        }
        return C2_OK;
    }
    if (!mHidlBase1_1) {
        return C2_OMITTED;
    }
    c2_status_t status{};
    Return<void> transStatus = mHidlBase1_1->configureVideoTunnel(avSyncHwId,
            [&status, sidebandHandle](
                    c2_hidl::Status s, hardware::hidl_handle const& h) {
                status = static_cast<c2_status_t>(s);
                if (h.getNativeHandle()) {
                    *sidebandHandle = native_handle_clone(h.getNativeHandle());
                }
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "configureVideoTunnel -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    return status;
}

c2_status_t Codec2Client::Component::setOutputSurface(
        C2BlockPool::local_id_t blockPoolId,
        const sp<IGraphicBufferProducer>& surface,
        uint32_t generation,
        int maxDequeueCount) {
    if (mAidlBase) {
        std::shared_ptr<AidlGraphicBufferAllocator> gba =
              mGraphicBufferAllocators->current();
        if (!gba) {
            LOG(ERROR) << "setOutputSurface for AIDL -- "
                       "GraphicBufferAllocator was not created.";
            return C2_CORRUPTED;
        }
        // Note: Consumer usage is set ahead of the HAL allocator(gba) being set.
        // This is same as HIDL.
        uint64_t consumerUsage = configConsumerUsage(surface);
        bool ret = gba->configure(surface, generation, maxDequeueCount);
        ALOGD("setOutputSurface -- generation=%u consumer usage=%#llx",
              generation, (long long)consumerUsage);
        return ret ? C2_OK : C2_CORRUPTED;
    }
    uint64_t bqId = 0;
    sp<IGraphicBufferProducer> nullIgbp;
    sp<HGraphicBufferProducer2> nullHgbp;

    sp<HGraphicBufferProducer2> igbp = surface ?
            surface->getHalInterface<HGraphicBufferProducer2>() : nullHgbp;
    if (surface && !igbp) {
        igbp = new B2HGraphicBufferProducer2(surface);
    }

    std::scoped_lock lock(mOutputMutex);
    std::shared_ptr<SurfaceSyncObj> syncObj;

    if (!surface) {
        mOutputBufferQueue->configure(nullIgbp, generation, 0, maxDequeueCount, nullptr);
    } else if (surface->getUniqueId(&bqId) != OK) {
        LOG(ERROR) << "setOutputSurface -- "
                   "cannot obtain bufferqueue id.";
        bqId = 0;
        mOutputBufferQueue->configure(nullIgbp, generation, 0, maxDequeueCount, nullptr);
    } else {
        mOutputBufferQueue->configure(surface, generation, bqId, maxDequeueCount,
                                      mHidlBase1_2 ? &syncObj : nullptr);
    }

    uint64_t consumerUsage = configConsumerUsage(surface);
    ALOGD("setOutputSurface -- generation=%u consumer usage=%#llx%s",
          generation, (long long)consumerUsage, syncObj ? " sync" : "");

    Return<c2_hidl::Status> transStatus = syncObj ?
            mHidlBase1_2->setOutputSurfaceWithSyncObj(
                    static_cast<uint64_t>(blockPoolId),
                    bqId == 0 ? nullHgbp : igbp, *syncObj) :
            mHidlBase1_0->setOutputSurface(
                    static_cast<uint64_t>(blockPoolId),
                    bqId == 0 ? nullHgbp : igbp);

    mOutputBufferQueue->expireOldWaiters();

    if (!transStatus.isOk()) {
        LOG(ERROR) << "setOutputSurface -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status =
            static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
    if (status != C2_OK) {
        LOG(DEBUG) << "setOutputSurface -- call failed: " << status << ".";
    }
    ALOGD("Surface configure completed");
    return status;
}

status_t Codec2Client::Component::queueToOutputSurface(
        const C2ConstGraphicBlock& block,
        const QueueBufferInput& input,
        QueueBufferOutput* output) {
    ScopedTrace trace(ATRACE_TAG,"Codec2Client::Component::queueToOutputSurface");
    if (mAidlBase) {
        std::shared_ptr<AidlGraphicBufferAllocator> gba =
                mGraphicBufferAllocators->current();
        if (gba) {
            return gba->displayBuffer(block, input, output);
        } else {
            return C2_NOT_FOUND;
        }
    }
    return mOutputBufferQueue->outputBuffer(block, input, output);
}

uint64_t Codec2Client::Component::configConsumerUsage(
        const sp<IGraphicBufferProducer>& surface) {
    // set consumer bits
    // TODO: should this get incorporated into setOutputSurface method so that consumer bits
    // can be set atomically?
    uint64_t consumerUsage = kDefaultConsumerUsage;
    {
        if (surface) {
            uint64_t usage = 0;
            status_t err = surface->getConsumerUsage(&usage);
            if (err != NO_ERROR) {
                ALOGD("setOutputSurface -- failed to get consumer usage bits (%d/%s). ignoring",
                        err, asString(err));
            } else {
                // Note: we are adding the default usage because components must support
                // producing output frames that can be displayed an all output surfaces.

                // TODO: do not set usage for tunneled scenario. It is unclear if consumer usage
                // is meaningful in a tunneled scenario; on one hand output buffers exist, but
                // they do not exist inside of C2 scope. Any buffer usage shall be communicated
                // through the sideband channel.

                consumerUsage = usage | kDefaultConsumerUsage;
            }
        }

        C2StreamUsageTuning::output outputUsage{
                0u, C2AndroidMemoryUsage::FromGrallocUsage(consumerUsage).expected};
        std::vector<std::unique_ptr<C2SettingResult>> failures;
        c2_status_t err = config({&outputUsage}, C2_MAY_BLOCK, &failures);
        if (err != C2_OK) {
            ALOGD("setOutputSurface -- failed to set consumer usage (%d/%s)",
                    err, asString(err));
        }
    }
    return consumerUsage;
}

void Codec2Client::Component::pollForRenderedFrames(FrameEventHistoryDelta* delta) {
    if (mAidlBase) {
        // TODO b/311348680
        return;
    }
    mOutputBufferQueue->pollForRenderedFrames(delta);
}

void Codec2Client::Component::setOutputSurfaceMaxDequeueCount(
        int maxDequeueCount) {
    if (mAidlBase) {
        std::shared_ptr<AidlGraphicBufferAllocator> gba =
                mGraphicBufferAllocators->current();
        if (gba) {
            gba->updateMaxDequeueBufferCount(maxDequeueCount);
        }
        return;
    }
    mOutputBufferQueue->updateMaxDequeueBufferCount(maxDequeueCount);
}

void Codec2Client::Component::stopUsingOutputSurface(
        C2BlockPool::local_id_t blockPoolId) {
    if (mAidlBase) {
        std::shared_ptr<AidlGraphicBufferAllocator> gba =
                mGraphicBufferAllocators->current();
        if (gba) {
            gba->reset();
        }
        return;
    }
    std::scoped_lock lock(mOutputMutex);
    mOutputBufferQueue->stop();
    Return<c2_hidl::Status> transStatus = mHidlBase1_0->setOutputSurface(
            static_cast<uint64_t>(blockPoolId), nullptr);
    if (!transStatus.isOk()) {
        LOG(ERROR) << "setOutputSurface(stopUsingOutputSurface) -- transaction failed.";
    } else {
        c2_status_t status =
                static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
        if (status != C2_OK) {
            LOG(DEBUG) << "setOutputSurface(stopUsingOutputSurface) -- call failed: "
                       << status << ".";
        }
    }
    mOutputBufferQueue->expireOldWaiters();
}

void Codec2Client::Component::onBufferReleasedFromOutputSurface(
        uint32_t generation) {
    if (mAidlBase) {
        std::shared_ptr<AidlGraphicBufferAllocator> gba =
                mGraphicBufferAllocators->current();
        if (gba) {
            gba->onBufferReleased(generation);
        }
        return;
    }
    mOutputBufferQueue->onBufferReleased(generation);
}

void Codec2Client::Component::onBufferAttachedToOutputSurface(
        uint32_t generation) {
    if (mAidlBase) {
        std::shared_ptr<AidlGraphicBufferAllocator> gba =
                mGraphicBufferAllocators->current();
        if (gba) {
            gba->onBufferAttached(generation);
        }
        return;
    }
    mOutputBufferQueue->onBufferAttached(generation);
}

void Codec2Client::Component::holdIgbaBlocks(
        const std::list<std::unique_ptr<C2Work>>& workList) {
    if (!mAidlBase) {
        return;
    }
    std::shared_ptr<AidlGraphicBufferAllocator> gba =
            mGraphicBufferAllocators->current();
    if (!gba) {
        return;
    }
    std::shared_ptr<c2_aidl::IGraphicBufferAllocator> igba =
            c2_aidl::IGraphicBufferAllocator::fromBinder(gba->asBinder());
    for (const std::unique_ptr<C2Work>& work : workList) {
        if (!work) [[unlikely]] {
            continue;
        }
        for (const std::unique_ptr<C2Worklet>& worklet : work->worklets) {
            if (!worklet) {
                continue;
            }
            for (const std::shared_ptr<C2Buffer>& buffer : worklet->output.buffers) {
                if (buffer) {
                    for (const C2ConstGraphicBlock& block : buffer->data().graphicBlocks()) {
                        std::shared_ptr<_C2BlockPoolData> poolData =
                              _C2BlockFactory::GetGraphicBlockPoolData(block);
                        _C2BlockFactory::RegisterIgba(poolData, igba);
                    }
                }
            }
        }
    }
}

c2_status_t Codec2Client::Component::connectToInputSurface(
        const std::shared_ptr<InputSurface>& inputSurface,
        std::shared_ptr<InputSurfaceConnection>* connection) {
    if (mApexBase) {
        // FIXME
        return C2_OMITTED;
    }
    if (mAidlBase) {
        // FIXME
        return C2_OMITTED;
    }
    c2_status_t status;
    Return<void> transStatus = mHidlBase1_0->connectToInputSurface(
            inputSurface->mBase,
            [&status, connection](
                    c2_hidl::Status s, const sp<c2_hidl::IInputSurfaceConnection>& c) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    LOG(DEBUG) << "connectToInputSurface -- call failed: "
                               << status << ".";
                    return;
                }
                *connection = std::make_shared<InputSurfaceConnection>(c);
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "connectToInputSurface -- transaction failed";
        return C2_TRANSACTION_FAILED;
    }
    return status;
}

c2_status_t Codec2Client::Component::connectToOmxInputSurface(
        const sp<HGraphicBufferProducer1>& producer,
        const sp<HGraphicBufferSource>& source,
        std::shared_ptr<InputSurfaceConnection>* connection) {
    if (mApexBase) {
        LOG(WARNING) << "Connecting to OMX input surface is not supported for AIDL C2 HAL";
        return C2_OMITTED;
    }
    if (mAidlBase) {
        LOG(WARNING) << "Connecting to OMX input surface is not supported for AIDL C2 HAL";
        return C2_OMITTED;
    }
    c2_status_t status;
    Return<void> transStatus = mHidlBase1_0->connectToOmxInputSurface(
            producer, source,
            [&status, connection](
                    c2_hidl::Status s, const sp<c2_hidl::IInputSurfaceConnection>& c) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    LOG(DEBUG) << "connectToOmxInputSurface -- call failed: "
                               << status << ".";
                    return;
                }
                *connection = std::make_shared<InputSurfaceConnection>(c);
            });
    if (!transStatus.isOk()) {
        LOG(ERROR) << "connectToOmxInputSurface -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    return status;
}

c2_status_t Codec2Client::Component::disconnectFromInputSurface() {
    if (mApexBase) {
        // FIXME
        return C2_OMITTED;
    }
    if (mAidlBase) {
        // FIXME
        return C2_OMITTED;
    }
    Return<c2_hidl::Status> transStatus = mHidlBase1_0->disconnectFromInputSurface();
    if (!transStatus.isOk()) {
        LOG(ERROR) << "disconnectToInputSurface -- transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    c2_status_t status =
            static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transStatus));
    if (status != C2_OK) {
        LOG(DEBUG) << "disconnectFromInputSurface -- call failed: "
                   << status << ".";
    }
    return status;
}

Codec2Client::Component::AidlDeathManager *Codec2Client::Component::GetAidlDeathManager() {
    // This object never gets destructed
    static AidlDeathManager *sManager = new AidlDeathManager();
    return sManager;
}

c2_status_t Codec2Client::Component::initApexHandler(
            const std::shared_ptr<Listener> &listener,
            const std::shared_ptr<Component> &comp) {
    if (!mApexBase) {
        return C2_BAD_STATE;
    }
    mApexHandler = std::make_unique<ApexHandler>(mApexBase, listener, comp);
    return C2_OK;
}

c2_status_t Codec2Client::Component::setDeathListener(
        const std::shared_ptr<Component>& component,
        const std::shared_ptr<Listener>& listener) {

    struct HidlDeathRecipient : public hardware::hidl_death_recipient {
        std::weak_ptr<Component> component;
        std::weak_ptr<Listener> base;

        virtual void serviceDied(
                uint64_t /* cookie */,
                const wp<::android::hidl::base::V1_0::IBase>& /* who */
                ) override {
            if (std::shared_ptr<Codec2Client::Listener> listener = base.lock()) {
                listener->onDeath(component);
            } else {
                LOG(DEBUG) << "onDeath -- listener died.";
            }
        }
    };

    if (component->mAidlBase) {
        size_t seq;
        if (GetAidlDeathManager()->linkToDeath(component, listener, &seq)) {
            component->mAidlDeathSeq = seq;
        }
        return C2_OK;
    }

    sp<HidlDeathRecipient> deathRecipient = new HidlDeathRecipient();
    deathRecipient->base = listener;
    deathRecipient->component = component;

    component->mDeathRecipient = deathRecipient;
    Return<bool> transResult = component->mHidlBase1_0->linkToDeath(
            component->mDeathRecipient, 0);
    if (!transResult.isOk()) {
        LOG(ERROR) << "setDeathListener -- linkToDeath() transaction failed.";
        return C2_TRANSACTION_FAILED;
    }
    if (!static_cast<bool>(transResult)) {
        LOG(DEBUG) << "setDeathListener -- linkToDeath() call failed.";
        return C2_CORRUPTED;
    }
    return C2_OK;
}

// Codec2Client::InputSurface
Codec2Client::InputSurface::InputSurface(const sp<c2_hidl::IInputSurface>& base)
      : Configurable{
            [base]() -> sp<c2_hidl::IConfigurable> {
                Return<sp<c2_hidl::IConfigurable>> transResult =
                        base->getConfigurable();
                return transResult.isOk() ?
                        static_cast<sp<c2_hidl::IConfigurable>>(transResult) :
                        nullptr;
            }()
        },
        mBase{base},
        mGraphicBufferProducer{new
            H2BGraphicBufferProducer2([base]() -> sp<HGraphicBufferProducer2> {
                Return<sp<HGraphicBufferProducer2>> transResult =
                        base->getGraphicBufferProducer();
                return transResult.isOk() ?
                        static_cast<sp<HGraphicBufferProducer2>>(transResult) :
                        nullptr;
            }())} {
}

sp<IGraphicBufferProducer>
        Codec2Client::InputSurface::getGraphicBufferProducer() const {
    return mGraphicBufferProducer;
}

sp<c2_hidl::IInputSurface> Codec2Client::InputSurface::getHalInterface() const {
    return mBase;
}

// Codec2Client::InputSurfaceConnection
Codec2Client::InputSurfaceConnection::InputSurfaceConnection(
        const sp<c2_hidl::IInputSurfaceConnection>& base)
      : Configurable{
            [base]() -> sp<c2_hidl::IConfigurable> {
                Return<sp<c2_hidl::IConfigurable>> transResult =
                        base->getConfigurable();
                return transResult.isOk() ?
                        static_cast<sp<c2_hidl::IConfigurable>>(transResult) :
                        nullptr;
            }()
        },
        mBase{base} {
}

c2_status_t Codec2Client::InputSurfaceConnection::disconnect() {
    Return<c2_hidl::Status> transResult = mBase->disconnect();
    return static_cast<c2_status_t>(static_cast<c2_hidl::Status>(transResult));
}

}  // namespace android
