/******************************************************************************
 *
 *  Copyright (C) 2016 The Android Open Source Project
 *  Copyright (C) 2009-2012 Broadcom Corporation
 *  Copyright (C) 2019 Blake Felt
 *
 *  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.
 *
 ******************************************************************************/
/************************************************************************************
 *
 *  Filename:      btc_hd.c
 *
 *  Description:   HID Device Profile Bluetooth Interface
 *
 *
 ***********************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "btc/btc_util.h"
#include "btc/btc_manage.h"
#include "device/bdaddr.h"
#include "btc/btc_storage.h"
#include "osi/allocator.h"
#include "bta/utl.h"
#include "bta/bta_hh_api.h"
#include "stack/l2c_api.h"
// #include "bta_dm_int.h"

#if HID_HOST_INCLUDED == TRUE
#include "btc_hh.h"


/*******************************************************************************
 *  Static variables
 ******************************************************************************/
btc_hh_cb_t btc_hh_cb;
static bdstr_t bdstr;

/******************************************************************************
 *  Constants & Macros
 *****************************************************************************/
#define COD_MASK 0x07FF

#define COD_UNCLASSIFIED ((0x1F) << 8)
#define COD_HID_KEYBOARD 0x0540
#define COD_HID_POINTING 0x0580
#define COD_HID_COMBO 0x05C0
#define COD_HID_MAJOR 0x0500
#define COD_HID_MASK 0x0700

#define is_hidh_init() (btc_hh_cb.status > BTC_HH_DISABLED)
#define BTC_TIMEOUT_VUP_MS (3 * 1000)

static inline void btc_hh_cb_to_app(esp_hidh_cb_event_t event, esp_hidh_cb_param_t *param)
{
    esp_hh_cb_t btc_hh_cb = (esp_hh_cb_t)btc_profile_cb_get(BTC_PID_HH);
    if (btc_hh_cb) {
        btc_hh_cb(event, param);
    }
}

/*******************************************************************************
 *
 * Function         proto_mode_change_to_lower_layer
 *
 * Description      Change the upper layer protocol mode definition to the lower layer protocol mode definition
 *
 * Returns          Lower layer protocol mode definition
 ******************************************************************************/
static inline tBTA_HH_PROTO_MODE proto_mode_change_to_lower_layer(esp_hidh_protocol_mode_t protocol_mode)
{
    tBTA_HH_PROTO_MODE proto_mode = BTA_HH_PROTO_UNKNOWN;

    switch (protocol_mode) {
    case ESP_HIDH_REPORT_MODE:
        proto_mode = BTA_HH_PROTO_RPT_MODE;
        break;
    case ESP_HIDH_BOOT_MODE:
        proto_mode = BTA_HH_PROTO_BOOT_MODE;
        break;
    default:
        break;
    }

    return proto_mode;
}

/*******************************************************************************
 *
 * Function         proto_mode_change_to_upper_layer
 *
 * Description      Change the lower layer protocol mode definition to the upper layer protocol mode definition
 *
 * Returns          Upper layer protocol mode definition
 ******************************************************************************/
static inline esp_hidh_protocol_mode_t proto_mode_change_to_upper_layer(tBTA_HH_PROTO_MODE proto_mode)
{
    esp_hidh_protocol_mode_t protocol_mode = ESP_HIDH_UNSUPPORTED_MODE;

    switch (proto_mode) {
    case BTA_HH_PROTO_RPT_MODE:
        protocol_mode = ESP_HIDH_REPORT_MODE;
        break;
    case BTA_HH_PROTO_BOOT_MODE:
        protocol_mode = ESP_HIDH_BOOT_MODE;
        break;
    default:
        break;
    }

    return protocol_mode;
}

/*******************************************************************************
 *
 * Function         btc_hh_find_connected_dev_by_handle
 *
 * Description      Return the connected device pointer of the specified device
 *                  handle
 *
 * Returns          Device entry pointer in the device table
 ******************************************************************************/
btc_hh_device_t *btc_hh_find_connected_dev_by_handle(uint8_t handle)
{
    uint32_t i;
    for (i = 0; i < BTC_HH_MAX_HID; i++) {
        if (btc_hh_cb.devices[i].dev_status == ESP_HIDH_CONN_STATE_CONNECTED && btc_hh_cb.devices[i].dev_handle == handle) {
            return &btc_hh_cb.devices[i];
        }
    }
    return NULL;
}

/*******************************************************************************
 *
 * Function         btc_hh_find_dev_by_bda
 *
 * Description      Return the device pointer of the specified bd_addr.
 *
 * Returns          Device entry pointer in the device table
 ******************************************************************************/
static btc_hh_device_t *btc_hh_find_dev_by_bda(BD_ADDR bd_addr)
{
    uint8_t i;
    for (i = 0; i < BTC_HH_MAX_HID; i++) {
        if (btc_hh_cb.devices[i].dev_status != ESP_HIDH_CONN_STATE_UNKNOWN &&
            memcmp(btc_hh_cb.devices[i].bd_addr, bd_addr, BD_ADDR_LEN) == 0) {
            return &btc_hh_cb.devices[i];
        }
    }
    return NULL;
}

/*******************************************************************************
 *
 * Function         btc_hh_find_connected_dev_by_bda
 *
 * Description      Return the connected device pointer of the specified
 *                  RawAddress.
 *
 * Returns          Device entry pointer in the device table
 ******************************************************************************/
static btc_hh_device_t *btc_hh_find_connected_dev_by_bda(BD_ADDR bd_addr)
{
    uint32_t i;
    for (i = 0; i < BTC_HH_MAX_HID; i++) {
        if (btc_hh_cb.devices[i].dev_status == ESP_HIDH_CONN_STATE_CONNECTED &&
            memcmp(btc_hh_cb.devices[i].bd_addr, bd_addr, BD_ADDR_LEN) == 0) {
            return &btc_hh_cb.devices[i];
        }
    }
    return NULL;
}

/*******************************************************************************
 *
 * Function         btc_hh_stop_vup_timer
 *
 * Description      stop virtual unplug timer
 *
 * Returns          void
 ******************************************************************************/
void btc_hh_stop_vup_timer(BD_ADDR bd_addr)
{
    BTIF_TRACE_API("%s", __func__);
    btc_hh_device_t *p_dev = btc_hh_find_connected_dev_by_bda(bd_addr);

    if (p_dev != NULL) {
        BTC_TRACE_DEBUG("stop VUP timer");
        if (p_dev->vup_timer) {
            osi_alarm_free(p_dev->vup_timer);
            p_dev->vup_timer = NULL;
        }
    }
}

/*******************************************************************************
 *
 * Function         btc_hh_timer_timeout
 *
 * Description      Process timer timeout
 *
 * Returns          void
 ******************************************************************************/
void btc_hh_timer_timeout(void *data)
{
    btc_hh_device_t *p_dev = (btc_hh_device_t *)data;
    bt_status_t status;
    tBTA_HH p_data;
    btc_msg_t msg;
    msg.sig = BTC_SIG_API_CB;
    msg.pid = BTC_PID_HH;
    msg.act = BTA_HH_VC_UNPLUG_EVT;

    BTC_TRACE_API("%s", __func__);
    if (p_dev->dev_status != ESP_HIDH_CONN_STATE_CONNECTED){
        BTC_TRACE_ERROR("%s Device[%s] is not connected!", __func__,
                        bdaddr_to_string((const bt_bdaddr_t *)p_dev->bd_addr, bdstr, sizeof(bdstr)));
        return;
    }

    memset(&p_data, 0, sizeof(tBTA_HH));
    p_data.dev_status.status = BTA_HH_ERR;
    p_data.dev_status.handle = p_dev->dev_handle;

    /* switch context to btif task context */
    status = btc_transfer_context(&msg, &p_data, sizeof(tBTA_HH), NULL, NULL);
    if (status != BT_STATUS_SUCCESS) {
        BTC_TRACE_ERROR("%s context transfer failed", __func__);
    }
}

