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

#include "sles_allinclusive.h"
#include "android_prompts.h"
#include "channels.h"

#include <utils/String16.h>

#include <system/audio.h>
#include <SLES/OpenSLES_Android.h>

#include <android_runtime/AndroidRuntime.h>
#include <android/AudioRecordCallback.h>

#define KEY_RECORDING_SOURCE_PARAMSIZE  sizeof(SLuint32)
#define KEY_RECORDING_PRESET_PARAMSIZE  sizeof(SLuint32)
#define KEY_PERFORMANCE_MODE_PARAMSIZE  sizeof(SLuint32)

using android::content::AttributionSourceState;

//-----------------------------------------------------------------------------
// Internal utility functions
//----------------------------

SLresult audioRecorder_setPreset(CAudioRecorder* ar, SLuint32 recordPreset) {
    SLresult result = SL_RESULT_SUCCESS;

    audio_source_t newRecordSource = AUDIO_SOURCE_DEFAULT;
    switch (recordPreset) {
    case SL_ANDROID_RECORDING_PRESET_GENERIC:
        newRecordSource = AUDIO_SOURCE_DEFAULT;
        break;
    case SL_ANDROID_RECORDING_PRESET_CAMCORDER:
        newRecordSource = AUDIO_SOURCE_CAMCORDER;
        break;
    case SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION:
        newRecordSource = AUDIO_SOURCE_VOICE_RECOGNITION;
        break;
    case SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION:
        newRecordSource = AUDIO_SOURCE_VOICE_COMMUNICATION;
        break;
    case SL_ANDROID_RECORDING_PRESET_UNPROCESSED:
            newRecordSource = AUDIO_SOURCE_UNPROCESSED;
            break;
    case SL_ANDROID_RECORDING_PRESET_NONE:
        // it is an error to set preset "none"
    default:
        SL_LOGE(ERROR_RECORDERPRESET_SET_UNKNOWN_PRESET);
        result = SL_RESULT_PARAMETER_INVALID;
    }

    // recording preset needs to be set before the object is realized
    // (ap->mAudioRecord is supposed to be 0 until then)
    if (SL_OBJECT_STATE_UNREALIZED != ar->mObject.mState) {
        SL_LOGE(ERROR_RECORDERPRESET_REALIZED);
        result = SL_RESULT_PRECONDITIONS_VIOLATED;
    } else {
        ar->mRecordSource = newRecordSource;
    }

    return result;
}


//-----------------------------------------------------------------------------
SLresult audioRecorder_setPerformanceMode(CAudioRecorder* ar, SLuint32 mode) {
    SLresult result = SL_RESULT_SUCCESS;
    SL_LOGV("performance mode set to %d", mode);

    SLuint32 perfMode = ANDROID_PERFORMANCE_MODE_DEFAULT;
    switch (mode) {
    case SL_ANDROID_PERFORMANCE_LATENCY:
        perfMode = ANDROID_PERFORMANCE_MODE_LATENCY;
        break;
    case SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS:
        perfMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
        break;
    case SL_ANDROID_PERFORMANCE_NONE:
        perfMode = ANDROID_PERFORMANCE_MODE_NONE;
        break;
    case SL_ANDROID_PERFORMANCE_POWER_SAVING:
        perfMode = ANDROID_PERFORMANCE_MODE_POWER_SAVING;
        break;
    default:
        SL_LOGE(ERROR_CONFIG_PERF_MODE_UNKNOWN);
        result = SL_RESULT_PARAMETER_INVALID;
        break;
    }

    // performance mode needs to be set before the object is realized
    // (ar->mAudioRecord is supposed to be NULL until then)
    if (SL_OBJECT_STATE_UNREALIZED != ar->mObject.mState) {
        SL_LOGE(ERROR_CONFIG_PERF_MODE_REALIZED);
        result = SL_RESULT_PRECONDITIONS_VIOLATED;
    } else {
        ar->mPerformanceMode = perfMode;
    }

    return result;
}


