/*
 * Copyright (C) 2016 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 "StreamHalHidl"
//#define LOG_NDEBUG 0

#include <cinttypes>

#include <android/hidl/manager/1.0/IServiceManager.h>
#include <hwbinder/IPCThreadState.h>
#include <media/AudioParameter.h>
#include <mediautils/memory.h>
#include <mediautils/SchedulingPolicyService.h>
#include <mediautils/TimeCheck.h>
#include <utils/Log.h>
#if MAJOR_VERSION >= 4
#include <media/AidlConversion.h>
#endif

#include PATH(android/hardware/audio/CORE_TYPES_FILE_VERSION/IStreamOutCallback.h)
#include <HidlUtils.h>
#include <util/CoreUtils.h>

#include "DeviceHalHidl.h"
#include "EffectHalHidl.h"
#include "ParameterUtils.h"
#include "StreamHalHidl.h"

using ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::implementation::HidlUtils;
using ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::CoreUtils;
using ::android::hardware::MQDescriptorSync;
using ::android::hardware::Return;
using ::android::hardware::Void;

namespace android {

using ReadCommand = ::android::hardware::audio::CORE_TYPES_CPP_VERSION::IStreamIn::ReadCommand;

using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION;
using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION;

StreamHalHidl::StreamHalHidl(std::string_view className, IStream *stream)
        : CoreConversionHelperHidl(className),
          mStream(stream),
          mHalThreadPriority(HAL_THREAD_PRIORITY_DEFAULT),
          mCachedBufferSize(0){

    // Instrument audio signal power logging.
    // Note: This assumes channel mask, format, and sample rate do not change after creation.
    audio_config_base_t config = AUDIO_CONFIG_BASE_INITIALIZER;
    if (/* mStreamPowerLog.isUserDebugOrEngBuild() && */
        StreamHalHidl::getAudioProperties(&config) == NO_ERROR) {
        mStreamPowerLog.init(config.sample_rate, config.channel_mask, config.format);
    }
}

StreamHalHidl::~StreamHalHidl() {
    // The last step is to flush all binder commands so that the deletion
    // of IStreamIn / IStreamOut (mStream) is issued with less delay. See b/35394629.
    hardware::IPCThreadState::self()->flushCommands();
}

status_t StreamHalHidl::getBufferSize(size_t *size) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    status_t status = processReturn("getBufferSize", mStream->getBufferSize(), size);
    if (status == OK) {
        mCachedBufferSize = *size;
    }
    return status;
}

status_t StreamHalHidl::getAudioProperties(audio_config_base_t *configBase) {
    TIME_CHECK();
    *configBase = AUDIO_CONFIG_BASE_INITIALIZER;
    if (!mStream) return NO_INIT;
#if MAJOR_VERSION <= 6
    Return<void> ret = mStream->getAudioProperties(
            [&](uint32_t sr, auto m, auto f) {
                configBase->sample_rate = sr;
                configBase->channel_mask = static_cast<audio_channel_mask_t>(m);
                configBase->format = static_cast<audio_format_t>(f);
            });
    return processReturn("getAudioProperties", ret);
#else
    Result retval;
    status_t conversionStatus = BAD_VALUE;
    Return<void> ret = mStream->getAudioProperties(
            [&](Result r, const AudioConfigBase& config) {
                retval = r;
                if (retval == Result::OK) {
                    conversionStatus = HidlUtils::audioConfigBaseToHal(config, configBase);
                }
            });
    if (status_t status = processReturn("getAudioProperties", ret, retval); status == NO_ERROR) {
        return conversionStatus;
    } else {
        return status;
    }
#endif
}

status_t StreamHalHidl::setParameters(const String8& kvPairs) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    hidl_vec<ParameterValue> hidlParams;
    status_t status = parametersFromHal(kvPairs, &hidlParams);
    if (status != OK) return status;
    return processReturn("setParameters",
                         utils::setParameters(mStream, {} /* context */, hidlParams));
}

status_t StreamHalHidl::getParameters(const String8& keys, String8 *values) {
    TIME_CHECK();
    values->clear();
    if (!mStream) return NO_INIT;
    hidl_vec<hidl_string> hidlKeys;
    status_t status = keysFromHal(keys, &hidlKeys);
    if (status != OK) return status;
    Result retval;
    Return<void> ret = utils::getParameters(
            mStream,
            {} /* context */,
            hidlKeys,
            [&](Result r, const hidl_vec<ParameterValue>& parameters) {
                retval = r;
                if (retval == Result::OK) {
                    parametersToHal(parameters, values);
                }
            });
    return processReturn("getParameters", ret, retval);
}

status_t StreamHalHidl::addEffect(sp<EffectHalInterface> effect) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    auto hidlEffect = sp<effect::EffectHalHidl>::cast(effect);
    return processReturn("addEffect", mStream->addEffect(hidlEffect->effectId()));
}

status_t StreamHalHidl::removeEffect(sp<EffectHalInterface> effect) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    auto hidlEffect = sp<effect::EffectHalHidl>::cast(effect);
    return processReturn("removeEffect", mStream->removeEffect(hidlEffect->effectId()));
}

status_t StreamHalHidl::standby() {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    return processReturn("standby", mStream->standby());
}

