/*
**
** Copyright 2022, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

// #define LOG_NDEBUG 0
#define LOG_TAG "SoundDoseManager"

#include "SoundDoseManager.h"

#include "android/media/SoundDoseRecord.h"
#include <algorithm>
#include <android-base/stringprintf.h>
#include <cinttypes>
#include <ctime>
#include <functional>
#include <media/AidlConversionCppNdk.h>
#include <utils/Log.h>

namespace android {

using aidl::android::media::audio::common::AudioDevice;

namespace {

// Port handle used when CSD is computed on all devices. Should be a different value than
// AUDIO_PORT_HANDLE_NONE which is associated with a sound dose callback failure
constexpr audio_port_handle_t CSD_ON_ALL_DEVICES_PORT_HANDLE = -1;

int64_t getMonotonicSecond() {
    struct timespec now_ts;
    if (clock_gettime(CLOCK_MONOTONIC, &now_ts) != 0) {
        ALOGE("%s: cannot get timestamp", __func__);
        return -1;
    }
    return now_ts.tv_sec;
}

constexpr float kDefaultRs2LowerBound = 80.f;  // dBA

}  // namespace

sp<audio_utils::MelProcessor> SoundDoseManager::getOrCreateProcessorForDevice(
        audio_port_handle_t deviceId, audio_io_handle_t streamHandle, uint32_t sampleRate,
        size_t channelCount, audio_format_t format) {
    const std::lock_guard _l(mLock);

    if (!mUseFrameworkMel && mHalSoundDose.size() > 0 && mEnabledCsd) {
        ALOGD("%s: using HAL MEL computation, no MelProcessor needed.", __func__);
        return nullptr;
    }

    auto streamProcessor = mActiveProcessors.find(streamHandle);
    if (streamProcessor != mActiveProcessors.end()) {
        auto processor = streamProcessor->second.promote();
        // if processor is nullptr it means it was removed by the playback
        // thread and can be replaced in the mActiveProcessors map
        if (processor != nullptr) {
            ALOGV("%s: found callback for stream id %d", __func__, streamHandle);
            const auto activeTypeIt = mActiveDeviceTypes.find(deviceId);
            if (activeTypeIt != mActiveDeviceTypes.end()) {
                processor->setAttenuation(mMelAttenuationDB[activeTypeIt->second]);
            }
            processor->setDeviceId(deviceId);
            processor->setOutputRs2UpperBound(mRs2UpperBound);
            return processor;
        }
    }

    ALOGV("%s: creating new callback for stream id %d", __func__, streamHandle);
    sp<audio_utils::MelProcessor> melProcessor = sp<audio_utils::MelProcessor>::make(
            sampleRate, channelCount, format, this, deviceId, mRs2UpperBound);
    const auto activeTypeIt = mActiveDeviceTypes.find(deviceId);
    if (activeTypeIt != mActiveDeviceTypes.end()) {
        melProcessor->setAttenuation(mMelAttenuationDB[activeTypeIt->second]);
    }
    mActiveProcessors[streamHandle] = melProcessor;
    return melProcessor;
}

bool SoundDoseManager::setHalSoundDoseInterface(const std::string &module,
                                                const std::shared_ptr<ISoundDose> &halSoundDose) {
    ALOGV("%s", __func__);

    if (halSoundDose == nullptr) {
        ALOGI("%s: passed ISoundDose object is null", __func__);
        return false;
    }

    std::shared_ptr<HalSoundDoseCallback> halSoundDoseCallback;
    {
        const std::lock_guard _l(mLock);

        if (mHalSoundDose.find(module) != mHalSoundDose.end()) {
            ALOGW("%s: Module %s already has a sound dose HAL assigned, skipping", __func__,
                  module.c_str());
            return false;
        }
        mHalSoundDose[module] = halSoundDose;

        if (!halSoundDose->setOutputRs2UpperBound(mRs2UpperBound).isOk()) {
            ALOGW("%s: Cannot set RS2 value for momentary exposure %f",
                  __func__,
                  mRs2UpperBound);
        }

        // initialize the HAL sound dose callback lazily
        if (mHalSoundDoseCallback == nullptr) {
            mHalSoundDoseCallback =
                ndk::SharedRefBase::make<HalSoundDoseCallback>(this);
        }
        halSoundDoseCallback = mHalSoundDoseCallback;
    }

    auto status = halSoundDose->registerSoundDoseCallback(halSoundDoseCallback);

    if (!status.isOk()) {
        // Not a warning since this can happen if the callback was registered before
        ALOGI("%s: Cannot register HAL sound dose callback with status message: %s",
              __func__,
              status.getMessage());
    }

    return true;
}

void SoundDoseManager::resetHalSoundDoseInterfaces() {
    ALOGV("%s", __func__);

    const std::lock_guard _l(mLock);
    mHalSoundDose.clear();
}

void SoundDoseManager::setOutputRs2UpperBound(float rs2Value) {
    ALOGV("%s", __func__);
    const std::lock_guard _l(mLock);

    if (!mUseFrameworkMel && mHalSoundDose.size() > 0) {
        bool success = true;
        for (auto& halSoundDose : mHalSoundDose) {
            // using the HAL sound dose interface
            if (!halSoundDose.second->setOutputRs2UpperBound(rs2Value).isOk()) {
                ALOGE("%s: Cannot set RS2 value for momentary exposure %f", __func__, rs2Value);
                success = false;
                break;
            }
        }

        if (success) {
            mRs2UpperBound = rs2Value;
        } else {
            // restore all RS2 upper bounds to the previous value
            for (auto& halSoundDose : mHalSoundDose) {
                halSoundDose.second->setOutputRs2UpperBound(mRs2UpperBound);
            }
        }
        return;
    }

    for (auto& streamProcessor : mActiveProcessors) {
        const sp<audio_utils::MelProcessor> processor = streamProcessor.second.promote();
        if (processor != nullptr) {
            const status_t result = processor->setOutputRs2UpperBound(rs2Value);
            if (result != NO_ERROR) {
                ALOGW("%s: could not set RS2 upper bound %f for stream %d", __func__, rs2Value,
                      streamProcessor.first);
                return;
            }
            mRs2UpperBound = rs2Value;
        }
    }
}

void SoundDoseManager::removeStreamProcessor(audio_io_handle_t streamHandle) {
    const std::lock_guard _l(mLock);
    auto callbackToRemove = mActiveProcessors.find(streamHandle);
    if (callbackToRemove != mActiveProcessors.end()) {
        mActiveProcessors.erase(callbackToRemove);
    }
}

float SoundDoseManager::getAttenuationForDeviceId(audio_port_handle_t id) const {
    float attenuation = 0.f;

    const std::lock_guard _l(mLock);
    const auto deviceTypeIt = mActiveDeviceTypes.find(id);
    if (deviceTypeIt != mActiveDeviceTypes.end()) {
        auto attenuationIt = mMelAttenuationDB.find(deviceTypeIt->second);
        if (attenuationIt != mMelAttenuationDB.end()) {
            attenuation = attenuationIt->second;
        }
    }

    return attenuation;
}

audio_port_handle_t SoundDoseManager::getIdForAudioDevice(const AudioDevice& audioDevice) const {
    if (isComputeCsdForcedOnAllDevices()) {
        // If CSD is forced on all devices return random port id. Used only in testing.
        // This is necessary since the patches that are registered before
        // setComputeCsdOnAllDevices will not be contributing to mActiveDevices
        return CSD_ON_ALL_DEVICES_PORT_HANDLE;
    }

    const std::lock_guard _l(mLock);

    audio_devices_t type;
    std::string address;
    auto result = aidl::android::aidl2legacy_AudioDevice_audio_device(
            audioDevice, &type, &address);
    if (result != NO_ERROR) {
        ALOGE("%s: could not convert from AudioDevice to AudioDeviceTypeAddr", __func__);
        return AUDIO_PORT_HANDLE_NONE;
    }

    auto adt = AudioDeviceTypeAddr(type, address);
    auto deviceIt = mActiveDevices.find(adt);
    if (deviceIt == mActiveDevices.end()) {
        ALOGI("%s: could not find port id for device %s", __func__, adt.toString().c_str());
        return AUDIO_PORT_HANDLE_NONE;
    }

    if (audio_is_ble_out_device(type) || audio_is_a2dp_device(type)) {
        const auto btDeviceIt = mBluetoothDevicesWithCsd.find(std::make_pair(address, type));
        if (btDeviceIt == mBluetoothDevicesWithCsd.end() || !btDeviceIt->second) {
            ALOGI("%s: bt device %s does not support sound dose", __func__,
                  adt.toString().c_str());
            return AUDIO_PORT_HANDLE_NONE;
        }
    }
    return deviceIt->second;
}

void SoundDoseManager::mapAddressToDeviceId(const AudioDeviceTypeAddr& adt,
                                            const audio_port_handle_t deviceId) {
    const std::lock_guard _l(mLock);
    ALOGI("%s: map address: %d to device id: %d", __func__, adt.mType, deviceId);
    mActiveDevices[adt] = deviceId;
    mActiveDeviceTypes[deviceId] = adt.mType;
}

void SoundDoseManager::clearMapDeviceIdEntries(audio_port_handle_t deviceId) {
    const std::lock_guard _l(mLock);
    for (auto activeDevice = mActiveDevices.begin(); activeDevice != mActiveDevices.end();) {
        if (activeDevice->second == deviceId) {
            ALOGI("%s: clear mapping type: %d to deviceId: %d",
                  __func__, activeDevice->first.mType, deviceId);
            activeDevice = mActiveDevices.erase(activeDevice);
            continue;
        }
        ++activeDevice;
    }
    mActiveDeviceTypes.erase(deviceId);
}

ndk::ScopedAStatus SoundDoseManager::HalSoundDoseCallback::onMomentaryExposureWarning(
        float in_currentDbA, const AudioDevice& in_audioDevice) {
    sp<SoundDoseManager> soundDoseManager;
    {
        const std::lock_guard _l(mCbLock);
        soundDoseManager = mSoundDoseManager.promote();
        if (soundDoseManager == nullptr) {
            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
        }
    }

    if (!soundDoseManager->useHalSoundDose()) {
        ALOGW("%s: HAL sound dose interface deactivated. Ignoring", __func__);
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
    }

    auto id = soundDoseManager->getIdForAudioDevice(in_audioDevice);
    if (id == AUDIO_PORT_HANDLE_NONE) {
        ALOGI("%s: no mapped id for audio device with type %d and address %s",
                __func__, static_cast<int>(in_audioDevice.type.type),
                in_audioDevice.address.toString().c_str());
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
    }

    float attenuation = soundDoseManager->getAttenuationForDeviceId(id);
    ALOGV("%s: attenuating received momentary exposure with %f dB", __func__, attenuation);
    // TODO: remove attenuation when enforcing HAL MELs to always be attenuated
    soundDoseManager->onMomentaryExposure(in_currentDbA - attenuation, id);

    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus SoundDoseManager::HalSoundDoseCallback::onNewMelValues(
        const ISoundDose::IHalSoundDoseCallback::MelRecord& in_melRecord,
        const AudioDevice& in_audioDevice) {
    sp<SoundDoseManager> soundDoseManager;
    {
        const std::lock_guard _l(mCbLock);
        soundDoseManager = mSoundDoseManager.promote();
        if (soundDoseManager == nullptr) {
            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
        }
    }

    if (!soundDoseManager->useHalSoundDose()) {
        ALOGW("%s: HAL sound dose interface deactivated. Ignoring", __func__);
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
    }

    auto id = soundDoseManager->getIdForAudioDevice(in_audioDevice);
    if (id == AUDIO_PORT_HANDLE_NONE) {
        ALOGI("%s: no mapped id for audio device with type %d and address %s",
                __func__, static_cast<int>(in_audioDevice.type.type),
                in_audioDevice.address.toString().c_str());
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
    }

    // TODO: introduce timestamp in onNewMelValues callback
    soundDoseManager->onNewMelValues(in_melRecord.melValues, 0, in_melRecord.melValues.size(),
                                     id, /*attenuated=*/false);

    return ndk::ScopedAStatus::ok();
}