SLresult audioRecorder_getPreset(CAudioRecorder* ar, SLuint32* pPreset) {
    SLresult result = SL_RESULT_SUCCESS;

    switch (ar->mRecordSource) {
    case AUDIO_SOURCE_DEFAULT:
    case AUDIO_SOURCE_MIC:
        *pPreset = SL_ANDROID_RECORDING_PRESET_GENERIC;
        break;
    case AUDIO_SOURCE_VOICE_UPLINK:
    case AUDIO_SOURCE_VOICE_DOWNLINK:
    case AUDIO_SOURCE_VOICE_CALL:
        *pPreset = SL_ANDROID_RECORDING_PRESET_NONE;
        break;
    case AUDIO_SOURCE_VOICE_RECOGNITION:
        *pPreset = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
        break;
    case AUDIO_SOURCE_CAMCORDER:
        *pPreset = SL_ANDROID_RECORDING_PRESET_CAMCORDER;
        break;
    case AUDIO_SOURCE_VOICE_COMMUNICATION:
        *pPreset = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
        break;
    case AUDIO_SOURCE_UNPROCESSED:
        *pPreset = SL_ANDROID_RECORDING_PRESET_UNPROCESSED;
        break;
    default:
        *pPreset = SL_ANDROID_RECORDING_PRESET_NONE;
        result = SL_RESULT_INTERNAL_ERROR;
        break;
    }

    return result;
}


//-----------------------------------------------------------------------------
SLresult audioRecorder_getPerformanceMode(CAudioRecorder* ar, SLuint32 *pMode) {
    SLresult result = SL_RESULT_SUCCESS;

    switch (ar->mPerformanceMode) {
    case ANDROID_PERFORMANCE_MODE_LATENCY:
        *pMode = SL_ANDROID_PERFORMANCE_LATENCY;
        break;
    case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
        *pMode = SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS;
        break;
    case ANDROID_PERFORMANCE_MODE_NONE:
        *pMode = SL_ANDROID_PERFORMANCE_NONE;
        break;
    case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
        *pMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
        break;
    default:
        result = SL_RESULT_INTERNAL_ERROR;
        *pMode = SL_ANDROID_PERFORMANCE_LATENCY;
        break;
    }

    return result;
}


void audioRecorder_handleNewPos_lockRecord(CAudioRecorder* ar) {
    //SL_LOGV("received event EVENT_NEW_POS from AudioRecord");
    slRecordCallback callback = NULL;
    void* callbackPContext = NULL;

    interface_lock_shared(&ar->mRecord);
    callback = ar->mRecord.mCallback;
    callbackPContext = ar->mRecord.mContext;
    interface_unlock_shared(&ar->mRecord);

    if (NULL != callback) {
        // getting this event implies SL_RECORDEVENT_HEADATNEWPOS was set in the event mask
        (*callback)(&ar->mRecord.mItf, callbackPContext, SL_RECORDEVENT_HEADATNEWPOS);
    }
}


void audioRecorder_handleMarker_lockRecord(CAudioRecorder* ar) {
    //SL_LOGV("received event EVENT_MARKER from AudioRecord");
    slRecordCallback callback = NULL;
    void* callbackPContext = NULL;

    interface_lock_shared(&ar->mRecord);
    callback = ar->mRecord.mCallback;
    callbackPContext = ar->mRecord.mContext;
    interface_unlock_shared(&ar->mRecord);

    if (NULL != callback) {
        // getting this event implies SL_RECORDEVENT_HEADATMARKER was set in the event mask
        (*callback)(&ar->mRecord.mItf, callbackPContext, SL_RECORDEVENT_HEADATMARKER);
    }
}


void audioRecorder_handleOverrun_lockRecord(CAudioRecorder* ar) {
    //SL_LOGV("received event EVENT_OVERRUN from AudioRecord");
    slRecordCallback callback = NULL;
    void* callbackPContext = NULL;

    interface_lock_shared(&ar->mRecord);
    if (ar->mRecord.mCallbackEventsMask & SL_RECORDEVENT_HEADSTALLED) {
        callback = ar->mRecord.mCallback;
        callbackPContext = ar->mRecord.mContext;
    }
    interface_unlock_shared(&ar->mRecord);

    if (NULL != callback) {
        (*callback)(&ar->mRecord.mItf, callbackPContext, SL_RECORDEVENT_HEADSTALLED);
    }
}