status_t StreamHalHidl::dump(int fd, const Vector<String16>& args) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    native_handle_t* hidlHandle = native_handle_create(1, 0);
    hidlHandle->data[0] = fd;
    hidl_vec<hidl_string> hidlArgs;
    argsFromHal(args, &hidlArgs);
    Return<void> ret = mStream->debug(hidlHandle, hidlArgs);
    native_handle_delete(hidlHandle);

    // TODO(b/111997867, b/177271958)  Workaround - remove when fixed.
    // A Binder transmitted fd may not close immediately due to a race condition b/111997867
    // when the remote binder thread removes the last refcount to the fd blocks in the
    // kernel for binder activity. We send a Binder ping() command to unblock the thread
    // and complete the fd close / release.
    //
    // See DeviceHalHidl::dump(), EffectHalHidl::dump(), StreamHalHidl::dump(),
    //     EffectsFactoryHalHidl::dumpEffects().

    (void)mStream->ping(); // synchronous Binder call

    mStreamPowerLog.dump(fd);
    return processReturn("dump", ret);
}

status_t StreamHalHidl::start() {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    return processReturn("start", mStream->start());
}

status_t StreamHalHidl::stop() {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    return processReturn("stop", mStream->stop());
}

status_t StreamHalHidl::createMmapBuffer(int32_t minSizeFrames,
                                  struct audio_mmap_buffer_info *info) {
    TIME_CHECK();
    Result retval;
    Return<void> ret = mStream->createMmapBuffer(
            minSizeFrames,
            [&](Result r, const MmapBufferInfo& hidlInfo) {
                retval = r;
                if (retval == Result::OK) {
                    const native_handle *handle = hidlInfo.sharedMemory.handle();
                    if (handle->numFds > 0) {
                        info->shared_memory_fd = handle->data[0];
#if MAJOR_VERSION >= 4
                        info->flags = audio_mmap_buffer_flag(hidlInfo.flags);
#endif
                        info->buffer_size_frames = hidlInfo.bufferSizeFrames;
                        // Negative buffer size frame was a hack in O and P to
                        // indicate that the buffer is shareable to applications
                        if (info->buffer_size_frames < 0) {
                            info->buffer_size_frames *= -1;
                            info->flags = audio_mmap_buffer_flag(
                                    info->flags | AUDIO_MMAP_APPLICATION_SHAREABLE);
                        }
                        info->burst_size_frames = hidlInfo.burstSizeFrames;
                        // info->shared_memory_address is not needed in HIDL context
                        info->shared_memory_address = NULL;
                    } else {
                        retval = Result::NOT_INITIALIZED;
                    }
                }
            });
    return processReturn("createMmapBuffer", ret, retval);
}

status_t StreamHalHidl::getMmapPosition(struct audio_mmap_position *position) {
    TIME_CHECK();
    Result retval;
    Return<void> ret = mStream->getMmapPosition(
            [&](Result r, const MmapPosition& hidlPosition) {
                retval = r;
                if (retval == Result::OK) {
                    position->time_nanoseconds = hidlPosition.timeNanoseconds;
                    position->position_frames = hidlPosition.positionFrames;
                }
            });
    return processReturn("getMmapPosition", ret, retval);
}

status_t StreamHalHidl::setHalThreadPriority(int priority) {
    mHalThreadPriority = priority;
    return OK;
}

status_t StreamHalHidl::getCachedBufferSize(size_t *size) {
    if (mCachedBufferSize != 0) {
        *size = mCachedBufferSize;
        return OK;
    }
    return getBufferSize(size);
}

status_t StreamHalHidl::getHalPid(pid_t *pid) {
    using ::android::hidl::base::V1_0::DebugInfo;
    using ::android::hidl::manager::V1_0::IServiceManager;
    TIME_CHECK();
    DebugInfo debugInfo;
    auto ret = mStream->getDebugInfo([&] (const auto &info) {
        debugInfo = info;
    });
    if (!ret.isOk()) {
        return INVALID_OPERATION;
    }
    if (debugInfo.pid != (int)IServiceManager::PidConstant::NO_PID) {
        *pid = debugInfo.pid;
        return NO_ERROR;
    }
    return NAME_NOT_FOUND;
}

bool StreamHalHidl::requestHalThreadPriority(pid_t threadPid, pid_t threadId) {
    if (mHalThreadPriority == HAL_THREAD_PRIORITY_DEFAULT) {
        return true;
    }
    int err = requestPriority(
            threadPid, threadId,
            mHalThreadPriority, false /*isForApp*/, true /*asynchronous*/);
    ALOGE_IF(err, "failed to set priority %d for pid %d tid %d; error %d",
            mHalThreadPriority, threadPid, threadId, err);
    // Audio will still work, but latency will be higher and sometimes unacceptable.
    return err == 0;
}

status_t StreamHalHidl::legacyCreateAudioPatch(const struct audio_port_config& port,
                                               std::optional<audio_source_t> source,
                                               audio_devices_t type) {
    TIME_CHECK();
    LOG_ALWAYS_FATAL_IF(port.type != AUDIO_PORT_TYPE_DEVICE, "port type must be device");
    unique_malloced_ptr<char> address;
    if (strcmp(port.ext.device.address, "") != 0) {
        // FIXME: we only support address on first sink with HAL version < 3.0
        address.reset(
                audio_device_address_to_parameter(port.ext.device.type, port.ext.device.address));
    } else {
        address.reset((char*)calloc(1, 1));
    }
    AudioParameter param = AudioParameter(String8(address.get()));
    param.addInt(String8(AudioParameter::keyRouting), (int)type);
    if (source.has_value()) {
        param.addInt(String8(AudioParameter::keyInputSource), (int)source.value());
    }
    return setParameters(param.toString());
}

status_t StreamHalHidl::legacyReleaseAudioPatch() {
    TIME_CHECK();
    AudioParameter param;
    param.addInt(String8(AudioParameter::keyRouting), 0);
    return setParameters(param.toString());
}

