/*
 * Copyright (C) 2017-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_TAG "CamDevSession@3.4-impl"
#include <android/log.h>

#include <set>
#include <utils/Trace.h>
#include <hardware/gralloc.h>
#include <hardware/gralloc1.h>
#include "CameraDeviceSession.h"
#include "CameraModule.h"

namespace android {
namespace hardware {
namespace camera {
namespace device {
namespace V3_4 {
namespace implementation {

using ::android::hardware::camera::common::V1_0::helper::CameraModule;

CameraDeviceSession::CameraDeviceSession(
    camera3_device_t* device,
    const camera_metadata_t* deviceInfo,
    const sp<V3_2::ICameraDeviceCallback>& callback) :
        V3_3::implementation::CameraDeviceSession(device, deviceInfo, callback),
        mResultBatcher_3_4(callback) {

    mHasCallback_3_4 = false;

    auto castResult = ICameraDeviceCallback::castFrom(callback);
    if (castResult.isOk()) {
        sp<ICameraDeviceCallback> callback3_4 = castResult;
        if (callback3_4 != nullptr) {
            process_capture_result = sProcessCaptureResult_3_4;
            notify = sNotify_3_4;
            mHasCallback_3_4 = true;
            if (!mInitFail) {
                mResultBatcher_3_4.setResultMetadataQueue(mResultMetadataQueue);
            }
        }
    }

    mResultBatcher_3_4.setNumPartialResults(mNumPartialResults);

    // Parse and store current logical camera's physical ids.
    (void)CameraModule::isLogicalMultiCamera(mDeviceInfo, &mPhysicalCameraIds);

}

CameraDeviceSession::~CameraDeviceSession() {
}

Return<void> CameraDeviceSession::configureStreams_3_4(
        const StreamConfiguration& requestedConfiguration,
        ICameraDeviceSession::configureStreams_3_4_cb _hidl_cb)  {
    configureStreams_3_4_Impl(requestedConfiguration, _hidl_cb);
    return Void();
}

void CameraDeviceSession::configureStreams_3_4_Impl(
        const StreamConfiguration& requestedConfiguration,
        ICameraDeviceSession::configureStreams_3_4_cb _hidl_cb,
        uint32_t streamConfigCounter, bool useOverriddenFields)  {
    Status status = initStatus();
    HalStreamConfiguration outStreams;

    // If callback is 3.2, make sure no physical stream is configured
    if (!mHasCallback_3_4) {
        for (size_t i = 0; i < requestedConfiguration.streams.size(); i++) {
            if (requestedConfiguration.streams[i].physicalCameraId.size() > 0) {
                ALOGE("%s: trying to configureStreams with physical camera id with V3.2 callback",
                        __FUNCTION__);
                _hidl_cb(Status::INTERNAL_ERROR, outStreams);
                return;
            }
        }
    }

    // hold the inflight lock for entire configureStreams scope since there must not be any
    // inflight request/results during stream configuration.
    Mutex::Autolock _l(mInflightLock);
    if (!mInflightBuffers.empty()) {
        ALOGE("%s: trying to configureStreams while there are still %zu inflight buffers!",
                __FUNCTION__, mInflightBuffers.size());
        _hidl_cb(Status::INTERNAL_ERROR, outStreams);
        return;
    }

    if (!mInflightAETriggerOverrides.empty()) {
        ALOGE("%s: trying to configureStreams while there are still %zu inflight"
                " trigger overrides!", __FUNCTION__,
                mInflightAETriggerOverrides.size());
        _hidl_cb(Status::INTERNAL_ERROR, outStreams);
        return;
    }

    if (!mInflightRawBoostPresent.empty()) {
        ALOGE("%s: trying to configureStreams while there are still %zu inflight"
                " boost overrides!", __FUNCTION__,
                mInflightRawBoostPresent.size());
        _hidl_cb(Status::INTERNAL_ERROR, outStreams);
        return;
    }

    if (status != Status::OK) {
        _hidl_cb(status, outStreams);
        return;
    }

    const camera_metadata_t *paramBuffer = nullptr;
    if (0 < requestedConfiguration.sessionParams.size()) {
        V3_2::implementation::convertFromHidl(requestedConfiguration.sessionParams, &paramBuffer);
    }

    camera3_stream_configuration_t stream_list{};
    // Block reading mStreamConfigCounter until configureStream returns
    Mutex::Autolock _sccl(mStreamConfigCounterLock);
    mStreamConfigCounter = streamConfigCounter;
    hidl_vec<camera3_stream_t*> streams;
    stream_list.session_parameters = paramBuffer;
    if (!preProcessConfigurationLocked_3_4(requestedConfiguration,
            useOverriddenFields, &stream_list, &streams)) {
        _hidl_cb(Status::INTERNAL_ERROR, outStreams);
        return;
    }

    ATRACE_BEGIN("camera3->configure_streams");
    status_t ret = mDevice->ops->configure_streams(mDevice, &stream_list);
    ATRACE_END();

    // In case Hal returns error most likely it was not able to release
    // the corresponding resources of the deleted streams.
    if (ret == OK) {
        postProcessConfigurationLocked_3_4(requestedConfiguration);
    } else {
        postProcessConfigurationFailureLocked_3_4(requestedConfiguration);
    }

    if (ret == -EINVAL) {
        status = Status::ILLEGAL_ARGUMENT;
    } else if (ret != OK) {
        status = Status::INTERNAL_ERROR;
    } else {
        V3_4::implementation::convertToHidl(stream_list, &outStreams);
        mFirstRequest = true;
    }

    _hidl_cb(status, outStreams);
    return;
}

bool CameraDeviceSession::preProcessConfigurationLocked_3_4(
        const StreamConfiguration& requestedConfiguration, bool useOverriddenFields,
        camera3_stream_configuration_t *stream_list /*out*/,
        hidl_vec<camera3_stream_t*> *streams /*out*/) {

    if ((stream_list == nullptr) || (streams == nullptr)) {
        return false;
    }

    stream_list->operation_mode = (uint32_t) requestedConfiguration.operationMode;
    stream_list->num_streams = requestedConfiguration.streams.size();
    streams->resize(stream_list->num_streams);
    stream_list->streams = streams->data();

    for (uint32_t i = 0; i < stream_list->num_streams; i++) {
        int id = requestedConfiguration.streams[i].v3_2.id;

        if (mStreamMap.count(id) == 0) {
            Camera3Stream stream;
            convertFromHidl(requestedConfiguration.streams[i], &stream);
            mStreamMap[id] = stream;
            mPhysicalCameraIdMap[id] = requestedConfiguration.streams[i].physicalCameraId;
            mStreamMap[id].data_space = mapToLegacyDataspace(
                    mStreamMap[id].data_space);
            mCirculatingBuffers.emplace(stream.mId, CirculatingBuffers{});
        } else {
            // width/height/format must not change, but usage/rotation might need to change.
            // format and data_space may change.
            if (mStreamMap[id].stream_type !=
                    (int) requestedConfiguration.streams[i].v3_2.streamType ||
                    mStreamMap[id].width != requestedConfiguration.streams[i].v3_2.width ||
                    mStreamMap[id].height != requestedConfiguration.streams[i].v3_2.height ||
                    mPhysicalCameraIdMap[id] != requestedConfiguration.streams[i].physicalCameraId) {
                ALOGE("%s: stream %d configuration changed!", __FUNCTION__, id);
                return false;
            }
            if (useOverriddenFields) {
                android_dataspace_t requestedDataSpace =
                        mapToLegacyDataspace(static_cast<android_dataspace_t>(
                        requestedConfiguration.streams[i].v3_2.dataSpace));
                if (mStreamMap[id].format != (int) requestedConfiguration.streams[i].v3_2.format ||
                        mStreamMap[id].data_space != requestedDataSpace) {
                    ALOGE("%s: stream %d configuration changed!", __FUNCTION__, id);
                    return false;
                }
            } else {
                mStreamMap[id].format =
                        (int) requestedConfiguration.streams[i].v3_2.format;
                mStreamMap[id].data_space = (android_dataspace_t)
                        requestedConfiguration.streams[i].v3_2.dataSpace;
            }
            mStreamMap[id].rotation = (int) requestedConfiguration.streams[i].v3_2.rotation;
            mStreamMap[id].usage = (uint32_t) requestedConfiguration.streams[i].v3_2.usage;
        }
        // It is possible for the entry in 'mStreamMap' to get initialized by an older
        // HIDL API. Make sure that the physical id is always initialized when using
        // a more recent API call.
        mStreamMap[id].physical_camera_id = mPhysicalCameraIdMap[id].c_str();

        (*streams)[i] = &mStreamMap[id];
    }

    if (mFreeBufEarly) {
        // Remove buffers of deleted streams
        for(auto it = mStreamMap.begin(); it != mStreamMap.end(); it++) {
            int id = it->first;
            bool found = false;
            for (const auto& stream : requestedConfiguration.streams) {
                if (id == stream.v3_2.id) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                // Unmap all buffers of deleted stream
                cleanupBuffersLocked(id);
            }
        }
    }
    return true;
}