//-----------------------------------------------------------------------------
SLresult android_audioRecorder_checkSourceSink(CAudioRecorder* ar) {

    const SLDataSource *pAudioSrc = &ar->mDataSource.u.mSource;
    const SLDataSink   *pAudioSnk = &ar->mDataSink.u.mSink;

    const SLuint32 sinkLocatorType = *(SLuint32 *)pAudioSnk->pLocator;
    const SLuint32 sinkFormatType = *(SLuint32 *)pAudioSnk->pFormat;

    // sink must be an Android simple buffer queue with PCM data format
    switch (sinkLocatorType) {
    case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE: {
        switch (sinkFormatType) {
        case SL_ANDROID_DATAFORMAT_PCM_EX: {
            // checkDataFormat() already checked representation
        } // SL_ANDROID_DATAFORMAT_PCM_EX - fall through to next test.
            FALLTHROUGH_INTENDED;
        case SL_DATAFORMAT_PCM: {
            const SLDataFormat_PCM *df_pcm = (const SLDataFormat_PCM *) pAudioSnk->pFormat;
            // checkDataFormat already checked sample rate, channels, and mask
            ar->mNumChannels = df_pcm->numChannels;

            if (df_pcm->endianness != ar->mObject.mEngine->mEngine.mNativeEndianness) {
                SL_LOGE("Cannot create audio recorder: unsupported byte order %u",
                        df_pcm->endianness);
                return SL_RESULT_CONTENT_UNSUPPORTED;
            }

            ar->mSampleRateMilliHz = df_pcm->samplesPerSec; // Note: bad field name in SL ES
            SL_LOGV("AudioRecorder requested sample rate = %u mHz, %u channel(s)",
                    ar->mSampleRateMilliHz, ar->mNumChannels);

            // we don't support container size != sample depth
            if (df_pcm->containerSize != df_pcm->bitsPerSample) {
                SL_LOGE("Cannot create audio recorder: unsupported container size %u bits for "
                        "sample depth %u bits",
                        df_pcm->containerSize, (SLuint32)df_pcm->bitsPerSample);
                return SL_RESULT_CONTENT_UNSUPPORTED;
            }

            } break;
        default:
            SL_LOGE(ERROR_RECORDER_SINK_FORMAT_MUST_BE_PCM);
            return SL_RESULT_PARAMETER_INVALID;
        }   // switch (sourceFormatType)
        } break;    // case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
    default:
        SL_LOGE(ERROR_RECORDER_SINK_MUST_BE_ANDROIDSIMPLEBUFFERQUEUE);
        return SL_RESULT_PARAMETER_INVALID;
    }   // switch (sourceLocatorType)

    // Source check:
    // only input device sources are supported
    // check it's an IO device
    if (SL_DATALOCATOR_IODEVICE != *(SLuint32 *)pAudioSrc->pLocator) {
        SL_LOGE(ERROR_RECORDER_SOURCE_MUST_BE_IODEVICE);
        return SL_RESULT_PARAMETER_INVALID;
    } else {

        // check it's an input device
        SLDataLocator_IODevice *dl_iod = (SLDataLocator_IODevice *) pAudioSrc->pLocator;
        if (SL_IODEVICE_AUDIOINPUT != dl_iod->deviceType) {
            SL_LOGE(ERROR_RECORDER_IODEVICE_MUST_BE_AUDIOINPUT);
            return SL_RESULT_PARAMETER_INVALID;
        }

        // check it's the default input device, others aren't supported here
        if (SL_DEFAULTDEVICEID_AUDIOINPUT != dl_iod->deviceID) {
            SL_LOGE(ERROR_RECORDER_INPUT_ID_MUST_BE_DEFAULT);
            return SL_RESULT_PARAMETER_INVALID;
        }
    }

    return SL_RESULT_SUCCESS;
}
//-----------------------------------------------------------------------------
size_t audioRecorder_handleMoreData_lockRecord(CAudioRecorder* ar,
                                               const android::AudioRecord::Buffer& buffer) {
    //SL_LOGV("audioRecorder_callback(%d, %p, %p) entering", event, user, info);

    void * callbackPContext = NULL;
    size_t bytesRead = 0;
    slBufferQueueCallback callback = NULL;

    // push data to the buffer queue
    interface_lock_exclusive(&ar->mBufferQueue);

    if (ar->mBufferQueue.mState.count != 0) {
        assert(ar->mBufferQueue.mFront != ar->mBufferQueue.mRear);

        BufferHeader *oldFront = ar->mBufferQueue.mFront;
        BufferHeader *newFront = &oldFront[1];

        size_t availSink = oldFront->mSize - ar->mBufferQueue.mSizeConsumed;
        size_t availSource = buffer.size();
        size_t bytesToCopy = availSink < availSource ? availSink : availSource;
        void *pDest = (char *)oldFront->mBuffer + ar->mBufferQueue.mSizeConsumed;
        memcpy(pDest, buffer.data(), bytesToCopy);
        bytesRead = bytesToCopy;
        if (bytesToCopy < availSink) {
            // can't consume the whole or rest of the buffer in one shot
            ar->mBufferQueue.mSizeConsumed += availSource;
        } else {
            // finish pushing the buffer or push the buffer in one shot
            ar->mBufferQueue.mSizeConsumed = 0;
            if (newFront == &ar->mBufferQueue.mArray[ar->mBufferQueue.mNumBuffers + 1]) {
                newFront = ar->mBufferQueue.mArray;
            }
            ar->mBufferQueue.mFront = newFront;

            ar->mBufferQueue.mState.count--;
            ar->mBufferQueue.mState.playIndex++;

            // data has been copied to the buffer, and the buffer queue state has been updated
            // we will notify the client if applicable
            callback = ar->mBufferQueue.mCallback;
            // save callback data
            callbackPContext = ar->mBufferQueue.mContext;
        }
    }

    interface_unlock_exclusive(&ar->mBufferQueue);

    // notify client
    if (NULL != callback) {
        (*callback)(&ar->mBufferQueue.mItf, callbackPContext);
    }
    return bytesRead;
}