/*******************************************************************************
 *
 * Function      btc_hh_start_vup_timer
 *
 * Description  start virtual unplug timer
 *
 * Returns      void
 ******************************************************************************/
void btc_hh_start_vup_timer(BD_ADDR bd_addr)
{
    BTC_TRACE_API("%s", __func__);

    btc_hh_device_t *p_dev = btc_hh_find_connected_dev_by_bda(bd_addr);
    if (p_dev == NULL) {
        BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
        return;
    }

    if (p_dev->vup_timer) {
        osi_alarm_free(p_dev->vup_timer);
        p_dev->vup_timer = NULL;
    }
    if ((p_dev->vup_timer = osi_alarm_new("btc_hh.vup_timer", btc_hh_timer_timeout, p_dev, BTC_TIMEOUT_VUP_MS)) ==
        NULL) {
        BTC_TRACE_ERROR("%s unable to malloc vup_timer!", __func__);
    }
}

/*******************************************************************************
 *
 * Function         btc_hh_add_added_dev
 *
 * Description      Add a new device to the added device list.
 *
 * Returns          true if add successfully, otherwise false.
 ******************************************************************************/
bool btc_hh_add_added_dev(BD_ADDR bd_addr, tBTA_HH_ATTR_MASK attr_mask)
{
    int i;
    for (i = 0; i < BTC_HH_MAX_ADDED_DEV; i++) {
        if (memcmp(btc_hh_cb.added_devices[i].bd_addr, bd_addr, BD_ADDR_LEN) == 0) {
            return false;
        }
    }
    for (i = 0; i < BTC_HH_MAX_ADDED_DEV; i++) {
        if (memcmp(btc_hh_cb.added_devices[i].bd_addr, bd_addr_null, BD_ADDR_LEN) == 0) {
            memcpy(btc_hh_cb.added_devices[i].bd_addr, bd_addr, BD_ADDR_LEN);
            btc_hh_cb.added_devices[i].dev_handle = BTA_HH_INVALID_HANDLE;
            btc_hh_cb.added_devices[i].attr_mask = attr_mask;
            return true;
        }
    }

    BTC_TRACE_ERROR("%s: Error, out of space to add device", __func__);
    return false;
}

/*******************************************************************************
 **
 ** Function         btc_hh_remove_device
 **
 ** Description      Remove an added device from the stack.
 **
 ** Returns          void
 ******************************************************************************/
void btc_hh_remove_device(BD_ADDR bd_addr)
{
    int i;
    btc_hh_device_t *p_dev;
    btc_hh_added_device_t *p_added_dev;

    for (i = 0; i < BTC_HH_MAX_ADDED_DEV; i++) {
        p_added_dev = &btc_hh_cb.added_devices[i];
        if (memcmp(p_added_dev->bd_addr, bd_addr, BD_ADDR_LEN) == 0) {
            BTA_HhRemoveDev(p_added_dev->dev_handle);
            btc_storage_remove_hid_info((bt_bdaddr_t *)p_added_dev->bd_addr);
            memset(p_added_dev->bd_addr, 0, 6);
            p_added_dev->dev_handle = BTA_HH_INVALID_HANDLE;
            break;
        }
    }

    p_dev = btc_hh_find_dev_by_bda(bd_addr);
    if (p_dev == NULL) {
        BTC_TRACE_ERROR("%s Oops, can't find device", __func__);
        return;
    }

    /* need to notify up-layer device is disconnected to avoid state out of sync
     * with up-layer */ //[boblane]
    // HAL_CBACK(bt_hh_callbacks, connection_state_cb, &(p_dev->bd_addr), BTHH_CONN_STATE_DISCONNECTED);

    p_dev->dev_status = ESP_HIDH_CONN_STATE_UNKNOWN;
    p_dev->dev_handle = BTA_HH_INVALID_HANDLE;
    p_dev->ready_for_data = false;

    if (btc_hh_cb.device_num > 0) {
        btc_hh_cb.device_num--;
    } else {
        BTC_TRACE_WARNING("%s: device_num = 0", __func__);
    }
}

static void bte_hh_arg_deep_copy(btc_msg_t *msg, void *p_dst, void *p_src)
{
    tBTA_HH *p_dst_data = (tBTA_HH *)p_dst;
    tBTA_HH *p_src_data = (tBTA_HH *)p_src;
    switch (msg->act)
    {
    case BTA_HH_GET_RPT_EVT: {
        BT_HDR *src_hdr = p_src_data->hs_data.rsp_data.p_rpt_data;
        if (src_hdr) {
            p_dst_data->hs_data.rsp_data.p_rpt_data = osi_malloc(sizeof(BT_HDR) + src_hdr->offset + src_hdr->len);
            if (p_dst_data->hs_data.rsp_data.p_rpt_data == NULL) {
                BTC_TRACE_ERROR("%s malloc p_rpt_data failed!", __func__);
                p_dst_data->hs_data.status = ESP_HIDH_ERR_NO_RES;
                break;
            }
            memcpy(p_dst_data->hs_data.rsp_data.p_rpt_data, src_hdr, sizeof(BT_HDR) + src_hdr->offset + src_hdr->len);
        }
        break;
    }
    default:
        break;
    }
}

static void bte_hh_evt(tBTA_HH_EVT event, tBTA_HH *p_data)
{
    bt_status_t status;
    int param_len = 0;

    BTC_TRACE_API("%s event=%d", __func__, event);

    switch (event) {
    case BTA_HH_ENABLE_EVT:
        param_len = sizeof(tBTA_HH_STATUS);
        break;
    case BTA_HH_DISABLE_EVT:
        param_len = sizeof(tBTA_HH_STATUS);
        break;
    case BTA_HH_OPEN_EVT:
        param_len = sizeof(tBTA_HH_CONN);
        break;
    case BTA_HH_CLOSE_EVT:
        param_len = sizeof(tBTA_HH_CBDATA);
        break;
    case BTA_HH_GET_RPT_EVT:
        param_len = sizeof(tBTA_HH_HSDATA);
        break;
    case BTA_HH_SET_RPT_EVT:
        param_len = sizeof(tBTA_HH_CBDATA);
        break;
    case BTA_HH_GET_PROTO_EVT:
        param_len = sizeof(tBTA_HH_HSDATA);
        break;
    case BTA_HH_SET_PROTO_EVT:
        param_len = sizeof(tBTA_HH_CBDATA);
        break;
    case BTA_HH_GET_IDLE_EVT:
        param_len = sizeof(tBTA_HH_HSDATA);
        break;
    case BTA_HH_SET_IDLE_EVT:
        param_len = sizeof(tBTA_HH_CBDATA);
        break;
    case BTA_HH_GET_DSCP_EVT:
        param_len = sizeof(tBTA_HH_DEV_DSCP_INFO);
        break;
    case BTA_HH_ADD_DEV_EVT:
        param_len = sizeof(tBTA_HH_DEV_INFO);
        break;
    case BTA_HH_RMV_DEV_EVT:
        param_len = sizeof(tBTA_HH_DEV_INFO);
        break;
    case BTA_HH_VC_UNPLUG_EVT:
        param_len = sizeof(tBTA_HH_CBDATA);
        break;
    case BTA_HH_DATA_EVT:
        param_len = sizeof(tBTA_HH_API_SENDDATA);
        break;
    case BTA_HH_API_ERR_EVT:
        param_len = 0;
        break;
    }

    btc_msg_t msg;
    msg.sig = BTC_SIG_API_CB;
    msg.pid = BTC_PID_HH;
    msg.act = event;

    status = btc_transfer_context(&msg, p_data, param_len, bte_hh_arg_deep_copy, btc_hh_cb_arg_deep_free);
    assert(status == BT_STATUS_SUCCESS);
}