void SoundDoseManager::SoundDose::binderDied(__unused const wp<IBinder>& who) {
    ALOGV("%s", __func__);

    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->resetSoundDose();
    }
}

binder::Status SoundDoseManager::SoundDose::setOutputRs2UpperBound(float value) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->setOutputRs2UpperBound(value);
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::resetCsd(
        float currentCsd, const std::vector<media::SoundDoseRecord>& records) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->resetCsd(currentCsd, records);
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::updateAttenuation(float attenuationDB, int device) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->updateAttenuation(attenuationDB, static_cast<audio_devices_t>(device));
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::setCsdEnabled(bool enabled) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->setCsdEnabled(enabled);
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::initCachedAudioDeviceCategories(
        const std::vector<media::ISoundDose::AudioDeviceCategory>& btDeviceCategories) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->initCachedAudioDeviceCategories(btDeviceCategories);
    }
    return binder::Status::ok();
}
binder::Status SoundDoseManager::SoundDose::setAudioDeviceCategory(
        const media::ISoundDose::AudioDeviceCategory& btAudioDevice) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->setAudioDeviceCategory(btAudioDevice);
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::getOutputRs2UpperBound(float* value) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        const std::lock_guard _l(soundDoseManager->mLock);
        *value = soundDoseManager->mRs2UpperBound;
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::getCsd(float* value) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        *value = soundDoseManager->mMelAggregator->getCsd();
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::forceUseFrameworkMel(bool useFrameworkMel) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->setUseFrameworkMel(useFrameworkMel);
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::forceComputeCsdOnAllDevices(
        bool computeCsdOnAllDevices) {
    ALOGV("%s", __func__);
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        soundDoseManager->setComputeCsdOnAllDevices(computeCsdOnAllDevices);
    }
    return binder::Status::ok();
}