//-----------------------------------------------------------------------------
SLresult android_audioRecorder_create(CAudioRecorder* ar) {
    SL_LOGV("android_audioRecorder_create(%p) entering", ar);

    const SLDataSource *pAudioSrc = &ar->mDataSource.u.mSource;
    const SLDataSink *pAudioSnk = &ar->mDataSink.u.mSink;
    SLresult result = SL_RESULT_SUCCESS;

    const SLuint32 sourceLocatorType = *(SLuint32 *)pAudioSrc->pLocator;
    const SLuint32 sinkLocatorType = *(SLuint32 *)pAudioSnk->pLocator;

    //  the following platform-independent fields have been initialized in CreateAudioRecorder()
    //    ar->mNumChannels
    //    ar->mSampleRateMilliHz

    if ((SL_DATALOCATOR_IODEVICE == sourceLocatorType) &&
            (SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE == sinkLocatorType)) {
        // microphone to simple buffer queue
        ar->mAndroidObjType = AUDIORECORDER_FROM_MIC_TO_PCM_BUFFERQUEUE;
        ar->mAudioRecord.clear();
        ar->mCallbackHandle.clear();
        ar->mCallbackProtector = new android::CallbackProtector();
        ar->mRecordSource = AUDIO_SOURCE_DEFAULT;
        ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_DEFAULT;
    } else {
        result = SL_RESULT_CONTENT_UNSUPPORTED;
    }

    return result;
}


//-----------------------------------------------------------------------------
SLresult android_audioRecorder_setConfig(CAudioRecorder* ar, const SLchar *configKey,
        const void *pConfigValue, SLuint32 valueSize) {

    SLresult result;

    assert(NULL != ar && NULL != configKey && NULL != pConfigValue);
    if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_RECORDING_PRESET) == 0) {

        // recording preset
        if (KEY_RECORDING_PRESET_PARAMSIZE > valueSize) {
            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
            result = SL_RESULT_BUFFER_INSUFFICIENT;
        } else {
            result = audioRecorder_setPreset(ar, *(SLuint32*)pConfigValue);
        }

    } else if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_PERFORMANCE_MODE) == 0) {

        // performance mode
        if (KEY_PERFORMANCE_MODE_PARAMSIZE > valueSize) {
            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
            result = SL_RESULT_BUFFER_INSUFFICIENT;
        } else {
            result = audioRecorder_setPerformanceMode(ar, *(SLuint32*)pConfigValue);
        }
    } else {
        SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
        result = SL_RESULT_PARAMETER_INVALID;
    }

    return result;
}


//-----------------------------------------------------------------------------
SLresult android_audioRecorder_getConfig(CAudioRecorder* ar, const SLchar *configKey,
        SLuint32* pValueSize, void *pConfigValue) {

    SLresult result;

    assert(NULL != ar && NULL != configKey && NULL != pValueSize);
    if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_RECORDING_PRESET) == 0) {

        // recording preset
        if (NULL == pConfigValue) {
            result = SL_RESULT_SUCCESS;
        } else if (KEY_RECORDING_PRESET_PARAMSIZE > *pValueSize) {
            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
            result = SL_RESULT_BUFFER_INSUFFICIENT;
        } else {
            result = audioRecorder_getPreset(ar, (SLuint32*)pConfigValue);
        }
        *pValueSize = KEY_RECORDING_PRESET_PARAMSIZE;

    } else if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_PERFORMANCE_MODE) == 0) {

        // performance mode
        if (NULL == pConfigValue) {
            result = SL_RESULT_SUCCESS;
        } else if (KEY_PERFORMANCE_MODE_PARAMSIZE > *pValueSize) {
            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
            result = SL_RESULT_BUFFER_INSUFFICIENT;
        } else {
            result = audioRecorder_getPerformanceMode(ar, (SLuint32*)pConfigValue);
        }
        *pValueSize = KEY_PERFORMANCE_MODE_PARAMSIZE;

    } else {
        SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
        result = SL_RESULT_PARAMETER_INVALID;
    }

    return result;
}

