/******************************************************************************
 *
 *  Copyright (c) 2014 The Android Open Source Project
 *  Copyright (C) 2003-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 action functions for the handsfree client.
 *
 ******************************************************************************/

#include <string.h>
#include "bt_utils.h"
#include "bta_api.h"
#include "bta_dm_api.h"
#include "bta_hf_client_api.h"
#include "bta_hf_client_int.h"
#include "bta_sys.h"
#include "l2c_api.h"
#include "osi/include/compat.h"
#include "osi/include/osi.h"
#include "port_api.h"
#include "utl.h"

/*****************************************************************************
 *  Constants
 ****************************************************************************/

/* maximum length of data to read from RFCOMM */
#define BTA_HF_CLIENT_RFC_READ_MAX 512

/*******************************************************************************
 *
 * Function         bta_hf_client_start_close
 *
 * Description      Start the process of closing SCO and RFCOMM connection.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_start_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;
  }

  /* Take the link out of sniff and set L2C idle time to 0 */
  bta_dm_pm_active(client_cb->peer_addr);
  L2CA_SetIdleTimeoutByBdAddr(client_cb->peer_addr, 0, BT_TRANSPORT_BR_EDR);

  /* if SCO is open close SCO and wait on RFCOMM close */
  if (client_cb->sco_state == BTA_HF_CLIENT_SCO_OPEN_ST) {
    client_cb->sco_close_rfc = true;
  } else {
    bta_hf_client_rfc_do_close(p_data);
  }

  /* always do SCO shutdown to handle all SCO corner cases */
  bta_hf_client_sco_shutdown(client_cb);
}

/*******************************************************************************
 *
 * Function         bta_hf_client_start_open
 *
 * Description      This starts an HF Client open.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_start_open(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;
  }

  /* store parameters */
  if (p_data) {
    bdcpy(client_cb->peer_addr, p_data->api_open.bd_addr);
    client_cb->cli_sec_mask = p_data->api_open.sec_mask;
  }

  /* Check if RFCOMM has any incoming connection to avoid collision. */
  BD_ADDR pending_bd_addr;
  if (PORT_IsOpening(pending_bd_addr)) {
    /* Let the incoming connection goes through.                        */
    /* Issue collision for now.                                         */
    /* We will decide what to do when we find incoming connection later.*/
    bta_hf_client_collision_cback(0, BTA_ID_HS, 0, client_cb->peer_addr);
    return;
  }

  /* set role */
  client_cb->role = BTA_HF_CLIENT_INT;

  /* do service search */
  bta_hf_client_do_disc(client_cb);
}

/*******************************************************************************
 *
 * Function         bta_hf_client_rfc_open
 *
 * Description      Handle RFCOMM channel open.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_rfc_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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }

  bta_sys_conn_open(BTA_ID_HS, 1, client_cb->peer_addr);

  /* start SLC procedure */
  bta_hf_client_slc_seq(client_cb, false);
}

/*******************************************************************************
 *
 * Function         bta_hf_client_rfc_acp_open
 *
 * Description      Handle RFCOMM channel open when accepting connection.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_rfc_acp_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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }

  uint16_t lcid;
  BD_ADDR dev_addr;
  int status;

  /* set role */
  client_cb->role = BTA_HF_CLIENT_ACP;

  APPL_TRACE_DEBUG("%s: conn_handle %d", __func__, client_cb->conn_handle);

  /* get bd addr of peer */
  if (PORT_SUCCESS != (status = PORT_CheckConnection(client_cb->conn_handle,
                                                     dev_addr, &lcid))) {
    APPL_TRACE_DEBUG("%s: error PORT_CheckConnection returned status %d",
                     __func__, status);
  }

  /* Collision Handling */
  if (alarm_is_scheduled(client_cb->collision_timer)) {
    alarm_cancel(client_cb->collision_timer);

    if (bdcmp(dev_addr, client_cb->peer_addr) == 0) {
      /* If incoming and outgoing device are same, nothing more to do. */
      /* Outgoing conn will be aborted because we have successful incoming conn.
       */
    } else {
      /* Resume outgoing connection. */
      bta_hf_client_resume_open(client_cb);
    }
  }

  bdcpy(client_cb->peer_addr, dev_addr);

  /* do service discovery to get features */
  bta_hf_client_do_disc(client_cb);

  /* continue with open processing */
  bta_hf_client_rfc_open(p_data);
}

/*******************************************************************************
 *
 * Function         bta_hf_client_rfc_fail
 *
 * Description      RFCOMM connection failed.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_rfc_fail(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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }

  /* reinitialize stuff */
  client_cb->peer_features = 0;
  client_cb->chld_features = 0;
  client_cb->role = BTA_HF_CLIENT_ACP;
  client_cb->svc_conn = false;
  client_cb->send_at_reply = false;
  client_cb->negotiated_codec = BTM_SCO_CODEC_CVSD;

  bta_hf_client_at_reset(client_cb);
}