void CameraDeviceSession::postProcessConfigurationLocked_3_4(
        const StreamConfiguration& requestedConfiguration) {
    // delete unused streams, note we do this after adding new streams to ensure new stream
    // will not have the same address as deleted stream, and HAL has a chance to reference
    // the to be deleted stream in configure_streams call
    for(auto it = mStreamMap.begin(); it != mStreamMap.end();) {
        int id = it->first;
        bool found = false;
        for (const auto& stream : requestedConfiguration.streams) {
            if (id == stream.v3_2.id) {
                found = true;
                break;
            }
        }
        if (!found) {
            // Unmap all buffers of deleted stream
            // in case the configuration call succeeds and HAL
            // is able to release the corresponding resources too.
            if (!mFreeBufEarly) {
                cleanupBuffersLocked(id);
            }
            it = mStreamMap.erase(it);
        } else {
            ++it;
        }
    }

    // Track video streams
    mVideoStreamIds.clear();
    for (const auto& stream : requestedConfiguration.streams) {
        if (stream.v3_2.streamType == StreamType::OUTPUT &&
            stream.v3_2.usage &
                graphics::common::V1_0::BufferUsage::VIDEO_ENCODER) {
            mVideoStreamIds.push_back(stream.v3_2.id);
        }
    }
    mResultBatcher_3_4.setBatchedStreams(mVideoStreamIds);
}