namespace {

/* Notes on callback ownership.

This is how (Hw)Binder ownership model looks like. The server implementation
is owned by Binder framework (via sp<>). Proxies are owned by clients.
When the last proxy disappears, Binder framework releases the server impl.

Thus, it is not needed to keep any references to StreamOutCallback (this is
the server impl) -- it will live as long as HAL server holds a strong ref to
IStreamOutCallback proxy. We clear that reference by calling 'clearCallback'
from the destructor of StreamOutHalHidl.

The callback only keeps a weak reference to the stream. The stream is owned
by AudioFlinger.

*/

struct StreamOutCallback : public IStreamOutCallback {
    StreamOutCallback(const wp<StreamOutHalHidl>& stream) : mStream(stream) {}

    // IStreamOutCallback implementation
    Return<void> onWriteReady()  override {
        sp<StreamOutHalHidl> stream = mStream.promote();
        if (stream != 0) {
            stream->onWriteReady();
        }
        return Void();
    }

    Return<void> onDrainReady()  override {
        sp<StreamOutHalHidl> stream = mStream.promote();
        if (stream != 0) {
            stream->onDrainReady();
        }
        return Void();
    }

    Return<void> onError()  override {
        sp<StreamOutHalHidl> stream = mStream.promote();
        if (stream != 0) {
            stream->onError();
        }
        return Void();
    }

  private:
    const wp<StreamOutHalHidl> mStream;
};

}  // namespace

StreamOutHalHidl::StreamOutHalHidl(
        const sp<::android::hardware::audio::CPP_VERSION::IStreamOut>& stream)
        : StreamHalHidl("StreamOutHalHidl", stream.get())
        , mStream(stream), mWriterClient(0), mEfGroup(nullptr) {
}

StreamOutHalHidl::~StreamOutHalHidl() {
    if (mStream != 0) {
        if (mCallback.load().unsafe_get()) {
            processReturn("clearCallback", mStream->clearCallback());
        }
#if MAJOR_VERSION >= 6
        if (mEventCallback.load().unsafe_get() != nullptr) {
            processReturn("setEventCallback",
                    mStream->setEventCallback(nullptr));
        }
#endif
        processReturn("close", mStream->close());
    }
    mCallback = nullptr;
    mEventCallback = nullptr;
    if (mEfGroup) {
        EventFlag::deleteEventFlag(&mEfGroup);
    }
}

status_t StreamOutHalHidl::getFrameSize(size_t *size) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("getFrameSize", mStream->getFrameSize(), size);
}

status_t StreamOutHalHidl::getLatency(uint32_t *latency) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    if (mWriterClient == gettid() && mCommandMQ) {
        return callWriterThread(
                WriteCommand::GET_LATENCY, "getLatency", nullptr, 0,
                [&](const WriteStatus& writeStatus) {
                    *latency = writeStatus.reply.latencyMs;
                });
    } else {
        return processReturn("getLatency", mStream->getLatency(), latency);
    }
}

status_t StreamOutHalHidl::setVolume(float left, float right) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("setVolume", mStream->setVolume(left, right));
}

#if MAJOR_VERSION == 2
status_t StreamOutHalHidl::selectPresentation(int presentationId, int programId) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    std::vector<ParameterValue> parameters;
    String8 halParameters;
    parameters.push_back({AudioParameter::keyPresentationId, std::to_string(presentationId)});
    parameters.push_back({AudioParameter::keyProgramId, std::to_string(programId)});
    parametersToHal(hidl_vec<ParameterValue>(parameters), &halParameters);
    return setParameters(halParameters);
}
#elif MAJOR_VERSION >= 4
status_t StreamOutHalHidl::selectPresentation(int presentationId, int programId) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("selectPresentation",
            mStream->selectPresentation(presentationId, programId));
}
#endif

status_t StreamOutHalHidl::write(const void *buffer, size_t bytes, size_t *written) {
    // TIME_CHECK();  // TODO(b/243839867) reenable only when optimized.
    if (mStream == 0) return NO_INIT;
    *written = 0;

    if (bytes == 0 && !mDataMQ) {
        // Can't determine the size for the MQ buffer. Wait for a non-empty write request.
        ALOGW_IF(mCallback.load().unsafe_get(), "First call to async write with 0 bytes");
        return OK;
    }

    status_t status;
    if (!mDataMQ) {
        // In case if playback starts close to the end of a compressed track, the bytes
        // that need to be written is less than the actual buffer size. Need to use
        // full buffer size for the MQ since otherwise after seeking back to the middle
        // data will be truncated.
        size_t bufferSize;
        if ((status = getCachedBufferSize(&bufferSize)) != OK) {
            return status;
        }
        if (bytes > bufferSize) bufferSize = bytes;
        if ((status = prepareForWriting(bufferSize)) != OK) {
            return status;
        }
    }

    status = callWriterThread(
            WriteCommand::WRITE, "write", static_cast<const uint8_t*>(buffer), bytes,
            [&] (const WriteStatus& writeStatus) {
                *written = writeStatus.reply.written;
                // Diagnostics of the cause of b/35813113.
                ALOGE_IF(*written > bytes,
                        "hal reports more bytes written than asked for: %lld > %lld",
                        (long long)*written, (long long)bytes);
            });
    mStreamPowerLog.log(buffer, *written);
    return status;
}

status_t StreamOutHalHidl::callWriterThread(
        WriteCommand cmd, const char* cmdName,
        const uint8_t* data, size_t dataSize, StreamOutHalHidl::WriterCallback callback) {
    if (!mCommandMQ->write(&cmd)) {
        ALOGE("command message queue write failed for \"%s\"", cmdName);
        return -EAGAIN;
    }
    if (data != nullptr) {
        size_t availableToWrite = mDataMQ->availableToWrite();
        if (dataSize > availableToWrite) {
            ALOGW("truncating write data from %lld to %lld due to insufficient data queue space",
                    (long long)dataSize, (long long)availableToWrite);
            dataSize = availableToWrite;
        }
        if (!mDataMQ->write(data, dataSize)) {
            ALOGE("data message queue write failed for \"%s\"", cmdName);
        }
    }
    mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));

    // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422
    uint32_t efState = 0;