binder::Status SoundDoseManager::SoundDose::isSoundDoseHalSupported(bool* value) {
    ALOGV("%s", __func__);
    *value = false;
    auto soundDoseManager = mSoundDoseManager.promote();
    if (soundDoseManager != nullptr) {
        *value = soundDoseManager->isSoundDoseHalSupported();
    }
    return binder::Status::ok();
}

void SoundDoseManager::updateAttenuation(float attenuationDB, audio_devices_t deviceType) {
    const std::lock_guard _l(mLock);
    ALOGV("%s: updating MEL processor attenuation for device type %d to %f",
            __func__, deviceType, attenuationDB);
    mMelAttenuationDB[deviceType] = attenuationDB;
    for (const auto& mp : mActiveProcessors) {
        auto melProcessor = mp.second.promote();
        if (melProcessor != nullptr) {
            auto deviceId = melProcessor->getDeviceId();
            const auto deviceTypeIt = mActiveDeviceTypes.find(deviceId);
            if (deviceTypeIt != mActiveDeviceTypes.end() &&
                deviceTypeIt->second == deviceType) {
                ALOGV("%s: set attenuation for deviceId %d to %f",
                        __func__, deviceId, attenuationDB);
                melProcessor->setAttenuation(attenuationDB);
            }
        }
    }
}