/*******************************************************************************
 *
 * Function         btc_hh_init
 *
 * Description      initializes the hh interface
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_init(void)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    do {
        if (is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has been initiated, shall uninit first!", __func__);
            ret = ESP_HIDH_NEED_DEINIT;
            break;
        }

        memset(&btc_hh_cb, 0, sizeof(btc_hh_cb));
        for (uint8_t i = 0; i < BTC_HH_MAX_HID; i++) {
            btc_hh_cb.devices[i].dev_status = ESP_HIDH_CONN_STATE_UNKNOWN;
        }
        BTA_HhEnable(BTA_SEC_AUTHENTICATE | BTA_SEC_ENCRYPT, bte_hh_evt);
    } while (0);

    if (ret != ESP_HIDH_OK) {
        esp_hidh_cb_param_t param;
        param.init.status = ret;
        btc_hh_cb_to_app(ESP_HIDH_INIT_EVT, &param);
    }
}

/*******************************************************************************
 *
 * Function         btc_hh_deinit
 *
 * Description      de-initializes the hh interface
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_deinit(void)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }

        // close all connections
        for (uint8_t i = 0; i < BTC_HH_MAX_HID; i++) {
            if(btc_hh_cb.devices[i].dev_status == ESP_HIDH_CONN_STATE_CONNECTED){
                BTA_HhClose(btc_hh_cb.devices[i].dev_handle);
            }
        }
        btc_hh_cb.service_dereg_active = TRUE;
        btc_hh_cb.status = BTC_HH_DISABLING;
        BTA_HhDisable();
    } while (0);

    if (ret != ESP_HIDH_OK) {
        esp_hidh_cb_param_t param;
        param.deinit.status = ret;
        btc_hh_cb_to_app(ESP_HIDH_DEINIT_EVT, &param);
    }
}

/*******************************************************************************
 *
 * Function         btc_hh_connect
 *
 * Description      connection initiated from the BTC thread context
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_connect(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    btc_hh_added_device_t* added_dev = NULL;
    btc_hh_device_t* dev = NULL;
    esp_hidh_cb_param_t param;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }

        if (btc_hh_cb.status == BTC_HH_DEV_CONNECTING) {
            BTC_TRACE_ERROR("%s HH is connecting, ignore!", __func__);
            ret = ESP_HIDH_BUSY;
            break;
        }

        dev = btc_hh_find_dev_by_bda(arg->connect.bd_addr);
        if (!dev && btc_hh_cb.device_num >= BTC_HH_MAX_HID) {
            BTC_TRACE_ERROR("%s exceeded the maximum supported HID device number %d!", __func__, BTC_HH_MAX_HID);
            ret = ESP_HIDH_ERR_NO_RES;
            break;
        } else if (dev && dev->dev_status == ESP_HIDH_CONN_STATE_CONNECTED) {
            BTC_TRACE_WARNING("%s Device[%s] already connected", __func__,
                              bdaddr_to_string((const bt_bdaddr_t *)arg->connect.bd_addr, bdstr, sizeof(bdstr)));
            param.open.conn_status = ESP_HIDH_CONN_STATE_CONNECTED;
            break;
        }

        for (int i = 0; i < BTC_HH_MAX_ADDED_DEV; i++) {
            if (memcmp(btc_hh_cb.added_devices[i].bd_addr, arg->connect.bd_addr, BD_ADDR_LEN) == 0) {
                added_dev = &btc_hh_cb.added_devices[i];
                BTC_TRACE_WARNING("%s Device[%s] already added, attr_mask = 0x%x", __func__,
                                  bdaddr_to_string((const bt_bdaddr_t *)arg->connect.bd_addr, bdstr, sizeof(bdstr)),
                                  added_dev->attr_mask);
            }
        }

        if (added_dev != NULL) {
            if (added_dev->dev_handle == BTA_HH_INVALID_HANDLE) {
                // No space for more HID device now.
                BTC_TRACE_ERROR("device added but addition failed");
                memset(added_dev->bd_addr, 0, sizeof(added_dev->bd_addr));
                ret = ESP_HIDH_ERR;
                break;
            }
        }

        /* Not checking the NORMALLY_Connectible flags from sdp record, and anyways
        sending this request from host, for subsequent user initiated connection. If the remote is
        not in pagescan mode, we will do 2 retries to connect before giving up */
        btc_hh_cb.status = BTC_HH_DEV_CONNECTING;
        memcpy(btc_hh_cb.pending_conn_address, arg->connect.bd_addr, BD_ADDR_LEN);
        BTA_HhOpen(arg->connect.bd_addr, BTA_HH_PROTO_RPT_MODE, (BTA_SEC_AUTHENTICATE | BTA_SEC_ENCRYPT));
        param.open.conn_status = ESP_HIDH_CONN_STATE_CONNECTING;
        ret = ESP_HIDH_OK;
    } while (0);

    if (ret != ESP_HIDH_OK) {
        param.open.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
    }
    param.open.status = ret;
    param.open.handle = BTA_HH_INVALID_HANDLE;
    memcpy(param.open.bd_addr, arg->connect.bd_addr, BD_ADDR_LEN);
    param.open.is_orig = true;
    btc_hh_cb_to_app(ESP_HIDH_OPEN_EVT, &param);
}