void CameraDeviceSession::postProcessConfigurationFailureLocked_3_4(
        const StreamConfiguration& requestedConfiguration) {
    if (mFreeBufEarly) {
        // Re-build the buf cache entry for deleted streams
        for(auto it = mStreamMap.begin(); it != mStreamMap.end(); it++) {
            int id = it->first;
            bool found = false;
            for (const auto& stream : requestedConfiguration.streams) {
                if (id == stream.v3_2.id) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                mCirculatingBuffers.emplace(id, CirculatingBuffers{});
            }
        }
    }
}

Return<void> CameraDeviceSession::processCaptureRequest_3_4(
        const hidl_vec<V3_4::CaptureRequest>& requests,
        const hidl_vec<V3_2::BufferCache>& cachesToRemove,
        ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb)  {
    updateBufferCaches(cachesToRemove);

    uint32_t numRequestProcessed = 0;
    Status s = Status::OK;
    for (size_t i = 0; i < requests.size(); i++, numRequestProcessed++) {
        s = processOneCaptureRequest_3_4(requests[i]);
        if (s != Status::OK) {
            break;
        }
    }

    if (s == Status::OK && requests.size() > 1) {
        mResultBatcher_3_4.registerBatch(requests[0].v3_2.frameNumber, requests.size());
    }

    _hidl_cb(s, numRequestProcessed);
    return Void();
}