// Called from android_audioRecorder_realize for a PCM buffer queue recorder before creating the
// AudioRecord to determine which performance modes are allowed based on effect interfaces present
static void checkAndSetPerformanceModePre(CAudioRecorder* ar)
{
    SLuint32 allowedModes = ANDROID_PERFORMANCE_MODE_ALL;
    assert(ar->mAndroidObjType == AUDIORECORDER_FROM_MIC_TO_PCM_BUFFERQUEUE);

    // no need to check the buffer queue size, application side
    // double-buffering (and more) is not a requirement for using fast tracks

    // Check a denylist of interfaces that are incompatible with fast tracks.
    // The alternative, to check a allowlist of compatible interfaces, is
    // more maintainable but is too slow.  As a compromise, in a debug build
    // we use both methods and warn if they produce different results.
    // In release builds, we only use the denylist method.
    // If a denylisted interface is added after realization using
    // DynamicInterfaceManagement::AddInterface,
    // then this won't be detected but the interface will be ineffective.
    static const unsigned denylist[] = {
        MPH_ANDROIDACOUSTICECHOCANCELLATION,
        MPH_ANDROIDAUTOMATICGAINCONTROL,
        MPH_ANDROIDNOISESUPPRESSION,
        MPH_ANDROIDEFFECT,
        // FIXME The problem with a denylist is remembering to add new interfaces here
    };

    for (unsigned i = 0; i < sizeof(denylist)/sizeof(denylist[0]); ++i) {
        if (IsInterfaceInitialized(&ar->mObject, denylist[i])) {
            uint32_t flags = 0;

            allowedModes &= ~ANDROID_PERFORMANCE_MODE_LATENCY;

            // if generic effect interface is used we don't know which effect will be used and
            // disable all low latency performance modes
            if (denylist[i] != MPH_ANDROIDEFFECT) {
                switch (denylist[i]) {
                case MPH_ANDROIDACOUSTICECHOCANCELLATION:
                    SL_LOGV("checkAndSetPerformanceModePre found AEC name %s",
                            ar->mAcousticEchoCancellation.mAECDescriptor.name);
                    flags = ar->mAcousticEchoCancellation.mAECDescriptor.flags;
                    break;
                case MPH_ANDROIDAUTOMATICGAINCONTROL:
                    SL_LOGV("checkAndSetPerformanceModePre found AGC name %s",
                            ar->mAutomaticGainControl.mAGCDescriptor.name);
                    flags = ar->mAutomaticGainControl.mAGCDescriptor.flags;
                    break;
                case MPH_ANDROIDNOISESUPPRESSION:
                    SL_LOGV("checkAndSetPerformanceModePre found NS name %s",
                            ar->mNoiseSuppression.mNSDescriptor.name);
                    flags = ar->mNoiseSuppression.mNSDescriptor.flags;
                    break;
                default:
                    break;
                }
            }
            if ((flags & EFFECT_FLAG_HW_ACC_TUNNEL) == 0) {
                allowedModes &= ~ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
                break;
            }
        }
    }
#if LOG_NDEBUG == 0
    bool denylistResult = (
            (allowedModes &
                (ANDROID_PERFORMANCE_MODE_LATENCY|ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS)) != 0);
    bool allowlistResult = true;
    static const unsigned allowlist[] = {
        MPH_BUFFERQUEUE,
        MPH_DYNAMICINTERFACEMANAGEMENT,
        MPH_OBJECT,
        MPH_RECORD,
        MPH_ANDROIDCONFIGURATION,
        MPH_ANDROIDSIMPLEBUFFERQUEUE,
    };
    for (unsigned mph = MPH_MIN; mph < MPH_MAX; ++mph) {
        for (unsigned i = 0; i < sizeof(allowlist)/sizeof(allowlist[0]); ++i) {
            if (mph == allowlist[i]) {
                goto compatible;
            }
        }
        if (IsInterfaceInitialized(&ar->mObject, mph)) {
            allowlistResult = false;
            break;
        }
compatible: ;
    }
    if (allowlistResult != denylistResult) {
        SL_LOGW("allowlistResult != denylistResult");
    }
#endif
    if (ar->mPerformanceMode == ANDROID_PERFORMANCE_MODE_LATENCY) {
        if ((allowedModes & ANDROID_PERFORMANCE_MODE_LATENCY) == 0) {
            ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
        }
    }
    if (ar->mPerformanceMode == ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) {
        if ((allowedModes & ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) == 0) {
            ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
        }
    }
}

