/*
 * Copyright (C) 2009 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 "AudioPolicyManager"
//#define LOG_NDEBUG 0
#include <utils/Log.h>
#include "AudioPolicyManager.h"
#include <media/mediarecorder.h>

namespace android {


// ----------------------------------------------------------------------------
// AudioPolicyInterface implementation
// ----------------------------------------------------------------------------


status_t AudioPolicyManager::setDeviceConnectionState(AudioSystem::audio_devices device,
                                                  AudioSystem::device_connection_state state,
                                                  const char *device_address)
{

    LOGV("setDeviceConnectionState() device: %x, state %d, address %s", device, state, device_address);

    // connect/disconnect only 1 device at a time
    if (AudioSystem::popCount(device) != 1) return BAD_VALUE;

    if (strlen(device_address) >= MAX_DEVICE_ADDRESS_LEN) {
        LOGE("setDeviceConnectionState() invalid address: %s", device_address);
        return BAD_VALUE;
    }

    // handle output devices
    if (AudioSystem::isOutputDevice(device)) {

#ifndef WITH_A2DP
        if (AudioSystem::isA2dpDevice(device)) {
            LOGE("setDeviceConnectionState() invalid device: %x", device);
            return BAD_VALUE;
        }
#endif

        switch (state)
        {
        // handle output device connection
        case AudioSystem::DEVICE_STATE_AVAILABLE:
            if (mAvailableOutputDevices & device) {
                LOGW("setDeviceConnectionState() device already connected: %x", device);
                return INVALID_OPERATION;
            }
            LOGW_IF((getOutputForDevice((uint32_t)device) != 0), "setDeviceConnectionState(): output using unconnected device %x", device);

            LOGV("setDeviceConnectionState() connecting device %x", device);

            // register new device as available
            mAvailableOutputDevices |= device;

#ifdef WITH_A2DP
            // handle A2DP device connection
            if (AudioSystem::isA2dpDevice(device)) {
                // when an A2DP device is connected, open an A2DP and a duplicated output
                LOGV("opening A2DP output for device %s", device_address);
                AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
                outputDesc->mDevice = device;
                mA2dpOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
                                                        &outputDesc->mSamplingRate,
                                                        &outputDesc->mFormat,
                                                        &outputDesc->mChannels,
                                                        &outputDesc->mLatency,
                                                        outputDesc->mFlags);
                if (mA2dpOutput) {
                    // add A2DP output descriptor
                    mOutputs.add(mA2dpOutput, outputDesc);
                    // set initial stream volume for A2DP device
                    applyStreamVolumes(mA2dpOutput, device);
                    mDuplicatedOutput = mpClientInterface->openDuplicateOutput(mA2dpOutput, mHardwareOutput);
                    if (mDuplicatedOutput != 0) {
                        // If both A2DP and duplicated outputs are open, send device address to A2DP hardware
                        // interface
                        AudioParameter param;
                        param.add(String8("a2dp_sink_address"), String8(device_address));
                        mpClientInterface->setParameters(mA2dpOutput, param.toString());
                        mA2dpDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN);

                        // add duplicated output descriptor
                        AudioOutputDescriptor *dupOutputDesc = new AudioOutputDescriptor();
                        dupOutputDesc->mOutput1 = mOutputs.valueFor(mHardwareOutput);
                        dupOutputDesc->mOutput2 = mOutputs.valueFor(mA2dpOutput);
                        dupOutputDesc->mSamplingRate = outputDesc->mSamplingRate;
                        dupOutputDesc->mFormat = outputDesc->mFormat;
                        dupOutputDesc->mChannels = outputDesc->mChannels;
                        dupOutputDesc->mLatency = outputDesc->mLatency;
                        mOutputs.add(mDuplicatedOutput, dupOutputDesc);
                        applyStreamVolumes(mDuplicatedOutput, device);
                    } else {
                        LOGW("getOutput() could not open duplicated output for %d and %d",
                                mHardwareOutput, mA2dpOutput);
                        mAvailableOutputDevices &= ~device;
                        delete outputDesc;
                        return NO_INIT;
                    }
                } else {
                    LOGW("setDeviceConnectionState() could not open A2DP output for device %x", device);
                    mAvailableOutputDevices &= ~device;
                    delete outputDesc;
                    return NO_INIT;
                }
                AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
                // move streams pertaining to STRATEGY_MEDIA to the newly opened A2DP output
                if (getDeviceForStrategy(STRATEGY_MEDIA) & device) {
                    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                        if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_MEDIA) {
                            mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mA2dpOutput);
                            outputDesc->mRefCount[i] = hwOutputDesc->mRefCount[i];
                            hwOutputDesc->mRefCount[i] = 0;
                        }
                    }

                }
                // move streams pertaining to STRATEGY_DTMF to the newly opened A2DP output
                if (getDeviceForStrategy(STRATEGY_DTMF) & device) {
                    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                        if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_DTMF) {
                            mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mA2dpOutput);
                            outputDesc->mRefCount[i] = hwOutputDesc->mRefCount[i];
                            hwOutputDesc->mRefCount[i] = 0;
                        }
                    }

                }
                // move streams pertaining to STRATEGY_SONIFICATION to the newly opened duplicated output
                if (getDeviceForStrategy(STRATEGY_SONIFICATION) & device) {
                    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                        if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_SONIFICATION) {
                            mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mDuplicatedOutput);
                            outputDesc->mRefCount[i] =
                                hwOutputDesc->mRefCount[i];
                            mOutputs.valueFor(mDuplicatedOutput)->mRefCount[i] =
                                hwOutputDesc->mRefCount[i];
                        }
                    }
                }
            } else
#endif
            // handle wired and SCO device connection (accessed via hardware output)
            {

                uint32_t newDevice = 0;
                if (AudioSystem::isBluetoothScoDevice(device)) {
                    LOGV("setDeviceConnectionState() BT SCO  device, address %s", device_address);
                    // keep track of SCO device address
                    mScoDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN);
                    // if in call and connecting SCO device, check if we must reroute hardware output
                    if (mPhoneState == AudioSystem::MODE_IN_CALL &&
                        getDeviceForStrategy(STRATEGY_PHONE) == device) {
                        newDevice = device;
                    } else if (mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_DTMF) &&
                               getDeviceForStrategy(STRATEGY_DTMF) == device) {
                        newDevice = device;
                    }
                } else if (device == AudioSystem::DEVICE_OUT_WIRED_HEADSET ||
                           device == AudioSystem::DEVICE_OUT_WIRED_HEADPHONE) {
                    LOGV("setDeviceConnectionState() wired headset device");
                    // if connecting a wired headset, we check the following by order of priority
                    // to request a routing change if necessary:
                    // 1: we are in call or the strategy phone is active on the hardware output:
                    //      use device for strategy phone
                    // 2: the strategy sonification is active on the hardware output:
                    //      use device for strategy sonification
                    // 3: the strategy media is active on the hardware output:
                    //      use device for strategy media
                    // 4: the strategy DTMF is active on the hardware output:
                    //      use device for strategy DTMF
                    if (getDeviceForStrategy(STRATEGY_PHONE) == device &&
                        (mPhoneState == AudioSystem::MODE_IN_CALL ||
                        mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_PHONE))) {
                        newDevice = device;
                    } else if ((getDeviceForStrategy(STRATEGY_SONIFICATION) & device) &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_SONIFICATION)){
                        newDevice = getDeviceForStrategy(STRATEGY_SONIFICATION);
                    } else if ((getDeviceForStrategy(STRATEGY_MEDIA) == device) &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_MEDIA)){
                        newDevice = device;
                    } else if (getDeviceForStrategy(STRATEGY_DTMF) == device &&
                            mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_DTMF)) {
                         newDevice = device;
                    }
                } else if (device == AudioSystem::DEVICE_OUT_TTY) {
                    LOGV("setDeviceConnectionState() tty device");
                    // if connecting a wired headset, we check the following by order of priority
                    // to request a routing change if necessary:
                    // 1: we are in call or the strategy phone is active on the hardware output:
                    //      use device for strategy phone
                    if (getDeviceForStrategy(STRATEGY_PHONE) == device &&
                        (mPhoneState == AudioSystem::MODE_IN_CALL ||
                        mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_PHONE))) {
                        newDevice = device;
                    }
                } else if (device == AudioSystem::DEVICE_OUT_FM_SPEAKER ||
                           device == AudioSystem::DEVICE_OUT_FM_HEADPHONE) {
                    LOGV("setDeviceConnectionState() no mic headphone device");
                    // if connecting a wired headset, we check the following by order of priority
                    // to request a routing change if necessary:
                    // 1: the strategy sonification is active on the hardware output:
                    //      use device for strategy sonification
                    // 2: the strategy media is active on the hardware output:
                    //      use device for strategy media
                    if ((getDeviceForStrategy(STRATEGY_SONIFICATION) & device) &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_SONIFICATION)){
                        newDevice = getDeviceForStrategy(STRATEGY_SONIFICATION);
                    } else if ((getDeviceForStrategy(STRATEGY_MEDIA) == device) &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_MEDIA)){
                        newDevice = device;
                    }
                }

                // request routing change if necessary
                setOutputDevice(mHardwareOutput, newDevice);
            }
            break;
        // handle output device disconnection
        case AudioSystem::DEVICE_STATE_UNAVAILABLE: {
            if (!(mAvailableOutputDevices & device)) {
                LOGW("setDeviceConnectionState() device not connected: %x", device);
                return INVALID_OPERATION;
            }

            uint32_t newDevice = 0;
            // get usage of disconnected device by all strategies
            bool wasUsedForMedia = (getDeviceForStrategy(STRATEGY_MEDIA) & device) != 0;
            bool wasUsedForSonification = (getDeviceForStrategy(STRATEGY_SONIFICATION) & device) != 0;
            bool wasUsedforPhone = (getDeviceForStrategy(STRATEGY_PHONE) & device) != 0;
            bool wasUsedforDtmf = (getDeviceForStrategy(STRATEGY_DTMF) & device) != 0;
            LOGV("setDeviceConnectionState() disconnecting device %x used by media %d, sonification %d, phone %d",
                    device, wasUsedForMedia, wasUsedForSonification, wasUsedforPhone);
            // remove device from available output devices
            mAvailableOutputDevices &= ~device;

#ifdef WITH_A2DP
            // handle A2DP device disconnection
            if (AudioSystem::isA2dpDevice(device)) {
                if (mA2dpOutput == 0 || mDuplicatedOutput == 0) {
                    LOGW("setDeviceConnectionState() disconnecting A2DP and no A2DP output!");
                    mAvailableOutputDevices |= device;
                    return INVALID_OPERATION;
                }

                if (mA2dpDeviceAddress != device_address) {
                    LOGW("setDeviceConnectionState() disconnecting unknow A2DP sink address %s", device_address);
                    mAvailableOutputDevices |= device;
                    return INVALID_OPERATION;
                }

                AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
                AudioOutputDescriptor *a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput);

                // mute media during 2 seconds to avoid outputing sound on hardware output while music stream
                // is switched from A2DP output and before music is paused by music application
                setStrategyMute(STRATEGY_MEDIA, true, mHardwareOutput);
                setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, 2000);

                // If the A2DP device was used by DTMF strategy, move all streams pertaining to DTMF strategy to
                // hardware output
                if (wasUsedforDtmf) {
                    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                        if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_DTMF) {
                            mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput);
                            hwOutputDesc->changeRefCount((AudioSystem::stream_type)i,
                                    a2dpOutputDesc->mRefCount[i]);
                        }
                    }
                    if (a2dpOutputDesc->isUsedByStrategy(STRATEGY_DTMF)) {
                        newDevice = getDeviceForStrategy(STRATEGY_DTMF);
                    }
                }

                // If the A2DP device was used by media strategy, move all streams pertaining to media strategy to
                // hardware output
                if (wasUsedForMedia) {
                    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                        if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_MEDIA) {
                            mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput);
                            hwOutputDesc->changeRefCount((AudioSystem::stream_type)i,
                                    a2dpOutputDesc->mRefCount[i]);
                        }
                    }
                    if (a2dpOutputDesc->isUsedByStrategy(STRATEGY_MEDIA)) {
                        newDevice = getDeviceForStrategy(STRATEGY_MEDIA);
                    }
                }

                // If the A2DP device was used by sonification strategy, move all streams pertaining to
                // sonification strategy to hardware output.
                // Note that newDevice is overwritten here giving sonification strategy a higher priority than
                // media strategy.
                if (wasUsedForSonification) {
                    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                        if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_SONIFICATION) {
                            mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput);
                        }
                    }
                    if (a2dpOutputDesc->isUsedByStrategy(STRATEGY_SONIFICATION)) {
                        newDevice = getDeviceForStrategy(STRATEGY_SONIFICATION);
                    }
                }

                // close A2DP and duplicated outputs
                AudioParameter param;
                param.add(String8("closing"), String8("true"));
                mpClientInterface->setParameters(mA2dpOutput, param.toString());

                LOGW("setDeviceConnectionState() closing A2DP and duplicated output!");
                mpClientInterface->closeOutput(mDuplicatedOutput);
                delete mOutputs.valueFor(mDuplicatedOutput);
                mOutputs.removeItem(mDuplicatedOutput);
                mDuplicatedOutput = 0;
                mpClientInterface->closeOutput(mA2dpOutput);
                delete mOutputs.valueFor(mA2dpOutput);
                mOutputs.removeItem(mA2dpOutput);
                mA2dpOutput = 0;
            } else
#endif
            {
                if (AudioSystem::isBluetoothScoDevice(device)) {
                    // handle SCO device disconnection
                    if (wasUsedforPhone &&
                        mPhoneState == AudioSystem::MODE_IN_CALL) {
                        // if in call, find new suitable device for phone strategy
                        newDevice = getDeviceForStrategy(STRATEGY_PHONE);
                    } else if (wasUsedforDtmf &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_DTMF)) {
                        newDevice = getDeviceForStrategy(STRATEGY_DTMF);
                    }
                } else if (device == AudioSystem::DEVICE_OUT_WIRED_HEADSET ||
                           device == AudioSystem::DEVICE_OUT_WIRED_HEADPHONE) {
                    // if disconnecting a wired headset, we check the following by order of priority
                    // to request a routing change if necessary:
                    // 1: we are in call or the strategy phone is active on the hardware output:
                    //      use device for strategy phone
                    // 2: the strategy sonification is active on the hardware output:
                    //      use device for strategy sonification
                    // 3: the strategy media is active on the hardware output:
                    //      use device for strategy media
                    // 4: the strategy DTMF is active on the hardware output:
                    //      use device for strategy DTMF
                    if (wasUsedforPhone &&
                        (mPhoneState == AudioSystem::MODE_IN_CALL ||
                         mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_PHONE))) {
                        newDevice = getDeviceForStrategy(STRATEGY_PHONE);
                    } else if (wasUsedForSonification &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_SONIFICATION)){
                        newDevice = getDeviceForStrategy(STRATEGY_SONIFICATION);
                    } else if (wasUsedForMedia &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_MEDIA)){
                        newDevice = getDeviceForStrategy(STRATEGY_MEDIA);
                    } else if (wasUsedforDtmf &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_DTMF)){
                        newDevice = getDeviceForStrategy(STRATEGY_DTMF);
                    }
                } else if (device == AudioSystem::DEVICE_OUT_TTY) {
                    LOGV("setDeviceConnectionState() tty device");
                    if (wasUsedforPhone &&
                        (mPhoneState == AudioSystem::MODE_IN_CALL ||
                         mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_PHONE))) {
                        newDevice = getDeviceForStrategy(STRATEGY_PHONE);
                    }
                } else if (device == AudioSystem::DEVICE_OUT_FM_SPEAKER ||
                           device == AudioSystem::DEVICE_OUT_FM_HEADPHONE) {
                    LOGV("setDeviceConnectionState() no mic headphone device");
                    if (wasUsedForSonification &&
                        mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_SONIFICATION)){
                        newDevice = getDeviceForStrategy(STRATEGY_SONIFICATION);
                    } else if (wasUsedForMedia &&
                               mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_MEDIA)){
                        newDevice = getDeviceForStrategy(STRATEGY_MEDIA);
                    }
                }
            }
            // request routing change if necessary
            setOutputDevice(mHardwareOutput, newDevice);

            // clear A2DP and SCO device address if necessary
#ifdef WITH_A2DP
            if (AudioSystem::isA2dpDevice(device)) {
                mA2dpDeviceAddress = "";
            }
#endif
            if (AudioSystem::isBluetoothScoDevice(device)) {
                mScoDeviceAddress = "";
            }
            } break;

        default:
            LOGE("setDeviceConnectionState() invalid state: %x", state);
            return BAD_VALUE;
        }

        if (device == AudioSystem::DEVICE_OUT_WIRED_HEADSET) {
            device = AudioSystem::DEVICE_IN_WIRED_HEADSET;
        } else if (device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO ||
                   device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET ||
                   device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT) {
            device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET;
        } else {
            return NO_ERROR;
        }
    }
    // handle input devices
    if (AudioSystem::isInputDevice(device)) {

        switch (state)
        {
        // handle input device connection
        case AudioSystem::DEVICE_STATE_AVAILABLE: {
            if (mAvailableInputDevices & device) {
                LOGW("setDeviceConnectionState() device already connected: %d", device);
                return INVALID_OPERATION;
            }
            mAvailableInputDevices |= device;
            }
            break;

        // handle input device disconnection
        case AudioSystem::DEVICE_STATE_UNAVAILABLE: {
            if (!(mAvailableInputDevices & device)) {
                LOGW("setDeviceConnectionState() device not connected: %d", device);
                return INVALID_OPERATION;
            }
            mAvailableInputDevices &= ~device;
            } break;

        default:
            LOGE("setDeviceConnectionState() invalid state: %x", state);
            return BAD_VALUE;
        }
        return NO_ERROR;
    }

    LOGW("setDeviceConnectionState() invalid device: %x", device);
    return BAD_VALUE;
}

AudioSystem::device_connection_state AudioPolicyManager::getDeviceConnectionState(AudioSystem::audio_devices device,
                                                  const char *device_address)
{
    AudioSystem::device_connection_state state = AudioSystem::DEVICE_STATE_UNAVAILABLE;
    String8 address = String8(device_address);
    if (AudioSystem::isOutputDevice(device)) {
        if (device & mAvailableOutputDevices) {
#ifdef WITH_A2DP
            if (AudioSystem::isA2dpDevice(device) &&
                address != "" && mA2dpDeviceAddress != address) {
                return state;
            }
#endif
            if (AudioSystem::isBluetoothScoDevice(device) &&
                address != "" && mScoDeviceAddress != address) {
                return state;
            }
            state = AudioSystem::DEVICE_STATE_AVAILABLE;
        }
    } else if (AudioSystem::isInputDevice(device)) {
        if (device & mAvailableInputDevices) {
            state = AudioSystem::DEVICE_STATE_AVAILABLE;
        }
    }

    return state;
}

void AudioPolicyManager::setPhoneState(int state)
{
    LOGV("setPhoneState() state %d", state);
    uint32_t newDevice = 0;
    if (state < 0 || state >= AudioSystem::NUM_MODES) {
        LOGW("setPhoneState() invalid state %d", state);
        return;
    }

    if (state == mPhoneState ) {
        LOGW("setPhoneState() setting same state %d", state);
        return;
    }

    // if leaving call state, handle special case of active streams
    // pertaining to sonification strategy see handleIncallSonification()
    if (mPhoneState == AudioSystem::MODE_IN_CALL) {
        LOGV("setPhoneState() in call state management: new state is %d", state);
        for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
            handleIncallSonification(stream, false, true);
        }
    }

    // store previous phone state for management of sonification strategy below
    int oldState = mPhoneState;
    uint32_t oldDtmfDevice = getDeviceForStrategy(STRATEGY_DTMF);
    uint32_t oldSonificationDevice = getDeviceForStrategy(STRATEGY_SONIFICATION) & ~AudioSystem::DEVICE_OUT_SPEAKER;
    mPhoneState = state;
    bool force = false;
    // check if a routing change is required for hardware output in the following
    // order of priority:
    // 1: a stream pertaining to sonification strategy is active
    // 2: new state is incall
    // 3: a stream pertaining to media strategy is active
    // 4: a stream pertaining to DTMF strategy is active
    if (mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_SONIFICATION)) {
        newDevice = getDeviceForStrategy(STRATEGY_SONIFICATION);
    } else if (mPhoneState == AudioSystem::MODE_IN_CALL) {
        newDevice = getDeviceForStrategy(STRATEGY_PHONE);
        // force routing command to audio hardware when starting call
        // even if no device change is needed
        force = true;
    } else if (mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_MEDIA)) {
        newDevice = getDeviceForStrategy(STRATEGY_MEDIA);
    } else if (mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_DTMF)) {
        newDevice = getDeviceForStrategy(STRATEGY_DTMF);
    }


    if (mA2dpOutput != 0) {
        // If entering or exiting in call state, switch DTMF streams to/from A2DP output
        // if necessary
        uint32_t newDtmfDevice = getDeviceForStrategy(STRATEGY_DTMF);
        uint32_t newSonificationDevice = getDeviceForStrategy(STRATEGY_SONIFICATION) & ~AudioSystem::DEVICE_OUT_SPEAKER;
        if (state == AudioSystem::MODE_IN_CALL) {  // entering in call mode
            // move DTMF streams from A2DP output to hardware output if necessary
            if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)oldDtmfDevice) &&
                !AudioSystem::isA2dpDevice((AudioSystem::audio_devices)newDtmfDevice)) {
                for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                    if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_DTMF) {
                        mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput);
                        int refCount = mOutputs.valueFor(mA2dpOutput)->mRefCount[i];
                        mOutputs.valueFor(mHardwareOutput)->changeRefCount((AudioSystem::stream_type)i,
                                refCount);
                        mOutputs.valueFor(mA2dpOutput)->changeRefCount((AudioSystem::stream_type)i,-refCount);
                    }
                }
                if (newDevice == 0 && mOutputs.valueFor(mA2dpOutput)->isUsedByStrategy(STRATEGY_DTMF)) {
                    newDevice = newDtmfDevice;
                }
            }
            // move SONIFICATION streams from duplicated output to hardware output if necessary
            if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)oldSonificationDevice) &&
                !AudioSystem::isA2dpDevice((AudioSystem::audio_devices)newSonificationDevice)) {
                for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                    if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_SONIFICATION) {
                        mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput);
                        int refCount = mOutputs.valueFor(mDuplicatedOutput)->mRefCount[i];
                        mOutputs.valueFor(mHardwareOutput)->changeRefCount((AudioSystem::stream_type)i,
                                refCount);
                        mOutputs.valueFor(mDuplicatedOutput)->changeRefCount((AudioSystem::stream_type)i,-refCount);
                    }
                }
            }
        } else {  // exiting in call mode
            // move DTMF streams from hardware output to A2DP output if necessary
            if (!AudioSystem::isA2dpDevice((AudioSystem::audio_devices)oldDtmfDevice) &&
                AudioSystem::isA2dpDevice((AudioSystem::audio_devices)newDtmfDevice)) {
                for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                    if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_DTMF) {
                        mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mA2dpOutput);
                        int refCount = mOutputs.valueFor(mHardwareOutput)->mRefCount[i];
                        mOutputs.valueFor(mA2dpOutput)->changeRefCount((AudioSystem::stream_type)i, refCount);
                        mOutputs.valueFor(mHardwareOutput)->changeRefCount((AudioSystem::stream_type)i, -refCount);
                    }
                }
            }
            // move SONIFICATION streams from hardware output to A2DP output if necessary
            if (!AudioSystem::isA2dpDevice((AudioSystem::audio_devices)oldSonificationDevice) &&
                AudioSystem::isA2dpDevice((AudioSystem::audio_devices)newSonificationDevice)) {
                for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
                    if (getStrategy((AudioSystem::stream_type)i) == STRATEGY_SONIFICATION) {
                        mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mDuplicatedOutput);
                        int refCount = mOutputs.valueFor(mHardwareOutput)->mRefCount[i];
                        mOutputs.valueFor(mDuplicatedOutput)->changeRefCount((AudioSystem::stream_type)i, refCount);
                        mOutputs.valueFor(mHardwareOutput)->changeRefCount((AudioSystem::stream_type)i, -refCount);
                    }
                }
            }
        }
        // suspend A2DP output if SCO device address is the same as A2DP device address.
        // no need to check that a SCO device is actually connected as mScoDeviceAddress == ""
        // if none is connected and the test below will fail.
        if (mA2dpDeviceAddress == mScoDeviceAddress) {
            if (state == AudioSystem::MODE_RINGTONE) {
                mpClientInterface->suspendOutput(mA2dpOutput);
            } else if (oldState == AudioSystem::MODE_RINGTONE) {
                mpClientInterface->restoreOutput(mA2dpOutput);
            }
        }
    }

    // force routing command to audio hardware when ending call
    // even if no device change is needed
    if (oldState == AudioSystem::MODE_IN_CALL) {
        if (newDevice == 0) {
            newDevice = mOutputs.valueFor(mHardwareOutput)->device();
        }
        force = true;
    }
    // change routing is necessary
    setOutputDevice(mHardwareOutput, newDevice, force);

    // if entering in call state, handle special case of active streams
    // pertaining to sonification strategy see handleIncallSonification()
    if (state == AudioSystem::MODE_IN_CALL) {
        LOGV("setPhoneState() in call state management: new state is %d", state);
        for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
            handleIncallSonification(stream, true, true);
        }
    }
}

void AudioPolicyManager::setRingerMode(uint32_t mode, uint32_t mask)
{
    LOGV("setRingerMode() mode %x, mask %x", mode, mask);

    mRingerMode = mode;
}

void AudioPolicyManager::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config)
{
    LOGV("setForceUse() usage %d, config %d, mPhoneState %d", usage, config, mPhoneState);

    switch(usage) {
    case AudioSystem::FOR_COMMUNICATION:
        if (config != AudioSystem::FORCE_SPEAKER && config != AudioSystem::FORCE_BT_SCO &&
            config != AudioSystem::FORCE_NONE) {
            LOGW("setForceUse() invalid config %d for FOR_COMMUNICATION", config);
            return;
        }
        mForceUse[usage] = config;
        // update hardware output routing immediately if in call, or if there is an active
        // VOICE_CALL stream, as would be the case with an application that uses this stream
        // for it to behave like in a telephony app (e.g. voicemail app that plays audio files
        // streamed or downloaded to the device)
        if ((mPhoneState == AudioSystem::MODE_IN_CALL) ||
                (mOutputs.valueFor(mHardwareOutput)->isUsedByStream(AudioSystem::VOICE_CALL))) {
            uint32_t device = getDeviceForStrategy(STRATEGY_PHONE);
            setOutputDevice(mHardwareOutput, device);
        }
        break;
    case AudioSystem::FOR_MEDIA:
        if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP &&
            config != AudioSystem::FORCE_WIRED_ACCESSORY && config != AudioSystem::FORCE_NONE) {
            LOGW("setForceUse() invalid config %d for FOR_MEDIA", config);
            return;
        }
        mForceUse[usage] = config;
        break;
    case AudioSystem::FOR_RECORD:
        if (config != AudioSystem::FORCE_BT_SCO && config != AudioSystem::FORCE_WIRED_ACCESSORY &&
            config != AudioSystem::FORCE_NONE) {
            LOGW("setForceUse() invalid config %d for FOR_RECORD", config);
            return;
        }
        mForceUse[usage] = config;
        break;
    default:
        LOGW("setForceUse() invalid usage %d", usage);
        break;
    }
}

AudioSystem::forced_config AudioPolicyManager::getForceUse(AudioSystem::force_use usage)
{
    return mForceUse[usage];
}

void AudioPolicyManager::setSystemProperty(const char* property, const char* value)
{
    LOGV("setSystemProperty() property %s, value %s", property, value);
    if (strcmp(property, "ro.camera.sound.forced") == 0) {
        if (atoi(value)) {
            LOGV("ENFORCED_AUDIBLE cannot be muted");
            mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = false;
        } else {
            LOGV("ENFORCED_AUDIBLE can be muted");
            mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = true;
        }
    }
}

audio_io_handle_t AudioPolicyManager::getOutput(AudioSystem::stream_type stream,
                                    uint32_t samplingRate,
                                    uint32_t format,
                                    uint32_t channels,
                                    AudioSystem::output_flags flags)
{
    audio_io_handle_t output = 0;
    uint32_t latency = 0;
    routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);
    uint32_t device = getDeviceForStrategy(strategy);
    LOGV("getOutput() stream %d, samplingRate %d, format %d, channels %x, flags %x", stream, samplingRate, format, channels, flags);


    // open a direct output if:
    // 1 a direct output is explicitely requested
    // 2 the audio format is compressed
    if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) ||
         (format !=0 && !AudioSystem::isLinearPCM(format))) {

        LOGV("getOutput() opening direct output device %x", device);
        AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
        outputDesc->mDevice = device;
        outputDesc->mSamplingRate = samplingRate;
        outputDesc->mFormat = format;
        outputDesc->mChannels = channels;
        outputDesc->mLatency = 0;
        outputDesc->mFlags = (AudioSystem::output_flags)(flags | AudioSystem::OUTPUT_FLAG_DIRECT);
        outputDesc->mRefCount[stream] = 1;
        output = mpClientInterface->openOutput(&outputDesc->mDevice,
                                        &outputDesc->mSamplingRate,
                                        &outputDesc->mFormat,
                                        &outputDesc->mChannels,
                                        &outputDesc->mLatency,
                                        outputDesc->mFlags);

        // only accept an output with the requeted parameters
        if ((samplingRate != 0 && samplingRate != outputDesc->mSamplingRate) ||
            (format != 0 && format != outputDesc->mFormat) ||
            (channels != 0 && channels != outputDesc->mChannels)) {
            LOGV("getOutput() failed opening direct output: samplingRate %d, format %d, channels %d",
                    samplingRate, format, channels);
            mpClientInterface->closeOutput(output);
            delete outputDesc;
            return NULL;
        }
        mOutputs.add(output, outputDesc);
        return output;
    }

    if (channels != 0 && channels != AudioSystem::CHANNEL_OUT_MONO &&
        channels != AudioSystem::CHANNEL_OUT_STEREO) {
        return NULL;
    }
    // open a non direct output

    // get which output is suitable for the specified stream. The actual routing change will happen
    // when startOutput() will be called
    uint32_t device2 = device & ~AudioSystem::DEVICE_OUT_SPEAKER;
    if (AudioSystem::popCount((AudioSystem::audio_devices)device) == 2) {
#ifdef WITH_A2DP
        if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)device2)) {
            // if playing on 2 devices among which one is A2DP, use duplicated output
            LOGV("getOutput() using duplicated output");
            LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device in multiple %x selected but A2DP output not opened", device);
            output = mDuplicatedOutput;
        } else
#endif
        {
            // if playing on 2 devices among which none is A2DP, use hardware output
            output = mHardwareOutput;
        }
        LOGV("getOutput() using output %d for 2 devices %x", output, device);
    } else {
#ifdef WITH_A2DP
        if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)device2)) {
            // if playing on A2DP device, use a2dp output
            LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device %x selected but A2DP output not opened", device);
            output = mA2dpOutput;
        } else
#endif
        {
            // if playing on not A2DP device, use hardware output
            output = mHardwareOutput;
        }
    }


    LOGW_IF((output ==0), "getOutput() could not find output for stream %d, samplingRate %d, format %d, channels %x, flags %x",
                stream, samplingRate, format, channels, flags);

    return output;
}

status_t AudioPolicyManager::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
{
    LOGV("startOutput() output %d, stream %d", output, stream);
    ssize_t index = mOutputs.indexOfKey(output);
    if (index < 0) {
        LOGW("startOutput() unknow output %d", output);
        return BAD_VALUE;
    }

    AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
    routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);
    uint32_t device = getDeviceForStrategy(strategy);

    if (!outputDesc->isUsedByStrategy(strategy)) {
        // if the stream started is the first active stream in its strategy, check if routing change
        // must be done on hardware output
        uint32_t newDevice = 0;
        if (AudioSystem::popCount((AudioSystem::audio_devices)device) == 2) {
#ifdef WITH_A2DP
            uint32_t device2 = device & ~AudioSystem::DEVICE_OUT_SPEAKER;
            if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)device2)) {
                // if one device is A2DP, selected the second device for hardware output
                device &= ~device2;
            } else
#endif
            {
                // we only support speaker + headset and speaker + headphone combinations on hardware output.
                // other combinations will leave device = 0 and no routing will happen.
                if (device != (AudioSystem::DEVICE_OUT_SPEAKER | AudioSystem::DEVICE_OUT_WIRED_HEADSET) &&
                    device != (AudioSystem::DEVICE_OUT_SPEAKER | AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) {
                    device = AudioSystem::DEVICE_OUT_SPEAKER;
                }
            }
        }

        // By order of priority
        // 1 apply routing for phone strategy in any case
        // 2 apply routing for notification strategy if no stream pertaining to
        //   phone strategies is playing
        // 3 apply routing for media strategy is not incall and neither phone nor sonification
        //   strategies is active.
        // 4 apply routing for DTMF strategy if no stream pertaining to
        //   neither phone, sonification nor media strategy is playing
        if (strategy == STRATEGY_PHONE) {
            newDevice = device;
        } else if (!mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_PHONE)) {
            if (strategy == STRATEGY_SONIFICATION) {
                newDevice = device;
            } else if (!mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_SONIFICATION)) {
                if (strategy == STRATEGY_MEDIA) {
                    newDevice = device;
                } else if (!mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_MEDIA)) {
                    // strategy == STRATEGY_DTMF
                    newDevice = device;
                }
            }
        }

        // TODO: maybe mute stream is selected device was refused
        setOutputDevice(mHardwareOutput, newDevice);
    }

    // incremenent usage count for this stream on the requested output:
    // NOTE that the usage count is the same for duplicated output and hardware output which is
    // necassary for a correct control of hardware output routing by startOutput() and stopOutput()
    outputDesc->changeRefCount(stream, 1);

    // handle special case for sonification while in call
    if (mPhoneState == AudioSystem::MODE_IN_CALL) {
        handleIncallSonification(stream, true, false);
    }

    // apply volume rules for current stream and device if necessary
    checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, outputDesc->device());

    return NO_ERROR;
}

status_t AudioPolicyManager::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
{
    LOGV("stopOutput() output %d, stream %d", output, stream);
    ssize_t index = mOutputs.indexOfKey(output);
    if (index < 0) {
        LOGW("stopOutput() unknow output %d", output);
        return BAD_VALUE;
    }

    AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
    routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);

    // handle special case for sonification while in call
    if (mPhoneState == AudioSystem::MODE_IN_CALL) {
        handleIncallSonification(stream, false, false);
    }

    if (outputDesc->isUsedByStrategy(strategy)) {
        // decrement usage count of this stream on the output
        outputDesc->changeRefCount(stream, -1);
        if (!outputDesc->isUsedByStrategy(strategy)) {
            // if the stream is the last of its strategy to use this output, change routing
            // in the following order or priority:
            // PHONE > SONIFICATION > MEDIA > DTMF
            uint32_t newDevice = 0;
            if (outputDesc->isUsedByStrategy(STRATEGY_PHONE)) {
                newDevice = getDeviceForStrategy(STRATEGY_PHONE);
            } else if (outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION)) {
                newDevice = getDeviceForStrategy(STRATEGY_SONIFICATION);
            } else if (mPhoneState == AudioSystem::MODE_IN_CALL) {
                newDevice = getDeviceForStrategy(STRATEGY_PHONE);
            } else if (outputDesc->isUsedByStrategy(STRATEGY_MEDIA)) {
                newDevice = getDeviceForStrategy(STRATEGY_MEDIA);
            } else if (outputDesc->isUsedByStrategy(STRATEGY_DTMF)) {
                newDevice = getDeviceForStrategy(STRATEGY_DTMF);
            }

            // apply routing change if necessary.
            // insert a delay of 2 times the audio hardware latency to ensure PCM
            // buffers in audio flinger and audio hardware are emptied before the
            // routing change is executed.
            setOutputDevice(mHardwareOutput, newDevice, false, mOutputs.valueFor(mHardwareOutput)->mLatency*2);
        }
        // store time at which the last music track was stopped - see computeVolume()
        if (stream == AudioSystem::MUSIC) {
            mMusicStopTime = systemTime();
        }
        return NO_ERROR;
    } else {
        LOGW("stopOutput() refcount is already 0 for output %d", output);
        return INVALID_OPERATION;
    }
}

void AudioPolicyManager::releaseOutput(audio_io_handle_t output)
{
    LOGV("releaseOutput() %d", output);
    ssize_t index = mOutputs.indexOfKey(output);
    if (index < 0) {
        LOGW("releaseOutput() releasing unknown output %d", output);
        return;
    }
    if (mOutputs.valueAt(index)->mFlags & AudioSystem::OUTPUT_FLAG_DIRECT) {
        mpClientInterface->closeOutput(output);
        delete mOutputs.valueAt(index);
        mOutputs.removeItem(output);
    }
}

audio_io_handle_t AudioPolicyManager::getInput(int inputSource,
                                    uint32_t samplingRate,
                                    uint32_t format,
                                    uint32_t channels,
                                    AudioSystem::audio_in_acoustics acoustics)
{
    audio_io_handle_t input = 0;
    uint32_t device;

    LOGV("getInput() inputSource %d, samplingRate %d, format %d, channels %x, acoustics %x", inputSource, samplingRate, format, channels, acoustics);

    AudioInputDescriptor *inputDesc = new AudioInputDescriptor();
    // convert input source to input device
    switch(inputSource) {
    case AUDIO_SOURCE_DEFAULT:
    case AUDIO_SOURCE_MIC:
        if (mForceUse[AudioSystem::FOR_RECORD] == AudioSystem::FORCE_BT_SCO &&
            mAvailableInputDevices & AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET) {
            device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET;
        } else if (mAvailableInputDevices & AudioSystem::DEVICE_IN_WIRED_HEADSET) {
            device = AudioSystem::DEVICE_IN_WIRED_HEADSET;
        } else {
            device = AudioSystem::DEVICE_IN_BUILTIN_MIC;
        }
        break;
    case AUDIO_SOURCE_VOICE_UPLINK:
        device = AudioSystem::DEVICE_IN_VOICE_CALL;
        channels = AudioSystem::CHANNEL_IN_VOICE_UPLINK;
        break;
    case AUDIO_SOURCE_VOICE_DOWNLINK:
        device = AudioSystem::DEVICE_IN_VOICE_CALL;
        channels = AudioSystem::CHANNEL_IN_VOICE_DNLINK;
        break;
    case AUDIO_SOURCE_VOICE_CALL:
        device = AudioSystem::DEVICE_IN_VOICE_CALL;
        channels = (AudioSystem::CHANNEL_IN_VOICE_UPLINK | AudioSystem::CHANNEL_IN_VOICE_DNLINK);
        break;
    default:
        LOGW("getInput() invalid input source %d", inputSource);
        return NULL;
    }
    inputDesc->mDevice = device;
    inputDesc->mSamplingRate = samplingRate;
    inputDesc->mFormat = format;
    inputDesc->mChannels = channels;
    inputDesc->mAcoustics = acoustics;
    inputDesc->mRefCount = 0;
    input = mpClientInterface->openInput(&inputDesc->mDevice,
                                    &inputDesc->mSamplingRate,
                                    &inputDesc->mFormat,
                                    &inputDesc->mChannels,
                                    inputDesc->mAcoustics);

    // only accept input with the exact requested set of parameters
    if ((samplingRate != inputDesc->mSamplingRate) ||
        (format != inputDesc->mFormat) ||
        (channels != inputDesc->mChannels)) {
        LOGV("getOutput() failed opening input: samplingRate %d, format %d, channels %d",
                samplingRate, format, channels);
        mpClientInterface->closeInput(input);
        delete inputDesc;
        return NULL;
    }
    mInputs.add(input, inputDesc);
    return input;
}

status_t AudioPolicyManager::startInput(audio_io_handle_t input)
{
    LOGV("startInput() input %d", input);
    ssize_t index = mInputs.indexOfKey(input);
    if (index < 0) {
        LOGW("startInput() unknow input %d", input);
        return BAD_VALUE;
    }
    AudioInputDescriptor *inputDesc = mInputs.valueAt(index);

    // refuse 2 active AudioRecord clients at the same time
    for (size_t i = 0; i < mInputs.size(); i++) {
        if (mInputs.valueAt(i)->mRefCount > 0) {
            LOGW("startInput() input %d, other input %d already started", input, mInputs.keyAt(i));
            return INVALID_OPERATION;
        }
    }
    AudioParameter param = AudioParameter();
    param.addInt(String8(AudioParameter::keyRouting), (int)inputDesc->mDevice);
    mpClientInterface->setParameters(input, param.toString());

    inputDesc->mRefCount = 1;
    return NO_ERROR;
}

status_t AudioPolicyManager::stopInput(audio_io_handle_t input)
{
    LOGV("stopInput() input %d", input);
    ssize_t index = mInputs.indexOfKey(input);
    if (index < 0) {
        LOGW("stopInput() unknow input %d", input);
        return BAD_VALUE;
    }
    AudioInputDescriptor *inputDesc = mInputs.valueAt(index);

    if (inputDesc->mRefCount == 0) {
        LOGW("stopInput() input %d already stopped", input);
        return INVALID_OPERATION;
    } else {
        AudioParameter param = AudioParameter();
        param.addInt(String8(AudioParameter::keyRouting), 0);
        mpClientInterface->setParameters(input, param.toString());
        inputDesc->mRefCount = 0;
        return NO_ERROR;
    }
}

void AudioPolicyManager::releaseInput(audio_io_handle_t input)
{
    LOGV("releaseInput() %d", input);
    ssize_t index = mInputs.indexOfKey(input);
    if (index < 0) {
        LOGW("releaseInput() releasing unknown input %d", input);
        return;
    }
    mpClientInterface->closeInput(input);
    delete mInputs.valueAt(index);
    mInputs.removeItem(input);
    LOGV("releaseInput() exit");
}



void AudioPolicyManager::initStreamVolume(AudioSystem::stream_type stream,
                                            int indexMin,
                                            int indexMax)
{
    LOGV("initStreamVolume() stream %d, min %d, max %d", stream , indexMin, indexMax);
    mStreams[stream].mIndexMin = indexMin;
    mStreams[stream].mIndexMax = indexMax;
}

status_t AudioPolicyManager::setStreamVolumeIndex(AudioSystem::stream_type stream, int index)
{

    if ((index < mStreams[stream].mIndexMin) || (index > mStreams[stream].mIndexMax)) {
        return BAD_VALUE;
    }

    LOGV("setStreamVolumeIndex() stream %d, index %d", stream, index);
    mStreams[stream].mIndexCur = index;

    // compute and apply stream volume on all outputs according to connected device
    status_t status = NO_ERROR;
    for (size_t i = 0; i < mOutputs.size(); i++) {
        status_t volStatus = checkAndSetVolume(stream, index, mOutputs.keyAt(i), mOutputs.valueAt(i)->device());
        if (volStatus != NO_ERROR) {
            status = volStatus;
        }
    }
    return status;
}

status_t AudioPolicyManager::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index)
{
    if (index == 0) {
        return BAD_VALUE;
    }
    LOGV("getStreamVolumeIndex() stream %d", stream);
    *index =  mStreams[stream].mIndexCur;
    return NO_ERROR;
}

// ----------------------------------------------------------------------------
// AudioPolicyManager
// ----------------------------------------------------------------------------

// ---  class factory


extern "C" AudioPolicyInterface* createAudioPolicyManager(AudioPolicyClientInterface *clientInterface)
{
    return new AudioPolicyManager(clientInterface);
}

extern "C" void destroyAudioPolicyManager(AudioPolicyInterface *interface)
{
    delete interface;
}

AudioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface)
: mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0)
{
    mpClientInterface = clientInterface;

    for (int i = 0; i < AudioSystem::NUM_FORCE_USE; i++) {
        mForceUse[i] = AudioSystem::FORCE_NONE;
    }

    // devices available by default are speaker, ear piece and microphone
    mAvailableOutputDevices = AudioSystem::DEVICE_OUT_EARPIECE |
                        AudioSystem::DEVICE_OUT_SPEAKER;
    mAvailableInputDevices = AudioSystem::DEVICE_IN_BUILTIN_MIC;

    mA2dpDeviceAddress = String8("");
    mScoDeviceAddress = String8("");

    // open hardware output
    AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
    outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
    mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
                                    &outputDesc->mSamplingRate,
                                    &outputDesc->mFormat,
                                    &outputDesc->mChannels,
                                    &outputDesc->mLatency,
                                    outputDesc->mFlags);

    if (mHardwareOutput == 0) {
        LOGE("Failed to initialize hardware output stream, samplingRate: %d, format %d, channels %d",
                outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
    } else {
        mOutputs.add(mHardwareOutput, outputDesc);
        setOutputDevice(mHardwareOutput, (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER, true);
    }

    mA2dpOutput = 0;
    mDuplicatedOutput = 0;
}

AudioPolicyManager::~AudioPolicyManager()
{
   for (size_t i = 0; i < mOutputs.size(); i++) {
        mpClientInterface->closeOutput(mOutputs.keyAt(i));
        delete mOutputs.valueAt(i);
   }
   mOutputs.clear();
   for (size_t i = 0; i < mInputs.size(); i++) {
        mpClientInterface->closeInput(mInputs.keyAt(i));
        delete mInputs.valueAt(i);
   }
   mInputs.clear();
}

// ---

audio_io_handle_t AudioPolicyManager::getOutputForDevice(uint32_t device)
{
    audio_io_handle_t output = 0;
    uint32_t lDevice;

    for (size_t i = 0; i < mOutputs.size(); i++) {
        lDevice = mOutputs.valueAt(i)->device();
        LOGV("getOutputForDevice() output %d devices %x", mOutputs.keyAt(i), lDevice);

        // We are only considering outputs connected to a mixer here => exclude direct outputs
        if ((lDevice == device) &&
           !(mOutputs.valueAt(i)->mFlags & AudioSystem::OUTPUT_FLAG_DIRECT)) {
            output = mOutputs.keyAt(i);
            LOGV("getOutputForDevice() found output %d for device %x", output, device);
            break;
        }
    }
    return output;
}

AudioPolicyManager::routing_strategy AudioPolicyManager::getStrategy(AudioSystem::stream_type stream)
{
    // stream to strategy mapping
    switch (stream) {
    case AudioSystem::VOICE_CALL:
    case AudioSystem::BLUETOOTH_SCO:
        return STRATEGY_PHONE;
    case AudioSystem::RING:
    case AudioSystem::NOTIFICATION:
    case AudioSystem::ALARM:
    case AudioSystem::ENFORCED_AUDIBLE:
        return STRATEGY_SONIFICATION;
    case AudioSystem::DTMF:
        return STRATEGY_DTMF;
    default:
        LOGE("unknown stream type");
    case AudioSystem::SYSTEM:
        // NOTE: SYSTEM stream uses MEDIA strategy because muting music and switching outputs
        // while key clicks are played produces a poor result
    case AudioSystem::TTS:
    case AudioSystem::MUSIC:
        return STRATEGY_MEDIA;
    }
}

uint32_t AudioPolicyManager::getDeviceForStrategy(routing_strategy strategy)
{
    uint32_t device = 0;

    switch (strategy) {
    case STRATEGY_DTMF:
        if (mPhoneState != AudioSystem::MODE_IN_CALL) {
            // when off call, DTMF strategy follows the same rules as MEDIA strategy
            device = getDeviceForStrategy(STRATEGY_MEDIA);
            break;
        }
        // when in call, DTMF and PHONE strategies follow the same rules
        // FALL THROUGH

    case STRATEGY_PHONE:
        // for phone strategy, we first consider the forced use and then the available devices by order
        // of priority
        switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) {
        case AudioSystem::FORCE_BT_SCO:
            if (mPhoneState != AudioSystem::MODE_IN_CALL || strategy != STRATEGY_DTMF) {
                device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
                if (device) break;
            }
            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
            if (device) break;
            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO;
            if (device) break;
            // if SCO device is requested but no SCO device is available, fall back to default case
            // FALL THROUGH

        default:    // FORCE_NONE
            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_TTY;
            if (device) break;
            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
            if (device) break;
            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
            if (device) break;
            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_EARPIECE;
            if (device == 0) {
                LOGE("getDeviceForStrategy() earpiece device not found");
            }
            break;

        case AudioSystem::FORCE_SPEAKER:
            if (mPhoneState != AudioSystem::MODE_IN_CALL || strategy != STRATEGY_DTMF) {
                device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
                if (device) break;
            }
            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_FM_SPEAKER;
            if (device) break;
            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
            if (device == 0) {
                LOGE("getDeviceForStrategy() speaker device not found");
            }
            break;
        }
    break;

    case STRATEGY_SONIFICATION:

        // If incall, just select the STRATEGY_PHONE device: The rest of the behavior is handled by
        // handleIncallSonification().
        if (mPhoneState == AudioSystem::MODE_IN_CALL) {
            device = getDeviceForStrategy(STRATEGY_PHONE);
            break;
        }
        device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
        if (device == 0) {
            LOGE("getDeviceForStrategy() speaker device not found");
        }
        // The second device used for sonification is the same as the device used by media strategy
        // FALL THROUGH

    case STRATEGY_MEDIA: {
        uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL;
        if (device2 == 0) {
            device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP;
            if (device2 == 0) {
                device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
                if (device2 == 0) {
                    device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
                    if (device2 == 0) {
                        device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_FM_HEADPHONE;
                        if (device2 == 0) {
                            device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_FM_SPEAKER;
                            if (device2 == 0) {
                                device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
                                if (device2 == 0) {
                                    device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
                                    if (device2 == 0) {
                                        device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
                                        if (device == 0) {
                                            LOGE("getDeviceForStrategy() speaker device not found");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        // device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION, 0 otherwise
        device |= device2;
        // Do not play media stream if in call and the requested device would change the hardware
        // output routing
        if (mPhoneState == AudioSystem::MODE_IN_CALL &&
            !AudioSystem::isA2dpDevice((AudioSystem::audio_devices)device) &&
            device != getDeviceForStrategy(STRATEGY_PHONE)) {
            device = 0;
            LOGV("getDeviceForStrategy() incompatible media and phone devices");
        }
        } break;

    default:
        LOGW("getDeviceForStrategy() unknown strategy: %d", strategy);
        break;
    }

    LOGV("getDeviceForStrategy() strategy %d, device %x", strategy, device);
    return device;
}

void AudioPolicyManager::setOutputDevice(audio_io_handle_t output, uint32_t device, bool force, int delayMs)
{
    LOGV("setOutputDevice() output %d device %x delayMs %d", output, device, delayMs);
    if (mOutputs.indexOfKey(output) < 0) {
        LOGW("setOutputDevice() unknown output %d", output);
        return;
    }
#ifdef WITH_A2DP
    if (output == mHardwareOutput) {
        // clear A2DP devices from device bit field here so that the caller does not have to
        // do it in case of multiple device selections
        uint32_t device2 = device & ~AudioSystem::DEVICE_OUT_SPEAKER;
        if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)device2)) {
            LOGV("setOutputDevice() removing A2DP device");
            device &= ~device2;
        }
    } else if (output == mA2dpOutput) {
        // clear hardware devices from device bit field here so that the caller does not have to
        // do it in case of multiple device selections (the second device is always DEVICE_OUT_SPEAKER)
        // in this case
        device &= ~AudioSystem::DEVICE_OUT_SPEAKER;
    }
#endif

    // doing this check here allows the caller to call setOutputDevice() without conditions
    if (device == 0) return;

    uint32_t oldDevice = (uint32_t)mOutputs.valueFor(output)->device();
    // Do not change the routing if the requested device is the same as current device. Doing this check
    // here allows the caller to call setOutputDevice() without conditions
    if (device == oldDevice && !force) {
        LOGV("setOutputDevice() setting same device %x for output %d", device, output);
        return;
    }

    mOutputs.valueFor(output)->mDevice = device;
    // mute media streams if both speaker and headset are selected
    if (device == (AudioSystem::DEVICE_OUT_SPEAKER | AudioSystem::DEVICE_OUT_WIRED_HEADSET) ||
        device == (AudioSystem::DEVICE_OUT_SPEAKER | AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) {
        setStrategyMute(STRATEGY_MEDIA, true, output);
        // wait for the PCM output buffers to empty before proceeding with the rest of the command
        usleep(mOutputs.valueFor(output)->mLatency*2*1000);
    }
    // suspend A2D output if SCO device is selected
    if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)device)) {
         if (mA2dpOutput && mScoDeviceAddress == mA2dpDeviceAddress) {
             LOGV("suspend A2DP output");
             mpClientInterface->suspendOutput(mA2dpOutput);
         }
    }
    // do the routing
    AudioParameter param = AudioParameter();
    param.addInt(String8(AudioParameter::keyRouting), (int)device);
    mpClientInterface->setParameters(mHardwareOutput, param.toString(), delayMs);
    // update stream volumes according to new device
    applyStreamVolumes(output, device, delayMs);

    // if disconnecting SCO device, restore A2DP output
    if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)oldDevice)) {
         if (mA2dpOutput && mScoDeviceAddress == mA2dpDeviceAddress) {
             LOGV("restore A2DP output");
             mpClientInterface->restoreOutput(mA2dpOutput);
         }
    }
    // if changing from a combined headset + speaker route, unmute media streams
    if (oldDevice == (AudioSystem::DEVICE_OUT_SPEAKER | AudioSystem::DEVICE_OUT_WIRED_HEADSET) ||
        oldDevice == (AudioSystem::DEVICE_OUT_SPEAKER | AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) {
        setStrategyMute(STRATEGY_MEDIA, false, output, delayMs);
    }
}

float AudioPolicyManager::computeVolume(int stream, int index, audio_io_handle_t output, uint32_t device)
{
    float volume = 1.0;

    StreamDescriptor &streamDesc = mStreams[stream];

    // Force max volume if stream cannot be muted
    if (!streamDesc.mCanBeMuted) index = streamDesc.mIndexMax;

    int volInt = (100 * (index - streamDesc.mIndexMin)) / (streamDesc.mIndexMax - streamDesc.mIndexMin);
    volume = AudioSystem::linearToLog(volInt);

    // if a heaset is connected, apply the following rules to ring tones and notifications
    // to avoid sound level bursts in user's ears:
    // - always attenuate ring tones and notifications volume by 6dB
    // - if music is playing, always limit the volume to current music volume,
    // with a minimum threshold at -36dB so that notification is always perceived.
    if ((device &
        (AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP |
        AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
        AudioSystem::DEVICE_OUT_WIRED_HEADSET |
        AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) &&
        (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION)) {
        volume *= SONIFICATION_HEADSET_VOLUME_FACTOR;
        // when the phone is ringing we must consider that music could have been paused just before
        // by the music application and behave as if music was active if the last music track was
        // just stopped
        if (mOutputs.valueFor(output)->isUsedByStream(AudioSystem::MUSIC) ||
            ((mPhoneState == AudioSystem::MODE_RINGTONE) &&
             (systemTime() - mMusicStopTime < seconds(SONIFICATION_HEADSET_MUSIC_DELAY)))) {
            float musicVol = computeVolume(AudioSystem::MUSIC, mStreams[AudioSystem::MUSIC].mIndexCur, output, device);
            float minVol = (musicVol > SONIFICATION_HEADSET_VOLUME_MIN) ? musicVol : SONIFICATION_HEADSET_VOLUME_MIN;
            if (volume > minVol) {
                volume = minVol;
                LOGV("computeVolume limiting volume to %f musicVol %f", minVol, musicVol);
            }
        }
    }

    return volume;
}

status_t AudioPolicyManager::checkAndSetVolume(int stream, int index, audio_io_handle_t output, uint32_t device, int delayMs)
{

    // do not change actual stream volume if the stream is muted
    if (mStreams[stream].mMuteCount != 0) {
        LOGV("checkAndSetVolume() stream %d muted count %d", stream, mStreams[stream].mMuteCount);
        return NO_ERROR;
    }

    // do not change in call volume if bluetooth is connected and vice versa
    if ((stream == AudioSystem::VOICE_CALL && mForceUse[AudioSystem::FOR_COMMUNICATION] == AudioSystem::FORCE_BT_SCO) ||
        (stream == AudioSystem::BLUETOOTH_SCO && mForceUse[AudioSystem::FOR_COMMUNICATION] != AudioSystem::FORCE_BT_SCO)) {
        LOGV("checkAndSetVolume() cannot set stream %d volume with force use = %d for comm",
             stream, mForceUse[AudioSystem::FOR_COMMUNICATION]);
        return INVALID_OPERATION;
    }

    float volume = computeVolume(stream, index, output, device);
    // do not set volume if the float value did not change
    if (volume != mOutputs.valueFor(output)->mCurVolume[stream]) {
        mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs);
        mOutputs.valueFor(output)->mCurVolume[stream] = volume;
        LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs);
    }

    return NO_ERROR;
}

void AudioPolicyManager::applyStreamVolumes(audio_io_handle_t output, uint32_t device, int delayMs)
{
    LOGV("applyStreamVolumes() for output %d and device %x", output, device);

    for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
        checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, device, delayMs);
    }
}

void AudioPolicyManager::setStrategyMute(routing_strategy strategy, bool on, audio_io_handle_t output, int delayMs)
{
    LOGV("setStrategyMute() strategy %d, mute %d, output %d", strategy, on, output);
    for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
        if (getStrategy((AudioSystem::stream_type)stream) == strategy) {
            setStreamMute(stream, on, output, delayMs);
        }
    }
}

void AudioPolicyManager::setStreamMute(int stream, bool on, audio_io_handle_t output, int delayMs)
{
    StreamDescriptor &streamDesc = mStreams[stream];
    uint32_t device = mOutputs.valueFor(output)->mDevice;

    LOGV("setStreamMute() stream %d, mute %d, output %d, mMuteCount %d", stream, on, output, streamDesc.mMuteCount);

    if (on) {
        if (streamDesc.mMuteCount == 0) {
            if (streamDesc.mCanBeMuted) {
                checkAndSetVolume(stream, 0, output, device, delayMs);
            }
        }
        // increment mMuteCount after calling checkAndSetVolume() so that volume change is not ignored
        streamDesc.mMuteCount++;
    } else {
        if (streamDesc.mMuteCount == 0) {
            LOGW("setStreamMute() unmuting non muted stream!");
            return;
        }
        if (--streamDesc.mMuteCount == 0) {
            checkAndSetVolume(stream, streamDesc.mIndexCur, output, device, delayMs);
        }
    }
}

void AudioPolicyManager::handleIncallSonification(int stream, bool starting, bool stateChange)
{
    // if the stream pertains to sonification strategy and we are in call we must
    // mute the stream if it is low visibility. If it is high visibility, we must play a tone
    // in the device used for phone strategy and play the tone if the selected device does not
    // interfere with the device used for phone strategy
    // if stateChange is true, we are called from setPhoneState() and we must mute or unmute as
    // many times as there are active tracks on the output

    if (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) {
        AudioOutputDescriptor *outputDesc = mOutputs.valueFor(mHardwareOutput);
        LOGV("handleIncallSonification() stream %d starting %d device %x stateChange %d",
                stream, starting, outputDesc->mDevice, stateChange);
        if (outputDesc->isUsedByStream((AudioSystem::stream_type)stream)) {
            int muteCount = 1;
            if (stateChange) {
                muteCount = outputDesc->mRefCount[stream];
            }
            if (AudioSystem::isLowVisibility((AudioSystem::stream_type)stream)) {
                LOGV("handleIncallSonification() low visibility, muteCount %d", muteCount);
                for (int i = 0; i < muteCount; i++) {
                    setStreamMute(stream, starting, mHardwareOutput);
                }
            } else {
                LOGV("handleIncallSonification() high visibility ");
                if (outputDesc->mDevice & getDeviceForStrategy(STRATEGY_PHONE)) {
                    LOGV("handleIncallSonification() high visibility muted, muteCount %d", muteCount);
                    for (int i = 0; i < muteCount; i++) {
                        setStreamMute(stream, starting, mHardwareOutput);
                    }
                }
                if (starting) {
                    mpClientInterface->startTone(ToneGenerator::TONE_SUP_CALL_WAITING, AudioSystem::VOICE_CALL);
                } else {
                    mpClientInterface->stopTone();
                }
            }
        }
    }
}


// --- AudioOutputDescriptor class implementation

AudioPolicyManager::AudioOutputDescriptor::AudioOutputDescriptor()
    : mSamplingRate(0), mFormat(0), mChannels(0), mLatency(0),
    mFlags((AudioSystem::output_flags)0), mDevice(0), mOutput1(0), mOutput2(0)
{
    // clear usage count for all stream types
    for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
        mRefCount[i] = 0;
        mCurVolume[i] = -1.0;
    }
}

uint32_t AudioPolicyManager::AudioOutputDescriptor::device()
{
    uint32_t device = 0;
    if (isDuplicated()) {
        device = mOutput1->mDevice | mOutput2->mDevice;
    } else {
        device = mDevice;
    }
    return device;
}

void AudioPolicyManager::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta)
{
    // forward usage count change to attached outputs
    if (isDuplicated()) {
        mOutput1->changeRefCount(stream, delta);
        mOutput2->changeRefCount(stream, delta);
    }
    if ((delta + (int)mRefCount[stream]) < 0) {
        LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]);
        mRefCount[stream] = 0;
        return;
    }
    mRefCount[stream] += delta;
    LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]);
}

bool AudioPolicyManager::AudioOutputDescriptor::isUsedByStrategy(routing_strategy strategy)
{
    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
        if (AudioPolicyManager::getStrategy((AudioSystem::stream_type)i) == strategy &&
            isUsedByStream((AudioSystem::stream_type)i)) {
            return true;
        }
    }
    return false;
}


// --- AudioInputDescriptor class implementation

AudioPolicyManager::AudioInputDescriptor::AudioInputDescriptor()
    : mSamplingRate(0), mFormat(0), mChannels(0),
     mAcoustics((AudioSystem::audio_in_acoustics)0), mDevice(0), mRefCount(0)
{
}

}; // namespace android