Status CameraDeviceSession::processOneCaptureRequest_3_4(const V3_4::CaptureRequest& request)  {
    Status status = initStatus();
    if (status != Status::OK) {
        ALOGE("%s: camera init failed or disconnected", __FUNCTION__);
        return status;
    }
    // If callback is 3.2, make sure there are no physical settings.
    if (!mHasCallback_3_4) {
        if (request.physicalCameraSettings.size() > 0) {
            ALOGE("%s: trying to call processCaptureRequest_3_4 with physical camera id "
                    "and V3.2 callback", __FUNCTION__);
            return Status::INTERNAL_ERROR;
        }
    }

    camera3_capture_request_t halRequest;
    halRequest.frame_number = request.v3_2.frameNumber;

    bool converted = true;
    V3_2::CameraMetadata settingsFmq;  // settings from FMQ
    if (request.v3_2.fmqSettingsSize > 0) {
        // non-blocking read; client must write metadata before calling
        // processOneCaptureRequest
        settingsFmq.resize(request.v3_2.fmqSettingsSize);
        bool read = mRequestMetadataQueue->read(settingsFmq.data(), request.v3_2.fmqSettingsSize);
        if (read) {
            converted = V3_2::implementation::convertFromHidl(settingsFmq, &halRequest.settings);
        } else {
            ALOGE("%s: capture request settings metadata couldn't be read from fmq!", __FUNCTION__);
            converted = false;
        }
    } else {
        converted = V3_2::implementation::convertFromHidl(request.v3_2.settings,
                &halRequest.settings);
    }

    if (!converted) {
        ALOGE("%s: capture request settings metadata is corrupt!", __FUNCTION__);
        return Status::ILLEGAL_ARGUMENT;
    }

    if (mFirstRequest && halRequest.settings == nullptr) {
        ALOGE("%s: capture request settings must not be null for first request!",
                __FUNCTION__);
        return Status::ILLEGAL_ARGUMENT;
    }

    hidl_vec<buffer_handle_t*> allBufPtrs;
    hidl_vec<int> allFences;
    bool hasInputBuf = (request.v3_2.inputBuffer.streamId != -1 &&
            request.v3_2.inputBuffer.bufferId != 0);
    size_t numOutputBufs = request.v3_2.outputBuffers.size();
    size_t numBufs = numOutputBufs + (hasInputBuf ? 1 : 0);

    if (numOutputBufs == 0) {
        ALOGE("%s: capture request must have at least one output buffer!", __FUNCTION__);
        return Status::ILLEGAL_ARGUMENT;
    }

    status = importRequest(request.v3_2, allBufPtrs, allFences);
    if (status != Status::OK) {
        return status;
    }

    hidl_vec<camera3_stream_buffer_t> outHalBufs;
    outHalBufs.resize(numOutputBufs);
    bool aeCancelTriggerNeeded = false;
    ::android::hardware::camera::common::V1_0::helper::CameraMetadata settingsOverride;
    {
        Mutex::Autolock _l(mInflightLock);
        if (hasInputBuf) {
            auto streamId = request.v3_2.inputBuffer.streamId;
            auto key = std::make_pair(request.v3_2.inputBuffer.streamId, request.v3_2.frameNumber);
            auto& bufCache = mInflightBuffers[key] = camera3_stream_buffer_t{};
            convertFromHidl(
                    allBufPtrs[numOutputBufs], request.v3_2.inputBuffer.status,
                    &mStreamMap[request.v3_2.inputBuffer.streamId], allFences[numOutputBufs],
                    &bufCache);
            bufCache.stream->physical_camera_id = mPhysicalCameraIdMap[streamId].c_str();
            halRequest.input_buffer = &bufCache;
        } else {
            halRequest.input_buffer = nullptr;
        }

        halRequest.num_output_buffers = numOutputBufs;
        for (size_t i = 0; i < numOutputBufs; i++) {
            auto streamId = request.v3_2.outputBuffers[i].streamId;
            auto key = std::make_pair(streamId, request.v3_2.frameNumber);
            auto& bufCache = mInflightBuffers[key] = camera3_stream_buffer_t{};
            convertFromHidl(
                    allBufPtrs[i], request.v3_2.outputBuffers[i].status,
                    &mStreamMap[streamId], allFences[i],
                    &bufCache);
            bufCache.stream->physical_camera_id = mPhysicalCameraIdMap[streamId].c_str();
            outHalBufs[i] = bufCache;
        }
        halRequest.output_buffers = outHalBufs.data();

        AETriggerCancelOverride triggerOverride;
        aeCancelTriggerNeeded = handleAePrecaptureCancelRequestLocked(
                halRequest, &settingsOverride /*out*/, &triggerOverride/*out*/);
        if (aeCancelTriggerNeeded) {
            mInflightAETriggerOverrides[halRequest.frame_number] =
                    triggerOverride;
            halRequest.settings = settingsOverride.getAndLock();
        }
    }

    std::vector<const char *> physicalCameraIds;
    std::vector<const camera_metadata_t *> physicalCameraSettings;
    std::vector<V3_2::CameraMetadata> physicalFmq;
    size_t settingsCount = request.physicalCameraSettings.size();
    if (settingsCount > 0) {
        physicalCameraIds.reserve(settingsCount);
        physicalCameraSettings.reserve(settingsCount);
        physicalFmq.reserve(settingsCount);

        for (size_t i = 0; i < settingsCount; i++) {
            uint64_t settingsSize = request.physicalCameraSettings[i].fmqSettingsSize;
            const camera_metadata_t *settings = nullptr;
            if (settingsSize > 0) {
                physicalFmq.push_back(V3_2::CameraMetadata(settingsSize));
                bool read = mRequestMetadataQueue->read(physicalFmq[i].data(), settingsSize);
                if (read) {
                    converted = V3_2::implementation::convertFromHidl(physicalFmq[i], &settings);
                    physicalCameraSettings.push_back(settings);
                } else {
                    ALOGE("%s: physical camera settings metadata couldn't be read from fmq!",
                            __FUNCTION__);
                    converted = false;
                }
            } else {
                converted = V3_2::implementation::convertFromHidl(
                        request.physicalCameraSettings[i].settings, &settings);
                physicalCameraSettings.push_back(settings);
            }

            if (!converted) {
                ALOGE("%s: physical camera settings metadata is corrupt!", __FUNCTION__);
                return Status::ILLEGAL_ARGUMENT;
            }

            if (mFirstRequest && settings == nullptr) {
                ALOGE("%s: Individual request settings must not be null for first request!",
                        __FUNCTION__);
                return Status::ILLEGAL_ARGUMENT;
            }

            physicalCameraIds.push_back(request.physicalCameraSettings[i].physicalCameraId.c_str());
        }
    }
    halRequest.num_physcam_settings = settingsCount;
    halRequest.physcam_id = physicalCameraIds.data();
    halRequest.physcam_settings = physicalCameraSettings.data();

    ATRACE_ASYNC_BEGIN("frame capture", request.v3_2.frameNumber);
    ATRACE_BEGIN("camera3->process_capture_request");
    status_t ret = mDevice->ops->process_capture_request(mDevice, &halRequest);
    ATRACE_END();
    if (aeCancelTriggerNeeded) {
        settingsOverride.unlock(halRequest.settings);
    }
    if (ret != OK) {
        Mutex::Autolock _l(mInflightLock);
        ALOGE("%s: HAL process_capture_request call failed!", __FUNCTION__);

        cleanupInflightFences(allFences, numBufs);
        if (hasInputBuf) {
            auto key = std::make_pair(request.v3_2.inputBuffer.streamId, request.v3_2.frameNumber);
            mInflightBuffers.erase(key);
        }
        for (size_t i = 0; i < numOutputBufs; i++) {
            auto key = std::make_pair(request.v3_2.outputBuffers[i].streamId,
                    request.v3_2.frameNumber);
            mInflightBuffers.erase(key);
        }
        if (aeCancelTriggerNeeded) {
            mInflightAETriggerOverrides.erase(request.v3_2.frameNumber);
        }

        if (ret == BAD_VALUE) {
            return Status::ILLEGAL_ARGUMENT;
        } else {
            return Status::INTERNAL_ERROR;
        }
    }

    mFirstRequest = false;
    return Status::OK;
}