void SoundDoseManager::setCsdEnabled(bool enabled) {
    ALOGV("%s",  __func__);

    const std::lock_guard _l(mLock);
    mEnabledCsd = enabled;

    for (auto& activeEntry : mActiveProcessors) {
        auto melProcessor = activeEntry.second.promote();
        if (melProcessor != nullptr) {
            if (enabled) {
                melProcessor->resume();
            } else {
                melProcessor->pause();
            }
        }
    }
}

bool SoundDoseManager::isCsdEnabled() {
    const std::lock_guard _l(mLock);
    return mEnabledCsd;
}

void SoundDoseManager::initCachedAudioDeviceCategories(
        const std::vector<media::ISoundDose::AudioDeviceCategory>& deviceCategories) {
    ALOGV("%s", __func__);
    {
        const std::lock_guard _l(mLock);
        mBluetoothDevicesWithCsd.clear();
    }
    for (const auto& btDeviceCategory : deviceCategories) {
        setAudioDeviceCategory(btDeviceCategory);
    }
}

void SoundDoseManager::setAudioDeviceCategory(
        const media::ISoundDose::AudioDeviceCategory& audioDevice) {
    ALOGV("%s: set BT audio device type with address %s to headphone %d", __func__,
          audioDevice.address.c_str(), audioDevice.csdCompatible);

    std::vector<audio_port_handle_t> devicesToStart;
    std::vector<audio_port_handle_t> devicesToStop;
    {
        const std::lock_guard _l(mLock);
        const auto deviceIt = mBluetoothDevicesWithCsd.find(
                std::make_pair(audioDevice.address,
                               static_cast<audio_devices_t>(audioDevice.internalAudioType)));
        if (deviceIt != mBluetoothDevicesWithCsd.end()) {
            deviceIt->second = audioDevice.csdCompatible;
        } else {
            mBluetoothDevicesWithCsd.emplace(
                    std::make_pair(audioDevice.address,
                                   static_cast<audio_devices_t>(audioDevice.internalAudioType)),
                    audioDevice.csdCompatible);
        }

        for (const auto &activeDevice: mActiveDevices) {
            if (activeDevice.first.address() == audioDevice.address &&
                activeDevice.first.mType ==
                static_cast<audio_devices_t>(audioDevice.internalAudioType)) {
                if (audioDevice.csdCompatible) {
                    devicesToStart.push_back(activeDevice.second);
                } else {
                    devicesToStop.push_back(activeDevice.second);
                }
            }
        }
    }

    for (const auto& deviceToStart : devicesToStart) {
        mMelReporterCallback->startMelComputationForDeviceId(deviceToStart);
    }
    for (const auto& deviceToStop : devicesToStop) {
        mMelReporterCallback->stopMelComputationForDeviceId(deviceToStop);
    }
}

