/******************************************************************************
 *
 *  Copyright (C) 2004-2012 Broadcom Corporation
 *
 *  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.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *  This file contains functions for managing the SCO connection used in AG.
 *
 ******************************************************************************/

#include "bta_api.h"
#include "bta_ag_api.h"
#include "bta_ag_co.h"
#if (BTM_SCO_HCI_INCLUDED == TRUE )
#include "bta_dm_co.h"
#endif
#include "bta_ag_int.h"
#include "btm_api.h"
#include "gki.h"

#ifndef BTA_AG_SCO_DEBUG
#define BTA_AG_SCO_DEBUG FALSE
#endif

#ifndef BTA_AG_CODEC_NEGO_TIMEOUT
#define BTA_AG_CODEC_NEGO_TIMEOUT   3000
#endif

#if BTA_AG_SCO_DEBUG == TRUE
static char *bta_ag_sco_evt_str(UINT8 event);
static char *bta_ag_sco_state_str(UINT8 state);
#endif

#define BTA_AG_NO_EDR_ESCO  (BTM_SCO_PKT_TYPES_MASK_NO_2_EV3 | \
                             BTM_SCO_PKT_TYPES_MASK_NO_3_EV3 | \
                             BTM_SCO_PKT_TYPES_MASK_NO_2_EV5 | \
                             BTM_SCO_PKT_TYPES_MASK_NO_3_EV5)

/* sco events */
enum
{
    BTA_AG_SCO_LISTEN_E,        /* listen request */
    BTA_AG_SCO_OPEN_E,          /* open request */
    BTA_AG_SCO_XFER_E,          /* transfer request */
#if (BTM_WBS_INCLUDED == TRUE )
    BTA_AG_SCO_CN_DONE_E,       /* codec negotiation done */
    BTA_AG_SCO_REOPEN_E,        /* Retry with other codec when failed */
#endif
    BTA_AG_SCO_CLOSE_E,         /* close request */
    BTA_AG_SCO_SHUTDOWN_E,      /* shutdown request */
    BTA_AG_SCO_CONN_OPEN_E,     /* sco open */
    BTA_AG_SCO_CONN_CLOSE_E,    /* sco closed */
    BTA_AG_SCO_CI_DATA_E        /* SCO data ready */
};

#if (BTM_WBS_INCLUDED == TRUE )
#define BTA_AG_NUM_CODECS   2
static const tBTM_ESCO_PARAMS bta_ag_esco_params[BTA_AG_NUM_CODECS] =
{
    /* CVSD */
    {
        BTM_64KBITS_RATE,                   /* TX Bandwidth (64 kbits/sec)              */
        BTM_64KBITS_RATE,                   /* RX Bandwidth (64 kbits/sec)              */
        0x000a,                             /* 10 ms (HS/HF can use EV3, 2-EV3, 3-EV3)  */
        BTM_VOICE_SETTING_CVSD,             /* Inp Linear, Air CVSD, 2s Comp, 16bit     */
       (BTM_SCO_PKT_TYPES_MASK_HV1      +  /* Packet Types                             */
        BTM_SCO_PKT_TYPES_MASK_HV2      +
        BTM_SCO_PKT_TYPES_MASK_HV3      +
        BTM_SCO_PKT_TYPES_MASK_EV3      +
        BTM_SCO_PKT_TYPES_MASK_EV4      +
        BTM_SCO_PKT_TYPES_MASK_EV5      +
        BTM_SCO_PKT_TYPES_MASK_NO_2_EV5 +
        BTM_SCO_PKT_TYPES_MASK_NO_3_EV5),
        BTM_ESCO_RETRANS_POWER       /* Retransmission effort                      */
    },
    /* mSBC */
    {
        BTM_64KBITS_RATE,                   /* TX Bandwidth (64 kbits/sec), 8000        */
        BTM_64KBITS_RATE,                   /* RX Bandwidth (64 kbits/sec), 8000        */
        13,                                 /* 13 ms                                    */
        BTM_VOICE_SETTING_TRANS,            /* Inp Linear, Transparent, 2s Comp, 16bit  */
       (BTM_SCO_PKT_TYPES_MASK_EV3      |   /* Packet Types : EV3 + 2-EV3               */
        BTM_SCO_PKT_TYPES_MASK_NO_3_EV3 |
        BTM_SCO_PKT_TYPES_MASK_NO_2_EV5 |
        BTM_SCO_PKT_TYPES_MASK_NO_3_EV5),
        BTM_ESCO_RETRANS_QUALITY       /* Retransmission effort                      */
    }
};
#else
static const tBTM_ESCO_PARAMS bta_ag_esco_params =
{
    BTM_64KBITS_RATE,                   /* TX Bandwidth (64 kbits/sec)              */
    BTM_64KBITS_RATE,                   /* RX Bandwidth (64 kbits/sec)              */
    0x000a,                             /* 10 ms (HS/HF can use EV3, 2-EV3, 3-EV3)  */
    0x0060,                             /* Inp Linear, Air CVSD, 2s Comp, 16bit     */
    (BTM_SCO_PKT_TYPES_MASK_HV1      +  /* Packet Types                             */
     BTM_SCO_PKT_TYPES_MASK_HV2      +
     BTM_SCO_PKT_TYPES_MASK_HV3      +
     BTM_SCO_PKT_TYPES_MASK_EV3      +
     BTM_SCO_PKT_TYPES_MASK_EV4      +
     BTM_SCO_PKT_TYPES_MASK_EV5      +
     BTM_SCO_PKT_TYPES_MASK_NO_2_EV5 +
     BTM_SCO_PKT_TYPES_MASK_NO_3_EV5),
     BTM_ESCO_RETRANS_POWER       /* Retransmission effort                      */
};
#endif

/*******************************************************************************
**
** Function         bta_ag_sco_conn_cback
**
** Description      BTM SCO connection callback.
**
**
** Returns          void
**
*******************************************************************************/
static void bta_ag_sco_conn_cback(UINT16 sco_idx)
{
    UINT16  handle;
    BT_HDR  *p_buf;
    tBTA_AG_SCB *p_scb;

    /* match callback to scb; first check current sco scb */
    if (bta_ag_cb.sco.p_curr_scb != NULL && bta_ag_cb.sco.p_curr_scb->in_use)
    {
        handle = bta_ag_scb_to_idx(bta_ag_cb.sco.p_curr_scb);
    }
    /* then check for scb connected to this peer */
    else
    {
        /* Check if SLC is up */
        handle = bta_ag_idx_by_bdaddr(BTM_ReadScoBdAddr(sco_idx));
        p_scb = bta_ag_scb_by_idx(handle);
        if(p_scb && !p_scb->svc_conn)
            handle = 0;
    }

    if (handle != 0)
    {
        if ((p_buf = (BT_HDR *) GKI_getbuf(sizeof(BT_HDR))) != NULL)
        {
            p_buf->event = BTA_AG_SCO_OPEN_EVT;
            p_buf->layer_specific = handle;
            bta_sys_sendmsg(p_buf);
        }
    }
    /* no match found; disconnect sco, init sco variables */
    else
    {
        bta_ag_cb.sco.p_curr_scb = NULL;
        bta_ag_cb.sco.state = BTA_AG_SCO_SHUTDOWN_ST;
        BTM_RemoveSco(sco_idx);
    }
}

