/****************************************************************************** * * Copyright (c) 2014 The Android Open Source Project * Copyright 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. * ******************************************************************************/ #include <string.h> #include "bt_trace.h" #include "bt_utils.h" #include "bta_ag_api.h" #include "bta_hf_client_int.h" #include "device/include/esco_parameters.h" #include "osi/include/osi.h" #define BTA_HF_CLIENT_NO_EDR_ESCO \ (ESCO_PKT_TYPES_MASK_NO_2_EV3 | ESCO_PKT_TYPES_MASK_NO_3_EV3 | \ ESCO_PKT_TYPES_MASK_NO_2_EV5 | ESCO_PKT_TYPES_MASK_NO_3_EV5) enum { BTA_HF_CLIENT_SCO_LISTEN_E, BTA_HF_CLIENT_SCO_OPEN_E, /* open request */ BTA_HF_CLIENT_SCO_CLOSE_E, /* close request */ BTA_HF_CLIENT_SCO_SHUTDOWN_E, /* shutdown request */ BTA_HF_CLIENT_SCO_CONN_OPEN_E, /* SCO opened */ BTA_HF_CLIENT_SCO_CONN_CLOSE_E, /* SCO closed */ }; /******************************************************************************* * * Function bta_hf_client_remove_sco * * Description Removes the specified SCO from the system. * * Returns bool - true if SCO removal was started * ******************************************************************************/ static bool bta_hf_client_sco_remove(tBTA_HF_CLIENT_CB* client_cb) { bool removed_started = false; tBTM_STATUS status; APPL_TRACE_DEBUG("%s", __func__); if (client_cb->sco_idx != BTM_INVALID_SCO_INDEX) { status = BTM_RemoveSco(client_cb->sco_idx); APPL_TRACE_DEBUG("%s: idx 0x%04x, status:0x%x", __func__, client_cb->sco_idx, status); if (status == BTM_CMD_STARTED) { removed_started = true; } /* If no connection reset the SCO handle */ else if ((status == BTM_SUCCESS) || (status == BTM_UNKNOWN_ADDR)) { client_cb->sco_idx = BTM_INVALID_SCO_INDEX; } } return removed_started; } /******************************************************************************* * * Function bta_hf_client_cback_sco * * Description Call application callback function with SCO event. * * * Returns void * ******************************************************************************/ void bta_hf_client_cback_sco(tBTA_HF_CLIENT_CB* client_cb, uint8_t event) { tBTA_HF_CLIENT evt; memset(&evt, 0, sizeof(evt)); evt.bd_addr = client_cb->peer_addr; /* call app cback */ bta_hf_client_app_callback(event, &evt); } /******************************************************************************* * * Function bta_hf_client_sco_conn_rsp * * Description Process the SCO connection request * * * Returns void * ******************************************************************************/ static void bta_hf_client_sco_conn_rsp(tBTA_HF_CLIENT_CB* client_cb, tBTM_ESCO_CONN_REQ_EVT_DATA* p_data) { enh_esco_params_t resp; uint8_t hci_status = HCI_SUCCESS; APPL_TRACE_DEBUG("%s", __func__); if (client_cb->sco_state == BTA_HF_CLIENT_SCO_LISTEN_ST) { if (p_data->link_type == BTM_LINK_TYPE_SCO) { resp = esco_parameters_for_codec(ESCO_CODEC_CVSD); } else { if (client_cb->negotiated_codec == BTA_AG_CODEC_MSBC) { resp = esco_parameters_for_codec(ESCO_CODEC_MSBC_T1); } else { // default codec resp = esco_parameters_for_codec(ESCO_CODEC_CVSD); } } /* tell sys to stop av if any */ bta_sys_sco_use(BTA_ID_HS, 1, client_cb->peer_addr); } else { hci_status = HCI_ERR_HOST_REJECT_DEVICE; } BTM_EScoConnRsp(p_data->sco_inx, hci_status, &resp); } /******************************************************************************* * * Function bta_hf_client_sco_connreq_cback * * Description BTM eSCO connection requests and eSCO change requests * Only the connection requests are processed by BTA. * * Returns void * ******************************************************************************/ static void bta_hf_client_esco_connreq_cback(tBTM_ESCO_EVT event, tBTM_ESCO_EVT_DATA* p_data) { APPL_TRACE_DEBUG("%s: %d", __func__, event); tBTA_HF_CLIENT_CB* client_cb = bta_hf_client_find_cb_by_sco_handle(p_data->conn_evt.sco_inx); if (client_cb == NULL) { APPL_TRACE_ERROR("%s: wrong SCO handle to control block %d", __func__, p_data->conn_evt.sco_inx); return; } if (event != BTM_ESCO_CONN_REQ_EVT) { return; } bta_hf_client_sco_conn_rsp(client_cb, &p_data->conn_evt); client_cb->sco_state = BTA_HF_CLIENT_SCO_OPENING_ST; } /******************************************************************************* * * Function bta_hf_client_sco_conn_cback * * Description BTM SCO connection callback. * * * Returns void * ******************************************************************************/ static void bta_hf_client_sco_conn_cback(uint16_t sco_idx) { APPL_TRACE_DEBUG("%s: %d", __func__, sco_idx); tBTA_HF_CLIENT_CB* client_cb = bta_hf_client_find_cb_by_sco_handle(sco_idx); if (client_cb == NULL) { APPL_TRACE_ERROR("%s: wrong SCO handle to control block %d", __func__, sco_idx); return; } BT_HDR* p_buf = (BT_HDR*)osi_malloc(sizeof(BT_HDR)); p_buf->event = BTA_HF_CLIENT_SCO_OPEN_EVT; p_buf->layer_specific = client_cb->handle; bta_sys_sendmsg(p_buf); } /******************************************************************************* * * Function bta_hf_client_sco_disc_cback * * Description BTM SCO disconnection callback. * * * Returns void * ******************************************************************************/ static void bta_hf_client_sco_disc_cback(uint16_t sco_idx) { APPL_TRACE_DEBUG("%s: sco_idx %d", __func__, sco_idx); tBTA_HF_CLIENT_CB* client_cb = bta_hf_client_find_cb_by_sco_handle(sco_idx); if (client_cb == NULL) { APPL_TRACE_ERROR("%s: wrong handle to control block %d", __func__, sco_idx); return; } BT_HDR* p_buf = (BT_HDR*)osi_malloc(sizeof(BT_HDR)); p_buf->event = BTA_HF_CLIENT_SCO_CLOSE_EVT; p_buf->layer_specific = client_cb->handle; bta_sys_sendmsg(p_buf); } /******************************************************************************* * * Function bta_hf_client_create_sco * * Description * * * Returns void * ******************************************************************************/ static void bta_hf_client_sco_create(tBTA_HF_CLIENT_CB* client_cb, bool is_orig) { tBTM_STATUS status; APPL_TRACE_DEBUG("%s: %d", __func__, is_orig); /* Make sure this SCO handle is not already in use */ if (client_cb->sco_idx != BTM_INVALID_SCO_INDEX) { APPL_TRACE_WARNING("%s: Index 0x%04x already in use", __func__, client_cb->sco_idx); return; } enh_esco_params_t params = esco_parameters_for_codec(ESCO_CODEC_CVSD); /* if initiating set current scb and peer bd addr */ if (is_orig) { BTM_SetEScoMode(¶ms); /* tell sys to stop av if any */ bta_sys_sco_use(BTA_ID_HS, 1, client_cb->peer_addr); } status = BTM_CreateSco(&client_cb->peer_addr, is_orig, params.packet_types, &client_cb->sco_idx, bta_hf_client_sco_conn_cback, bta_hf_client_sco_disc_cback); if (status == BTM_CMD_STARTED && !is_orig) { if (!BTM_RegForEScoEvts(client_cb->sco_idx, bta_hf_client_esco_connreq_cback)) APPL_TRACE_DEBUG("%s: SCO registration success", __func__); } APPL_TRACE_API("%s: orig %d, inx 0x%04x, status 0x%x, pkt types 0x%04x", __func__, is_orig, client_cb->sco_idx, status, params.packet_types); } /******************************************************************************* * * Function bta_hf_client_sco_event * * Description Handle SCO events * * * Returns void * ******************************************************************************/ static void bta_hf_client_sco_event(tBTA_HF_CLIENT_CB* client_cb, uint8_t event) { APPL_TRACE_DEBUG("%s: before state: %d event: %d", __func__, client_cb->sco_state, event); switch (client_cb->sco_state) { case BTA_HF_CLIENT_SCO_SHUTDOWN_ST: switch (event) { // For WBS we only listen to SCO requests. Even for outgoing SCO // requests we first do a AT+BCC and wait for remote to initiate SCO case BTA_HF_CLIENT_SCO_LISTEN_E: /* create SCO listen connection */ bta_hf_client_sco_create(client_cb, false); client_cb->sco_state = BTA_HF_CLIENT_SCO_LISTEN_ST; break; // For non WBS cases and enabling outgoing SCO requests we need to force // open a SCO channel case BTA_HF_CLIENT_SCO_OPEN_E: /* remove listening connection */ bta_hf_client_sco_remove(client_cb); /* create SCO connection to peer */ bta_hf_client_sco_create(client_cb, true); client_cb->sco_state = BTA_HF_CLIENT_SCO_OPENING_ST; break; default: APPL_TRACE_WARNING("BTA_HF_CLIENT_SCO_SHUTDOWN_ST: Ignoring event %d", event); break; } break; case BTA_HF_CLIENT_SCO_LISTEN_ST: switch (event) { case BTA_HF_CLIENT_SCO_LISTEN_E: /* create SCO listen connection */ bta_hf_client_sco_create(client_cb, false); case BTA_HF_CLIENT_SCO_OPEN_E: /* remove listening connection */ bta_hf_client_sco_remove(client_cb); /* create SCO connection to peer */ bta_hf_client_sco_create(client_cb, true); client_cb->sco_state = BTA_HF_CLIENT_SCO_OPENING_ST; break; case BTA_HF_CLIENT_SCO_SHUTDOWN_E: case BTA_HF_CLIENT_SCO_CLOSE_E: /* remove listening connection */ bta_hf_client_sco_remove(client_cb); client_cb->sco_state = BTA_HF_CLIENT_SCO_SHUTDOWN_ST; break; case BTA_HF_CLIENT_SCO_CONN_CLOSE_E: /* SCO failed; create SCO listen connection */ bta_hf_client_sco_create(client_cb, false); client_cb->sco_state = BTA_HF_CLIENT_SCO_LISTEN_ST; break; default: APPL_TRACE_WARNING( "%s: BTA_HF_CLIENT_SCO_LISTEN_ST: Ignoring event %d", __func__, event); break; } break; case BTA_HF_CLIENT_SCO_OPENING_ST: switch (event) { case BTA_HF_CLIENT_SCO_CLOSE_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_OPEN_CL_ST; break; case BTA_HF_CLIENT_SCO_SHUTDOWN_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_SHUTTING_ST; break; case BTA_HF_CLIENT_SCO_CONN_OPEN_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_OPEN_ST; break; case BTA_HF_CLIENT_SCO_CONN_CLOSE_E: /* SCO failed; create SCO listen connection */ bta_hf_client_sco_create(client_cb, false); client_cb->sco_state = BTA_HF_CLIENT_SCO_LISTEN_ST; break; default: APPL_TRACE_WARNING("BTA_HF_CLIENT_SCO_OPENING_ST: Ignoring event %d", event); break; } break; case BTA_HF_CLIENT_SCO_OPEN_CL_ST: switch (event) { case BTA_HF_CLIENT_SCO_OPEN_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_OPENING_ST; break; case BTA_HF_CLIENT_SCO_SHUTDOWN_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_SHUTTING_ST; break; case BTA_HF_CLIENT_SCO_CONN_OPEN_E: /* close SCO connection */ bta_hf_client_sco_remove(client_cb); client_cb->sco_state = BTA_HF_CLIENT_SCO_CLOSING_ST; break; case BTA_HF_CLIENT_SCO_CONN_CLOSE_E: /* SCO failed; create SCO listen connection */ client_cb->sco_state = BTA_HF_CLIENT_SCO_LISTEN_ST; break; default: APPL_TRACE_WARNING("BTA_HF_CLIENT_SCO_OPEN_CL_ST: Ignoring event %d", event); break; } break; case BTA_HF_CLIENT_SCO_OPEN_ST: switch (event) { case BTA_HF_CLIENT_SCO_CLOSE_E: if (bta_hf_client_sco_remove(client_cb)) { client_cb->sco_state = BTA_HF_CLIENT_SCO_CLOSING_ST; } break; case BTA_HF_CLIENT_SCO_SHUTDOWN_E: /* remove listening connection */ bta_hf_client_sco_remove(client_cb); client_cb->sco_state = BTA_HF_CLIENT_SCO_SHUTTING_ST; break; case BTA_HF_CLIENT_SCO_CONN_CLOSE_E: /* peer closed SCO */ bta_hf_client_sco_create(client_cb, false); client_cb->sco_state = BTA_HF_CLIENT_SCO_LISTEN_ST; break; default: APPL_TRACE_WARNING("BTA_HF_CLIENT_SCO_OPEN_ST: Ignoring event %d", event); break; } break; case BTA_HF_CLIENT_SCO_CLOSING_ST: switch (event) { case BTA_HF_CLIENT_SCO_OPEN_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_CLOSE_OP_ST; break; case BTA_HF_CLIENT_SCO_SHUTDOWN_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_SHUTTING_ST; break; case BTA_HF_CLIENT_SCO_CONN_CLOSE_E: /* peer closed sco; create SCO listen connection */ bta_hf_client_sco_create(client_cb, false); client_cb->sco_state = BTA_HF_CLIENT_SCO_LISTEN_ST; break; default: APPL_TRACE_WARNING("BTA_HF_CLIENT_SCO_CLOSING_ST: Ignoring event %d", event); break; } break; case BTA_HF_CLIENT_SCO_CLOSE_OP_ST: switch (event) { case BTA_HF_CLIENT_SCO_CLOSE_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_CLOSING_ST; break; case BTA_HF_CLIENT_SCO_SHUTDOWN_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_SHUTTING_ST; break; case BTA_HF_CLIENT_SCO_CONN_CLOSE_E: /* open SCO connection */ bta_hf_client_sco_create(client_cb, true); client_cb->sco_state = BTA_HF_CLIENT_SCO_OPENING_ST; break; default: APPL_TRACE_WARNING("BTA_HF_CLIENT_SCO_CLOSE_OP_ST: Ignoring event %d", event); break; } break; case BTA_HF_CLIENT_SCO_SHUTTING_ST: switch (event) { case BTA_HF_CLIENT_SCO_CONN_OPEN_E: /* close SCO connection; wait for conn close event */ bta_hf_client_sco_remove(client_cb); break; case BTA_HF_CLIENT_SCO_CONN_CLOSE_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_SHUTDOWN_ST; break; case BTA_HF_CLIENT_SCO_SHUTDOWN_E: client_cb->sco_state = BTA_HF_CLIENT_SCO_SHUTDOWN_ST; break; default: APPL_TRACE_WARNING("BTA_HF_CLIENT_SCO_SHUTTING_ST: Ignoring event %d", event); break; } break; default: break; } APPL_TRACE_DEBUG("%s: after state: %d", __func__, client_cb->sco_state); } /******************************************************************************* * * Function bta_hf_client_sco_listen * * Description Initialize SCO listener * * * Returns void * ******************************************************************************/ void bta_hf_client_sco_listen(tBTA_HF_CLIENT_DATA* p_data) { APPL_TRACE_DEBUG("%s", __func__); tBTA_HF_CLIENT_CB* client_cb = bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific); if (client_cb == NULL) { APPL_TRACE_ERROR("%s: wrong handle to control block %d", __func__, p_data->hdr.layer_specific); return; } bta_hf_client_sco_event(client_cb, BTA_HF_CLIENT_SCO_LISTEN_E); } /******************************************************************************* * * Function bta_hf_client_sco_shutdown * * Description * * * Returns void * ******************************************************************************/ void bta_hf_client_sco_shutdown(tBTA_HF_CLIENT_CB* client_cb) { APPL_TRACE_DEBUG("%s", __func__); bta_hf_client_sco_event(client_cb, BTA_HF_CLIENT_SCO_SHUTDOWN_E); } /******************************************************************************* * * Function bta_hf_client_sco_conn_open * * Description * * * Returns void * ******************************************************************************/ void bta_hf_client_sco_conn_open(tBTA_HF_CLIENT_DATA* p_data) { APPL_TRACE_DEBUG("%s", __func__); tBTA_HF_CLIENT_CB* client_cb = bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific); if (client_cb == NULL) { APPL_TRACE_ERROR("%s: wrong handle to control block %d", __func__, p_data->hdr.layer_specific); return; } bta_hf_client_sco_event(client_cb, BTA_HF_CLIENT_SCO_CONN_OPEN_E); bta_sys_sco_open(BTA_ID_HS, 1, client_cb->peer_addr); if (client_cb->negotiated_codec == BTM_SCO_CODEC_MSBC) { bta_hf_client_cback_sco(client_cb, BTA_HF_CLIENT_AUDIO_MSBC_OPEN_EVT); } else { bta_hf_client_cback_sco(client_cb, BTA_HF_CLIENT_AUDIO_OPEN_EVT); } } /******************************************************************************* * * Function bta_hf_client_sco_conn_close * * Description * * * Returns void * ******************************************************************************/ void bta_hf_client_sco_conn_close(tBTA_HF_CLIENT_DATA* p_data) { APPL_TRACE_DEBUG("%s", __func__); tBTA_HF_CLIENT_CB* client_cb = bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific); if (client_cb == NULL) { APPL_TRACE_ERROR("%s: wrong handle to control block %d", __func__, p_data->hdr.layer_specific); return; } /* clear current scb */ client_cb->sco_idx = BTM_INVALID_SCO_INDEX; bta_hf_client_sco_event(client_cb, BTA_HF_CLIENT_SCO_CONN_CLOSE_E); bta_sys_sco_close(BTA_ID_HS, 1, client_cb->peer_addr); bta_sys_sco_unuse(BTA_ID_HS, 1, client_cb->peer_addr); /* call app callback */ bta_hf_client_cback_sco(client_cb, BTA_HF_CLIENT_AUDIO_CLOSE_EVT); if (client_cb->sco_close_rfc) { client_cb->sco_close_rfc = false; bta_hf_client_rfc_do_close(p_data); } } /******************************************************************************* * * Function bta_hf_client_sco_open * * Description * * * Returns void * ******************************************************************************/ void bta_hf_client_sco_open(tBTA_HF_CLIENT_DATA* p_data) { APPL_TRACE_DEBUG("%s", __func__); tBTA_HF_CLIENT_CB* client_cb = bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific); if (client_cb == NULL) { APPL_TRACE_ERROR("%s: wrong handle to control block %d", __func__, p_data->hdr.layer_specific); return; } bta_hf_client_sco_event(client_cb, BTA_HF_CLIENT_SCO_OPEN_E); } /******************************************************************************* * * Function bta_hf_client_sco_close * * Description * * * Returns void * ******************************************************************************/ void bta_hf_client_sco_close(tBTA_HF_CLIENT_DATA* p_data) { tBTA_HF_CLIENT_CB* client_cb = bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific); if (client_cb == NULL) { APPL_TRACE_ERROR("%s: wrong handle to control block %d", __func__, p_data->hdr.layer_specific); return; } APPL_TRACE_DEBUG("%s: sco_idx 0x%x", __func__, client_cb->sco_idx); if (client_cb->sco_idx != BTM_INVALID_SCO_INDEX) { bta_hf_client_sco_event(client_cb, BTA_HF_CLIENT_SCO_CLOSE_E); } }