/**
 * Static callback forwarding methods from HAL to instance
 */
void CameraDeviceSession::sProcessCaptureResult_3_4(
        const camera3_callback_ops *cb,
        const camera3_capture_result *hal_result) {
    CameraDeviceSession *d =
            const_cast<CameraDeviceSession*>(static_cast<const CameraDeviceSession*>(cb));

    CaptureResult result = {};
    camera3_capture_result shadowResult;
    bool handlePhysCam = (d->mDeviceVersion >= CAMERA_DEVICE_API_VERSION_3_5);
    std::vector<::android::hardware::camera::common::V1_0::helper::CameraMetadata> compactMds;
    std::vector<const camera_metadata_t*> physCamMdArray;
    sShrinkCaptureResult(&shadowResult, hal_result, &compactMds, &physCamMdArray, handlePhysCam);

    status_t ret = d->constructCaptureResult(result.v3_2, &shadowResult);
    if (ret != OK) {
        return;
    }

    if (handlePhysCam) {
        if (shadowResult.num_physcam_metadata > d->mPhysicalCameraIds.size()) {
            ALOGE("%s: Fatal: Invalid num_physcam_metadata %u", __FUNCTION__,
                    shadowResult.num_physcam_metadata);
            return;
        }
        result.physicalCameraMetadata.resize(shadowResult.num_physcam_metadata);
        for (uint32_t i = 0; i < shadowResult.num_physcam_metadata; i++) {
            std::string physicalId = shadowResult.physcam_ids[i];
            if (d->mPhysicalCameraIds.find(physicalId) == d->mPhysicalCameraIds.end()) {
                ALOGE("%s: Fatal: Invalid physcam_ids[%u]: %s", __FUNCTION__,
                      i, shadowResult.physcam_ids[i]);
                return;
            }
            V3_2::CameraMetadata physicalMetadata;
            V3_2::implementation::convertToHidl(
                    shadowResult.physcam_metadata[i], &physicalMetadata);
            PhysicalCameraMetadata physicalCameraMetadata = {
                    .fmqMetadataSize = 0,
                    .physicalCameraId = physicalId,
                    .metadata = physicalMetadata };
            result.physicalCameraMetadata[i] = physicalCameraMetadata;
        }
    }
    d->mResultBatcher_3_4.processCaptureResult_3_4(result);
}