/*******************************************************************************
**
** Function         bta_ag_sco_disc_cback
**
** Description      BTM SCO disconnection callback.
**
**
** Returns          void
**
*******************************************************************************/
static void bta_ag_sco_disc_cback(UINT16 sco_idx)
{
    BT_HDR  *p_buf;
    UINT16  handle = 0;

    APPL_TRACE_DEBUG3 ("bta_ag_sco_disc_cback(): sco_idx: 0x%x  p_cur_scb: 0x%08x  sco.state: %d", sco_idx, bta_ag_cb.sco.p_curr_scb, bta_ag_cb.sco.state);

    APPL_TRACE_DEBUG4 ("bta_ag_sco_disc_cback(): scb[0] addr: 0x%08x  in_use: %u  sco_idx: 0x%x  sco state: %u",
                       &bta_ag_cb.scb[0], bta_ag_cb.scb[0].in_use, bta_ag_cb.scb[0].sco_idx, bta_ag_cb.scb[0].state);
    APPL_TRACE_DEBUG4 ("bta_ag_sco_disc_cback(): scb[1] addr: 0x%08x  in_use: %u  sco_idx: 0x%x  sco state: %u",
                       &bta_ag_cb.scb[1], bta_ag_cb.scb[1].in_use, bta_ag_cb.scb[1].sco_idx, bta_ag_cb.scb[1].state);

    /* match callback to scb */
    if (bta_ag_cb.sco.p_curr_scb != NULL && bta_ag_cb.sco.p_curr_scb->in_use)
    {
        /* We only care about callbacks for the active SCO */
        if (bta_ag_cb.sco.p_curr_scb->sco_idx != sco_idx)
        {
            if (bta_ag_cb.sco.p_curr_scb->sco_idx != 0xFFFF)
                return;
        }
        handle  = bta_ag_scb_to_idx(bta_ag_cb.sco.p_curr_scb);
    }

    if (handle != 0)
    {
#if (BTM_SCO_HCI_INCLUDED == TRUE )
        tBTM_STATUS status = BTM_ConfigScoPath(BTM_SCO_ROUTE_PCM, NULL, NULL, TRUE);
        APPL_TRACE_DEBUG1("bta_ag_sco_disc_cback sco close config status = %d", status);
	    /* SCO clean up here */
        bta_dm_sco_co_close();
#endif

#if (BTM_WBS_INCLUDED == TRUE )
        /* Restore settings */
        if(bta_ag_cb.sco.p_curr_scb->inuse_codec == BTA_AG_CODEC_MSBC)
        {
            BTM_SetWBSCodec (BTM_SCO_CODEC_NONE);
            BTM_WriteVoiceSettings (BTM_VOICE_SETTING_CVSD);

            /* If SCO open was initiated by AG and failed for mSBC, try CVSD again. */
            if (bta_ag_sco_is_opening (bta_ag_cb.sco.p_curr_scb))
            {
                bta_ag_cb.sco.p_curr_scb->codec_fallback = TRUE;
                APPL_TRACE_DEBUG0("Fallback to CVSD");
            }
        }

        bta_ag_cb.sco.p_curr_scb->inuse_codec = BTA_AG_CODEC_NONE;
#endif

        if ((p_buf = (BT_HDR *) GKI_getbuf(sizeof(BT_HDR))) != NULL)
        {
            p_buf->event = BTA_AG_SCO_CLOSE_EVT;
            p_buf->layer_specific = handle;
            bta_sys_sendmsg(p_buf);
        }
    }
    /* no match found */
    else
    {
        APPL_TRACE_DEBUG0("no scb for ag_sco_disc_cback");

        /* sco could be closed after scb dealloc'ed */
        if (bta_ag_cb.sco.p_curr_scb != NULL)
        {
            bta_ag_cb.sco.p_curr_scb->sco_idx = BTM_INVALID_SCO_INDEX;
            bta_ag_cb.sco.p_curr_scb = NULL;
            bta_ag_cb.sco.state = BTA_AG_SCO_SHUTDOWN_ST;
        }
    }
}
#if (BTM_SCO_HCI_INCLUDED == TRUE )
/*******************************************************************************
**
** Function         bta_ag_sco_read_cback
**
** Description      Callback function is the callback function for incoming
**                  SCO data over HCI.
**
** Returns          void
**
*******************************************************************************/
static void bta_ag_sco_read_cback (UINT16 sco_inx, BT_HDR *p_data, tBTM_SCO_DATA_FLAG status)
{
    if (status != BTM_SCO_DATA_CORRECT)
    {
        APPL_TRACE_DEBUG1("bta_ag_sco_read_cback: status(%d)", status);
    }

    /* Callout function must free the data. */
    bta_dm_sco_co_in_data (p_data, status);
}
#endif
/*******************************************************************************
**
** Function         bta_ag_remove_sco
**
** Description      Removes the specified SCO from the system.
**                  If only_active is TRUE, then SCO is only removed if connected
**
** Returns          BOOLEAN   - TRUE if Sco removal was started
**
*******************************************************************************/
static BOOLEAN bta_ag_remove_sco(tBTA_AG_SCB *p_scb, BOOLEAN only_active)
{
    BOOLEAN     removed_started = FALSE;
    tBTM_STATUS	status;

    if (p_scb->sco_idx != BTM_INVALID_SCO_INDEX)
    {
        if (!only_active || p_scb->sco_idx == bta_ag_cb.sco.cur_idx)
        {
            status = BTM_RemoveSco(p_scb->sco_idx);

            APPL_TRACE_DEBUG2("ag remove sco: inx 0x%04x, status:0x%x", p_scb->sco_idx, status);

            if (status == BTM_CMD_STARTED)
            {
                /* Sco is connected; set current control block */
                bta_ag_cb.sco.p_curr_scb = p_scb;

                removed_started = TRUE;
            }
            /* If no connection reset the sco handle */
            else if ( (status == BTM_SUCCESS) || (status == BTM_UNKNOWN_ADDR) )
            {
                p_scb->sco_idx = BTM_INVALID_SCO_INDEX;
            }
        }
    }
    return removed_started;
}