retry:
    status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL), &efState);
    if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)) {
        WriteStatus writeStatus;
        writeStatus.retval = Result::NOT_INITIALIZED;
        if (!mStatusMQ->read(&writeStatus)) {
            ALOGE("status message read failed for \"%s\"", cmdName);
        }
        if (writeStatus.retval == Result::OK) {
            ret = OK;
            callback(writeStatus);
        } else {
            ret = processReturn(cmdName, writeStatus.retval);
        }
        return ret;
    }
    if (ret == -EAGAIN || ret == -EINTR) {
        // Spurious wakeup. This normally retries no more than once.
        goto retry;
    }
    return ret;
}

status_t StreamOutHalHidl::prepareForWriting(size_t bufferSize) {
    std::unique_ptr<CommandMQ> tempCommandMQ;
    std::unique_ptr<DataMQ> tempDataMQ;
    std::unique_ptr<StatusMQ> tempStatusMQ;
    Result retval;
    pid_t halThreadPid, halThreadTid;
    Return<void> ret = mStream->prepareForWriting(
            1, bufferSize,
            [&](Result r,
                    const CommandMQ::Descriptor& commandMQ,
                    const DataMQ::Descriptor& dataMQ,
                    const StatusMQ::Descriptor& statusMQ,
                    const auto& halThreadInfo) {
                retval = r;
                if (retval == Result::OK) {
                    tempCommandMQ.reset(new CommandMQ(commandMQ));
                    tempDataMQ.reset(new DataMQ(dataMQ));
                    tempStatusMQ.reset(new StatusMQ(statusMQ));
                    if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
                        EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
                    }
#if MAJOR_VERSION <= 6
                    halThreadPid = halThreadInfo.pid;
                    halThreadTid = halThreadInfo.tid;
#else
                    halThreadTid = halThreadInfo;
#endif
                }
            });
    if (!ret.isOk() || retval != Result::OK) {
        return processReturn("prepareForWriting", ret, retval);
    }
    if (!tempCommandMQ || !tempCommandMQ->isValid() ||
            !tempDataMQ || !tempDataMQ->isValid() ||
            !tempStatusMQ || !tempStatusMQ->isValid() ||
            !mEfGroup) {
        ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing");
        ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
                "Command message queue for writing is invalid");
        ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for writing");
        ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for writing is invalid");
        ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for writing");
        ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
                "Status message queue for writing is invalid");
        ALOGE_IF(!mEfGroup, "Event flag creation for writing failed");
        return NO_INIT;
    }
#if MAJOR_VERSION >= 7
    if (status_t status = getHalPid(&halThreadPid); status != NO_ERROR) {
        return status;
    }
#endif
    requestHalThreadPriority(halThreadPid, halThreadTid);

    mCommandMQ = std::move(tempCommandMQ);
    mDataMQ = std::move(tempDataMQ);
    mStatusMQ = std::move(tempStatusMQ);
    mWriterClient = gettid();
    return OK;
}

status_t StreamOutHalHidl::getRenderPosition(uint64_t *dspFrames) {
    // TIME_CHECK();  // TODO(b/243839867) reenable only when optimized.
    if (mStream == 0) return NO_INIT;
    Result retval;
    uint32_t halPosition = 0;
    Return<void> ret = mStream->getRenderPosition(
            [&](Result r, uint32_t d) {
                retval = r;
                if (retval == Result::OK) {
                    halPosition = d;
                }
            });
    status_t status = processReturn("getRenderPosition", ret, retval);
    if (status != OK) {
        return status;
    }
    // Maintain a 64-bit render position using the 32-bit result from the HAL.
    // This delta calculation relies on the arithmetic overflow behavior
    // of integers. For example (100 - 0xFFFFFFF0) = 116.
    std::lock_guard l(mPositionMutex);
    const auto truncatedPosition = (uint32_t)mRenderPosition;
    int32_t deltaHalPosition; // initialization not needed, overwitten by __builtin_sub_overflow()
    (void) __builtin_sub_overflow(halPosition, truncatedPosition, &deltaHalPosition);

    if (deltaHalPosition >= 0) {
        mRenderPosition += deltaHalPosition;
    } else if (mExpectRetrograde) {
        mExpectRetrograde = false;
        mRenderPosition -= static_cast<uint64_t>(-deltaHalPosition);
        ALOGW("Retrograde motion of %" PRId32 " frames", -deltaHalPosition);
    }
    *dspFrames = mRenderPosition;
    return OK;
}

status_t StreamOutHalHidl::setCallback(wp<StreamOutHalInterfaceCallback> callback) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    status_t status = processReturn(
            "setCallback", mStream->setCallback(new StreamOutCallback(this)));
    if (status == OK) {
        mCallback = callback;
    }
    return status;
}

status_t StreamOutHalHidl::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    Return<void> ret = mStream->supportsPauseAndResume(
            [&](bool p, bool r) {
                *supportsPause = p;
                *supportsResume = r;
            });
    return processReturn("supportsPauseAndResume", ret);
}

status_t StreamOutHalHidl::pause() {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("pause", mStream->pause());
}

status_t StreamOutHalHidl::resume() {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("pause", mStream->resume());
}

status_t StreamOutHalHidl::supportsDrain(bool *supportsDrain) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("supportsDrain", mStream->supportsDrain(), supportsDrain);
}

status_t StreamOutHalHidl::drain(bool earlyNotify) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn(
            "drain", mStream->drain(earlyNotify ? AudioDrain::EARLY_NOTIFY : AudioDrain::ALL));
}