void CameraDeviceSession::sNotify_3_4(
        const camera3_callback_ops *cb,
        const camera3_notify_msg *msg) {
    CameraDeviceSession *d =
            const_cast<CameraDeviceSession*>(static_cast<const CameraDeviceSession*>(cb));
    V3_2::NotifyMsg hidlMsg;
    V3_2::implementation::convertToHidl(msg, &hidlMsg);

    if (hidlMsg.type == (V3_2::MsgType) CAMERA3_MSG_ERROR &&
            hidlMsg.msg.error.errorStreamId != -1) {
        if (d->mStreamMap.count(hidlMsg.msg.error.errorStreamId) != 1) {
            ALOGE("%s: unknown stream ID %d reports an error!",
                    __FUNCTION__, hidlMsg.msg.error.errorStreamId);
            return;
        }
    }

    if (static_cast<camera3_msg_type_t>(hidlMsg.type) == CAMERA3_MSG_ERROR) {
        switch (hidlMsg.msg.error.errorCode) {
            case V3_2::ErrorCode::ERROR_DEVICE:
            case V3_2::ErrorCode::ERROR_REQUEST:
            case V3_2::ErrorCode::ERROR_RESULT: {
                Mutex::Autolock _l(d->mInflightLock);
                auto entry = d->mInflightAETriggerOverrides.find(
                        hidlMsg.msg.error.frameNumber);
                if (d->mInflightAETriggerOverrides.end() != entry) {
                    d->mInflightAETriggerOverrides.erase(
                            hidlMsg.msg.error.frameNumber);
                }

                auto boostEntry = d->mInflightRawBoostPresent.find(
                        hidlMsg.msg.error.frameNumber);
                if (d->mInflightRawBoostPresent.end() != boostEntry) {
                    d->mInflightRawBoostPresent.erase(
                            hidlMsg.msg.error.frameNumber);
                }

            }
                break;
            case V3_2::ErrorCode::ERROR_BUFFER:
            default:
                break;
        }

    }

    d->mResultBatcher_3_4.notify(hidlMsg);
}