/*******************************************************************************
**
** Function         bta_ag_esco_connreq_cback
**
** Description      BTM eSCO connection requests and eSCO change requests
**                  Only the connection requests are processed by BTA.
**
** Returns          void
**
*******************************************************************************/
static void bta_ag_esco_connreq_cback(tBTM_ESCO_EVT event, tBTM_ESCO_EVT_DATA *p_data)
{
    tBTA_AG_SCB         *p_scb;
    UINT16               handle;
    UINT16               sco_inx = p_data->conn_evt.sco_inx;

    /* Only process connection requests */
    if (event == BTM_ESCO_CONN_REQ_EVT)
    {
        if ((handle = bta_ag_idx_by_bdaddr(BTM_ReadScoBdAddr(sco_inx))) != 0 &&
            ((p_scb = bta_ag_scb_by_idx(handle)) != NULL) && p_scb->svc_conn)
        {
            p_scb->sco_idx = sco_inx;

            /* If no other SCO active, allow this one */
            if (!bta_ag_cb.sco.p_curr_scb)
            {
                APPL_TRACE_EVENT1("bta_ag_esco_connreq_cback: Accept Conn Request (sco_inx 0x%04x)", sco_inx);
                bta_ag_sco_conn_rsp(p_scb, &p_data->conn_evt);

                bta_ag_cb.sco.state = BTA_AG_SCO_OPENING_ST;
                bta_ag_cb.sco.p_curr_scb = p_scb;
                bta_ag_cb.sco.cur_idx = p_scb->sco_idx;
            }
            else    /* Begin a transfer: Close current SCO before responding */
            {
                APPL_TRACE_DEBUG0("bta_ag_esco_connreq_cback: Begin XFER");
                bta_ag_cb.sco.p_xfer_scb = p_scb;
                bta_ag_cb.sco.conn_data = p_data->conn_evt;
                bta_ag_cb.sco.state = BTA_AG_SCO_OPEN_XFER_ST;

                if (!bta_ag_remove_sco(bta_ag_cb.sco.p_curr_scb, TRUE))
                {
                    APPL_TRACE_ERROR1("bta_ag_esco_connreq_cback: Nothing to remove so accept Conn Request (sco_inx 0x%04x)", sco_inx);
                    bta_ag_cb.sco.p_xfer_scb = NULL;
                    bta_ag_cb.sco.state = BTA_AG_SCO_LISTEN_ST;

                    bta_ag_sco_conn_rsp(p_scb, &p_data->conn_evt);
                }
            }
        }
        /* If error occurred send reject response immediately */
        else
        {
            APPL_TRACE_WARNING0("no scb for bta_ag_esco_connreq_cback or no resources");
            BTM_EScoConnRsp(p_data->conn_evt.sco_inx, HCI_ERR_HOST_REJECT_RESOURCES, NULL);
        }
    }
    /* Received a change in the esco link */
    else if (event == BTM_ESCO_CHG_EVT)
    {
        APPL_TRACE_EVENT5("eSCO change event (inx %d): rtrans %d, rxlen %d, txlen %d, txint %d",
            p_data->chg_evt.sco_inx,
            p_data->chg_evt.retrans_window, p_data->chg_evt.rx_pkt_len,
            p_data->chg_evt.tx_pkt_len, p_data->chg_evt.tx_interval);
    }
}

/*******************************************************************************
**
** Function         bta_ag_cback_sco
**
** Description      Call application callback function with SCO event.
**
**
** Returns          void
**
*******************************************************************************/
static void bta_ag_cback_sco(tBTA_AG_SCB *p_scb, UINT8 event)
{
    tBTA_AG_HDR    sco;

    sco.handle = bta_ag_scb_to_idx(p_scb);
    sco.app_id = p_scb->app_id;

    /* call close cback */
    (*bta_ag_cb.p_cback)(event, (tBTA_AG *) &sco);
}

/*******************************************************************************
**
** Function         bta_ag_create_sco
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
static void bta_ag_create_sco(tBTA_AG_SCB *p_scb, BOOLEAN is_orig)
{
    tBTM_STATUS       status;
    UINT8            *p_bd_addr = NULL;
    tBTM_ESCO_PARAMS params;
#if (BTM_WBS_INCLUDED == TRUE )
    tBTA_AG_PEER_CODEC  esco_codec = BTM_SCO_CODEC_CVSD;
    int codec_index = 0;
#endif
#if (BTM_SCO_HCI_INCLUDED == TRUE )
    tBTM_SCO_ROUTE_TYPE sco_route;
    tBTA_CODEC_INFO     codec_info = {BTA_SCO_CODEC_PCM};
    UINT32              pcm_sample_rate;
#endif

    /* Make sure this sco handle is not already in use */
    if (p_scb->sco_idx != BTM_INVALID_SCO_INDEX)
    {
        APPL_TRACE_WARNING1("bta_ag_create_sco: Index 0x%04x Already In Use!",
                             p_scb->sco_idx);
        return;
    }

#if (BTM_WBS_INCLUDED == TRUE )
    if ((p_scb->sco_codec == BTM_SCO_CODEC_MSBC) &&
        !p_scb->codec_fallback &&
        !p_scb->retry_with_sco_only)
        esco_codec = BTM_SCO_CODEC_MSBC;

    if (p_scb->codec_fallback)
    {
        p_scb->codec_fallback = FALSE;

        /* Force AG to send +BCS for the next audio connection. */
        p_scb->codec_updated = TRUE;
    }

    if (esco_codec == BTM_SCO_CODEC_MSBC)
        codec_index = esco_codec - 1;

    params = bta_ag_esco_params[codec_index];
#else
    params = bta_ag_esco_params;