bool SoundDoseManager::shouldComputeCsdForDeviceType(audio_devices_t device) {
    if (!isCsdEnabled()) {
        ALOGV("%s csd is disabled", __func__);
        return false;
    }
    if (isComputeCsdForcedOnAllDevices()) {
        return true;
    }

    switch (device) {
        case AUDIO_DEVICE_OUT_WIRED_HEADSET:
        case AUDIO_DEVICE_OUT_WIRED_HEADPHONE:
        case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP:
        case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
        case AUDIO_DEVICE_OUT_USB_HEADSET:
        case AUDIO_DEVICE_OUT_BLE_HEADSET:
        case AUDIO_DEVICE_OUT_BLE_BROADCAST:
            return true;
        default:
            return false;
    }
}

bool SoundDoseManager::shouldComputeCsdForDeviceWithAddress(const audio_devices_t type,
                                                            const std::string& deviceAddress) {
    if (!isCsdEnabled()) {
        ALOGV("%s csd is disabled", __func__);
        return false;
    }
    if (isComputeCsdForcedOnAllDevices()) {
        return true;
    }

    if (!audio_is_ble_out_device(type) && !audio_is_a2dp_device(type)) {
        return shouldComputeCsdForDeviceType(type);
    }

    const std::lock_guard _l(mLock);
    const auto deviceIt = mBluetoothDevicesWithCsd.find(std::make_pair(deviceAddress, type));
    return deviceIt != mBluetoothDevicesWithCsd.end() && deviceIt->second;
}

void SoundDoseManager::setUseFrameworkMel(bool useFrameworkMel) {
    const std::lock_guard _l(mLock);
    mUseFrameworkMel = useFrameworkMel;
}

bool SoundDoseManager::isFrameworkMelForced() const {
    const std::lock_guard _l(mLock);
    return mUseFrameworkMel;
}