CameraDeviceSession::ResultBatcher_3_4::ResultBatcher_3_4(
        const sp<V3_2::ICameraDeviceCallback>& callback) :
        V3_3::implementation::CameraDeviceSession::ResultBatcher(callback) {
    auto castResult = ICameraDeviceCallback::castFrom(callback);
    if (castResult.isOk()) {
        mCallback_3_4 = castResult;
    }
}

void CameraDeviceSession::ResultBatcher_3_4::processCaptureResult_3_4(CaptureResult& result) {
    auto pair = getBatch(result.v3_2.frameNumber);
    int batchIdx = pair.first;
    if (batchIdx == NOT_BATCHED) {
        processOneCaptureResult_3_4(result);
        return;
    }
    std::shared_ptr<InflightBatch> batch = pair.second;
    {
        Mutex::Autolock _l(batch->mLock);
        // Check if the batch is removed (mostly by notify error) before lock was acquired
        if (batch->mRemoved) {
            // Fall back to non-batch path
            processOneCaptureResult_3_4(result);
            return;
        }

        // queue metadata
        if (result.v3_2.result.size() != 0) {
            // Save a copy of metadata
            batch->mResultMds[result.v3_2.partialResult].mMds.push_back(
                    std::make_pair(result.v3_2.frameNumber, result.v3_2.result));
        }

        // queue buffer
        std::vector<int> filledStreams;
        std::vector<V3_2::StreamBuffer> nonBatchedBuffers;
        for (auto& buffer : result.v3_2.outputBuffers) {
            auto it = batch->mBatchBufs.find(buffer.streamId);
            if (it != batch->mBatchBufs.end()) {
                InflightBatch::BufferBatch& bb = it->second;
                auto id = buffer.streamId;
                pushStreamBuffer(std::move(buffer), bb.mBuffers);
                filledStreams.push_back(id);
            } else {
                pushStreamBuffer(std::move(buffer), nonBatchedBuffers);
            }
        }

        // send non-batched buffers up
        if (nonBatchedBuffers.size() > 0 || result.v3_2.inputBuffer.streamId != -1) {
            CaptureResult nonBatchedResult;
            nonBatchedResult.v3_2.frameNumber = result.v3_2.frameNumber;
            nonBatchedResult.v3_2.fmqResultSize = 0;
            nonBatchedResult.v3_2.outputBuffers.resize(nonBatchedBuffers.size());
            for (size_t i = 0; i < nonBatchedBuffers.size(); i++) {
                moveStreamBuffer(
                        std::move(nonBatchedBuffers[i]), nonBatchedResult.v3_2.outputBuffers[i]);
            }
            moveStreamBuffer(std::move(result.v3_2.inputBuffer), nonBatchedResult.v3_2.inputBuffer);
            nonBatchedResult.v3_2.partialResult = 0; // 0 for buffer only results
            processOneCaptureResult_3_4(nonBatchedResult);
        }

        if (result.v3_2.frameNumber == batch->mLastFrame) {
            // Send data up
            if (result.v3_2.partialResult > 0) {
                sendBatchMetadataLocked(batch, result.v3_2.partialResult);
            }
            // send buffer up
            if (filledStreams.size() > 0) {
                sendBatchBuffersLocked(batch, filledStreams);
            }
        }
    } // end of batch lock scope

    // see if the batch is complete
    if (result.v3_2.frameNumber == batch->mLastFrame) {
        checkAndRemoveFirstBatch();
    }
}