#endif

    if(bta_ag_cb.sco.param_updated) /* If we do not use the default parameters */
        params = bta_ag_cb.sco.params;

    if(!bta_ag_cb.sco.param_updated)
    {
#if (BTM_WBS_INCLUDED == TRUE)
        if (!codec_index)   /* For non-WBS */
#endif
        {
            /* Use the application packet types (5 slot EV packets not allowed) */
            params.packet_types = p_bta_ag_cfg->sco_pkt_types     |
                                  BTM_SCO_PKT_TYPES_MASK_NO_2_EV5 |
                                  BTM_SCO_PKT_TYPES_MASK_NO_3_EV5;
        }
    }

    /* if initiating set current scb and peer bd addr */
    if (is_orig)
    {
        /* Attempt to use eSCO if remote host supports HFP >= 1.5 */
        /* Need to find out from SIG if HSP can use eSCO; for now use SCO */
        if (p_scb->conn_service == BTA_AG_HFP && p_scb->peer_version >= HFP_VERSION_1_5 && !p_scb->retry_with_sco_only)
        {

            BTM_SetEScoMode(BTM_LINK_TYPE_ESCO, &params);
            /* If ESCO or EDR ESCO, retry with SCO only in case of failure */
            if((params.packet_types & BTM_ESCO_LINK_ONLY_MASK)
               ||!((params.packet_types & ~(BTM_ESCO_LINK_ONLY_MASK | BTM_SCO_LINK_ONLY_MASK)) ^ BTA_AG_NO_EDR_ESCO))
            {
#if (BTM_WBS_INCLUDED == TRUE )
                if (esco_codec != BTA_AG_CODEC_MSBC)
                {
                    p_scb->retry_with_sco_only = TRUE;
                    APPL_TRACE_API0("Setting retry_with_sco_only to TRUE");
                }
                else    /* Do not use SCO when using mSBC */
                {
                    p_scb->retry_with_sco_only = FALSE;
                    APPL_TRACE_API0("Setting retry_with_sco_only to FALSE");
                }
#else
                p_scb->retry_with_sco_only = TRUE;
                APPL_TRACE_API0("Setting retry_with_sco_only to TRUE");
#endif
            }
        }
        else
        {
            if(p_scb->retry_with_sco_only)
                APPL_TRACE_API0("retrying with SCO only");
            p_scb->retry_with_sco_only = FALSE;

            BTM_SetEScoMode(BTM_LINK_TYPE_SCO, &params);
        }

        bta_ag_cb.sco.p_curr_scb = p_scb;

        /* tell sys to stop av if any */
        bta_sys_sco_use(BTA_ID_AG, p_scb->app_id, p_scb->peer_addr);

        /* Allow any platform specific pre-SCO set up to take place */
        bta_ag_co_audio_state(bta_ag_scb_to_idx(p_scb), p_scb->app_id, BTA_AG_CO_AUD_STATE_SETUP);

#if (BTM_SCO_HCI_INCLUDED == TRUE )
#if (BTM_WBS_INCLUDED == TRUE)
        if (esco_codec == BTA_AG_CODEC_MSBC)
            pcm_sample_rate = BTA_DM_SCO_SAMP_RATE_16K;
        else
#endif
            pcm_sample_rate = BTA_DM_SCO_SAMP_RATE_8K;

        sco_route = bta_dm_sco_co_init(pcm_sample_rate, pcm_sample_rate, &codec_info, p_scb->app_id);
#endif

#if (BTM_WBS_INCLUDED == TRUE )
        if (esco_codec == BTA_AG_CODEC_MSBC)
        {
            /* Enable mSBC codec in fw */
            BTM_SetWBSCodec (esco_codec);
        }

        /* Specify PCM input for SBC codec in fw */
        BTM_ConfigI2SPCM (esco_codec, (UINT8)HCI_BRCM_I2SPCM_IS_DEFAULT_ROLE, (UINT8)HCI_BRCM_I2SPCM_SAMPLE_DEFAULT, (UINT8)HCI_BRCM_I2SPCM_CLOCK_DEFAULT);

        /* This setting may not be necessary */
        /* To be verified with stable 2049 boards */
        if (esco_codec == BTA_AG_CODEC_MSBC)
            BTM_WriteVoiceSettings (BTM_VOICE_SETTING_TRANS);
        else
            BTM_WriteVoiceSettings (BTM_VOICE_SETTING_CVSD);

        /* save the current codec because sco_codec can be updated while SCO is open. */
        p_scb->inuse_codec = esco_codec;
#endif

#if (BTM_SCO_HCI_INCLUDED == TRUE )
        /* initialize SCO setup, no voice setting for AG, data rate <==> sample rate */
        BTM_ConfigScoPath(sco_route, bta_ag_sco_read_cback, NULL, TRUE);
#endif
        bta_ag_cb.sco.cur_idx = p_scb->sco_idx;
    }
    else
        p_scb->retry_with_sco_only = FALSE;

    p_bd_addr = p_scb->peer_addr;

    status = BTM_CreateSco(p_bd_addr, is_orig, params.packet_types,
                           &p_scb->sco_idx, bta_ag_sco_conn_cback,
                           bta_ag_sco_disc_cback);
    if (status == BTM_CMD_STARTED)
    {
        if (!is_orig)
        {
            BTM_RegForEScoEvts(p_scb->sco_idx, bta_ag_esco_connreq_cback);
        }
        else    /* Initiating the connection, set the current sco handle */
        {
            bta_ag_cb.sco.cur_idx = p_scb->sco_idx;
        }
    }

    APPL_TRACE_API4("ag create sco: orig %d, inx 0x%04x, status 0x%x, pkt types 0x%04x",
                      is_orig, p_scb->sco_idx, status, params.packet_types);
}

#if (BTM_WBS_INCLUDED == TRUE )
/*******************************************************************************
**
** Function         bta_ag_cn_timer_cback
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
static void bta_ag_cn_timer_cback (TIMER_LIST_ENT *p_tle)
{
    tBTA_AG_SCB *p_scb;

    if (p_tle)
    {
        p_scb = (tBTA_AG_SCB *)p_tle->param;

        if (p_scb)
        {
            /* Announce that codec negotiation failed. */
            bta_ag_sco_codec_nego(p_scb, FALSE);

            /* call app callback */
            bta_ag_cback_sco(p_scb, BTA_AG_AUDIO_CLOSE_EVT);
        }
    }
}

/*******************************************************************************
**
** Function         bta_ag_codec_negotiate
**
** Description      Initiate codec negotiation by sending AT command.
**                  If not necessary, skip negotiation.
**
** Returns          void
**
*******************************************************************************/
void bta_ag_codec_negotiate(tBTA_AG_SCB *p_scb)
{
    bta_ag_cb.sco.p_curr_scb = p_scb;

    if (p_scb->codec_updated || p_scb->codec_fallback)
    {
        /* Change the power mode to Active until sco open is completed. */
        bta_sys_busy(BTA_ID_AG, p_scb->app_id, p_scb->peer_addr);

        /* Send +BCS to the peer */
        bta_ag_send_bcs(p_scb, NULL);

        /* Start timer to handle timeout */
        p_scb->cn_timer.p_cback = (TIMER_CBACK*)&bta_ag_cn_timer_cback;
        p_scb->cn_timer.param = (INT32)p_scb;
        bta_sys_start_timer(&p_scb->cn_timer, 0, BTA_AG_CODEC_NEGO_TIMEOUT);
    }
    else
    {
        /* use same codec type as previous SCO connection, skip codec negotiation */
        bta_ag_sco_codec_nego(p_scb, TRUE);
    }
}
#endif

/*******************************************************************************
**
** Function         bta_ag_sco_event
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
static void bta_ag_sco_event(tBTA_AG_SCB *p_scb, UINT8 event)
{
    tBTA_AG_SCO_CB *p_sco = &bta_ag_cb.sco;
#if (BTM_WBS_INCLUDED == TRUE )
    tBTA_AG_SCB *p_cn_scb = NULL;   /* For codec negotiation */
#endif
#if (BTM_SCO_HCI_INCLUDED == TRUE )
    BT_HDR  *p_buf;
#endif
#if BTA_AG_SCO_DEBUG == TRUE
    UINT8   in_state = p_sco->state;

    APPL_TRACE_EVENT5("BTA ag sco evt (hdl 0x%04x): State %d (%s), Event %d (%s)",
                        p_scb->sco_idx,
                        p_sco->state, bta_ag_sco_state_str(p_sco->state),
                        event, bta_ag_sco_evt_str(event));
#else
    APPL_TRACE_EVENT3("BTA ag sco evt (hdl 0x%04x): State %d, Event %d",
                      p_scb->sco_idx, p_sco->state, event);
#endif

#if (BTM_SCO_HCI_INCLUDED == TRUE )
    if (event == BTA_AG_SCO_CI_DATA_E)
    {
        while (TRUE)
        {
            bta_dm_sco_co_out_data(&p_buf);
            if (p_buf)
            {
                if (p_sco->state == BTA_AG_SCO_OPEN_ST)
                    BTM_WriteScoData(p_sco->p_curr_scb->sco_idx, p_buf);
                else
                    GKI_freebuf(p_buf);
            }
            else
                break;
        }

        return;
    }