// Called from android_audioRecorder_realize for a PCM buffer queue recorder after creating the
// AudioRecord to adjust performance mode based on actual input flags
static void checkAndSetPerformanceModePost(CAudioRecorder* ar)
{
    audio_input_flags_t flags = ar->mAudioRecord->getFlags();
    switch (ar->mPerformanceMode) {
    case ANDROID_PERFORMANCE_MODE_LATENCY:
        if ((flags & (AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW)) ==
                (AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW)) {
            break;
        }
        ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
        FALLTHROUGH_INTENDED;
    case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
        if ((flags & AUDIO_INPUT_FLAG_FAST) == 0) {
            ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
        }
        break;
    case ANDROID_PERFORMANCE_MODE_NONE:
    default:
        break;
    }
}
//-----------------------------------------------------------------------------
SLresult android_audioRecorder_realize(CAudioRecorder* ar, SLboolean async) {
    SL_LOGV("android_audioRecorder_realize(%p) entering", ar);

    SLresult result = SL_RESULT_SUCCESS;

    // already checked in created and checkSourceSink
    assert(ar->mDataSink.mLocator.mLocatorType == SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE);

    const SLDataFormat_PCM *df_pcm = &ar->mDataSink.mFormat.mPCM;

    //  the following platform-independent fields have been initialized in CreateAudioRecorder()
    //    ar->mNumChannels
    //    ar->mSampleRateMilliHz

    uint32_t sampleRate = sles_to_android_sampleRate(df_pcm->samplesPerSec);

    checkAndSetPerformanceModePre(ar);

    audio_input_flags_t policy;
    switch (ar->mPerformanceMode) {
    case ANDROID_PERFORMANCE_MODE_NONE:
    case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
        policy = AUDIO_INPUT_FLAG_NONE;
        break;
    case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
        policy = AUDIO_INPUT_FLAG_FAST;
        break;
    case ANDROID_PERFORMANCE_MODE_LATENCY:
    default:
        policy = (audio_input_flags_t)(AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW);
        break;
    }

    SL_LOGV("Audio Record format: %dch(0x%x), %dbit, %dKHz",
            df_pcm->numChannels,
            df_pcm->channelMask,
            df_pcm->bitsPerSample,
            df_pcm->samplesPerSec / 1000000);

    // note that df_pcm->channelMask has already been validated during object creation.
    audio_channel_mask_t channelMask = sles_to_audio_input_channel_mask(df_pcm->channelMask);

    // To maintain backward compatibility with previous releases, ignore
    // channel masks that are not indexed.
    if (channelMask == AUDIO_CHANNEL_INVALID
            || audio_channel_mask_get_representation(channelMask)
                == AUDIO_CHANNEL_REPRESENTATION_POSITION) {
        channelMask = audio_channel_in_mask_from_count(df_pcm->numChannels);
        SL_LOGI("Emulating old channel mask behavior "
                "(ignoring positional mask %#x, using default mask %#x based on "
                "channel count of %d)", df_pcm->channelMask, channelMask,
                df_pcm->numChannels);
    }
    SL_LOGV("SLES channel mask %#x converted to Android mask %#x", df_pcm->channelMask,
            channelMask);

    // TODO b/182392769: use attribution source util
    AttributionSourceState attributionSource;
    attributionSource.uid = VALUE_OR_FATAL(android::legacy2aidl_uid_t_int32_t(getuid()));
    attributionSource.pid = VALUE_OR_FATAL(android::legacy2aidl_pid_t_int32_t(getpid()));
    attributionSource.token = android::sp<android::BBinder>::make();
    ar->mCallbackHandle = android::sp<android::AudioRecordCallback>::make(ar);
    // initialize platform-specific CAudioRecorder fields
    ar->mAudioRecord = new android::AudioRecord(
            ar->mRecordSource,     // source
            sampleRate,            // sample rate in Hertz
            sles_to_android_sampleFormat(df_pcm),               // format
            channelMask,           // channel mask
            attributionSource,
            0,                     // frameCount
            ar->mCallbackHandle,
            0,                     // notificationFrames
            AUDIO_SESSION_ALLOCATE,
            android::AudioRecord::TRANSFER_CALLBACK,
                                   // transfer type
            policy);               // audio_input_flags_t

    // Set it here so it can be logged by the destructor if the open failed.
    ar->mAudioRecord->setCallerName(ANDROID_OPENSLES_CALLER_NAME);

    android::status_t status = ar->mAudioRecord->initCheck();
    if (android::NO_ERROR != status) {
        SL_LOGE("android_audioRecorder_realize(%p) error creating AudioRecord object; status %d",
                ar, status);
        // FIXME should return a more specific result depending on status
        result = SL_RESULT_CONTENT_UNSUPPORTED;
        ar->mAudioRecord.clear();
        ar->mCallbackHandle.clear();
        return result;
    }

    // update performance mode according to actual flags granted to AudioRecord
    checkAndSetPerformanceModePost(ar);

    // If there is a JavaAudioRoutingProxy associated with this recorder, hook it up...
    JNIEnv* j_env = NULL;
    jclass clsAudioRecord = NULL;
    jmethodID midRoutingProxy_connect = NULL;
    if (ar->mAndroidConfiguration.mRoutingProxy != NULL &&
            (j_env = android::AndroidRuntime::getJNIEnv()) != NULL &&
            (clsAudioRecord = j_env->FindClass("android/media/AudioRecord")) != NULL &&
            (midRoutingProxy_connect =
                j_env->GetMethodID(clsAudioRecord, "deferred_connect", "(J)V")) != NULL) {
        j_env->ExceptionClear();
        j_env->CallVoidMethod(ar->mAndroidConfiguration.mRoutingProxy,
                              midRoutingProxy_connect,
                              ar->mAudioRecord.get());
        if (j_env->ExceptionCheck()) {
            SL_LOGE("Java exception releasing recorder routing object.");
            result = SL_RESULT_INTERNAL_ERROR;
            ar->mAudioRecord.clear();
            ar->mCallbackHandle.clear();
            return result;
        }
   }

    if (ar->mPerformanceMode != ANDROID_PERFORMANCE_MODE_LATENCY) {
        audio_session_t sessionId = ar->mAudioRecord->getSessionId();
        // initialize AEC
        effect_descriptor_t *descriptor = &ar->mAcousticEchoCancellation.mAECDescriptor;
        if (memcmp(SL_IID_ANDROIDACOUSTICECHOCANCELLATION, &descriptor->type,
                   sizeof(effect_uuid_t)) == 0) {
            if ((ar->mPerformanceMode != ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) ||
                    (descriptor->flags & EFFECT_FLAG_HW_ACC_TUNNEL)) {
                SL_LOGV("Need to initialize AEC for AudioRecorder=%p", ar);
                android_aec_init(sessionId, &ar->mAcousticEchoCancellation);
            }
        }

        // initialize AGC
        descriptor = &ar->mAutomaticGainControl.mAGCDescriptor;
        if (memcmp(SL_IID_ANDROIDAUTOMATICGAINCONTROL, &descriptor->type,
                   sizeof(effect_uuid_t)) == 0) {
            if ((ar->mPerformanceMode != ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) ||
                    (descriptor->flags & EFFECT_FLAG_HW_ACC_TUNNEL)) {
                SL_LOGV("Need to initialize AGC for AudioRecorder=%p", ar);
                android_agc_init(sessionId, &ar->mAutomaticGainControl);
            }
        }

        // initialize NS
        descriptor = &ar->mNoiseSuppression.mNSDescriptor;
        if (memcmp(SL_IID_ANDROIDNOISESUPPRESSION, &descriptor->type,
                   sizeof(effect_uuid_t)) == 0) {
            if ((ar->mPerformanceMode != ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) ||
                    (descriptor->flags & EFFECT_FLAG_HW_ACC_TUNNEL)) {
                SL_LOGV("Need to initialize NS for AudioRecorder=%p", ar);
                android_ns_init(sessionId, &ar->mNoiseSuppression);
            }
        }
    }

    return result;
}