/*******************************************************************************
 *
 * Function         btc_hh_disconnect
 *
 * Description      disconnection initiated from the BTC thread context
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_disconnect(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    btc_hh_device_t *p_dev;
    esp_hidh_cb_param_t param;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }
        p_dev = btc_hh_find_connected_dev_by_bda(arg->disconnect.bd_addr);
        if (p_dev != NULL) {
            BTA_HhClose(p_dev->dev_handle);
            param.close.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTING;
            param.close.handle = p_dev->dev_handle;
        } else {
            ret = ESP_HIDH_NO_CONNECTION;
            BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
        }

    } while (0);

    if (ret != ESP_HIDH_OK) {
        param.close.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
        param.close.handle = BTA_HH_INVALID_HANDLE;
    }
    param.close.status = ret;
    btc_hh_cb_to_app(ESP_HIDH_CLOSE_EVT, &param);
}

/*******************************************************************************
 *
 * Function         btc_hh_virtual_unplug
 *
 * Description      Virtual unplug initiated from the BTC thread context
 *                  Special handling for HID mouse-
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_virtual_unplug(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    btc_hh_device_t *p_dev;
    esp_hidh_cb_param_t param;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }
        p_dev = btc_hh_find_dev_by_bda(arg->unplug.bd_addr);
        if ((p_dev != NULL) && (p_dev->dev_status == ESP_HIDH_CONN_STATE_CONNECTED) &&
            (p_dev->attr_mask & HID_VIRTUAL_CABLE)) {
            BTC_TRACE_DEBUG("%s: Sending BTA_HH_CTRL_VIRTUAL_CABLE_UNPLUG", __func__);
            /* start the timer */
            btc_hh_start_vup_timer(arg->unplug.bd_addr);
            p_dev->local_vup = true;
            BTA_HhSendCtrl(p_dev->dev_handle, BTA_HH_CTRL_VIRTUAL_CABLE_UNPLUG);

            param.unplug.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTING;
            param.unplug.handle = p_dev->dev_handle;
        } else if ((p_dev != NULL) && (p_dev->dev_status == ESP_HIDH_CONN_STATE_CONNECTED)) {
            BTC_TRACE_WARNING("%s: Virtual unplug not supported, disconnecting device", __func__);
            /* start the timer */
            btc_hh_start_vup_timer(arg->unplug.bd_addr);
            p_dev->local_vup = true;
            BTA_HhClose(p_dev->dev_handle);

            param.unplug.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTING;
            param.unplug.handle = p_dev->dev_handle;
        } else {
            BTC_TRACE_ERROR("%s: Error, device not opened, status = %d", __func__, btc_hh_cb.status);
            ret = ESP_HIDH_NO_CONNECTION;
            if (memcmp(btc_hh_cb.pending_conn_address, arg->unplug.bd_addr, BD_ADDR_LEN) == 0 &&
                (btc_hh_cb.status == BTC_HH_DEV_CONNECTING)) {
                btc_hh_cb.status = (BTC_HH_STATUS)BTC_HH_DEV_DISCONNECTED;
                memset(btc_hh_cb.pending_conn_address, 0, BD_ADDR_LEN);
            }
        }
    } while (0);

    if (ret != ESP_HIDH_OK) {
        param.unplug.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
        param.unplug.handle = BTA_HH_INVALID_HANDLE;
    }
    param.unplug.status = ret;
    btc_hh_cb_to_app(ESP_HIDH_VC_UNPLUG_EVT, &param);
}

/*******************************************************************************
 *
 * Function         btc_hh_set_info
 *
 * Description      Set the HID device descriptor for the specified HID device.
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_set_info(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    esp_hidh_cb_param_t param;
    tBTA_HH_DEV_DSCP_INFO dscp_info;

    BTC_TRACE_DEBUG("%s: sub_class = 0x%02x, app_id = %d, vendor_id = 0x%04x, "
                    "product_id = 0x%04x, version= 0x%04x",
                    __func__, arg->set_info.hid_info->sub_class, arg->set_info.hid_info->app_id,
                    arg->set_info.hid_info->vendor_id, arg->set_info.hid_info->product_id,
                    arg->set_info.hid_info->version);
    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }

        memset(&dscp_info, 0, sizeof(dscp_info));
        dscp_info.vendor_id = arg->set_info.hid_info->vendor_id;
        dscp_info.product_id = arg->set_info.hid_info->product_id;
        dscp_info.version = arg->set_info.hid_info->version;
        dscp_info.ctry_code = arg->set_info.hid_info->ctry_code;

        dscp_info.descriptor.dl_len = arg->set_info.hid_info->dl_len;
        dscp_info.descriptor.dsc_list = (uint8_t *)osi_malloc(dscp_info.descriptor.dl_len);
        if (dscp_info.descriptor.dsc_list == NULL) {
            BTC_TRACE_ERROR("%s malloc dsc_list failed!", __func__);
            ret = ESP_HIDH_ERR_NO_RES;
            break;
        }
        memcpy(dscp_info.descriptor.dsc_list, arg->set_info.hid_info->dsc_list, dscp_info.descriptor.dl_len);

        if (btc_hh_add_added_dev(arg->set_info.bd_addr, arg->set_info.hid_info->attr_mask)) {
            btc_hh_cb.add_event = BTC_HH_SET_INFO_EVT;
            BTA_HhAddDev(arg->set_info.bd_addr, arg->set_info.hid_info->attr_mask, arg->set_info.hid_info->sub_class,
                         arg->set_info.hid_info->app_id, dscp_info);
        } else {
            BTC_TRACE_ERROR("%s malloc dsc_list failed!", __func__);
            ret = ESP_HIDH_ERR;
            break;
        }
    } while(0);
    utl_freebuf((void **)&dscp_info.descriptor.dsc_list);

    if (ret != ESP_HIDH_OK) {
        param.set_info.status = ret;
        param.set_info.handle = BTA_HH_INVALID_HANDLE;
        memcpy(param.set_info.bd_addr, arg->set_info.bd_addr, BD_ADDR_LEN);
        btc_hh_cb_to_app(ESP_HIDH_SET_INFO_EVT, &param);
    }
}

/*******************************************************************************
 *
 * Function         btc_hh_get_protocol
 *
 * Description      Get the HID proto mode.
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_get_protocol(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    esp_hidh_cb_param_t param;
    btc_hh_device_t *p_dev;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }
        p_dev = btc_hh_find_connected_dev_by_bda(arg->get_protocol.bd_addr);
        if (p_dev == NULL) {
            ret = ESP_HIDH_NO_CONNECTION;
            BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
            break;
        }
        BTA_HhGetProtoMode(p_dev->dev_handle);
    } while(0);

    if (ret != ESP_HIDH_OK) {
        param.get_proto.proto_mode = ESP_HIDH_UNSUPPORTED_MODE;
        param.get_proto.handle = BTA_HH_INVALID_HANDLE;
        param.get_proto.status = ret;
        btc_hh_cb_to_app(ESP_HIDH_GET_PROTO_EVT, &param);
    }
}

/*******************************************************************************
 *
 * Function         btc_hh_set_protocol
 *
 * Description      Set the HID proto mode.
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_set_protocol(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    esp_hidh_cb_param_t param;
    btc_hh_device_t *p_dev;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }
        p_dev = btc_hh_find_connected_dev_by_bda(arg->set_protocol.bd_addr);
        if (p_dev == NULL) {
            ret = ESP_HIDH_NO_CONNECTION;
            BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
            break;
        } else if (arg->set_protocol.protocol_mode != ESP_HIDH_REPORT_MODE && arg->set_protocol.protocol_mode != ESP_HIDH_BOOT_MODE) {
            BTC_TRACE_ERROR("%s: Error, device proto_mode = %d.", __func__, arg->set_protocol.protocol_mode);
            ret = ESP_HIDH_HS_INVALID_PARAM;
            break;
        } else {
            BTA_HhSetProtoMode(p_dev->dev_handle, proto_mode_change_to_lower_layer(arg->set_protocol.protocol_mode));
        }
    } while (0);

    if (ret != ESP_HIDH_OK) {
        param.set_proto.handle = BTA_HH_INVALID_HANDLE;
        param.set_proto.status = ret;
        btc_hh_cb_to_app(ESP_HIDH_SET_PROTO_EVT, &param);
    }
}

/*******************************************************************************
 *
 * Function         btc_hh_get_report
 *
 * Description      Send a GET_REPORT to HID device.
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_get_report(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    esp_hidh_cb_param_t param;
    btc_hh_device_t *p_dev;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }

        p_dev = btc_hh_find_connected_dev_by_bda(arg->get_report.bd_addr);
        if (p_dev == NULL) {
            ret = ESP_HIDH_NO_CONNECTION;
            BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
            break;
        } else if (((int)arg->get_report.report_type) <= BTA_HH_RPTT_RESRV ||
                   ((int)arg->get_report.report_type) > BTA_HH_RPTT_FEATURE) {
            BTC_TRACE_ERROR("%s Error: report type=%d not supported!", __func__, arg->get_report.report_type);
            ret = ESP_HIDH_HS_INVALID_PARAM;
            break;
        } else {
            BTA_HhGetReport(p_dev->dev_handle, arg->get_report.report_type, arg->get_report.report_id,
                            arg->get_report.buffer_size);
        }
    } while (0);

    if (ret != ESP_HIDH_OK) {
        param.get_rpt.handle = BTA_HH_INVALID_HANDLE;
        param.get_rpt.status = ret;
        param.get_rpt.len = 0;
        param.get_rpt.data = NULL;
        btc_hh_cb_to_app(ESP_HIDH_GET_RPT_EVT, &param);
    }
}

/*******************************************************************************
 *
 * Function         create_pbuf
 *
 * Description      Helper function to create p_buf for send_data or set_report
 *
 * Returns          BT_HDR *
 *
 ******************************************************************************/