status_t StreamOutHalHidl::flush() {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    {
        std::lock_guard l(mPositionMutex);
        mRenderPosition = 0;
        mExpectRetrograde = false;
    }
    return processReturn("pause", mStream->flush());
}

status_t StreamOutHalHidl::standby() {
    {
        std::lock_guard l(mPositionMutex);
        mRenderPosition = 0;
        mExpectRetrograde = false;
    }
    return StreamHalHidl::standby();
}

status_t StreamOutHalHidl::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) {
    // TIME_CHECK();  // TODO(b/243839867) reenable only when optimized.
    if (mStream == 0) return NO_INIT;
    if (mWriterClient == gettid() && mCommandMQ) {
        return callWriterThread(
                WriteCommand::GET_PRESENTATION_POSITION, "getPresentationPosition", nullptr, 0,
                [&](const WriteStatus& writeStatus) {
                    *frames = writeStatus.reply.presentationPosition.frames;
                    timestamp->tv_sec = writeStatus.reply.presentationPosition.timeStamp.tvSec;
                    timestamp->tv_nsec = writeStatus.reply.presentationPosition.timeStamp.tvNSec;
                });
    } else {
        Result retval;
        Return<void> ret = mStream->getPresentationPosition(
                [&](Result r, uint64_t hidlFrames, const TimeSpec& hidlTimeStamp) {
                    retval = r;
                    if (retval == Result::OK) {
                        *frames = hidlFrames;
                        timestamp->tv_sec = hidlTimeStamp.tvSec;
                        timestamp->tv_nsec = hidlTimeStamp.tvNSec;
                    }
                });
        return processReturn("getPresentationPosition", ret, retval);
    }
}

status_t StreamOutHalHidl::presentationComplete() {
    // Avoid suppressing retrograde motion in mRenderPosition for gapless offload/direct when
    // transitioning between tracks.
    // The HAL resets the frame position without flush/stop being called, but calls back prior to
    // this event. So, on the next occurrence of retrograde motion, we permit backwards movement of
    // mRenderPosition.
    mExpectRetrograde = true;
    return OK;
}

#if MAJOR_VERSION == 2
status_t StreamOutHalHidl::updateSourceMetadata(
        const StreamOutHalInterface::SourceMetadata& /* sourceMetadata */) {
    // Audio HAL V2.0 does not support propagating source metadata
    return INVALID_OPERATION;
}
#elif MAJOR_VERSION >= 4
status_t StreamOutHalHidl::updateSourceMetadata(
        const StreamOutHalInterface::SourceMetadata& sourceMetadata) {
    TIME_CHECK();
#if MAJOR_VERSION == 4
    ::android::hardware::audio::CORE_TYPES_CPP_VERSION::SourceMetadata hidlMetadata;
#else
    ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::SourceMetadata hidlMetadata;
#endif
    if (status_t status = CoreUtils::sourceMetadataFromHalV7(
                    sourceMetadata.tracks, true /*ignoreNonVendorTags*/, &hidlMetadata);
            status != OK) {
        return status;
    }
    return processReturn("updateSourceMetadata", mStream->updateSourceMetadata(hidlMetadata));
}
#endif

#if MAJOR_VERSION < 6
status_t StreamOutHalHidl::getDualMonoMode(audio_dual_mono_mode_t* mode __unused) {
    return INVALID_OPERATION;
}

status_t StreamOutHalHidl::setDualMonoMode(audio_dual_mono_mode_t mode __unused) {
    return INVALID_OPERATION;
}

status_t StreamOutHalHidl::getAudioDescriptionMixLevel(float* leveldB __unused) {
    return INVALID_OPERATION;
}

status_t StreamOutHalHidl::setAudioDescriptionMixLevel(float leveldB __unused) {
    return INVALID_OPERATION;
}

status_t StreamOutHalHidl::getPlaybackRateParameters(
        audio_playback_rate_t* playbackRate __unused) {
    return INVALID_OPERATION;
}

status_t StreamOutHalHidl::setPlaybackRateParameters(
        const audio_playback_rate_t& playbackRate __unused) {
    return INVALID_OPERATION;
}

status_t StreamOutHalHidl::setEventCallback(
        const sp<StreamOutHalInterfaceEventCallback>& callback __unused) {
    // Codec format callback is supported starting from audio HAL V6.0
    return INVALID_OPERATION;
}

#else

status_t StreamOutHalHidl::getDualMonoMode(audio_dual_mono_mode_t* mode) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    Result retval;
    Return<void> ret = mStream->getDualMonoMode(
            [&](Result r, DualMonoMode hidlMode) {
                retval = r;
                if (retval == Result::OK) {
                    *mode = static_cast<audio_dual_mono_mode_t>(hidlMode);
                }
            });
    return processReturn("getDualMonoMode", ret, retval);
}

status_t StreamOutHalHidl::setDualMonoMode(audio_dual_mono_mode_t mode) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn(
            "setDualMonoMode", mStream->setDualMonoMode(static_cast<DualMonoMode>(mode)));
}

status_t StreamOutHalHidl::getAudioDescriptionMixLevel(float* leveldB) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    Result retval;
    Return<void> ret = mStream->getAudioDescriptionMixLevel(
            [&](Result r, float hidlLeveldB) {
                retval = r;
                if (retval == Result::OK) {
                    *leveldB = hidlLeveldB;
                }
            });
    return processReturn("getAudioDescriptionMixLevel", ret, retval);
}

status_t StreamOutHalHidl::setAudioDescriptionMixLevel(float leveldB) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn(
            "setAudioDescriptionMixLevel", mStream->setAudioDescriptionMixLevel(leveldB));
}