#endif

    switch (p_sco->state)
    {
        case BTA_AG_SCO_SHUTDOWN_ST:
            switch (event)
            {
                case BTA_AG_SCO_LISTEN_E:
                    /* create sco listen connection */
                    bta_ag_create_sco(p_scb, FALSE);
                    p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_SHUTDOWN_ST: Ignoring event %d", event);
                    break;
            }
            break;

        case BTA_AG_SCO_LISTEN_ST:
            switch (event)
            {
                case BTA_AG_SCO_LISTEN_E:
                    /* create sco listen connection (Additional channel) */
                    bta_ag_create_sco(p_scb, FALSE);
                    break;

                case BTA_AG_SCO_OPEN_E:
                    /* remove listening connection */
                    bta_ag_remove_sco(p_scb, FALSE);

#if (BTM_WBS_INCLUDED == TRUE )
                    /* start codec negotiation */
                    p_sco->state = BTA_AG_SCO_CODEC_ST;
                    p_cn_scb = p_scb;
#else
                    /* create sco connection to peer */
                    bta_ag_create_sco(p_scb, TRUE);
                    p_sco->state = BTA_AG_SCO_OPENING_ST;
#endif
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    /* remove listening connection */
                    bta_ag_remove_sco(p_scb, FALSE);

                    if (p_scb == p_sco->p_curr_scb)
                        p_sco->p_curr_scb = NULL;

                    /* If last SCO instance then finish shutting down */
                    if (!bta_ag_other_scb_open(p_scb))
                    {
                        p_sco->state = BTA_AG_SCO_SHUTDOWN_ST;
                    }
                    break;

                case BTA_AG_SCO_CLOSE_E:
                    /* remove listening connection */
                    /* Ignore the event. We need to keep listening SCO for the active SLC */
                    APPL_TRACE_WARNING1("BTA_AG_SCO_LISTEN_ST: Ignoring event %d", event);
                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* sco failed; create sco listen connection */
                    bta_ag_create_sco(p_scb, FALSE);
                    p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_LISTEN_ST: Ignoring event %d", event);
                    break;
            }
            break;

#if (BTM_WBS_INCLUDED == TRUE )
        case BTA_AG_SCO_CODEC_ST:
            switch (event)
            {
                case BTA_AG_SCO_LISTEN_E:
                    /* create sco listen connection (Additional channel) */
                    bta_ag_create_sco(p_scb, FALSE);
                    break;

                case BTA_AG_SCO_CN_DONE_E:
                    /* create sco connection to peer */
                    bta_ag_create_sco(p_scb, TRUE);
                    p_sco->state = BTA_AG_SCO_OPENING_ST;
                    break;

                case BTA_AG_SCO_XFER_E:
                    /* save xfer scb */
                    p_sco->p_xfer_scb = p_scb;
                    p_sco->state = BTA_AG_SCO_CLOSE_XFER_ST;
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    /* remove listening connection */
                    bta_ag_remove_sco(p_scb, FALSE);

                    if (p_scb == p_sco->p_curr_scb)
                        p_sco->p_curr_scb = NULL;

                    /* If last SCO instance then finish shutting down */
                    if (!bta_ag_other_scb_open(p_scb))
                    {
                        p_sco->state = BTA_AG_SCO_SHUTDOWN_ST;
                    }
                    break;

                case BTA_AG_SCO_CLOSE_E:
                    /* sco open is not started yet. just go back to listening */
                    p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* sco failed; create sco listen connection */
                    bta_ag_create_sco(p_scb, FALSE);
                    p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_CODEC_ST: Ignoring event %d", event);
                    break;
            }
            break;
#endif

        case BTA_AG_SCO_OPENING_ST:
            switch (event)
            {
                case BTA_AG_SCO_LISTEN_E:
                    /* second headset has now joined */
                    /* create sco listen connection (Additional channel) */
                    if (p_scb != p_sco->p_curr_scb)
                    {
                        bta_ag_create_sco(p_scb, FALSE);
                    }
                    break;

#if (BTM_WBS_INCLUDED == TRUE)
                case BTA_AG_SCO_REOPEN_E:
                    /* start codec negotiation */
                    p_sco->state = BTA_AG_SCO_CODEC_ST;
                    p_cn_scb = p_scb;
                    break;
#endif

                case BTA_AG_SCO_XFER_E:
                    /* save xfer scb */
                    p_sco->p_xfer_scb = p_scb;
                    p_sco->state = BTA_AG_SCO_CLOSE_XFER_ST;
                    break;

                case BTA_AG_SCO_CLOSE_E:
                    p_sco->state = BTA_AG_SCO_OPEN_CL_ST;
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    /* If not opening scb, just close it */
                    if (p_scb != p_sco->p_curr_scb)
                    {
                        /* remove listening connection */
                        bta_ag_remove_sco(p_scb, FALSE);
                    }
                    else
                        p_sco->state = BTA_AG_SCO_SHUTTING_ST;

                    break;

                case BTA_AG_SCO_CONN_OPEN_E:
                    p_sco->state = BTA_AG_SCO_OPEN_ST;
                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* sco failed; create sco listen connection */
                    bta_ag_create_sco(p_scb, FALSE);
                    p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_OPENING_ST: Ignoring event %d", event);
                    break;
            }
            break;

        case BTA_AG_SCO_OPEN_CL_ST:
            switch (event)
            {
                case BTA_AG_SCO_XFER_E:
                    /* save xfer scb */
                    p_sco->p_xfer_scb = p_scb;

                    p_sco->state = BTA_AG_SCO_CLOSE_XFER_ST;
                    break;

                case BTA_AG_SCO_OPEN_E:
                    p_sco->state = BTA_AG_SCO_OPENING_ST;
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    /* If not opening scb, just close it */
                    if (p_scb != p_sco->p_curr_scb)
                    {
                        /* remove listening connection */
                        bta_ag_remove_sco(p_scb, FALSE);
                    }
                    else
                        p_sco->state = BTA_AG_SCO_SHUTTING_ST;

                    break;

                case BTA_AG_SCO_CONN_OPEN_E:
                    /* close sco connection */
                    bta_ag_remove_sco(p_scb, TRUE);

                    p_sco->state = BTA_AG_SCO_CLOSING_ST;
                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* sco failed; create sco listen connection */

                    p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_OPEN_CL_ST: Ignoring event %d", event);
                    break;
            }
            break;

        case BTA_AG_SCO_OPEN_XFER_ST:
            switch (event)
            {
                case BTA_AG_SCO_CLOSE_E:
                    /* close sco connection */
                    bta_ag_remove_sco(p_scb, TRUE);

                    p_sco->state = BTA_AG_SCO_CLOSING_ST;
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    /* remove all connection */
                    bta_ag_remove_sco(p_scb, FALSE);
                    p_sco->state = BTA_AG_SCO_SHUTTING_ST;

                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* closed sco; place in listen mode and
                       accept the transferred connection */
                    bta_ag_create_sco(p_scb, FALSE);    /* Back into listen mode */

                    /* Accept sco connection with xfer scb */
                    bta_ag_sco_conn_rsp(p_sco->p_xfer_scb, &p_sco->conn_data);
                    p_sco->state = BTA_AG_SCO_OPENING_ST;
                    p_sco->p_curr_scb = p_sco->p_xfer_scb;
                    p_sco->cur_idx = p_sco->p_xfer_scb->sco_idx;
                    p_sco->p_xfer_scb = NULL;
                     break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_OPEN_XFER_ST: Ignoring event %d", event);
                    break;
            }
            break;

        case BTA_AG_SCO_OPEN_ST:
            switch (event)
            {
                case BTA_AG_SCO_LISTEN_E:
                    /* second headset has now joined */
                    /* create sco listen connection (Additional channel) */
                    if (p_scb != p_sco->p_curr_scb)
                    {
                        bta_ag_create_sco(p_scb, FALSE);
                    }
                    break;

                case BTA_AG_SCO_XFER_E:
                    /* close current sco connection */
                    bta_ag_remove_sco(p_sco->p_curr_scb, TRUE);

                    /* save xfer scb */
                    p_sco->p_xfer_scb = p_scb;

                    p_sco->state = BTA_AG_SCO_CLOSE_XFER_ST;
                    break;

                case BTA_AG_SCO_CLOSE_E:
                    /* close sco connection if active */
                    if (bta_ag_remove_sco(p_scb, TRUE))
                    {
                        p_sco->state = BTA_AG_SCO_CLOSING_ST;
                    }
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    /* remove all listening connections */
                    bta_ag_remove_sco(p_scb, FALSE);

                    /* If SCO was active on this scb, close it */
                    if (p_scb == p_sco->p_curr_scb)
                    {
                        p_sco->state = BTA_AG_SCO_SHUTTING_ST;
                    }
                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* peer closed sco; create sco listen connection */
                    bta_ag_create_sco(p_scb, FALSE);
                    p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_OPEN_ST: Ignoring event %d", event);
                    break;
            }
            break;

        case BTA_AG_SCO_CLOSING_ST:
            switch (event)
            {
                case BTA_AG_SCO_LISTEN_E:
                    /* create sco listen connection (Additional channel) */
                    if (p_scb != p_sco->p_curr_scb)
                    {
                        bta_ag_create_sco(p_scb, FALSE);
                    }
                    break;

                case BTA_AG_SCO_OPEN_E:
                    p_sco->state = BTA_AG_SCO_CLOSE_OP_ST;
                    break;

                case BTA_AG_SCO_XFER_E:
                    /* save xfer scb */
                    p_sco->p_xfer_scb = p_scb;

                    p_sco->state = BTA_AG_SCO_CLOSE_XFER_ST;
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    /* If not closing scb, just close it */
                    if (p_scb != p_sco->p_curr_scb)
                    {
                        /* remove listening connection */
                        bta_ag_remove_sco(p_scb, FALSE);
                    }
                    else
                        p_sco->state = BTA_AG_SCO_SHUTTING_ST;

                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* peer closed sco; create sco listen connection */
                    bta_ag_create_sco(p_scb, FALSE);

                    p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_CLOSING_ST: Ignoring event %d", event);
                    break;
            }
            break;

        case BTA_AG_SCO_CLOSE_OP_ST:
            switch (event)
            {
                case BTA_AG_SCO_CLOSE_E:
                    p_sco->state = BTA_AG_SCO_CLOSING_ST;
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    p_sco->state = BTA_AG_SCO_SHUTTING_ST;
                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
#if (BTM_WBS_INCLUDED == TRUE )
                    /* start codec negotiation */
                    p_sco->state = BTA_AG_SCO_CODEC_ST;
                    p_cn_scb = p_scb;
#else
                    /* open sco connection */
                    bta_ag_create_sco(p_scb, TRUE);
                    p_sco->state = BTA_AG_SCO_OPENING_ST;
#endif
                    break;

                case BTA_AG_SCO_LISTEN_E:
                    /* create sco listen connection (Additional channel) */
                    if (p_scb != p_sco->p_curr_scb)
                    {
                        bta_ag_create_sco(p_scb, FALSE);
                    }
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_CLOSE_OP_ST: Ignoring event %d", event);
                    break;
            }
            break;

        case BTA_AG_SCO_CLOSE_XFER_ST:
            switch (event)
            {
                case BTA_AG_SCO_CONN_OPEN_E:
                    /* close sco connection so headset can be transferred
                       Probably entered this state from "opening state" */
                    bta_ag_remove_sco(p_scb, TRUE);
                    break;

                case BTA_AG_SCO_CLOSE_E:
                    /* clear xfer scb */
                    p_sco->p_xfer_scb = NULL;

                    p_sco->state = BTA_AG_SCO_CLOSING_ST;
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    /* clear xfer scb */
                    p_sco->p_xfer_scb = NULL;

                    p_sco->state = BTA_AG_SCO_SHUTTING_ST;
                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* closed sco; place old sco in listen mode,
                       take current sco out of listen, and
                       create originating sco for current */
                    bta_ag_create_sco(p_scb, FALSE);
                    bta_ag_remove_sco(p_sco->p_xfer_scb, FALSE);

#if (BTM_WBS_INCLUDED == TRUE )
                    /* start codec negotiation */
                    p_sco->state = BTA_AG_SCO_CODEC_ST;
                    p_cn_scb = p_sco->p_xfer_scb;
                    p_sco->p_xfer_scb = NULL;
#else
                    /* create sco connection to peer */
                    bta_ag_create_sco(p_sco->p_xfer_scb, TRUE);
                    p_sco->p_xfer_scb = NULL;
                    p_sco->state = BTA_AG_SCO_OPENING_ST;
#endif
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_CLOSE_XFER_ST: Ignoring event %d", event);
                    break;
            }
            break;

        case BTA_AG_SCO_SHUTTING_ST:
            switch (event)
            {
                case BTA_AG_SCO_CONN_OPEN_E:
                    /* close sco connection; wait for conn close event */
                    bta_ag_remove_sco(p_scb, TRUE);
                    break;

                case BTA_AG_SCO_CONN_CLOSE_E:
                    /* If last SCO instance then finish shutting down */
                    if (!bta_ag_other_scb_open(p_scb))
                    {
                        p_sco->state = BTA_AG_SCO_SHUTDOWN_ST;
                    }
                    else    /* Other instance is still listening */
                    {
                        p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    }

                    if (p_scb == p_sco->p_curr_scb)
                    {
                        p_sco->p_curr_scb->sco_idx = BTM_INVALID_SCO_INDEX;
                        p_sco->p_curr_scb = NULL;
                    }
                    break;

                case BTA_AG_SCO_LISTEN_E:
                    /* create sco listen connection (Additional channel) */
                    if (p_scb != p_sco->p_curr_scb)
                    {
                        bta_ag_create_sco(p_scb, FALSE);
                    }
                    break;

                case BTA_AG_SCO_SHUTDOWN_E:
                    if (!bta_ag_other_scb_open(p_scb))
                    {
                        p_sco->state = BTA_AG_SCO_SHUTDOWN_ST;
                    }
                    else    /* Other instance is still listening */
                    {
                        p_sco->state = BTA_AG_SCO_LISTEN_ST;
                    }

                    if (p_scb == p_sco->p_curr_scb)
                    {
                        p_sco->p_curr_scb->sco_idx = BTM_INVALID_SCO_INDEX;
                        p_sco->p_curr_scb = NULL;
                    }
                    break;

                default:
                    APPL_TRACE_WARNING1("BTA_AG_SCO_SHUTTING_ST: Ignoring event %d", event);
                    break;
            }
            break;

        default:
            break;
    }