/*******************************************************************************
 *
 * Function         bta_hf_client_disc_fail
 *
 * Description      This function handles a discovery failure.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_disc_fail(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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }
}

/*******************************************************************************
 *
 * Function         bta_hf_client_open_fail
 *
 * Description      open connection failed.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_open_fail(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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }
}

/*******************************************************************************
 *
 * Function         bta_hf_client_rfc_close
 *
 * Description      RFCOMM connection closed.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_rfc_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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }

  bta_hf_client_at_reset(client_cb);

  bta_sys_conn_close(BTA_ID_HS, 1, client_cb->peer_addr);

  /* call close cback */
  tBTA_HF_CLIENT evt;
  memset(&evt, 0, sizeof(evt));
  bdcpy(evt.conn.bd_addr, client_cb->peer_addr);

  /* if not deregistering reopen server */
  if (bta_hf_client_cb_arr.deregister == false) {
    /* Make sure SCO is shutdown */
    bta_hf_client_sco_shutdown(client_cb);

    bta_sys_sco_unuse(BTA_ID_HS, 1, client_cb->peer_addr);
  }
  /* else close port and deallocate scb */
  else {
    tBTA_HF_CLIENT evt;
    memset(&evt, 0, sizeof(evt));
    bdcpy(evt.reg.bd_addr, client_cb->peer_addr);
    bta_hf_client_app_callback(BTA_HF_CLIENT_DISABLE_EVT, &evt);
  }
}

/*******************************************************************************
 *
 * Function         bta_hf_client_disc_int_res
 *
 * Description      This function handles a discovery result when initiator.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_disc_int_res(tBTA_HF_CLIENT_DATA* p_data) {
  uint16_t event = BTA_HF_CLIENT_DISC_FAIL_EVT;

  APPL_TRACE_DEBUG("%s: Status: %d", __func__, p_data->disc_result.status);
  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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }

  /* if found service */
  if (p_data->disc_result.status == SDP_SUCCESS ||
      p_data->disc_result.status == SDP_DB_FULL) {
    /* get attributes */
    if (bta_hf_client_sdp_find_attr(client_cb)) {
      event = BTA_HF_CLIENT_DISC_OK_EVT;
    }
  }

  /* free discovery db */
  bta_hf_client_free_db(p_data);

  /* send ourselves sdp ok/fail event */
  bta_hf_client_sm_execute(event, p_data);
}

/*******************************************************************************
 *
 * Function         bta_hf_client_disc_acp_res
 *
 * Description      This function handles a discovery result when acceptor.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_disc_acp_res(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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }

  /* if found service */
  if (p_data->disc_result.status == SDP_SUCCESS ||
      p_data->disc_result.status == SDP_DB_FULL) {
    /* get attributes */
    bta_hf_client_sdp_find_attr(client_cb);
  }

  /* free discovery db */
  bta_hf_client_free_db(p_data);
}

/*******************************************************************************
 *
 * Function         bta_hf_client_rfc_data
 *
 * Description      Read and process data from RFCOMM.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_rfc_data(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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }

  uint16_t len;
  char buf[BTA_HF_CLIENT_RFC_READ_MAX];
  memset(buf, 0, sizeof(buf));
  /* read data from rfcomm; if bad status, we're done */
  while (PORT_ReadData(client_cb->conn_handle, buf, BTA_HF_CLIENT_RFC_READ_MAX,
                       &len) == PORT_SUCCESS) {
    /* if no data, we're done */
    if (len == 0) {
      break;
    }

    bta_hf_client_at_parse(client_cb, buf, len);

    /* no more data to read, we're done */
    if (len < BTA_HF_CLIENT_RFC_READ_MAX) {
      break;
    }
  }
}

/*******************************************************************************
 *
 * Function         bta_hf_client_svc_conn_open
 *
 * Description      Service level connection opened
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_svc_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: cb not found for handle %d", __func__,
                     p_data->hdr.layer_specific);
    return;
  }

  tBTA_HF_CLIENT evt;

  memset(&evt, 0, sizeof(evt));

  if (!client_cb->svc_conn) {
    /* set state variable */
    client_cb->svc_conn = true;

    /* call callback */
    bdcpy(evt.conn.bd_addr, client_cb->peer_addr);
    evt.conn.peer_feat = client_cb->peer_features;
    evt.conn.chld_feat = client_cb->chld_features;

    bta_hf_client_app_callback(BTA_HF_CLIENT_CONN_EVT, &evt);
  }
}