//-----------------------------------------------------------------------------
/**
 * Called with a lock on AudioRecorder, and blocks until safe to destroy
 */
void android_audioRecorder_preDestroy(CAudioRecorder* ar) {
    object_unlock_exclusive(&ar->mObject);
    if (ar->mCallbackProtector != 0) {
        ar->mCallbackProtector->requestCbExitAndWait();
    }
    object_lock_exclusive(&ar->mObject);
}


//-----------------------------------------------------------------------------
void android_audioRecorder_destroy(CAudioRecorder* ar) {
    SL_LOGV("android_audioRecorder_destroy(%p) entering", ar);

    if (ar->mAudioRecord != 0) {
        ar->mAudioRecord->stop();
        ar->mAudioRecord.clear();
    }
    // explicit destructor
    ar->mCallbackHandle.~sp();
    ar->mAudioRecord.~sp();
    ar->mCallbackProtector.~sp();
}


//-----------------------------------------------------------------------------
void android_audioRecorder_setRecordState(CAudioRecorder* ar, SLuint32 state) {
    SL_LOGV("android_audioRecorder_setRecordState(%p, %u) entering", ar, state);

    if (ar->mAudioRecord == 0) {
        return;
    }

    switch (state) {
     case SL_RECORDSTATE_STOPPED:
         ar->mAudioRecord->stop();
         break;
     case SL_RECORDSTATE_PAUSED:
         // Note that pausing is treated like stop as this implementation only records to a buffer
         //  queue, so there is no notion of destination being "opened" or "closed" (See description
         //  of SL_RECORDSTATE in specification)
         ar->mAudioRecord->stop();
         break;
     case SL_RECORDSTATE_RECORDING:
         ar->mAudioRecord->start();
         break;
     default:
         break;
     }

}