static BT_HDR *create_pbuf(uint16_t len, uint8_t *data)
{
    uint8_t *pbuf_data;
    BT_HDR *p_buf = (BT_HDR *)osi_malloc(len + BTA_HH_MIN_OFFSET + sizeof(BT_HDR));
    if (p_buf == NULL) {
        BTC_TRACE_ERROR("%s failed!", __func__);
        return NULL;
    }
    p_buf->len = len;
    p_buf->offset = BTA_HH_MIN_OFFSET;

    pbuf_data = (uint8_t *)(p_buf + 1) + p_buf->offset;
    memcpy(pbuf_data, data, len);

    return p_buf;
}

/*******************************************************************************
 *
 * Function         btc_hh_set_report
 *
 * Description      Send a SET_REPORT to HID device.
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_set_report(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    esp_hidh_cb_param_t param;
    btc_hh_device_t *p_dev;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }

        p_dev = btc_hh_find_connected_dev_by_bda(arg->set_report.bd_addr);
        if (p_dev == NULL) {
            ret = ESP_HIDH_NO_CONNECTION;
            BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
            break;
        } else if (((int)arg->set_report.report_type) <= BTA_HH_RPTT_RESRV ||
                   ((int)arg->set_report.report_type) > BTA_HH_RPTT_FEATURE) {
            BTC_TRACE_ERROR("%s Error: report type=%d not supported!", __func__, arg->set_report.report_type);
            ret = ESP_HIDH_HS_INVALID_PARAM;
            break;
        } else if (arg->set_report.report == NULL || arg->set_report.len == 0) {
            BTC_TRACE_ERROR("%s Error: report is empty!", __func__);
            ret = ESP_HIDH_HS_INVALID_PARAM;
            break;
        } else {
            BT_HDR* p_buf = create_pbuf(arg->set_report.len, arg->set_report.report);
            if (p_buf == NULL) {
                ret = ESP_HIDH_ERR_NO_RES;
                break;
            }
            BTA_HhSetReport(p_dev->dev_handle, arg->set_report.report_type, p_buf);
        }
    } while(0);

    if (ret != ESP_HIDH_OK) {
        param.set_rpt.handle = BTA_HH_INVALID_HANDLE;
        param.set_rpt.status = ret;
        btc_hh_cb_to_app(ESP_HIDH_SET_RPT_EVT, &param);
    }
}

/*******************************************************************************
 *
 * Function         btc_hh_send_data
 *
 * Description      Send a SEND_DATA to HID device.
 *
 * Returns          void
 *
 ******************************************************************************/
static void btc_hh_send_data(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    esp_hidh_cb_param_t param;
    btc_hh_device_t *p_dev;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }

        p_dev = btc_hh_find_connected_dev_by_bda(arg->send_data.bd_addr);
        if (p_dev == NULL) {
            ret = ESP_HIDH_NO_CONNECTION;
            BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
            break;
        } else if (arg->send_data.data == NULL || arg->send_data.len == 0) {
            BTC_TRACE_ERROR("%s Error: send data is empty!", __func__);
            ret = ESP_HIDH_HS_INVALID_PARAM;
            break;
        } else {
            BT_HDR *p_buf = create_pbuf(arg->send_data.len, arg->send_data.data);
            if (p_buf == NULL) {
                ret = ESP_HIDH_ERR_NO_RES;
                break;
            }
            p_buf->layer_specific = BTA_HH_RPTT_OUTPUT;
            BTA_HhSendData(p_dev->dev_handle, arg->send_data.bd_addr, p_buf);
        }
    } while(0);

    if (ret != ESP_HIDH_OK) {
        param.send_data.handle = BTA_HH_INVALID_HANDLE;
        param.send_data.status = ret;
        param.send_data.reason = 0;
        btc_hh_cb_to_app(ESP_HIDH_DATA_EVT, &param);
    }
}

/*******************************************************************************
**
** Function         btc_hh_get_idle_time
**
** Description      Get the HID idle time
**
** Returns          void
**
*******************************************************************************/
static void btc_hh_get_idle_time(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    esp_hidh_cb_param_t param;
    btc_hh_device_t *p_dev;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }

        p_dev = btc_hh_find_connected_dev_by_bda(arg->get_idle.bd_addr);
        if (p_dev == NULL) {
            ret = ESP_HIDH_NO_CONNECTION;
            BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
            break;
        }
        BTA_HhGetIdle(p_dev->dev_handle);
    } while (0);

    if (ret != ESP_HIDH_OK) {
        param.get_idle.handle = BTA_HH_INVALID_HANDLE;
        param.get_idle.status = ret;
        param.get_idle.idle_rate = 0;
        btc_hh_cb_to_app(ESP_HIDH_GET_IDLE_EVT, &param);
    }
}

/*******************************************************************************
**
** Function         btc_hh_set_idle_time
**
** Description      Set the HID idle time
**
** Returns          void
**
*******************************************************************************/
static void btc_hh_set_idle_time(btc_hidh_args_t *arg)
{
    BTC_TRACE_API("%s", __func__);
    esp_hidh_status_t ret = ESP_HIDH_OK;
    esp_hidh_cb_param_t param;
    btc_hh_device_t *p_dev;

    do {
        if (!is_hidh_init()) {
            BTC_TRACE_ERROR("%s HH has not been initiated, shall init first!", __func__);
            ret = ESP_HIDH_NEED_INIT;
            break;
        }

        p_dev = btc_hh_find_connected_dev_by_bda(arg->set_idle.bd_addr);
        if (p_dev == NULL) {
            ret = ESP_HIDH_NO_CONNECTION;
            BTC_TRACE_ERROR("%s Error: device not connected!", __func__);
            break;
        }
        BTA_HhSetIdle(p_dev->dev_handle, arg->set_idle.idle_time);
    } while (0);

    if (ret != ESP_HIDH_OK) {
        param.set_idle.handle = BTA_HH_INVALID_HANDLE;
        param.set_idle.status = ret;
        btc_hh_cb_to_app(ESP_HIDH_SET_IDLE_EVT, &param);
    }
}

static void btc_hh_call_arg_deep_free(btc_msg_t *msg)
{
    btc_hidh_args_t *arg = (btc_hidh_args_t *)msg->arg;

    switch (msg->act) {
    case BTC_HH_SET_INFO_EVT:
        utl_freebuf((void **)&arg->set_info.hid_info);
        break;
    case BTC_HH_SET_REPORT_EVT:
        utl_freebuf((void **)&arg->set_report.report);
        break;
    case BTC_HH_SEND_DATA_EVT:
        utl_freebuf((void **)&arg->send_data.data);
        break;
    default:
        break;
    }
}