status_t StreamOutHalHidl::getPlaybackRateParameters(audio_playback_rate_t* playbackRate) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    Result retval;
    Return<void> ret = mStream->getPlaybackRateParameters(
            [&](Result r, PlaybackRate hidlPlaybackRate) {
                retval = r;
                if (retval == Result::OK) {
                    playbackRate->mSpeed = hidlPlaybackRate.speed;
                    playbackRate->mPitch = hidlPlaybackRate.pitch;
                    playbackRate->mStretchMode =
                        static_cast<audio_timestretch_stretch_mode_t>(
                            hidlPlaybackRate.timestretchMode);
                    playbackRate->mFallbackMode =
                        static_cast<audio_timestretch_fallback_mode_t>(
                            hidlPlaybackRate.fallbackMode);
                }
            });
    return processReturn("getPlaybackRateParameters", ret, retval);
}

status_t StreamOutHalHidl::setPlaybackRateParameters(const audio_playback_rate_t& playbackRate) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn(
            "setPlaybackRateParameters", mStream->setPlaybackRateParameters(
                PlaybackRate{playbackRate.mSpeed, playbackRate.mPitch,
                    static_cast<TimestretchMode>(playbackRate.mStretchMode),
                    static_cast<TimestretchFallbackMode>(playbackRate.mFallbackMode)}));
}

#include PATH(android/hardware/audio/CORE_TYPES_FILE_VERSION/IStreamOutEventCallback.h)

namespace {

struct StreamOutEventCallback : public IStreamOutEventCallback {
    StreamOutEventCallback(const wp<StreamOutHalHidl>& stream) : mStream(stream) {}

    // IStreamOutEventCallback implementation
    Return<void> onCodecFormatChanged(
            const android::hardware::hidl_vec<uint8_t>& audioMetadata)  override {
        sp<StreamOutHalHidl> stream = mStream.promote();
        if (stream != nullptr) {
            std::vector<uint8_t> metadataBs(audioMetadata.begin(), audioMetadata.end());
            stream->onCodecFormatChanged(metadataBs);
        }
        return Void();
    }

  private:
    wp<StreamOutHalHidl> mStream;
};

}  // namespace

status_t StreamOutHalHidl::setEventCallback(
        const sp<StreamOutHalInterfaceEventCallback>& callback) {
    TIME_CHECK();
    if (mStream == nullptr) return NO_INIT;
    mEventCallback = callback;
    status_t status = processReturn(
            "setEventCallback",
            mStream->setEventCallback(
                    callback.get() == nullptr ? nullptr : new StreamOutEventCallback(this)));
    return status;
}
#endif

#if MAJOR_VERSION == 7 && MINOR_VERSION == 1
using hardware::audio::V7_1::LatencyMode;

status_t StreamOutHalHidl::setLatencyMode(audio_latency_mode_t mode) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn(
            "setLatencyMode", mStream->setLatencyMode(static_cast<LatencyMode>(mode)));
};

status_t StreamOutHalHidl::getRecommendedLatencyModes(std::vector<audio_latency_mode_t> *modes) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    Result retval;
    Return<void> ret = mStream->getRecommendedLatencyModes(
            [&](Result r, hidl_vec<LatencyMode> hidlModes) {
        retval = r;
        for (size_t i = 0; i < hidlModes.size(); i++) {
            modes->push_back(static_cast<audio_latency_mode_t>(hidlModes[i]));
        }
    });
    return processReturn("getRecommendedLatencyModes", ret, retval);
};

#include PATH(android/hardware/audio/FILE_VERSION/IStreamOutLatencyModeCallback.h)

using hardware::audio::V7_1::IStreamOutLatencyModeCallback;

namespace {
struct StreamOutLatencyModeCallback : public IStreamOutLatencyModeCallback {
    StreamOutLatencyModeCallback(const wp<StreamOutHalHidl>& stream) : mStream(stream) {}

    // IStreamOutLatencyModeCallback implementation
    Return<void> onRecommendedLatencyModeChanged(const hidl_vec<LatencyMode>& hidlModes) override {
        sp<StreamOutHalHidl> stream = mStream.promote();
        if (stream != nullptr) {
            std::vector<audio_latency_mode_t> modes;
            for (size_t i = 0; i < hidlModes.size(); i++) {
                modes.push_back(static_cast<audio_latency_mode_t>(hidlModes[i]));
            }
            stream->onRecommendedLatencyModeChanged(modes);
        }
        return Void();
    }

  private:
    wp<StreamOutHalHidl> mStream;
};
}  // namespace

status_t StreamOutHalHidl::setLatencyModeCallback(
        const sp<StreamOutHalInterfaceLatencyModeCallback>& callback) {
    TIME_CHECK();

    if (mStream == nullptr) return NO_INIT;
    mLatencyModeCallback = callback;
    status_t status = processReturn(
            "setLatencyModeCallback",
            mStream->setLatencyModeCallback(
                    callback.get() == nullptr ? nullptr : new StreamOutLatencyModeCallback(this)));
    return status;
};

#else

status_t StreamOutHalHidl::setLatencyMode(audio_latency_mode_t mode __unused) {
    return INVALID_OPERATION;
};

status_t StreamOutHalHidl::getRecommendedLatencyModes(
        std::vector<audio_latency_mode_t> *modes __unused) {
    return INVALID_OPERATION;
};

status_t StreamOutHalHidl::setLatencyModeCallback(
        const sp<StreamOutHalInterfaceLatencyModeCallback>& callback __unused) {
    return INVALID_OPERATION;
};

#endif

void StreamOutHalHidl::onWriteReady() {
    sp<StreamOutHalInterfaceCallback> callback = mCallback.load().promote();
    if (callback == 0) return;
    ALOGV("asyncCallback onWriteReady");
    callback->onWriteReady();
}