//-----------------------------------------------------------------------------
void android_audioRecorder_useRecordEventMask(CAudioRecorder *ar) {
    IRecord *pRecordItf = &ar->mRecord;
    SLuint32 eventFlags = pRecordItf->mCallbackEventsMask;

    if (ar->mAudioRecord == 0) {
        return;
    }

    if ((eventFlags & SL_RECORDEVENT_HEADATMARKER) && (pRecordItf->mMarkerPosition != 0)) {
        ar->mAudioRecord->setMarkerPosition((uint32_t)((((int64_t)pRecordItf->mMarkerPosition
                * sles_to_android_sampleRate(ar->mSampleRateMilliHz)))/1000));
    } else {
        // clear marker
        ar->mAudioRecord->setMarkerPosition(0);
    }

    if (eventFlags & SL_RECORDEVENT_HEADATNEWPOS) {
        SL_LOGV("pos update period %d", pRecordItf->mPositionUpdatePeriod);
         ar->mAudioRecord->setPositionUpdatePeriod(
                (uint32_t)((((int64_t)pRecordItf->mPositionUpdatePeriod
                * sles_to_android_sampleRate(ar->mSampleRateMilliHz)))/1000));
    } else {
        // clear periodic update
        ar->mAudioRecord->setPositionUpdatePeriod(0);
    }

    if (eventFlags & SL_RECORDEVENT_HEADATLIMIT) {
        // FIXME support SL_RECORDEVENT_HEADATLIMIT
        SL_LOGD("[ FIXME: IRecord_SetCallbackEventsMask(SL_RECORDEVENT_HEADATLIMIT) on an "
                    "SL_OBJECTID_AUDIORECORDER to be implemented ]");
    }

    if (eventFlags & SL_RECORDEVENT_HEADMOVING) {
        // FIXME support SL_RECORDEVENT_HEADMOVING
        SL_LOGD("[ FIXME: IRecord_SetCallbackEventsMask(SL_RECORDEVENT_HEADMOVING) on an "
                "SL_OBJECTID_AUDIORECORDER to be implemented ]");
    }

    if (eventFlags & SL_RECORDEVENT_BUFFER_FULL) {
        // nothing to do for SL_RECORDEVENT_BUFFER_FULL since this will not be encountered on
        // recording to buffer queues
    }

    if (eventFlags & SL_RECORDEVENT_HEADSTALLED) {
        // nothing to do for SL_RECORDEVENT_HEADSTALLED, callback event will be checked against mask
        // when AudioRecord::EVENT_OVERRUN is encountered

    }

}


//-----------------------------------------------------------------------------
void android_audioRecorder_getPosition(CAudioRecorder *ar, SLmillisecond *pPosMsec) {
    if ((NULL == ar) || (ar->mAudioRecord == 0)) {
        *pPosMsec = 0;
    } else {
        uint32_t positionInFrames;
        ar->mAudioRecord->getPosition(&positionInFrames);
        if (ar->mSampleRateMilliHz == UNKNOWN_SAMPLERATE) {
            *pPosMsec = 0;
        } else {
            *pPosMsec = ((int64_t)positionInFrames * 1000) /
                    sles_to_android_sampleRate(ar->mSampleRateMilliHz);
        }
    }
}