void SoundDoseManager::setComputeCsdOnAllDevices(bool computeCsdOnAllDevices) {
    bool changed = false;
    {
        const std::lock_guard _l(mLock);
        if (mHalSoundDose.size() != 0) {
            // when using the HAL path we cannot enforce to deliver values for all devices
            changed = mUseFrameworkMel != computeCsdOnAllDevices;
            mUseFrameworkMel = computeCsdOnAllDevices;
        }
        mComputeCsdOnAllDevices = computeCsdOnAllDevices;
    }
    if (changed && computeCsdOnAllDevices) {
        mMelReporterCallback->applyAllAudioPatches();
    }
}

bool SoundDoseManager::isComputeCsdForcedOnAllDevices() const {
    const std::lock_guard _l(mLock);
    return mComputeCsdOnAllDevices;
}

bool SoundDoseManager::isSoundDoseHalSupported() const {
    {
        const std::lock_guard _l(mLock);
        if (!mEnabledCsd) return false;
    }

    return useHalSoundDose();
}

bool SoundDoseManager::useHalSoundDose() const {
    const std::lock_guard _l(mLock);
    return !mUseFrameworkMel && mHalSoundDose.size() > 0;
}

void SoundDoseManager::resetSoundDose() {
    const std::lock_guard lock(mLock);
    mSoundDose = nullptr;
}

void SoundDoseManager::resetCsd(float currentCsd,
                                const std::vector<media::SoundDoseRecord>& records) {
    const std::lock_guard lock(mLock);
    std::vector<audio_utils::CsdRecord> resetRecords;
    resetRecords.reserve(records.size());
    for (const auto& record : records) {
        resetRecords.emplace_back(record.timestamp, record.duration, record.value,
                                  record.averageMel);
    }

    mMelAggregator->reset(currentCsd, resetRecords);
}

void SoundDoseManager::onNewMelValues(const std::vector<float>& mels, size_t offset, size_t length,
                                      audio_port_handle_t deviceId, bool attenuated) const {
    ALOGV("%s", __func__);

    sp<media::ISoundDoseCallback> soundDoseCallback;
    std::vector<audio_utils::CsdRecord> records;
    float currentCsd;

    // TODO: delete this case when enforcing HAL MELs to always be attenuated
    float attenuation = attenuated ? 0.0f : getAttenuationForDeviceId(deviceId);

    {
        const std::lock_guard _l(mLock);
        if (!mEnabledCsd) {
            return;
        }

        const int64_t timestampSec = getMonotonicSecond();

        if (attenuated) {
            records = mMelAggregator->aggregateAndAddNewMelRecord(audio_utils::MelRecord(
                    deviceId,
                    std::vector<float>(mels.begin() + offset, mels.begin() + offset + length),
                    timestampSec - length));
        } else {
            ALOGV("%s: attenuating received values with %f dB", __func__, attenuation);

            // Extracting all intervals that contain values >= RS2 low limit (80dBA) after the
            // attenuation is applied
            size_t start = offset;
            size_t stop = offset;
            for (; stop < mels.size() && stop < offset + length; ++stop) {
                if (mels[stop] - attenuation < kDefaultRs2LowerBound) {
                    if (start < stop) {
                        std::vector<float> attMel(stop-start, -attenuation);
                        // attMel[i] = mels[i] + attenuation, i in [start, stop)
                        std::transform(mels.begin() + start, mels.begin() + stop, attMel.begin(),
                                       attMel.begin(), std::plus<float>());
                        std::vector<audio_utils::CsdRecord> newRec =
                                mMelAggregator->aggregateAndAddNewMelRecord(
                                        audio_utils::MelRecord(deviceId,
                                                               attMel,
                                                               timestampSec - length + start -
                                                               offset));
                        std::copy(newRec.begin(), newRec.end(), std::back_inserter(records));
                    }
                    start = stop+1;
                }
            }
            if (start < stop) {
                std::vector<float> attMel(stop-start, -attenuation);
                // attMel[i] = mels[i] + attenuation, i in [start, stop)
                std::transform(mels.begin() + start, mels.begin() + stop, attMel.begin(),
                               attMel.begin(), std::plus<float>());
                std::vector<audio_utils::CsdRecord> newRec =
                        mMelAggregator->aggregateAndAddNewMelRecord(
                                audio_utils::MelRecord(deviceId,
                                                       attMel,
                                                       timestampSec - length + start -
                                                       offset));
                std::copy(newRec.begin(), newRec.end(), std::back_inserter(records));
            }
        }

        currentCsd = mMelAggregator->getCsd();
    }

    soundDoseCallback = getSoundDoseCallback();

    if (records.size() > 0 && soundDoseCallback != nullptr) {
        std::vector<media::SoundDoseRecord> newRecordsToReport;
        newRecordsToReport.resize(records.size());
        for (const auto& record : records) {
            newRecordsToReport.emplace_back(csdRecordToSoundDoseRecord(record));
        }

        soundDoseCallback->onNewCsdValue(currentCsd, newRecordsToReport);
    }
}