void StreamOutHalHidl::onDrainReady() {
    sp<StreamOutHalInterfaceCallback> callback = mCallback.load().promote();
    if (callback == 0) return;
    ALOGV("asyncCallback onDrainReady");
    callback->onDrainReady();
}

void StreamOutHalHidl::onError() {
    sp<StreamOutHalInterfaceCallback> callback = mCallback.load().promote();
    if (callback == 0) return;
    ALOGV("asyncCallback onError");
    callback->onError(false /*isHardError*/);
}

void StreamOutHalHidl::onCodecFormatChanged(const std::vector<uint8_t>& metadataBs) {
    sp<StreamOutHalInterfaceEventCallback> callback = mEventCallback.load().promote();
    if (callback == nullptr) return;
    ALOGV("asyncCodecFormatCallback %s", __func__);
    callback->onCodecFormatChanged(metadataBs);
}

void StreamOutHalHidl::onRecommendedLatencyModeChanged(
        const std::vector<audio_latency_mode_t>& modes) {
    sp<StreamOutHalInterfaceLatencyModeCallback> callback = mLatencyModeCallback.load().promote();
    if (callback == nullptr) return;
    callback->onRecommendedLatencyModeChanged(modes);
}

status_t StreamOutHalHidl::exit() {
    // Signal exiting to HALs that use intermediate pipes to close them.
    AudioParameter param;
    param.addInt(String8(AudioParameter::keyExiting), 1);
    param.add(String8(AudioParameter::keyClosing), String8(AudioParameter::valueTrue));
    return setParameters(param.toString());
}

StreamInHalHidl::StreamInHalHidl(
        const sp<::android::hardware::audio::CORE_TYPES_CPP_VERSION::IStreamIn>& stream)
        : StreamHalHidl("StreamInHalHidl", stream.get())
        , mStream(stream), mReaderClient(0), mEfGroup(nullptr) {
}

StreamInHalHidl::~StreamInHalHidl() {
    if (mStream != 0) {
        processReturn("close", mStream->close());
    }
    if (mEfGroup) {
        EventFlag::deleteEventFlag(&mEfGroup);
    }
}

status_t StreamInHalHidl::getFrameSize(size_t *size) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("getFrameSize", mStream->getFrameSize(), size);
}

status_t StreamInHalHidl::setGain(float gain) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("setGain", mStream->setGain(gain));
}

status_t StreamInHalHidl::read(void *buffer, size_t bytes, size_t *read) {
    // TIME_CHECK();  // TODO(b/243839867) reenable only when optimized.
    if (mStream == 0) return NO_INIT;
    *read = 0;

    if (bytes == 0 && !mDataMQ) {
        // Can't determine the size for the MQ buffer. Wait for a non-empty read request.
        return OK;
    }

    status_t status;
    if (!mDataMQ && (status = prepareForReading(bytes)) != OK) {
        return status;
    }

    ReadParameters params;
    params.command = ReadCommand::READ;
    params.params.read = bytes;
    status = callReaderThread(params, "read",
            [&](const ReadStatus& readStatus) {
                const size_t availToRead = mDataMQ->availableToRead();
                if (!mDataMQ->read(static_cast<uint8_t*>(buffer), std::min(bytes, availToRead))) {
                    ALOGE("data message queue read failed for \"read\"");
                }
                ALOGW_IF(availToRead != readStatus.reply.read,
                        "HAL read report inconsistent: mq = %d, status = %d",
                        (int32_t)availToRead, (int32_t)readStatus.reply.read);
                *read = readStatus.reply.read;
            });
    mStreamPowerLog.log(buffer, *read);
    return status;
}

status_t StreamInHalHidl::callReaderThread(
        const ReadParameters& params, const char* cmdName,
        StreamInHalHidl::ReaderCallback callback) {
    if (!mCommandMQ->write(&params)) {
        ALOGW("command message queue write failed");
        return -EAGAIN;
    }
    mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));

    // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422
    uint32_t efState = 0;
retry:
    status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState);
    if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) {
        ReadStatus readStatus;
        readStatus.retval = Result::NOT_INITIALIZED;
        if (!mStatusMQ->read(&readStatus)) {
            ALOGE("status message read failed for \"%s\"", cmdName);
        }
         if (readStatus.retval == Result::OK) {
            ret = OK;
            callback(readStatus);
        } else {
            ret = processReturn(cmdName, readStatus.retval);
        }
        return ret;
    }
    if (ret == -EAGAIN || ret == -EINTR) {
        // Spurious wakeup. This normally retries no more than once.
        goto retry;
    }
    return ret;
}

status_t StreamInHalHidl::prepareForReading(size_t bufferSize) {
    std::unique_ptr<CommandMQ> tempCommandMQ;
    std::unique_ptr<DataMQ> tempDataMQ;
    std::unique_ptr<StatusMQ> tempStatusMQ;
    Result retval;
    pid_t halThreadPid, halThreadTid;
    Return<void> ret = mStream->prepareForReading(
            1, bufferSize,
            [&](Result r,
                    const CommandMQ::Descriptor& commandMQ,
                    const DataMQ::Descriptor& dataMQ,
                    const StatusMQ::Descriptor& statusMQ,
                    const auto& halThreadInfo) {
                retval = r;
                if (retval == Result::OK) {
                    tempCommandMQ.reset(new CommandMQ(commandMQ));
                    tempDataMQ.reset(new DataMQ(dataMQ));
                    tempStatusMQ.reset(new StatusMQ(statusMQ));
                    if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
                        EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
                    }
#if MAJOR_VERSION <= 6
                    halThreadPid = halThreadInfo.pid;
                    halThreadTid = halThreadInfo.tid;
#else
                    halThreadTid = halThreadInfo;
#endif
                }
            });
    if (!ret.isOk() || retval != Result::OK) {
        return processReturn("prepareForReading", ret, retval);
    }
    if (!tempCommandMQ || !tempCommandMQ->isValid() ||
            !tempDataMQ || !tempDataMQ->isValid() ||
            !tempStatusMQ || !tempStatusMQ->isValid() ||
            !mEfGroup) {
        ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing");
        ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
                "Command message queue for writing is invalid");
        ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for reading");
        ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for reading is invalid");
        ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for reading");
        ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
                "Status message queue for reading is invalid");
        ALOGE_IF(!mEfGroup, "Event flag creation for reading failed");
        return NO_INIT;
    }