void btc_hh_call_handler(btc_msg_t *msg)
{
    btc_hidh_args_t *arg = (btc_hidh_args_t *)(msg->arg);
    switch (msg->act) {
    case BTC_HH_INIT_EVT:
        btc_hh_init();
        break;
    case BTC_HH_CONNECT_EVT:
        btc_hh_connect(arg);
        break;
    case BTC_HH_DISCONNECT_EVT:
        btc_hh_disconnect(arg);
        break;
    case BTC_HH_UNPLUG_EVT:
        btc_hh_virtual_unplug(arg);
        break;
    case BTC_HH_SET_INFO_EVT:
        btc_hh_set_info(arg);
        break;
    case BTC_HH_GET_PROTO_EVT:
        btc_hh_get_protocol(arg);
        break;
    case BTC_HH_SET_PROTO_EVT:
        btc_hh_set_protocol(arg);
        break;
    case BTC_HH_GET_IDLE_EVT:
        btc_hh_get_idle_time(arg);
        break;
    case BTC_HH_SET_IDLE_EVT:
        btc_hh_set_idle_time(arg);
        break;
    case BTC_HH_GET_REPORT_EVT:
        btc_hh_get_report(arg);
        break;
    case BTC_HH_SET_REPORT_EVT:
        btc_hh_set_report(arg);
        break;
    case BTC_HH_SEND_DATA_EVT:
        btc_hh_send_data(arg);
        break;
    case BTC_HH_DEINIT_EVT:
        btc_hh_deinit();
        break;
    default:
        BTC_TRACE_WARNING("unknown hidh action %d", msg->act);
        break;
    }
    btc_hh_call_arg_deep_free(msg);
}

void btc_hh_cb_arg_deep_free(btc_msg_t *msg)
{
    tBTA_HH *arg = (tBTA_HH *)msg->arg;

    switch (msg->act) {
    case BTA_HH_GET_RPT_EVT:
        utl_freebuf((void **)&arg->hs_data.rsp_data.p_rpt_data);
        break;
    case BTA_HH_DATA_IND_EVT:
        utl_freebuf((void **)&arg->int_data.p_data);
        break;
    default:
        break;
    }
}

bool btc_hh_copy_hid_info(tBTA_HH_DEV_DSCP_INFO *dest, tBTA_HH_DEV_DSCP_INFO *src)
{
    dest->descriptor.dl_len = 0;
    if (src->descriptor.dl_len > 0) {
        dest->descriptor.dsc_list = (uint8_t *)osi_malloc(src->descriptor.dl_len);
    }
    if (dest->descriptor.dsc_list) {
        memcpy(dest->descriptor.dsc_list, src->descriptor.dsc_list, src->descriptor.dl_len);
        dest->descriptor.dl_len = src->descriptor.dl_len;
    }
    dest->vendor_id = src->vendor_id;
    dest->product_id = src->product_id;
    dest->version = src->version;
    dest->ctry_code = src->ctry_code;
    dest->ssr_max_latency = src->ssr_max_latency;
    dest->ssr_min_tout = src->ssr_min_tout;
    return true;
}

bool btc_hh_cb_copy_hid_info(esp_hidh_cb_param_t *param, tBTA_HH_DEV_DSCP_INFO *src)
{
    param->dscp.dl_len = 0;
    if (src->descriptor.dl_len > 0) {
        param->dscp.dsc_list = (uint8_t *)osi_malloc(src->descriptor.dl_len);
    }
    if (param->dscp.dsc_list) {
        memcpy(param->dscp.dsc_list, src->descriptor.dsc_list, src->descriptor.dl_len);
        param->dscp.dl_len = src->descriptor.dl_len;
    }
    param->dscp.vendor_id = src->vendor_id;
    param->dscp.product_id = src->product_id;
    param->dscp.version = src->version;
    param->dscp.ctry_code = src->ctry_code;
    param->dscp.ssr_max_latency = src->ssr_max_latency;
    param->dscp.ssr_min_tout = src->ssr_min_tout;
    return true;
}