sp<media::ISoundDoseCallback> SoundDoseManager::getSoundDoseCallback() const {
    const std::lock_guard _l(mLock);
    if (mSoundDose == nullptr) {
        return nullptr;
    }

    return mSoundDose->mSoundDoseCallback;
}

void SoundDoseManager::onMomentaryExposure(float currentMel, audio_port_handle_t deviceId) const {
    ALOGV("%s: Momentary exposure for device %d triggered: %f MEL", __func__, deviceId, currentMel);

    {
        const std::lock_guard _l(mLock);
        if (!mEnabledCsd) {
            return;
        }

        if (currentMel < mRs2UpperBound) {
            return;
        }
    }

    auto soundDoseCallback = getSoundDoseCallback();
    if (soundDoseCallback != nullptr) {
        soundDoseCallback->onMomentaryExposure(currentMel, deviceId);
    }
}

void SoundDoseManager::resetReferencesForTest() {
    mMelReporterCallback.clear();
}

sp<media::ISoundDose> SoundDoseManager::getSoundDoseInterface(
        const sp<media::ISoundDoseCallback>& callback) {
    ALOGV("%s: Register ISoundDoseCallback", __func__);

    const std::lock_guard _l(mLock);
    if (mSoundDose == nullptr) {
        mSoundDose = sp<SoundDose>::make(this, callback);
    }
    return mSoundDose;
}

std::string SoundDoseManager::dump() const {
    std::string output;
    {
        const std::lock_guard _l(mLock);
        if (!mEnabledCsd) {
            base::StringAppendF(&output, "CSD is disabled");
            return output;
        }
    }

    mMelAggregator->foreachCsd([&output](audio_utils::CsdRecord csdRecord) {
        base::StringAppendF(&output,
                            "CSD %f with average MEL %f in interval [%" PRId64 ", %" PRId64 "]",
                            csdRecord.value, csdRecord.averageMel, csdRecord.timestamp,
                            csdRecord.timestamp + csdRecord.duration);
        base::StringAppendF(&output, "\n");
    });

    base::StringAppendF(&output, "\nCached Mel Records:\n");
    mMelAggregator->foreachCachedMel([&output](const audio_utils::MelRecord& melRecord) {
        base::StringAppendF(&output, "Continuous MELs for portId=%d, ", melRecord.portId);
        base::StringAppendF(&output, "starting at timestamp %" PRId64 ": ", melRecord.timestamp);

        for (const auto& mel : melRecord.mels) {
            base::StringAppendF(&output, "%.2f ", mel);
        }
        base::StringAppendF(&output, "\n");
    });

    return output;
}

size_t SoundDoseManager::getCachedMelRecordsSize() const {
    return mMelAggregator->getCachedMelRecordsSize();
}

media::SoundDoseRecord SoundDoseManager::csdRecordToSoundDoseRecord(
        const audio_utils::CsdRecord& legacy) {
    media::SoundDoseRecord soundDoseRecord{};
    soundDoseRecord.timestamp = legacy.timestamp;
    soundDoseRecord.duration = legacy.duration;
    soundDoseRecord.value = legacy.value;
    soundDoseRecord.averageMel = legacy.averageMel;
    return soundDoseRecord;
}

}  // namespace android