#if MAJOR_VERSION >= 7
    if (status_t status = getHalPid(&halThreadPid); status != NO_ERROR) {
        return status;
    }
#endif
    requestHalThreadPriority(halThreadPid, halThreadTid);

    mCommandMQ = std::move(tempCommandMQ);
    mDataMQ = std::move(tempDataMQ);
    mStatusMQ = std::move(tempStatusMQ);
    mReaderClient = gettid();
    return OK;
}

status_t StreamInHalHidl::getInputFramesLost(uint32_t *framesLost) {
    TIME_CHECK();
    if (mStream == 0) return NO_INIT;
    return processReturn("getInputFramesLost", mStream->getInputFramesLost(), framesLost);
}

status_t StreamInHalHidl::getCapturePosition(int64_t *frames, int64_t *time) {
    // TIME_CHECK();  // TODO(b/243839867) reenable only when optimized.
    if (mStream == 0) return NO_INIT;
    if (mReaderClient == gettid() && mCommandMQ) {
        ReadParameters params;
        params.command = ReadCommand::GET_CAPTURE_POSITION;
        return callReaderThread(params, "getCapturePosition",
                [&](const ReadStatus& readStatus) {
                    *frames = readStatus.reply.capturePosition.frames;
                    *time = readStatus.reply.capturePosition.time;
                });
    } else {
        Result retval;
        Return<void> ret = mStream->getCapturePosition(
                [&](Result r, uint64_t hidlFrames, uint64_t hidlTime) {
                    retval = r;
                    if (retval == Result::OK) {
                        *frames = hidlFrames;
                        *time = hidlTime;
                    }
                });
        return processReturn("getCapturePosition", ret, retval);
    }
}

#if MAJOR_VERSION == 2
status_t StreamInHalHidl::getActiveMicrophones(
        std::vector<media::MicrophoneInfoFw> *microphones __unused) {
    if (mStream == 0) return NO_INIT;
    return INVALID_OPERATION;
}

status_t StreamInHalHidl::updateSinkMetadata(
        const StreamInHalInterface::SinkMetadata& /* sinkMetadata */) {
    // Audio HAL V2.0 does not support propagating sink metadata
    return INVALID_OPERATION;
}

#elif MAJOR_VERSION >= 4
status_t StreamInHalHidl::getActiveMicrophones(
        std::vector<media::MicrophoneInfoFw> *microphonesInfo) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    Result retval;
    Return<void> ret = mStream->getActiveMicrophones(
            [&](Result r, hidl_vec<MicrophoneInfo> micArrayHal) {
        retval = r;
        for (size_t k = 0; k < micArrayHal.size(); k++) {
            // Convert via legacy.
            audio_microphone_characteristic_t dst;
            (void)CoreUtils::microphoneInfoToHal(micArrayHal[k], &dst);
            auto conv = legacy2aidl_audio_microphone_characteristic_t_MicrophoneInfoFw(dst);
            if (conv.ok()) {
                microphonesInfo->push_back(conv.value());
            } else {
                ALOGW("getActiveMicrophones: could not convert %s to AIDL: %d",
                        toString(micArrayHal[k]).c_str(), conv.error());
                microphonesInfo->push_back(media::MicrophoneInfoFw{});
            }
        }
    });
    return processReturn("getActiveMicrophones", ret, retval);
}

status_t StreamInHalHidl::updateSinkMetadata(const
        StreamInHalInterface::SinkMetadata& sinkMetadata) {
    TIME_CHECK();
#if MAJOR_VERSION == 4
    ::android::hardware::audio::CORE_TYPES_CPP_VERSION::SinkMetadata hidlMetadata;
#else
    ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::SinkMetadata hidlMetadata;
#endif
    if (status_t status = CoreUtils::sinkMetadataFromHalV7(
                    sinkMetadata.tracks, true /*ignoreNonVendorTags*/, &hidlMetadata);
            status != OK) {
        return status;
    }
    return processReturn("updateSinkMetadata", mStream->updateSinkMetadata(hidlMetadata));
}
#endif

#if MAJOR_VERSION < 5
status_t StreamInHalHidl::setPreferredMicrophoneDirection(
            audio_microphone_direction_t direction __unused) {
    if (mStream == 0) return NO_INIT;
    return INVALID_OPERATION;
}

status_t StreamInHalHidl::setPreferredMicrophoneFieldDimension(float zoom __unused) {
    if (mStream == 0) return NO_INIT;
    return INVALID_OPERATION;
}
#else
status_t StreamInHalHidl::setPreferredMicrophoneDirection(audio_microphone_direction_t direction) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    return processReturn("setPreferredMicrophoneDirection",
        mStream->setMicrophoneDirection(static_cast<MicrophoneDirection>(direction)));
}

status_t StreamInHalHidl::setPreferredMicrophoneFieldDimension(float zoom) {
    TIME_CHECK();
    if (!mStream) return NO_INIT;
    return processReturn("setPreferredMicrophoneFieldDimension",
                mStream->setMicrophoneFieldDimension(zoom));
}
#endif

} // namespace android