void btc_hh_cb_handler(btc_msg_t *msg)
{
    esp_hidh_cb_param_t param = {0};
    tBTA_HH *p_data = (tBTA_HH *)msg->arg;
    btc_hh_device_t *p_dev = NULL;
    int len, i;
    BTC_TRACE_DEBUG("%s: event=%s dereg = %d", __func__, dump_hh_event(msg->act), btc_hh_cb.service_dereg_active);
    switch (msg->act) {
    case BTA_HH_ENABLE_EVT:
        if (p_data->status == BTA_HH_OK) {
            btc_hh_cb.status = BTC_HH_ENABLED;
            BTC_TRACE_DEBUG("Loading added devices");
            /* Add hid descriptors for already bonded hid devices*/
            // btc_storage_load_bonded_hid_info();
        } else {
            btc_hh_cb.status = BTC_HH_DISABLED;
            BTC_TRACE_ERROR("Error, HH enabling failed, status = %d", p_data->status);
        }
        param.init.status = p_data->status;
        btc_hh_cb_to_app(ESP_HIDH_INIT_EVT, &param);
        break;
    case BTA_HH_DISABLE_EVT:
        btc_hh_cb.status = BTC_HH_DISABLED;
        if (btc_hh_cb.service_dereg_active) {
            BTIF_TRACE_DEBUG("BTA_HH_DISABLE_EVT: enabling HID Device service");
            // btif_hd_service_registration();
            btc_hh_cb.service_dereg_active = FALSE;
        }
        if (p_data->status == BTA_HH_OK) {
            // Clear the control block
            for (uint8_t i = 0; i < BTC_HH_MAX_HID; i++) {
                if (btc_hh_cb.devices[i].vup_timer) {
                    osi_alarm_free(btc_hh_cb.devices[i].vup_timer);
                }
            }
            memset(&btc_hh_cb, 0, sizeof(btc_hh_cb));
            for (i = 0; i < BTC_HH_MAX_HID; i++) {
                btc_hh_cb.devices[i].dev_status = ESP_HIDH_CONN_STATE_UNKNOWN;
            }
        } else {
            BTC_TRACE_ERROR("Error, HH disabling failed, status = %d", p_data->status);
        }
        param.deinit.status = p_data->status;
        btc_hh_cb_to_app(ESP_HIDH_DEINIT_EVT, &param);
        break;
    case BTA_HH_OPEN_EVT:
        BTC_TRACE_DEBUG("handle=%d, status =%d", p_data->conn.handle, p_data->conn.status);
        memset(btc_hh_cb.pending_conn_address, 0, BD_ADDR_LEN);
        if (p_data->conn.status == BTA_HH_OK) {
            p_dev = btc_hh_find_connected_dev_by_handle(p_data->conn.handle);
            if (p_dev == NULL) {
                BTC_TRACE_ERROR("Error, cannot find device with handle %d", p_data->conn.handle);
                btc_hh_cb.status = (BTC_HH_STATUS)BTC_HH_DEV_DISCONNECTED;
                // The connect request must come from device side and exceeded the
                // connected HID device number.
                BTA_HhClose(p_data->conn.handle);

                param.open.status = ESP_HIDH_ERR;
                param.open.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
            } else {
                BTC_TRACE_DEBUG("Found device...Getting dscp info for handle "
                                "... %d",
                                p_data->conn.handle);
                memcpy(p_dev->bd_addr, p_data->conn.bda, BD_ADDR_LEN);
                btc_hh_cb.status = (BTC_HH_STATUS)BTC_HH_DEV_CONNECTED;
                // Send set_idle if the peer_device is a keyboard [boblane]
                // if (check_cod(&p_data->conn.bda, COD_HID_KEYBOARD) || check_cod(&p_data->conn.bda, COD_HID_COMBO))
                //     BTA_HhSetIdle(p_data->conn.handle, 0);
                btc_hh_cb.p_curr_dev = btc_hh_find_connected_dev_by_handle(p_data->conn.handle);
                BTA_HhGetDscpInfo(p_data->conn.handle);
                p_dev->dev_status = ESP_HIDH_CONN_STATE_CONNECTED;

                param.open.status = ESP_HIDH_OK;
                param.open.conn_status = ESP_HIDH_CONN_STATE_CONNECTED;
            }
        } else {
            p_dev = btc_hh_find_dev_by_bda(p_data->conn.bda);
            if (p_dev != NULL) {
                btc_hh_stop_vup_timer(p_dev->bd_addr);
                p_dev->dev_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
            }
            btc_hh_cb.status = (BTC_HH_STATUS)BTC_HH_DEV_DISCONNECTED;

            param.open.status = p_data->conn.status;
            param.open.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
        }
        param.open.handle = p_data->conn.handle;
        param.open.is_orig = p_data->conn.is_orig;
        memcpy(param.open.bd_addr, p_data->conn.bda, BD_ADDR_LEN);
        btc_hh_cb_to_app(ESP_HIDH_OPEN_EVT, &param);
        break;
    case BTA_HH_GET_DSCP_EVT:
        len = p_data->dscp_info.descriptor.dl_len;
        BTC_TRACE_DEBUG("len = %d", len);
        do {
            param.dscp.status = ESP_HIDH_OK;
            param.dscp.handle = BTA_HH_INVALID_HANDLE;
            param.dscp.added = false;
            p_dev = btc_hh_cb.p_curr_dev;
            if (p_dev == NULL) {
                BTC_TRACE_ERROR("No HID device is currently connected");
                param.dscp.status = ESP_HIDH_NO_CONNECTION;
                break;
            }

            if (btc_hh_add_added_dev(p_dev->bd_addr, p_dev->attr_mask)) {
                tBTA_HH_DEV_DSCP_INFO dscp_info;
                bt_status_t ret;
                btc_hh_copy_hid_info(&dscp_info, &p_data->dscp_info);
                btc_hh_cb.add_event = BTC_HH_CONNECT_EVT;
                BTA_HhAddDev(p_dev->bd_addr, p_dev->attr_mask, p_dev->sub_class, p_dev->app_id, dscp_info);
                // write hid info to nvs
                ret = btc_storage_add_hid_device_info((bt_bdaddr_t *)p_dev->bd_addr, p_dev->attr_mask, p_dev->sub_class, p_dev->app_id,
                                                      p_data->dscp_info.vendor_id, p_data->dscp_info.product_id,
                                                      p_data->dscp_info.version, p_data->dscp_info.ctry_code,
                                                      p_data->dscp_info.ssr_max_latency, p_data->dscp_info.ssr_min_tout,
                                                      len, p_data->dscp_info.descriptor.dsc_list);

                if (ret != BT_STATUS_SUCCESS) {
                    BTC_TRACE_ERROR("write hid info to nvs failed!");
                }
                // Free buffer created for dscp_info;
                if (dscp_info.descriptor.dl_len > 0 && dscp_info.descriptor.dsc_list != NULL) {
                    utl_freebuf((void **)&dscp_info.descriptor.dsc_list);
                    dscp_info.descriptor.dl_len = 0;
                }
            } else {
                // Device already added.
                BTC_TRACE_WARNING("%s: Device already added ", __func__);
                param.dscp.added = true;
            }
            btc_hh_cb_copy_hid_info(&param, &p_data->dscp_info);
            param.dscp.handle = p_dev->dev_handle;
        } while(0);
        btc_hh_cb_to_app(ESP_HIDH_GET_DSCP_EVT, &param);
        if (param.dscp.dl_len > 0 && param.dscp.dsc_list != NULL) {
            utl_freebuf((void **)&param.dscp.dsc_list);
            param.dscp.dl_len = 0;
        }
        break;
    case BTA_HH_CLOSE_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->dev_status.status,
                         p_data->dev_status.handle);
        p_dev = btc_hh_find_connected_dev_by_handle(p_data->dev_status.handle);
        if (p_dev != NULL) {
            BTC_TRACE_DEBUG("uhid local_vup=%d", p_dev->local_vup);
            btc_hh_stop_vup_timer(p_dev->bd_addr);
            /* If this is a locally initiated VUP, remove the bond as ACL got
             *  disconnected while VUP being processed.
             */
            if (p_dev->local_vup) {
                p_dev->local_vup = false;
                BTA_DmRemoveDevice(p_dev->bd_addr, BT_TRANSPORT_BR_EDR);
            }

            btc_hh_cb.status = (BTC_HH_STATUS)BTC_HH_DEV_DISCONNECTED;
            p_dev->dev_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
            param.close.status = p_data->dev_status.status;
        } else {
            BTC_TRACE_ERROR("Error: cannot find device with handle %d", p_data->dev_status.handle);
            param.close.status = ESP_HIDH_NO_CONNECTION;
        }
        param.close.handle = p_data->dev_status.handle;
        param.close.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
        btc_hh_cb_to_app(ESP_HIDH_CLOSE_EVT, &param);
        break;
    case BTA_HH_VC_UNPLUG_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->dev_status.status,
                        p_data->dev_status.handle);
        p_dev = btc_hh_find_connected_dev_by_handle(p_data->dev_status.handle);
        btc_hh_cb.status = (BTC_HH_STATUS)BTC_HH_DEV_DISCONNECTED;
        if (p_dev != NULL) {
            /* Stop the VUP timer */
            btc_hh_stop_vup_timer(p_dev->bd_addr);
            p_dev->dev_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
            BTC_TRACE_DEBUG("%s---Sending connection state change", __func__);
            param.close.status = ESP_HIDH_OK;
            param.close.handle = p_data->dev_status.handle;
            param.close.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
            btc_hh_cb_to_app(ESP_HIDH_CLOSE_EVT, &param);
            BTC_TRACE_DEBUG("%s---Removing HID bond", __func__);
            /* If it is locally initiated VUP or remote device has its major COD as
            Peripheral removed the bond.*/
            // [boblane]
            if (p_dev->local_vup) {
                p_dev->local_vup = false;
                BTA_DmRemoveDevice(p_dev->bd_addr, BT_TRANSPORT_BR_EDR);
            } else {
                btc_hh_remove_device(p_dev->bd_addr);
            }
            param.unplug.status = p_data->dev_status.status;
        } else {
            BTC_TRACE_ERROR("Error: cannot find device with handle %d", p_data->dev_status.handle);
            param.unplug.status = ESP_HIDH_NO_CONNECTION;
        }
        param.unplug.handle = p_data->dev_status.handle;
        param.unplug.conn_status = ESP_HIDH_CONN_STATE_DISCONNECTED;
        btc_hh_cb_to_app(ESP_HIDH_VC_UNPLUG_EVT, &param);
        break;
    case BTA_HH_DATA_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->send_data.status,
                         p_data->send_data.handle);
        param.send_data.handle = p_data->send_data.handle;
        param.send_data.status = p_data->send_data.status;
        param.send_data.reason = p_data->send_data.reason;
        btc_hh_cb_to_app(ESP_HIDH_DATA_EVT, &param);
        break;
    case BTA_HH_GET_PROTO_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d, proto = [%d], %s", p_data->hs_data.status,
                        p_data->hs_data.handle, p_data->hs_data.rsp_data.proto_mode,
                        (p_data->hs_data.rsp_data.proto_mode == BTA_HH_PROTO_RPT_MODE)
                            ? "Report Mode"
                            : (p_data->hs_data.rsp_data.proto_mode == BTA_HH_PROTO_BOOT_MODE) ? "Boot Mode"
                                                                                              : "Unsupported");
        param.get_proto.proto_mode = proto_mode_change_to_upper_layer(p_data->hs_data.rsp_data.proto_mode);
        param.get_proto.handle = p_data->hs_data.handle;
        param.get_proto.status = p_data->hs_data.status;
        btc_hh_cb_to_app(ESP_HIDH_GET_PROTO_EVT, &param);
        break;
    case BTA_HH_SET_PROTO_EVT:
        BTIF_TRACE_DEBUG("status = %d, handle = %d", p_data->dev_status.status,
                         p_data->dev_status.handle);
        param.set_proto.handle = p_data->dev_status.handle;
        param.set_proto.status = p_data->dev_status.status;
        btc_hh_cb_to_app(ESP_HIDH_SET_PROTO_EVT, &param);
        break;
    case BTA_HH_GET_RPT_EVT: {
        BT_HDR *hdr = p_data->hs_data.rsp_data.p_rpt_data;
        uint8_t *data = NULL;
        uint16_t len = 0;
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->hs_data.status, p_data->hs_data.handle);
        /* p_rpt_data is NULL in HANDSHAKE response case */
        if (hdr) {
            data = (uint8_t *)(hdr + 1) + hdr->offset;
            len = hdr->len;
        }
        param.get_rpt.handle = p_data->hs_data.handle;
        param.get_rpt.status = p_data->hs_data.status;
        param.get_rpt.len = len;
        param.get_rpt.data = data;
        btc_hh_cb_to_app(ESP_HIDH_GET_RPT_EVT, &param);
        break;
    }
    case BTA_HH_SET_RPT_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->dev_status.status,
                         p_data->dev_status.handle);
        param.set_rpt.handle = p_data->dev_status.handle;
        param.set_rpt.status = p_data->dev_status.status;
        btc_hh_cb_to_app(ESP_HIDH_SET_RPT_EVT, &param);
        break;
    case BTA_HH_GET_IDLE_EVT:
        BTC_TRACE_DEBUG("handle = %d, status = %d, rate = %d", p_data->hs_data.handle, p_data->hs_data.status,
                        p_data->hs_data.rsp_data.idle_rate);
        param.get_idle.handle = p_data->hs_data.handle;
        param.get_idle.status = p_data->hs_data.status;
        param.get_idle.idle_rate = p_data->hs_data.rsp_data.idle_rate;
        btc_hh_cb_to_app(ESP_HIDH_GET_IDLE_EVT, &param);
        break;
    case BTA_HH_SET_IDLE_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->dev_status.status, p_data->dev_status.handle);
        param.set_idle.handle = p_data->dev_status.handle;
        param.set_idle.status = p_data->dev_status.status;
        btc_hh_cb_to_app(BTA_HH_SET_IDLE_EVT, &param);
        break;
    case BTA_HH_ADD_DEV_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->dev_info.status, p_data->dev_info.handle);
        for (uint8_t i = 0; i < BTC_HH_MAX_ADDED_DEV; i++) {
            if (memcmp(btc_hh_cb.added_devices[i].bd_addr, p_data->dev_info.bda, BD_ADDR_LEN) == 0) {
                if (p_data->dev_info.status == BTA_HH_OK) {
                    btc_hh_cb.added_devices[i].dev_handle = p_data->dev_info.handle;
                } else {
                    memset(btc_hh_cb.added_devices[i].bd_addr, 0, BD_ADDR_LEN);
                    btc_hh_cb.added_devices[i].dev_handle = BTA_HH_INVALID_HANDLE;
                }
                break;
            }
        }
        if (btc_hh_cb.add_event == BTC_HH_SET_INFO_EVT) {
            param.set_info.handle = p_data->dev_info.handle;
            param.set_info.status = p_data->dev_info.status;
            memcpy(param.set_info.bd_addr, p_data->dev_info.bda, BD_ADDR_LEN);
            btc_hh_cb_to_app(ESP_HIDH_SET_INFO_EVT, &param);
        } else {
            param.add_dev.handle = p_data->dev_info.handle;
            param.add_dev.status = p_data->dev_info.status;
            memcpy(param.add_dev.bd_addr, p_data->dev_info.bda, BD_ADDR_LEN);
            btc_hh_cb_to_app(ESP_HIDH_ADD_DEV_EVT, &param);
        }
        break;
    case BTA_HH_RMV_DEV_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->dev_info.status, p_data->dev_info.handle);
        param.rmv_dev.handle = p_data->dev_info.status;
        param.rmv_dev.status = p_data->dev_info.handle;
        memcpy(param.rmv_dev.bd_addr, p_data->dev_info.bda, BD_ADDR_LEN);
        btc_hh_cb_to_app(ESP_HIDH_RMV_DEV_EVT, &param);
        break;
    case BTA_HH_DATA_IND_EVT:
        BTC_TRACE_DEBUG("status = %d, handle = %d", p_data->int_data.status, p_data->int_data.handle);
        if (p_data->int_data.status == BTA_HH_OK && p_data->int_data.p_data) {
            param.data_ind.len = p_data->int_data.p_data->len;
            param.data_ind.data = p_data->int_data.p_data->data + p_data->int_data.p_data->offset;
        }
        param.data_ind.handle = p_data->int_data.handle;
        param.data_ind.status = p_data->int_data.status;
        param.data_ind.proto_mode = proto_mode_change_to_upper_layer(p_data->int_data.proto_mode);
        btc_hh_cb_to_app(ESP_HIDH_DATA_IND_EVT, &param);
        break;
    case BTA_HH_API_ERR_EVT:
        break;
    default:
        BTC_TRACE_WARNING("%s: Unhandled event: %d", __func__, msg->act);
        break;
    }
    btc_hh_cb_arg_deep_free(msg);
}