#if BTA_AG_SCO_DEBUG == TRUE
    if (p_sco->state != in_state)
    {
        APPL_TRACE_EVENT3("BTA AG SCO State Change: [%s] -> [%s] after Event [%s]",
                      bta_ag_sco_state_str(in_state),
                      bta_ag_sco_state_str(p_sco->state),
                      bta_ag_sco_evt_str(event));
    }
#endif

#if (BTM_WBS_INCLUDED == TRUE )
    if (p_cn_scb)
    {
        bta_ag_codec_negotiate(p_cn_scb);
    }
#endif
}

/*******************************************************************************
**
** Function         bta_ag_sco_is_open
**
** Description      Check if sco is open for this scb.
**
**
** Returns          TRUE if sco open for this scb, FALSE otherwise.
**
*******************************************************************************/
BOOLEAN bta_ag_sco_is_open(tBTA_AG_SCB *p_scb)
{
    return ((bta_ag_cb.sco.state == BTA_AG_SCO_OPEN_ST) &&
            (bta_ag_cb.sco.p_curr_scb == p_scb));
}

/*******************************************************************************
**
** Function         bta_ag_sco_is_opening
**
** Description      Check if sco is in Opening state.
**
**
** Returns          TRUE if sco is in Opening state for this scb, FALSE otherwise.
**
*******************************************************************************/
BOOLEAN bta_ag_sco_is_opening(tBTA_AG_SCB *p_scb)
{
#if (BTM_WBS_INCLUDED == TRUE )
    return (((bta_ag_cb.sco.state == BTA_AG_SCO_OPENING_ST) ||
            (bta_ag_cb.sco.state == BTA_AG_SCO_OPENING_ST)) &&
            (bta_ag_cb.sco.p_curr_scb == p_scb));
#else
    return ((bta_ag_cb.sco.state == BTA_AG_SCO_OPENING_ST) &&
            (bta_ag_cb.sco.p_curr_scb == p_scb));
#endif
}