void CameraDeviceSession::ResultBatcher_3_4::processOneCaptureResult_3_4(CaptureResult& result) {
    hidl_vec<CaptureResult> results;
    results.resize(1);
    results[0] = std::move(result);
    invokeProcessCaptureResultCallback_3_4(results, /* tryWriteFmq */true);
    freeReleaseFences_3_4(results);
    return;
}

void CameraDeviceSession::ResultBatcher_3_4::invokeProcessCaptureResultCallback_3_4(
        hidl_vec<CaptureResult> &results, bool tryWriteFmq) {
    if (mProcessCaptureResultLock.tryLock() != OK) {
        ALOGV("%s: previous call is not finished! waiting 1s...", __FUNCTION__);
        if (mProcessCaptureResultLock.timedLock(1000000000 /* 1s */) != OK) {
            ALOGE("%s: cannot acquire lock in 1s, cannot proceed",
                    __FUNCTION__);
            return;
        }
    }
    if (tryWriteFmq && mResultMetadataQueue->availableToWrite() > 0) {
        for (CaptureResult &result : results) {
            if (result.v3_2.result.size() > 0) {
                if (mResultMetadataQueue->write(result.v3_2.result.data(),
                        result.v3_2.result.size())) {
                    result.v3_2.fmqResultSize = result.v3_2.result.size();
                    result.v3_2.result.resize(0);
                } else {
                    ALOGW("%s: couldn't utilize fmq, fall back to hwbinder", __FUNCTION__);
                    result.v3_2.fmqResultSize = 0;
                }
            }

            for (auto& onePhysMetadata : result.physicalCameraMetadata) {
                if (mResultMetadataQueue->write(onePhysMetadata.metadata.data(),
                        onePhysMetadata.metadata.size())) {
                    onePhysMetadata.fmqMetadataSize = onePhysMetadata.metadata.size();
                    onePhysMetadata.metadata.resize(0);
                } else {
                    ALOGW("%s: couldn't utilize fmq, fall back to hwbinder", __FUNCTION__);
                    onePhysMetadata.fmqMetadataSize = 0;
                }
            }
        }
    }
    mCallback_3_4->processCaptureResult_3_4(results);
    mProcessCaptureResultLock.unlock();
}

void CameraDeviceSession::ResultBatcher_3_4::freeReleaseFences_3_4(hidl_vec<CaptureResult>& results) {
    for (auto& result : results) {
        if (result.v3_2.inputBuffer.releaseFence.getNativeHandle() != nullptr) {
            native_handle_t* handle = const_cast<native_handle_t*>(
                    result.v3_2.inputBuffer.releaseFence.getNativeHandle());
            native_handle_close(handle);
            native_handle_delete(handle);
        }
        for (auto& buf : result.v3_2.outputBuffers) {
            if (buf.releaseFence.getNativeHandle() != nullptr) {
                native_handle_t* handle = const_cast<native_handle_t*>(
                        buf.releaseFence.getNativeHandle());
                native_handle_close(handle);
                native_handle_delete(handle);
            }
        }
    }
    return;
}

} // namespace implementation
}  // namespace V3_4
}  // namespace device
}  // namespace camera
}  // namespace hardware
}  // namespace android