void btc_hh_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src)
{
    btc_hidh_args_t *dst = (btc_hidh_args_t *)p_dest;
    btc_hidh_args_t *src = (btc_hidh_args_t *)p_src;

    switch (msg->act) {
    case BTC_HH_SET_INFO_EVT:
        dst->set_info.hid_info = (esp_hidh_hid_info_t *)osi_malloc(sizeof(esp_hidh_hid_info_t));
        if (dst->set_info.hid_info) {
            memcpy(dst->set_info.hid_info, src->set_info.hid_info, sizeof(esp_hidh_hid_info_t));
        } else {
            BTC_TRACE_ERROR("%s %d osi_malloc failed\n", __func__, msg->act);
        }
        break;
    case BTC_HH_SET_REPORT_EVT:
        dst->set_report.report = (uint8_t *)osi_malloc(src->set_report.len);
        if (dst->set_report.report) {
            memcpy(dst->set_report.report, src->set_report.report, src->set_report.len);
        } else {
            BTC_TRACE_ERROR("%s %d osi_malloc failed\n", __func__, msg->act);
        }
        break;
    case BTC_HH_SEND_DATA_EVT:
        dst->send_data.data = (uint8_t *)osi_malloc(src->send_data.len);
        if (dst->send_data.data) {
            memcpy(dst->send_data.data, src->send_data.data, src->send_data.len);
        } else {
            BTC_TRACE_ERROR("%s %d osi_malloc failed\n", __func__, msg->act);
        }
        break;
    default:
        break;
    }
}

#endif // HID_HOST_INCLUDED == TRUE