/*******************************************************************************
**
** Function         bta_ag_sco_listen
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_sco_listen(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data)
{
    bta_ag_sco_event(p_scb, BTA_AG_SCO_LISTEN_E);
}

/*******************************************************************************
**
** Function         bta_ag_sco_open
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_sco_open(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data)
{
    UINT8 event;

    /* if another scb using sco, this is a transfer */
    if (bta_ag_cb.sco.p_curr_scb != NULL && bta_ag_cb.sco.p_curr_scb != p_scb)
    {
        event = BTA_AG_SCO_XFER_E;
    }
    /* else it is an open */
    else
    {
        event = BTA_AG_SCO_OPEN_E;
    }

    bta_ag_sco_event(p_scb, event);
}

/*******************************************************************************
**
** Function         bta_ag_sco_close
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_sco_close(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data)
{
    /* if scb is in use */
#if (BTM_WBS_INCLUDED == TRUE )
    /* sco_idx is not allocated in SCO_CODEC_ST, we still need to move to listening state. */
    if ((p_scb->sco_idx != BTM_INVALID_SCO_INDEX) || (bta_ag_cb.sco.state == BTA_AG_SCO_CODEC_ST))
#else
    if (p_scb->sco_idx != BTM_INVALID_SCO_INDEX)
#endif
    {
        APPL_TRACE_DEBUG1("bta_ag_sco_close: sco_inx = %d", p_scb->sco_idx);
        bta_ag_sco_event(p_scb, BTA_AG_SCO_CLOSE_E);
    }
}

#if (BTM_WBS_INCLUDED == TRUE )

/*******************************************************************************
**
** Function         bta_ag_sco_codec_nego
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_sco_codec_nego(tBTA_AG_SCB *p_scb, BOOLEAN result)
{
    if(result == TRUE)
    {
        /* Subsequent sco connection will skip codec negotiation */
        p_scb->codec_updated = FALSE;

        bta_ag_sco_event(p_scb, BTA_AG_SCO_CN_DONE_E);
    }
    else    /* codec negotiation failed */
        bta_ag_sco_event(p_scb, BTA_AG_SCO_CLOSE_E);
}
#endif

/*******************************************************************************
**
** Function         bta_ag_sco_shutdown
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_sco_shutdown(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data)
{
    bta_ag_sco_event(p_scb, BTA_AG_SCO_SHUTDOWN_E);
}

/*******************************************************************************
**
** Function         bta_ag_sco_conn_open
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_sco_conn_open(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data)
{
    bta_ag_sco_event(p_scb, BTA_AG_SCO_CONN_OPEN_E);

    bta_sys_sco_open(BTA_ID_AG, p_scb->app_id, p_scb->peer_addr);

    bta_ag_co_audio_state(bta_ag_scb_to_idx(p_scb), p_scb->app_id, BTA_AG_CO_AUD_STATE_ON);

#if (BTM_SCO_HCI_INCLUDED == TRUE )
    /* open SCO codec if SCO is routed through transport */
    bta_dm_sco_co_open(bta_ag_scb_to_idx(p_scb), BTA_SCO_OUT_PKT_SIZE, BTA_AG_CI_SCO_DATA_EVT);
#endif

    /* call app callback */
    bta_ag_cback_sco(p_scb, BTA_AG_AUDIO_OPEN_EVT);

    p_scb->retry_with_sco_only = FALSE;
}

/*******************************************************************************
**
** Function         bta_ag_sco_conn_close
**
** Description
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_sco_conn_close(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data)
{
    UINT16 handle = bta_ag_scb_to_idx(p_scb);

    /* clear current scb */
    bta_ag_cb.sco.p_curr_scb = NULL;
    p_scb->sco_idx = BTM_INVALID_SCO_INDEX;

#if (BTM_WBS_INCLUDED == TRUE)
    /* codec_fallback is set when AG is initiator and connection failed for mSBC. */
    if (p_scb->codec_fallback && p_scb->svc_conn)
    {
        bta_ag_sco_event(p_scb, BTA_AG_SCO_REOPEN_E);
    }
    else if (p_scb->retry_with_sco_only && p_scb->svc_conn)
    {
        /* retry_with_sco_only is set when AG is initiator and connection failed for eSCO */
        bta_ag_create_sco(p_scb, TRUE);
    }
#else
    /* retry_with_sco_only, will be set only when AG is initiator
    ** and AG is first trying to establish an eSCO connection */
    if (p_scb->retry_with_sco_only && p_scb->svc_conn)
    {
        bta_ag_create_sco(p_scb, TRUE);
    }
#endif
    else
    {
        /* Indicate if the closing of audio is because of transfer */
        if (bta_ag_cb.sco.p_xfer_scb)
            bta_ag_co_audio_state(handle, p_scb->app_id, BTA_AG_CO_AUD_STATE_OFF_XFER);
        else
            bta_ag_co_audio_state(handle, p_scb->app_id, BTA_AG_CO_AUD_STATE_OFF);

        bta_ag_sco_event(p_scb, BTA_AG_SCO_CONN_CLOSE_E);

        bta_sys_sco_close(BTA_ID_AG, p_scb->app_id, p_scb->peer_addr);

        /* if av got suspended by this call, let it resume. */
        /* In case call stays alive regardless of sco, av should not be affected. */
        if(((p_scb->call_ind == BTA_AG_CALL_INACTIVE) && (p_scb->callsetup_ind == BTA_AG_CALLSETUP_NONE))
            || (p_scb->post_sco == BTA_AG_POST_SCO_CALL_END))
        {
            bta_sys_sco_unuse(BTA_ID_AG, p_scb->app_id, p_scb->peer_addr);
        }

        /* call app callback */
        bta_ag_cback_sco(p_scb, BTA_AG_AUDIO_CLOSE_EVT);
    }
    p_scb->retry_with_sco_only = FALSE;
}

/*******************************************************************************
**
** Function         bta_ag_sco_conn_rsp
**
** Description      Process the SCO connection request
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_sco_conn_rsp(tBTA_AG_SCB *p_scb, tBTM_ESCO_CONN_REQ_EVT_DATA *p_data)
{
    tBTM_ESCO_PARAMS    resp;
    UINT8               hci_status = HCI_SUCCESS;
#if (BTM_SCO_HCI_INCLUDED == TRUE )
    tBTA_CODEC_INFO     codec_info = {BTA_SCO_CODEC_PCM};
    UINT32              pcm_sample_rate;
#endif

    if (bta_ag_cb.sco.state == BTA_AG_SCO_LISTEN_ST     ||
        bta_ag_cb.sco.state == BTA_AG_SCO_CLOSE_XFER_ST ||
        bta_ag_cb.sco.state == BTA_AG_SCO_OPEN_XFER_ST)
    {
        /* If script overrided sco parameter by BTA_CMD_SET_ESCO_PARAM */
        if (bta_ag_cb.sco.param_updated)
        {
            resp = bta_ag_cb.sco.params;
        }
        else
        {
            resp.rx_bw = BTM_64KBITS_RATE;
            resp.tx_bw = BTM_64KBITS_RATE;
            resp.max_latency = 10;
            resp.voice_contfmt = 0x60;
            resp.retrans_effort = BTM_ESCO_RETRANS_POWER;

            if (p_data->link_type == BTM_LINK_TYPE_SCO)
            {
                resp.packet_types = (BTM_SCO_LINK_ONLY_MASK          |
                                     BTM_SCO_PKT_TYPES_MASK_NO_2_EV3 |
                                     BTM_SCO_PKT_TYPES_MASK_NO_3_EV3 |
                                     BTM_SCO_PKT_TYPES_MASK_NO_2_EV5 |
                                     BTM_SCO_PKT_TYPES_MASK_NO_3_EV5);
            }
            else    /* Allow controller to use all types available except 5-slot EDR */
            {
                resp.packet_types = (BTM_SCO_LINK_ALL_PKT_MASK |
                                     BTM_SCO_PKT_TYPES_MASK_NO_2_EV5 |
                                     BTM_SCO_PKT_TYPES_MASK_NO_3_EV5);
            }
        }

        /* tell sys to stop av if any */
        bta_sys_sco_use(BTA_ID_AG, p_scb->app_id, p_scb->peer_addr);

        /* Allow any platform specific pre-SCO set up to take place */
        bta_ag_co_audio_state(bta_ag_scb_to_idx(p_scb), p_scb->app_id, BTA_AG_CO_AUD_STATE_SETUP);

#if (BTM_WBS_INCLUDED == TRUE )
        /* When HS initiated SCO, it cannot be WBS. */
        BTM_ConfigI2SPCM (BTM_SCO_CODEC_CVSD, (UINT8)HCI_BRCM_I2SPCM_IS_DEFAULT_ROLE, (UINT8)HCI_BRCM_I2SPCM_SAMPLE_DEFAULT, (UINT8)HCI_BRCM_I2SPCM_CLOCK_DEFAULT);
#endif

#if (BTM_SCO_HCI_INCLUDED == TRUE )
        pcm_sample_rate = BTA_DM_SCO_SAMP_RATE_8K;

        /* initialize SCO setup, no voice setting for AG, data rate <==> sample rate */
        BTM_ConfigScoPath(bta_dm_sco_co_init(pcm_sample_rate, pcm_sample_rate, &codec_info, p_scb->app_id),
            bta_ag_sco_read_cback, NULL, TRUE);
#endif
    }
    else
        hci_status = HCI_ERR_HOST_REJECT_DEVICE;

#if (BTM_WBS_INCLUDED == TRUE )
    /* If SCO open was initiated from HS, it must be CVSD */
    p_scb->inuse_codec = BTA_AG_CODEC_NONE;
#endif

    BTM_EScoConnRsp(p_data->sco_inx, hci_status, &resp);
}

/*******************************************************************************
**
** Function         bta_ag_ci_sco_data
**
** Description      Process the SCO data ready callin event
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_ci_sco_data(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data)
{
#if (BTM_SCO_HCI_INCLUDED == TRUE )
    bta_ag_sco_event(p_scb, BTA_AG_SCO_CI_DATA_E);
#endif
}

/*******************************************************************************
**
** Function         bta_ag_set_esco_param
**
** Description      Update esco parameters from script wrapper.
**
**
** Returns          void
**
*******************************************************************************/
void bta_ag_set_esco_param(BOOLEAN set_reset, tBTM_ESCO_PARAMS *param)
{
    if(set_reset == FALSE)    /* reset the parameters to default */
    {
        bta_ag_cb.sco.param_updated = FALSE;
        APPL_TRACE_DEBUG0("bta_ag_set_esco_param : Resetting ESCO parameters to default");
    }
    else
    {
        bta_ag_cb.sco.param_updated = TRUE;
        bta_ag_cb.sco.params = *param;
        APPL_TRACE_DEBUG0("bta_ag_set_esco_param : Setting ESCO parameters");
    }
}

/*******************************************************************************
**  Debugging functions
*******************************************************************************/

#if BTA_AG_SCO_DEBUG == TRUE
static char *bta_ag_sco_evt_str(UINT8 event)
{
    switch (event)
    {
    case BTA_AG_SCO_LISTEN_E:
        return "Listen Request";
    case BTA_AG_SCO_OPEN_E:
        return "Open Request";
    case BTA_AG_SCO_XFER_E:
        return "Transfer Request";
#if (BTM_WBS_INCLUDED == TRUE )
    case BTA_AG_SCO_CN_DONE_E:
        return "Codec Negotiation Done";
    case BTA_AG_SCO_REOPEN_E:
        return "Reopen Request";
#endif
    case BTA_AG_SCO_CLOSE_E:
        return "Close Request";
    case BTA_AG_SCO_SHUTDOWN_E:
        return "Shutdown Request";
    case BTA_AG_SCO_CONN_OPEN_E:
        return "Opened";
    case BTA_AG_SCO_CONN_CLOSE_E:
        return "Closed";
    case BTA_AG_SCO_CI_DATA_E  :
        return "Sco Data";
    default:
        return "Unknown SCO Event";
    }
}

static char *bta_ag_sco_state_str(UINT8 state)
{
    switch (state)
    {
    case BTA_AG_SCO_SHUTDOWN_ST:
        return "Shutdown";
    case BTA_AG_SCO_LISTEN_ST:
        return "Listening";
#if (BTM_WBS_INCLUDED == TRUE )
    case BTA_AG_SCO_CODEC_ST:
        return "Codec Negotiation";
#endif
    case BTA_AG_SCO_OPENING_ST:
        return "Opening";
    case BTA_AG_SCO_OPEN_CL_ST:
        return "Open while closing";
    case BTA_AG_SCO_OPEN_XFER_ST:
        return "Opening while Transferring";
    case BTA_AG_SCO_OPEN_ST:
        return "Open";
    case BTA_AG_SCO_CLOSING_ST:
        return "Closing";
    case BTA_AG_SCO_CLOSE_OP_ST:
        return "Close while Opening";
    case BTA_AG_SCO_CLOSE_XFER_ST:
        return "Close while Transferring";
    case BTA_AG_SCO_SHUTTING_ST:
        return "Shutting Down";
    default:
        return "Unknown SCO State";
    }
}

#endif
