/****************************************************************************** * * Copyright (C) 2009-2013 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. * ******************************************************************************/ #define LOG_TAG "bt_bta_hh" #include "bta_api.h" #include "bta_hh_int.h" #if (defined BTA_HH_LE_INCLUDED && BTA_HH_LE_INCLUDED == TRUE) #include #include "bta_api.h" #include "bta_gatt_api.h" #include "bta_hh_co.h" #include "btm_api.h" #include "btm_ble_api.h" #include "btm_int.h" #include "osi/include/log.h" #include "srvc_api.h" #include "stack/include/l2c_api.h" #include "utl.h" #ifndef BTA_HH_LE_RECONN #define BTA_HH_LE_RECONN TRUE #endif #define BTA_HH_APP_ID_LE 0xff #define BTA_HH_LE_RPT_TYPE_VALID(x) ((x) <= BTA_LE_HID_RPT_FEATURE && (x)>=BTA_LE_HID_RPT_INPUT) #define BTA_HH_LE_PROTO_BOOT_MODE 0x00 #define BTA_HH_LE_PROTO_REPORT_MODE 0x01 #define BTA_LE_HID_RTP_UUID_MAX 5 static const UINT16 bta_hh_uuid_to_rtp_type[BTA_LE_HID_RTP_UUID_MAX][2] = { {GATT_UUID_HID_REPORT, BTA_HH_RPTT_INPUT}, {GATT_UUID_HID_BT_KB_INPUT, BTA_HH_RPTT_INPUT}, {GATT_UUID_HID_BT_KB_OUTPUT, BTA_HH_RPTT_OUTPUT}, {GATT_UUID_HID_BT_MOUSE_INPUT, BTA_HH_RPTT_INPUT}, {GATT_UUID_BATTERY_LEVEL, BTA_HH_RPTT_INPUT} }; static void bta_hh_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC *p_data); static void bta_hh_le_register_scpp_notif(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATT_STATUS status); static void bta_hh_le_register_scpp_notif_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATT_STATUS status); static void bta_hh_le_add_dev_bg_conn(tBTA_HH_DEV_CB *p_cb, BOOLEAN check_bond); //TODO(jpawlowski): uncomment when fixed // static void bta_hh_process_cache_rpt (tBTA_HH_DEV_CB *p_cb, // tBTA_HH_RPT_CACHE_ENTRY *p_rpt_cache, // UINT8 num_rpt); #define GATT_READ_CHAR 0 #define GATT_READ_DESC 1 #define GATT_WRITE_CHAR 2 #define GATT_WRITE_DESC 3 /* Holds pending GATT operations */ typedef struct { UINT8 type; UINT16 conn_id; UINT16 handle; /* write-specific fields */ tBTA_GATTC_WRITE_TYPE write_type; UINT16 len; UINT8 p_value[GATT_MAX_ATTR_LEN]; } gatt_operation; static list_t *gatt_op_queue = NULL; // list of gatt_operation static list_t *gatt_op_queue_executing = NULL; // list of UINT16 connection ids that currently execute static void mark_as_executing(UINT16 conn_id) { UINT16 *executing_conn_id = osi_malloc(sizeof(UINT16)); *executing_conn_id = conn_id; if (!gatt_op_queue_executing) gatt_op_queue_executing = list_new(osi_free); list_append(gatt_op_queue_executing, executing_conn_id); } static bool rm_exec_conn_id(void *data, void *context) { UINT16 *conn_id = context; UINT16 *conn_id2 = data; if (*conn_id == *conn_id2) list_remove(gatt_op_queue_executing, data); return TRUE; } static void mark_as_not_executing(UINT16 conn_id) { if (gatt_op_queue_executing) list_foreach(gatt_op_queue_executing, rm_exec_conn_id, &conn_id); } static bool exec_list_contains(void *data, void *context) { UINT16 *conn_id = context; UINT16 *conn_id2 = data; if (*conn_id == *conn_id2) return FALSE; return TRUE; } static bool rm_op_by_conn_id(void *data, void *context) { UINT16 *conn_id = context; gatt_operation *op = data; if(op->conn_id == *conn_id) list_remove(gatt_op_queue, data); return TRUE; } static void gatt_op_queue_clean(UINT16 conn_id) { if (gatt_op_queue) list_foreach(gatt_op_queue, rm_op_by_conn_id, &conn_id); mark_as_not_executing(conn_id); } static bool find_op_by_conn_id(void *data, void *context) { UINT16 *conn_id = context; gatt_operation *op = data; if(op->conn_id == *conn_id) return FALSE; return TRUE; } static void gatt_execute_next_op(UINT16 conn_id) { APPL_TRACE_DEBUG("%s:", __func__, conn_id); if (!gatt_op_queue || list_is_empty(gatt_op_queue)) { APPL_TRACE_DEBUG("%s: op queue is empty", __func__); return; } list_node_t *op_node = list_foreach(gatt_op_queue, find_op_by_conn_id, &conn_id); if (op_node == NULL) { APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__, conn_id); return; } gatt_operation *op = list_node(op_node); if (gatt_op_queue_executing && list_foreach(gatt_op_queue_executing, exec_list_contains, &conn_id)) { APPL_TRACE_DEBUG("%s: can't enqueue next op, already executing", __func__); return; } if (op->type == GATT_READ_CHAR) { const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(op->conn_id, op->handle); mark_as_executing(conn_id); BTA_GATTC_ReadCharacteristic(op->conn_id, p_char->handle, BTA_GATT_AUTH_REQ_NONE); list_remove(gatt_op_queue, op); } else if (op->type == GATT_READ_DESC) { const tBTA_GATTC_DESCRIPTOR *p_desc = BTA_GATTC_GetDescriptor(op->conn_id, op->handle); mark_as_executing(conn_id); BTA_GATTC_ReadCharDescr(op->conn_id, p_desc->handle, BTA_GATT_AUTH_REQ_NONE); list_remove(gatt_op_queue, op); } else if (op->type == GATT_WRITE_CHAR) { const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(op->conn_id, op->handle); mark_as_executing(conn_id); BTA_GATTC_WriteCharValue(op->conn_id, p_char->handle, op->write_type, op->len, op->p_value, BTA_GATT_AUTH_REQ_NONE); list_remove(gatt_op_queue, op); } else if (op->type == GATT_WRITE_DESC) { const tBTA_GATTC_DESCRIPTOR *p_desc = BTA_GATTC_GetDescriptor(op->conn_id, op->handle); tBTA_GATT_UNFMT value; value.len = op->len; value.p_value = op->p_value; mark_as_executing(conn_id); BTA_GATTC_WriteCharDescr(op->conn_id, p_desc->handle, BTA_GATTC_TYPE_WRITE, &value, BTA_GATT_AUTH_REQ_NONE); list_remove(gatt_op_queue, op); } } static void gatt_queue_read_op(UINT8 op_type, UINT16 conn_id, UINT16 handle) { if (gatt_op_queue == NULL) { gatt_op_queue = list_new(osi_free); } gatt_operation *op = osi_malloc(sizeof(gatt_operation)); op->type = op_type; op->conn_id = conn_id; op->handle = handle; list_append(gatt_op_queue, op); gatt_execute_next_op(conn_id); } static void gatt_queue_write_op(UINT8 op_type, UINT16 conn_id, UINT16 handle, UINT16 len, UINT8 *p_value, tBTA_GATTC_WRITE_TYPE write_type) { if (gatt_op_queue == NULL) { gatt_op_queue = list_new(osi_free); } gatt_operation *op = osi_malloc(sizeof(gatt_operation)); op->type = op_type; op->conn_id = conn_id; op->handle = handle; op->write_type = write_type; op->len = len; memcpy(op->p_value, p_value, len); list_append(gatt_op_queue, op); gatt_execute_next_op(conn_id); } #if BTA_HH_DEBUG == TRUE static const char *bta_hh_le_rpt_name[4] = { "UNKNOWN", "INPUT", "OUTPUT", "FEATURE" }; /******************************************************************************* ** ** Function bta_hh_le_hid_report_dbg ** ** Description debug function to print out all HID report available on remote ** device. ** ** Returns void ** *******************************************************************************/ static void bta_hh_le_hid_report_dbg(tBTA_HH_DEV_CB *p_cb) { APPL_TRACE_DEBUG("%s: HID Report DB", __func__); if (!p_cb->hid_srvc.in_use) return; tBTA_HH_LE_RPT *p_rpt = &p_cb->hid_srvc.report[0]; for (int j = 0; j < BTA_HH_LE_RPT_MAX; j ++, p_rpt++) { char * rpt_name = "Unknown"; if (!p_rpt->in_use) break; if (p_rpt->uuid == GATT_UUID_HID_REPORT) rpt_name = "Report"; if (p_rpt->uuid == GATT_UUID_HID_BT_KB_INPUT) rpt_name = "Boot KB Input"; if (p_rpt->uuid == GATT_UUID_HID_BT_KB_OUTPUT) rpt_name = "Boot KB Output"; if (p_rpt->uuid == GATT_UUID_HID_BT_MOUSE_INPUT) rpt_name = "Boot MI Input"; APPL_TRACE_DEBUG("\t\t [%s- 0x%04x] [Type: %s], [ReportID: %d] [srvc_inst_id: %d] [char_inst_id: %d] [Clt_cfg: %d]", rpt_name, p_rpt->uuid , ((p_rpt->rpt_type < 4) ? bta_hh_le_rpt_name[p_rpt->rpt_type] : "UNKNOWN"), p_rpt->rpt_id, p_rpt->srvc_inst_id, p_rpt->char_inst_id, p_rpt->client_cfg_value); } } /******************************************************************************* ** ** Function bta_hh_uuid_to_str ** ** Description ** ** Returns void ** *******************************************************************************/ static char *bta_hh_uuid_to_str(UINT16 uuid) { switch(uuid) { case GATT_UUID_HID_INFORMATION: return "GATT_UUID_HID_INFORMATION"; case GATT_UUID_HID_REPORT_MAP: return "GATT_UUID_HID_REPORT_MAP"; case GATT_UUID_HID_CONTROL_POINT: return "GATT_UUID_HID_CONTROL_POINT"; case GATT_UUID_HID_REPORT: return "GATT_UUID_HID_REPORT"; case GATT_UUID_HID_PROTO_MODE: return "GATT_UUID_HID_PROTO_MODE"; case GATT_UUID_HID_BT_KB_INPUT: return "GATT_UUID_HID_BT_KB_INPUT"; case GATT_UUID_HID_BT_KB_OUTPUT: return "GATT_UUID_HID_BT_KB_OUTPUT"; case GATT_UUID_HID_BT_MOUSE_INPUT: return "GATT_UUID_HID_BT_MOUSE_INPUT"; case GATT_UUID_CHAR_CLIENT_CONFIG: return "GATT_UUID_CHAR_CLIENT_CONFIG"; case GATT_UUID_EXT_RPT_REF_DESCR: return "GATT_UUID_EXT_RPT_REF_DESCR"; case GATT_UUID_RPT_REF_DESCR: return "GATT_UUID_RPT_REF_DESCR"; default: return "Unknown UUID"; } } #endif /******************************************************************************* ** ** Function bta_hh_le_enable ** ** Description initialize LE HID related functionality ** ** ** Returns void ** *******************************************************************************/ void bta_hh_le_enable(void) { char app_name[LEN_UUID_128 + 1]; tBT_UUID app_uuid = {LEN_UUID_128,{0}}; UINT8 xx; bta_hh_cb.gatt_if = BTA_GATTS_INVALID_IF; for (xx = 0; xx < BTA_HH_MAX_DEVICE; xx ++) bta_hh_cb.le_cb_index[xx] = BTA_HH_IDX_INVALID; memset (app_name, 0, LEN_UUID_128 + 1); strncpy(app_name, "BTA HH OVER LE", LEN_UUID_128); memcpy((void *)app_uuid.uu.uuid128, (void *)app_name, LEN_UUID_128); BTA_GATTC_AppRegister(&app_uuid, bta_hh_gattc_callback); return; } /******************************************************************************* ** ** Function bta_hh_le_register_cmpl ** ** Description BTA HH register with BTA GATTC completed ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_register_cmpl(tBTA_GATTC_REG *p_reg) { tBTA_HH_STATUS status = BTA_HH_ERR; if (p_reg->status == BTA_GATT_OK) { bta_hh_cb.gatt_if = p_reg->client_if; status = BTA_HH_OK; } else bta_hh_cb.gatt_if = BTA_GATTS_INVALID_IF; /* signal BTA call back event */ (* bta_hh_cb.p_cback)(BTA_HH_ENABLE_EVT, (tBTA_HH *)&status); } /******************************************************************************* ** ** Function bta_hh_le_is_hh_gatt_if ** ** Description Check to see if client_if is BTA HH LE GATT interface ** ** ** Returns whether it is HH GATT IF ** *******************************************************************************/ BOOLEAN bta_hh_le_is_hh_gatt_if(tBTA_GATTC_IF client_if) { return (bta_hh_cb.gatt_if == client_if); } /******************************************************************************* ** ** Function bta_hh_le_deregister ** ** Description De-register BTA HH from BTA GATTC ** ** ** Returns void ** *******************************************************************************/ void bta_hh_le_deregister(void) { BTA_GATTC_AppDeregister(bta_hh_cb.gatt_if); } /******************************************************************************* ** ** Function bta_hh_is_le_device ** ** Description Check to see if the remote device is a LE only device ** ** Parameters: ** *******************************************************************************/ BOOLEAN bta_hh_is_le_device(tBTA_HH_DEV_CB *p_cb, BD_ADDR remote_bda) { p_cb->is_le_device = BTM_UseLeLink (remote_bda); return p_cb->is_le_device; } /******************************************************************************* ** ** Function bta_hh_le_open_conn ** ** Description open a GATT connection first. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_open_conn(tBTA_HH_DEV_CB *p_cb, BD_ADDR remote_bda) { /* update cb_index[] map */ p_cb->hid_handle = BTA_HH_GET_LE_DEV_HDL(p_cb->index); memcpy(p_cb->addr, remote_bda, BD_ADDR_LEN); bta_hh_cb.le_cb_index[BTA_HH_GET_LE_CB_IDX(p_cb->hid_handle)] = p_cb->index; p_cb->in_use = TRUE; BTA_GATTC_Open(bta_hh_cb.gatt_if, remote_bda, TRUE, BTA_GATT_TRANSPORT_LE); } /******************************************************************************* ** ** Function bta_hh_le_find_dev_cb_by_conn_id ** ** Description Utility function find a device control block by connection ID. ** *******************************************************************************/ tBTA_HH_DEV_CB * bta_hh_le_find_dev_cb_by_conn_id(UINT16 conn_id) { UINT8 i; tBTA_HH_DEV_CB *p_dev_cb = &bta_hh_cb.kdev[0]; for (i = 0; i < BTA_HH_MAX_DEVICE; i ++, p_dev_cb ++) { if (p_dev_cb->in_use && p_dev_cb->conn_id == conn_id) return p_dev_cb; } return NULL; } /******************************************************************************* ** ** Function bta_hh_le_find_dev_cb_by_bda ** ** Description Utility function find a device control block by BD address. ** *******************************************************************************/ tBTA_HH_DEV_CB * bta_hh_le_find_dev_cb_by_bda(BD_ADDR bda) { UINT8 i; tBTA_HH_DEV_CB *p_dev_cb = &bta_hh_cb.kdev[0]; for (i = 0; i < BTA_HH_MAX_DEVICE; i ++, p_dev_cb ++) { if (p_dev_cb->in_use && memcmp(p_dev_cb->addr, bda, BD_ADDR_LEN) == 0) return p_dev_cb; } return NULL; } /******************************************************************************* ** ** Function bta_hh_le_find_service_inst_by_battery_inst_id ** ** Description find HID service instance ID by battery service instance ID ** *******************************************************************************/ UINT8 bta_hh_le_find_service_inst_by_battery_inst_id(tBTA_HH_DEV_CB *p_cb, UINT8 ba_inst_id) { if (p_cb->hid_srvc.in_use && p_cb->hid_srvc.incl_srvc_inst == ba_inst_id) { return p_cb->hid_srvc.srvc_inst_id; } return BTA_HH_IDX_INVALID; } /******************************************************************************* ** ** Function bta_hh_le_find_report_entry ** ** Description find the report entry by service instance and report UUID and ** instance ID ** *******************************************************************************/ tBTA_HH_LE_RPT * bta_hh_le_find_report_entry(tBTA_HH_DEV_CB *p_cb, UINT8 srvc_inst_id, /* service instance ID */ UINT16 rpt_uuid, UINT8 char_inst_id) { UINT8 i; UINT8 hid_inst_id = srvc_inst_id; tBTA_HH_LE_RPT *p_rpt; if (rpt_uuid == GATT_UUID_BATTERY_LEVEL) { hid_inst_id = bta_hh_le_find_service_inst_by_battery_inst_id(p_cb, srvc_inst_id); if (hid_inst_id == BTA_HH_IDX_INVALID) return NULL; } p_rpt = &p_cb->hid_srvc.report[0]; for (i = 0; i < BTA_HH_LE_RPT_MAX; i ++, p_rpt ++) { if (p_rpt->uuid == rpt_uuid && p_rpt->srvc_inst_id == srvc_inst_id && p_rpt->char_inst_id == char_inst_id) { return p_rpt; } } return NULL; } /******************************************************************************* ** ** Function bta_hh_le_find_rpt_by_idtype ** ** Description find a report entry by report ID and protocol mode ** ** Returns void ** *******************************************************************************/ tBTA_HH_LE_RPT * bta_hh_le_find_rpt_by_idtype(tBTA_HH_LE_RPT*p_head, UINT8 mode, tBTA_HH_RPT_TYPE r_type, UINT8 rpt_id) { tBTA_HH_LE_RPT *p_rpt = p_head; UINT8 i; #if BTA_HH_DEBUG == TRUE APPL_TRACE_DEBUG("bta_hh_le_find_rpt_by_idtype: r_type: %d rpt_id: %d", r_type, rpt_id); #endif for (i = 0 ; i < BTA_HH_LE_RPT_MAX; i ++, p_rpt++) { if (p_rpt->in_use && p_rpt->rpt_id == rpt_id && r_type == p_rpt->rpt_type) { /* return battery report w/o condition */ if (p_rpt->uuid == GATT_UUID_BATTERY_LEVEL) return p_rpt; if (mode == BTA_HH_PROTO_RPT_MODE && p_rpt->uuid == GATT_UUID_HID_REPORT) return p_rpt; if ( mode ==BTA_HH_PROTO_BOOT_MODE && (p_rpt->uuid >= GATT_UUID_HID_BT_KB_INPUT && p_rpt->uuid <= GATT_UUID_HID_BT_MOUSE_INPUT)) return p_rpt; } } return NULL; } /******************************************************************************* ** ** Function bta_hh_le_find_alloc_report_entry ** ** Description find or allocate a report entry in the HID service report list. ** *******************************************************************************/ tBTA_HH_LE_RPT * bta_hh_le_find_alloc_report_entry(tBTA_HH_DEV_CB *p_cb, UINT8 srvc_inst_id, UINT16 rpt_uuid, UINT8 inst_id) { UINT8 i, hid_inst_id = srvc_inst_id; tBTA_HH_LE_RPT *p_rpt; if (rpt_uuid == GATT_UUID_BATTERY_LEVEL) { hid_inst_id = bta_hh_le_find_service_inst_by_battery_inst_id(p_cb, srvc_inst_id); if (hid_inst_id == BTA_HH_IDX_INVALID) return NULL; } p_rpt = &p_cb->hid_srvc.report[0]; for (i = 0; i < BTA_HH_LE_RPT_MAX; i ++, p_rpt ++) { if (!p_rpt->in_use || (p_rpt->uuid == rpt_uuid && p_rpt->srvc_inst_id == srvc_inst_id && p_rpt->char_inst_id == inst_id)) { if (!p_rpt->in_use) { p_rpt->in_use = TRUE; p_rpt->index = i; p_rpt->srvc_inst_id = srvc_inst_id; p_rpt->char_inst_id = inst_id; p_rpt->uuid = rpt_uuid; /* assign report type */ for (i = 0; i < BTA_LE_HID_RTP_UUID_MAX; i ++) { if (bta_hh_uuid_to_rtp_type[i][0] == rpt_uuid) { p_rpt->rpt_type = (tBTA_HH_RPT_TYPE)bta_hh_uuid_to_rtp_type[i][1]; if (rpt_uuid == GATT_UUID_HID_BT_KB_INPUT || rpt_uuid == GATT_UUID_HID_BT_KB_OUTPUT) p_rpt->rpt_id = BTA_HH_KEYBD_RPT_ID; if (rpt_uuid == GATT_UUID_HID_BT_MOUSE_INPUT) p_rpt->rpt_id = BTA_HH_MOUSE_RPT_ID; break; } } } return p_rpt; } } return NULL; } static tBTA_GATTC_DESCRIPTOR *find_descriptor_by_short_uuid(UINT16 conn_id, UINT16 char_handle, UINT16 short_uuid) { const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(conn_id, char_handle); if (!p_char) { LOG_WARN(LOG_TAG, "%s No such characteristic: %d", __func__, char_handle); return NULL; } if (!p_char->descriptors || list_is_empty(p_char->descriptors)) return NULL; for (list_node_t *dn = list_begin(p_char->descriptors); dn != list_end(p_char->descriptors); dn = list_next(dn)) { tBTA_GATTC_DESCRIPTOR *p_desc = list_node(dn); if (p_char->uuid.len == LEN_UUID_16 && p_desc->uuid.uu.uuid16 == short_uuid) return p_desc; } return NULL; } /******************************************************************************* ** ** Function bta_hh_le_read_char_dscrpt ** ** Description read characteristic descriptor ** *******************************************************************************/ static tBTA_HH_STATUS bta_hh_le_read_char_dscrpt(tBTA_HH_DEV_CB *p_cb, UINT16 char_handle, UINT16 short_uuid) { const tBTA_GATTC_DESCRIPTOR *p_desc = find_descriptor_by_short_uuid(p_cb->conn_id, char_handle, short_uuid); if (!p_desc) return BTA_HH_ERR; gatt_queue_read_op(GATT_READ_DESC, p_cb->conn_id, p_desc->handle); return BTA_HH_OK; } /******************************************************************************* ** ** Function bta_hh_le_save_rpt_ref ** ** Description save report reference information and move to next one. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_save_rpt_ref(tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_LE_RPT *p_rpt, tBTA_GATTC_READ *p_data) { /* if the length of the descriptor value is right, parse it */ if (p_data->status == BTA_GATT_OK && p_data->p_value && p_data->p_value->len == 2) { UINT8 *pp = p_data->p_value->p_value; STREAM_TO_UINT8(p_rpt->rpt_id, pp); STREAM_TO_UINT8(p_rpt->rpt_type, pp); if (p_rpt->rpt_type > BTA_HH_RPTT_FEATURE) /* invalid report type */ p_rpt->rpt_type = BTA_HH_RPTT_RESRV; #if BTA_HH_DEBUG == TRUE APPL_TRACE_DEBUG("%s: report ID: %d", __func__, p_rpt->rpt_id); #endif tBTA_HH_RPT_CACHE_ENTRY rpt_entry; rpt_entry.rpt_id = p_rpt->rpt_id; rpt_entry.rpt_type = p_rpt->rpt_type; rpt_entry.rpt_uuid = p_rpt->uuid; rpt_entry.srvc_inst_id = p_rpt->srvc_inst_id; rpt_entry.char_inst_id = p_rpt->char_inst_id; bta_hh_le_co_rpt_info(p_dev_cb->addr, &rpt_entry, p_dev_cb->app_id); } else if (p_data->status == BTA_GATT_INSUF_AUTHENTICATION) { /* close connection right away */ p_dev_cb->status = BTA_HH_ERR_AUTH_FAILED; /* close the connection and report service discovery complete with error */ bta_hh_le_api_disc_act(p_dev_cb); return; } if (p_rpt->index < BTA_HH_LE_RPT_MAX - 1) p_rpt ++; else p_rpt = NULL; } /******************************************************************************* ** ** Function bta_hh_le_save_rpt_ref ** ** Description save report reference information and move to next one. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_save_ext_rpt_ref(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATTC_READ *p_data) { /* if the length of the descriptor value is right, parse it assume it's a 16 bits UUID */ if (p_data->status == BTA_GATT_OK && p_data->p_value && p_data->p_value->len == 2) { UINT8 *pp = p_data->p_value->p_value; STREAM_TO_UINT16(p_dev_cb->hid_srvc.ext_rpt_ref, pp); #if BTA_HH_DEBUG == TRUE APPL_TRACE_DEBUG("%s: External Report Reference UUID 0x%04x", __func__, p_dev_cb->hid_srvc.ext_rpt_ref); #endif } } /******************************************************************************* ** ** Function bta_hh_le_register_input_notif ** ** Description Register for all notifications for the report applicable ** for the protocol mode. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_register_input_notif(tBTA_HH_DEV_CB *p_dev_cb, UINT8 proto_mode, BOOLEAN register_ba) { tBTA_HH_LE_RPT *p_rpt = &p_dev_cb->hid_srvc.report[0]; #if BTA_HH_DEBUG == TRUE APPL_TRACE_DEBUG("%s: bta_hh_le_register_input_notif mode: %d", __func__, proto_mode); #endif for (int i = 0; i < BTA_HH_LE_RPT_MAX; i ++, p_rpt ++) { if (p_rpt->rpt_type == BTA_HH_RPTT_INPUT) { if (register_ba && p_rpt->uuid == GATT_UUID_BATTERY_LEVEL) { BTA_GATTC_RegisterForNotifications(bta_hh_cb.gatt_if, p_dev_cb->addr, p_rpt->char_inst_id); } /* boot mode, deregister report input notification */ else if (proto_mode == BTA_HH_PROTO_BOOT_MODE) { if (p_rpt->uuid == GATT_UUID_HID_REPORT && p_rpt->client_cfg_value == BTA_GATT_CLT_CONFIG_NOTIFICATION) { APPL_TRACE_DEBUG("---> Deregister Report ID: %d", p_rpt->rpt_id); BTA_GATTC_DeregisterForNotifications(bta_hh_cb.gatt_if, p_dev_cb->addr, p_rpt->char_inst_id); } /* register boot reports notification */ else if (p_rpt->uuid == GATT_UUID_HID_BT_KB_INPUT || p_rpt->uuid == GATT_UUID_HID_BT_MOUSE_INPUT) { APPL_TRACE_DEBUG("<--- Register Boot Report ID: %d", p_rpt->rpt_id); BTA_GATTC_RegisterForNotifications(bta_hh_cb.gatt_if, p_dev_cb->addr, p_rpt->char_inst_id); } } else if (proto_mode == BTA_HH_PROTO_RPT_MODE) { if ((p_rpt->uuid == GATT_UUID_HID_BT_KB_INPUT || p_rpt->uuid == GATT_UUID_HID_BT_MOUSE_INPUT) && p_rpt->client_cfg_value == BTA_GATT_CLT_CONFIG_NOTIFICATION) { APPL_TRACE_DEBUG("---> Deregister Boot Report ID: %d", p_rpt->rpt_id); BTA_GATTC_DeregisterForNotifications(bta_hh_cb.gatt_if, p_dev_cb->addr, p_rpt->char_inst_id); } else if (p_rpt->uuid == GATT_UUID_HID_REPORT && p_rpt->client_cfg_value == BTA_GATT_CLT_CONFIG_NOTIFICATION) { APPL_TRACE_DEBUG("<--- Register Report ID: %d", p_rpt->rpt_id); BTA_GATTC_RegisterForNotifications(bta_hh_cb.gatt_if, p_dev_cb->addr, p_rpt->char_inst_id); } } /* else unknow protocol mode */ } } } /******************************************************************************* ** ** Function bta_hh_le_open_cmpl ** ** Description HID over GATT connection sucessfully opened ** *******************************************************************************/ void bta_hh_le_open_cmpl(tBTA_HH_DEV_CB *p_cb) { if ( p_cb->disc_active == BTA_HH_LE_DISC_NONE) { #if BTA_HH_DEBUG bta_hh_le_hid_report_dbg(p_cb); #endif bta_hh_le_register_input_notif(p_cb, p_cb->mode, TRUE); bta_hh_sm_execute(p_cb, BTA_HH_OPEN_CMPL_EVT, NULL); #if (BTA_HH_LE_RECONN == TRUE) if (p_cb->status == BTA_HH_OK) { bta_hh_le_add_dev_bg_conn(p_cb, TRUE); } #endif } } /******************************************************************************* ** ** Function bta_hh_le_write_char_clt_cfg ** ** Description Utility function to find and write client configuration of ** a characteristic ** *******************************************************************************/ BOOLEAN bta_hh_le_write_char_clt_cfg(tBTA_HH_DEV_CB *p_cb, UINT8 char_handle, UINT16 clt_cfg_value) { UINT8 buf[2], *pp = buf; tBTA_GATTC_DESCRIPTOR *p_desc = find_descriptor_by_short_uuid(p_cb->conn_id, char_handle, GATT_UUID_CHAR_CLIENT_CONFIG); if (!p_desc) return FALSE; UINT16_TO_STREAM(pp, clt_cfg_value); gatt_queue_write_op(GATT_WRITE_DESC, p_cb->conn_id, p_desc->handle, 2, buf, BTA_GATTC_TYPE_WRITE); return TRUE; } /******************************************************************************* ** ** Function bta_hh_le_write_rpt_clt_cfg ** ** Description write client configuration. This is only for input report ** enable all input notification upon connection open. ** *******************************************************************************/ BOOLEAN bta_hh_le_write_rpt_clt_cfg(tBTA_HH_DEV_CB *p_cb, UINT8 srvc_inst_id) { UINT8 i; tBTA_HH_LE_RPT *p_rpt = &p_cb->hid_srvc.report[p_cb->clt_cfg_idx]; for (i = p_cb->clt_cfg_idx; i < BTA_HH_LE_RPT_MAX && p_rpt->in_use; i ++, p_rpt ++) { /* enable notification for all input report, regardless mode */ if (p_rpt->rpt_type == BTA_HH_RPTT_INPUT) { if (bta_hh_le_write_char_clt_cfg(p_cb, p_rpt->char_inst_id, BTA_GATT_CLT_CONFIG_NOTIFICATION)) { p_cb->clt_cfg_idx = i; return TRUE; } } } p_cb->clt_cfg_idx = 0; /* client configuration is completed, send open callback */ if (p_cb->state == BTA_HH_W4_CONN_ST) { p_cb->disc_active &= ~BTA_HH_LE_DISC_HIDS; bta_hh_le_open_cmpl(p_cb); } return FALSE; } /******************************************************************************* ** ** Function bta_hh_le_set_protocol_mode ** ** Description Set remote device protocol mode. ** *******************************************************************************/ BOOLEAN bta_hh_le_set_protocol_mode(tBTA_HH_DEV_CB *p_cb, tBTA_HH_PROTO_MODE mode) { tBTA_HH_CBDATA cback_data; BOOLEAN exec = FALSE; APPL_TRACE_DEBUG("%s attempt mode: %s", __func__, (mode == BTA_HH_PROTO_RPT_MODE)? "Report": "Boot"); cback_data.handle = p_cb->hid_handle; /* boot mode is not supported in the remote device */ if (p_cb->hid_srvc.proto_mode_handle == 0) { p_cb->mode = BTA_HH_PROTO_RPT_MODE; if (mode == BTA_HH_PROTO_BOOT_MODE) { APPL_TRACE_ERROR("Set Boot Mode failed!! No PROTO_MODE Char!"); cback_data.status = BTA_HH_ERR; } else { /* if set to report mode, need to de-register all input report notification */ bta_hh_le_register_input_notif(p_cb, p_cb->mode, FALSE); cback_data.status = BTA_HH_OK; } if (p_cb->state == BTA_HH_W4_CONN_ST) { p_cb->status = (cback_data.status == BTA_HH_OK)? BTA_HH_OK: BTA_HH_ERR_PROTO; } else (* bta_hh_cb.p_cback)(BTA_HH_SET_PROTO_EVT, (tBTA_HH *)&cback_data); } else if (p_cb->mode != mode) { p_cb->mode = mode; mode = (mode == BTA_HH_PROTO_BOOT_MODE)? BTA_HH_LE_PROTO_BOOT_MODE : BTA_HH_LE_PROTO_REPORT_MODE; gatt_queue_write_op(GATT_WRITE_CHAR, p_cb->conn_id, p_cb->hid_srvc.proto_mode_handle, 1, &mode, BTA_GATTC_TYPE_WRITE_NO_RSP); exec = TRUE; } return exec; } /******************************************************************************* ** ** Function bta_hh_le_get_protocol_mode ** ** Description Get remote device protocol mode. ** *******************************************************************************/ void bta_hh_le_get_protocol_mode(tBTA_HH_DEV_CB *p_cb) { tBTA_HH_HSDATA hs_data; p_cb->w4_evt = BTA_HH_GET_PROTO_EVT; if (p_cb->hid_srvc.in_use && p_cb->hid_srvc.proto_mode_handle != 0) { gatt_queue_read_op(GATT_READ_CHAR, p_cb->conn_id, p_cb->hid_srvc.proto_mode_handle); return; } /* no service support protocol_mode, by default report mode */ hs_data.status = BTA_HH_OK; hs_data.handle = p_cb->hid_handle; hs_data.rsp_data.proto_mode = BTA_HH_PROTO_RPT_MODE; p_cb->w4_evt = 0; (* bta_hh_cb.p_cback)(BTA_HH_GET_PROTO_EVT, (tBTA_HH *)&hs_data); } /******************************************************************************* ** ** Function bta_hh_le_dis_cback ** ** Description DIS read complete callback ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_dis_cback(BD_ADDR addr, tDIS_VALUE *p_dis_value) { tBTA_HH_DEV_CB *p_cb = bta_hh_le_find_dev_cb_by_bda(addr); if (p_cb == NULL || p_dis_value == NULL) { APPL_TRACE_ERROR("received unexpected/error DIS callback"); return; } p_cb->disc_active &= ~BTA_HH_LE_DISC_DIS; /* plug in the PnP info for this device */ if (p_dis_value->attr_mask & DIS_ATTR_PNP_ID_BIT) { #if BTA_HH_DEBUG == TRUE APPL_TRACE_DEBUG("Plug in PnP info: product_id = %02x, vendor_id = %04x, version = %04x", p_dis_value->pnp_id.product_id, p_dis_value->pnp_id.vendor_id, p_dis_value->pnp_id.product_version); #endif p_cb->dscp_info.product_id = p_dis_value->pnp_id.product_id; p_cb->dscp_info.vendor_id = p_dis_value->pnp_id.vendor_id; p_cb->dscp_info.version = p_dis_value->pnp_id.product_version; } bta_hh_le_open_cmpl(p_cb); } /******************************************************************************* ** ** Function bta_hh_le_pri_service_discovery ** ** Description Initialize GATT discovery on the remote LE HID device by opening ** a GATT connection first. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_pri_service_discovery(tBTA_HH_DEV_CB *p_cb) { tBT_UUID pri_srvc; bta_hh_le_co_reset_rpt_cache(p_cb->addr, p_cb->app_id); p_cb->disc_active |= (BTA_HH_LE_DISC_HIDS|BTA_HH_LE_DISC_DIS); /* read DIS info */ if (!DIS_ReadDISInfo(p_cb->addr, bta_hh_le_dis_cback, DIS_ATTR_PNP_ID_BIT)) { APPL_TRACE_ERROR("read DIS failed"); p_cb->disc_active &= ~BTA_HH_LE_DISC_DIS; } /* in parallel */ /* start primary service discovery for HID service */ pri_srvc.len = LEN_UUID_16; pri_srvc.uu.uuid16 = UUID_SERVCLASS_LE_HID; BTA_GATTC_ServiceSearchRequest(p_cb->conn_id, &pri_srvc); return; } /******************************************************************************* ** ** Function bta_hh_le_encrypt_cback ** ** Description link encryption complete callback for bond verification. ** ** Returns None ** *******************************************************************************/ void bta_hh_le_encrypt_cback(BD_ADDR bd_addr, tBTA_GATT_TRANSPORT transport, void *p_ref_data, tBTM_STATUS result) { UINT8 idx = bta_hh_find_cb(bd_addr); tBTA_HH_DEV_CB *p_dev_cb; UNUSED(p_ref_data); UNUSED (transport); if (idx != BTA_HH_IDX_INVALID) p_dev_cb = &bta_hh_cb.kdev[idx]; else { APPL_TRACE_ERROR("unexpected encryption callback, ignore"); return; } p_dev_cb->status = (result == BTM_SUCCESS) ? BTA_HH_OK : BTA_HH_ERR_SEC; p_dev_cb->reason = result; bta_hh_sm_execute(p_dev_cb, BTA_HH_ENC_CMPL_EVT, NULL); } /******************************************************************************* ** ** Function bta_hh_security_cmpl ** ** Description Security check completed, start the service discovery ** if no cache available, otherwise report connection open completed ** ** Parameters: ** *******************************************************************************/ void bta_hh_security_cmpl(tBTA_HH_DEV_CB *p_cb, tBTA_HH_DATA *p_buf) { UNUSED(p_buf); APPL_TRACE_DEBUG("%s", __func__); if (p_cb->status == BTA_HH_OK) { if (!p_cb->hid_srvc.in_use) { APPL_TRACE_DEBUG("bta_hh_security_cmpl no reports loaded, try to load"); /* start loading the cache if not in stack */ //TODO(jpawlowski): cache storage is broken, fix it // tBTA_HH_RPT_CACHE_ENTRY *p_rpt_cache; // UINT8 num_rpt = 0; // if ((p_rpt_cache = bta_hh_le_co_cache_load(p_cb->addr, &num_rpt, p_cb->app_id)) != NULL) // { // bta_hh_process_cache_rpt(p_cb, p_rpt_cache, num_rpt); // } } /* discovery has been done for HID service */ if (p_cb->app_id != 0 && p_cb->hid_srvc.in_use) { APPL_TRACE_DEBUG("%s: discovery has been done for HID service", __func__); /* configure protocol mode */ if (bta_hh_le_set_protocol_mode(p_cb, p_cb->mode) == FALSE) { bta_hh_le_open_cmpl(p_cb); } } /* start primary service discovery for HID service */ else { APPL_TRACE_DEBUG("%s: Starting service discovery", __func__); bta_hh_le_pri_service_discovery(p_cb); } } else { APPL_TRACE_ERROR("%s() - encryption failed; status=0x%04x, reason=0x%04x", __FUNCTION__, p_cb->status, p_cb->reason); if (!(p_cb->status == BTA_HH_ERR_SEC && p_cb->reason == BTM_ERR_PROCESSING)) bta_hh_le_api_disc_act(p_cb); } } /******************************************************************************* ** ** Function bta_hh_le_notify_enc_cmpl ** ** Description process GATT encryption complete event ** ** Returns ** *******************************************************************************/ void bta_hh_le_notify_enc_cmpl(tBTA_HH_DEV_CB *p_cb, tBTA_HH_DATA *p_buf) { if (p_cb == NULL || p_cb->security_pending == FALSE || p_buf == NULL || p_buf->le_enc_cmpl.client_if != bta_hh_cb.gatt_if) { return; } p_cb->security_pending = FALSE; bta_hh_start_security(p_cb, NULL); } /******************************************************************************* ** ** Function bta_hh_clear_service_cache ** ** Description clear the service cache ** ** Parameters: ** *******************************************************************************/ void bta_hh_clear_service_cache(tBTA_HH_DEV_CB *p_cb) { tBTA_HH_LE_HID_SRVC *p_hid_srvc = &p_cb->hid_srvc; p_cb->app_id = 0; p_cb->dscp_info.descriptor.dsc_list = NULL; osi_free_and_reset((void **)&p_hid_srvc->rpt_map); memset(p_hid_srvc, 0, sizeof(tBTA_HH_LE_HID_SRVC)); } /******************************************************************************* ** ** Function bta_hh_start_security ** ** Description start the security check of the established connection ** ** Parameters: ** *******************************************************************************/ void bta_hh_start_security(tBTA_HH_DEV_CB *p_cb, tBTA_HH_DATA *p_buf) { UINT8 sec_flag=0; tBTM_SEC_DEV_REC *p_dev_rec; UNUSED(p_buf); p_dev_rec = btm_find_dev(p_cb->addr); if (p_dev_rec) { if (p_dev_rec->sec_state == BTM_SEC_STATE_ENCRYPTING || p_dev_rec->sec_state == BTM_SEC_STATE_AUTHENTICATING) { /* if security collision happened, wait for encryption done */ p_cb->security_pending = TRUE; return; } } /* verify bond */ BTM_GetSecurityFlagsByTransport(p_cb->addr, &sec_flag, BT_TRANSPORT_LE); /* if link has been encrypted */ if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { bta_hh_sm_execute(p_cb, BTA_HH_ENC_CMPL_EVT, NULL); } /* if bonded and link not encrypted */ else if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { sec_flag = BTM_BLE_SEC_ENCRYPT; p_cb->status = BTA_HH_ERR_AUTH_FAILED; BTM_SetEncryption(p_cb->addr, BTA_TRANSPORT_LE, bta_hh_le_encrypt_cback, NULL, sec_flag); } /* unbonded device, report security error here */ else if (p_cb->sec_mask != BTA_SEC_NONE) { sec_flag = BTM_BLE_SEC_ENCRYPT_NO_MITM; p_cb->status = BTA_HH_ERR_AUTH_FAILED; bta_hh_clear_service_cache(p_cb); BTM_SetEncryption(p_cb->addr, BTA_TRANSPORT_LE, bta_hh_le_encrypt_cback, NULL, sec_flag); } /* otherwise let it go through */ else { bta_hh_sm_execute(p_cb, BTA_HH_ENC_CMPL_EVT, NULL); } } /******************************************************************************* ** ** Function bta_hh_gatt_open ** ** Description process GATT open event. ** ** Parameters: ** *******************************************************************************/ void bta_hh_gatt_open(tBTA_HH_DEV_CB *p_cb, tBTA_HH_DATA *p_buf) { tBTA_GATTC_OPEN *p_data = &p_buf->le_open; UINT8 *p2; tHID_STATUS status = BTA_HH_ERR; /* if received invalid callback data , ignore it */ if (p_cb == NULL || p_data == NULL) return; p2 = p_data->remote_bda; APPL_TRACE_DEBUG("bta_hh_gatt_open BTA_GATTC_OPEN_EVT bda= [%08x%04x] status =%d", ((p2[0])<<24)+((p2[1])<<16)+((p2[2])<<8)+(p2[3]), ((p2[4])<<8)+ p2[5],p_data->status); if (p_data->status == BTA_GATT_OK) { p_cb->is_le_device = TRUE; p_cb->in_use = TRUE; p_cb->conn_id = p_data->conn_id; p_cb->hid_handle = BTA_HH_GET_LE_DEV_HDL(p_cb->index); bta_hh_cb.le_cb_index[BTA_HH_GET_LE_CB_IDX(p_cb->hid_handle)] = p_cb->index; gatt_op_queue_clean(p_cb->conn_id); #if BTA_HH_DEBUG == TRUE APPL_TRACE_DEBUG("hid_handle = %2x conn_id = %04x cb_index = %d", p_cb->hid_handle, p_cb->conn_id, p_cb->index); #endif bta_hh_sm_execute(p_cb, BTA_HH_START_ENC_EVT, NULL); } else /* open failure */ { bta_hh_sm_execute(p_cb, BTA_HH_SDP_CMPL_EVT, (tBTA_HH_DATA *)&status); } } /******************************************************************************* ** ** Function bta_hh_le_close ** ** Description This function process the GATT close event and post it as a ** BTA HH internal event ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_close(tBTA_GATTC_CLOSE * p_data) { tBTA_HH_DEV_CB *p_dev_cb = bta_hh_le_find_dev_cb_by_bda(p_data->remote_bda); UINT16 sm_event = BTA_HH_GATT_CLOSE_EVT; if (p_dev_cb != NULL) { tBTA_HH_LE_CLOSE *p_buf = (tBTA_HH_LE_CLOSE *)osi_malloc(sizeof(tBTA_HH_LE_CLOSE)); p_buf->hdr.event = sm_event; p_buf->hdr.layer_specific = (UINT16)p_dev_cb->hid_handle; p_buf->conn_id = p_data->conn_id; p_buf->reason = p_data->reason; p_dev_cb->conn_id = BTA_GATT_INVALID_CONN_ID; p_dev_cb->security_pending = FALSE; bta_sys_sendmsg(p_buf); } } /******************************************************************************* ** ** Function bta_hh_le_gatt_disc_cmpl ** ** Description Check to see if the remote device is a LE only device ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_gatt_disc_cmpl(tBTA_HH_DEV_CB *p_cb, tBTA_HH_STATUS status) { APPL_TRACE_DEBUG("bta_hh_le_gatt_disc_cmpl "); /* if open sucessful or protocol mode not desired, keep the connection open but inform app */ if (status == BTA_HH_OK || status == BTA_HH_ERR_PROTO) { /* assign a special APP ID temp, since device type unknown */ p_cb->app_id = BTA_HH_APP_ID_LE; /* set report notification configuration */ p_cb->clt_cfg_idx = 0; bta_hh_le_write_rpt_clt_cfg(p_cb, p_cb->hid_srvc.srvc_inst_id); } else /* error, close the GATT connection */ { /* close GATT connection if it's on */ bta_hh_le_api_disc_act(p_cb); } } void process_included(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATTC_SERVICE *service) { if (!service->included_svc || list_is_empty(service->included_svc)) { APPL_TRACE_ERROR("%s: Remote device does not have battery level", __func__); return; } for (list_node_t *isn = list_begin(service->included_svc); isn != list_end(service->included_svc); isn = list_next(isn)) { tBTA_GATTC_INCLUDED_SVC *p_isvc = list_node(isn); tBTA_GATTC_SERVICE *incl_svc = p_isvc->included_service; if (incl_svc->uuid.uu.uuid16 == UUID_SERVCLASS_BATTERY && incl_svc->uuid.len == LEN_UUID_16) { /* read include service UUID */ p_dev_cb->hid_srvc.incl_srvc_inst = incl_svc->handle; for (list_node_t *cn = list_begin(incl_svc->characteristics); cn != list_end(incl_svc->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); if (p_char->uuid.uu.uuid16 == GATT_UUID_BATTERY_LEVEL && p_char->uuid.len == LEN_UUID_16) { if (bta_hh_le_find_alloc_report_entry(p_dev_cb, incl_svc->s_handle, GATT_UUID_BATTERY_LEVEL, p_char->handle) == NULL) APPL_TRACE_ERROR("Add battery report entry failed !!!"); gatt_queue_read_op(GATT_READ_CHAR, p_dev_cb->conn_id, p_char->handle); return; } } } } APPL_TRACE_ERROR("%s: Remote device does not have battery level", __func__); } /******************************************************************************* ** ** Function bta_hh_le_search_hid_chars ** ** Description This function discover all characteristics a service and ** all descriptors available. ** ** Parameters: ** *******************************************************************************/ static void bta_hh_le_search_hid_chars(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATTC_SERVICE *service) { tBTA_HH_LE_RPT *p_rpt; for (list_node_t *cn = list_begin(service->characteristics); cn != list_end(service->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); if(p_char->uuid.len != LEN_UUID_16) continue; LOG_DEBUG(LOG_TAG, "%s: %s 0x%04d", __func__, bta_hh_uuid_to_str(p_char->uuid.uu.uuid16), p_char->uuid.uu.uuid16); switch (p_char->uuid.uu.uuid16) { case GATT_UUID_HID_CONTROL_POINT: p_dev_cb->hid_srvc.control_point_handle = p_char->handle; break; case GATT_UUID_HID_INFORMATION: case GATT_UUID_HID_REPORT_MAP: /* read the char value */ gatt_queue_read_op(GATT_READ_CHAR, p_dev_cb->conn_id, p_char->handle); bta_hh_le_read_char_dscrpt(p_dev_cb, p_char->handle, GATT_UUID_EXT_RPT_REF_DESCR); break; case GATT_UUID_HID_REPORT: p_rpt = bta_hh_le_find_alloc_report_entry(p_dev_cb, p_dev_cb->hid_srvc.srvc_inst_id, GATT_UUID_HID_REPORT, p_char->handle); if (p_rpt == NULL) { APPL_TRACE_ERROR("%s: Add report entry failed !!!", __func__); break; } if (p_rpt->rpt_type != BTA_HH_RPTT_INPUT) break; bta_hh_le_read_char_dscrpt(p_dev_cb, p_char->handle, GATT_UUID_RPT_REF_DESCR); break; /* found boot mode report types */ case GATT_UUID_HID_BT_KB_OUTPUT: case GATT_UUID_HID_BT_MOUSE_INPUT: case GATT_UUID_HID_BT_KB_INPUT: if (bta_hh_le_find_alloc_report_entry(p_dev_cb, service->handle, p_char->uuid.uu.uuid16, p_char->handle) == NULL) APPL_TRACE_ERROR("%s: Add report entry failed !!!", __func__); break; default: APPL_TRACE_DEBUG("%s: not processing %s 0x%04d", __func__, bta_hh_uuid_to_str(p_char->uuid.uu.uuid16), p_char->uuid.uu.uuid16); } } /* Make sure PROTO_MODE is processed as last */ for (list_node_t *cn = list_begin(service->characteristics); cn != list_end(service->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); if(p_char->uuid.len != LEN_UUID_16 && p_char->uuid.uu.uuid16 == GATT_UUID_HID_PROTO_MODE) { p_dev_cb->hid_srvc.proto_mode_handle = p_char->handle; bta_hh_le_set_protocol_mode(p_dev_cb, p_dev_cb->mode); break; } } } /******************************************************************************* ** ** Function bta_hh_le_srvc_search_cmpl ** ** Description This function process the GATT service search complete. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_srvc_search_cmpl(tBTA_GATTC_SEARCH_CMPL *p_data) { tBTA_HH_DEV_CB *p_dev_cb = bta_hh_le_find_dev_cb_by_conn_id(p_data->conn_id); /* service search exception or no HID service is supported on remote */ if (p_dev_cb == NULL) return; if(p_data->status != BTA_GATT_OK) { p_dev_cb->status = BTA_HH_ERR_SDP; /* close the connection and report service discovery complete with error */ bta_hh_le_api_disc_act(p_dev_cb); return; } const list_t *services = BTA_GATTC_GetServices(p_data->conn_id); bool have_hid = false; for (list_node_t *sn = list_begin(services); sn != list_end(services); sn = list_next(sn)) { tBTA_GATTC_SERVICE *service = list_node(sn); if (service->uuid.uu.uuid16 == UUID_SERVCLASS_LE_HID && service->is_primary && !have_hid) { have_hid = true; /* found HID primamry service */ p_dev_cb->hid_srvc.in_use = TRUE; p_dev_cb->hid_srvc.srvc_inst_id = service->handle; p_dev_cb->hid_srvc.proto_mode_handle = 0; p_dev_cb->hid_srvc.control_point_handle = 0; process_included(p_dev_cb, service); bta_hh_le_search_hid_chars(p_dev_cb, service); APPL_TRACE_DEBUG("%s: have HID service inst_id= %d", __func__, p_dev_cb->hid_srvc.srvc_inst_id); } else if (service->uuid.uu.uuid16 == UUID_SERVCLASS_SCAN_PARAM) { p_dev_cb->scan_refresh_char_handle = 0; for (list_node_t *cn = list_begin(service->characteristics); cn != list_end(service->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); if (p_char->uuid.len == LEN_UUID_16 && p_char->uuid.uu.uuid16 == GATT_UUID_SCAN_REFRESH) { p_dev_cb->scan_refresh_char_handle = p_char->handle; if (p_char->properties & BTA_GATT_CHAR_PROP_BIT_NOTIFY) p_dev_cb->scps_notify |= BTA_HH_LE_SCPS_NOTIFY_SPT; else p_dev_cb->scps_notify = BTA_HH_LE_SCPS_NOTIFY_NONE; break; } } } else if (service->uuid.uu.uuid16 == UUID_SERVCLASS_GAP_SERVER) { //TODO(jpawlowski): this should be done by GAP profile, remove when GAP is fixed. for (list_node_t *cn = list_begin(service->characteristics); cn != list_end(service->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); if (p_char->uuid.len == LEN_UUID_16 && p_char->uuid.uu.uuid16 == GATT_UUID_GAP_PREF_CONN_PARAM) { /* read the char value */ gatt_queue_read_op(GATT_READ_CHAR, p_dev_cb->conn_id, p_char->handle); break; } } } } bta_hh_le_gatt_disc_cmpl(p_dev_cb, p_dev_cb->status); } /******************************************************************************* ** ** Function bta_hh_read_battery_level_cmpl ** ** Description This function process the battery level read ** ** Parameters: ** *******************************************************************************/ void bta_hh_read_battery_level_cmpl(UINT8 status, tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATTC_READ *p_data) { UNUSED(status); UNUSED(p_data); p_dev_cb->hid_srvc.expl_incl_srvc = TRUE; } /******************************************************************************* ** ** Function bta_hh_le_save_rpt_map ** ** Description save the report map into the control block. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_save_rpt_map(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATTC_READ *p_data) { tBTA_HH_LE_HID_SRVC *p_srvc = &p_dev_cb->hid_srvc; osi_free_and_reset((void **)&p_srvc->rpt_map); if (p_data->p_value->len > 0) p_srvc->rpt_map = (UINT8 *)osi_malloc(p_data->p_value->len); if (p_srvc->rpt_map != NULL) { UINT8 *pp = p_data->p_value->p_value; STREAM_TO_ARRAY(p_srvc->rpt_map, pp, p_data->p_value->len); p_srvc->descriptor.dl_len = p_data->p_value->len; p_srvc->descriptor.dsc_list = p_dev_cb->hid_srvc.rpt_map; } } /******************************************************************************* ** ** Function bta_hh_le_proc_get_rpt_cmpl ** ** Description Process the Read report complete, send GET_REPORT_EVT to application ** with the report data. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_proc_get_rpt_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATTC_READ *p_data) { BT_HDR *p_buf = NULL; tBTA_HH_LE_RPT *p_rpt; tBTA_HH_HSDATA hs_data; UINT8 *pp ; if (p_dev_cb->w4_evt != BTA_HH_GET_RPT_EVT) { APPL_TRACE_ERROR("Unexpected READ cmpl, w4_evt = %d", p_dev_cb->w4_evt); return; } const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(p_dev_cb->conn_id, p_data->handle); memset(&hs_data, 0, sizeof(hs_data)); hs_data.status = BTA_HH_ERR; hs_data.handle = p_dev_cb->hid_handle; if (p_data->status == BTA_GATT_OK) { p_rpt = bta_hh_le_find_report_entry(p_dev_cb, p_char->service->handle, p_char->uuid.uu.uuid16, p_char->handle); if (p_rpt != NULL && p_data->p_value != NULL) { p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + p_data->p_value->len + 1); /* pack data send to app */ hs_data.status = BTA_HH_OK; p_buf->len = p_data->p_value->len + 1; p_buf->layer_specific = 0; p_buf->offset = 0; /* attach report ID as the first byte of the report before sending it to USB HID driver */ pp = (UINT8*)(p_buf + 1); UINT8_TO_STREAM(pp, p_rpt->rpt_id); memcpy(pp, p_data->p_value->p_value, p_data->p_value->len); hs_data.rsp_data.p_rpt_data =p_buf; } } p_dev_cb->w4_evt = 0; (* bta_hh_cb.p_cback)(BTA_HH_GET_RPT_EVT, (tBTA_HH *)&hs_data); osi_free_and_reset((void **)&p_buf); } /******************************************************************************* ** ** Function bta_hh_le_proc_read_proto_mode ** ** Description Process the Read protocol mode, send GET_PROTO_EVT to application ** with the protocol mode. ** *******************************************************************************/ void bta_hh_le_proc_read_proto_mode(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATTC_READ *p_data) { tBTA_HH_HSDATA hs_data; hs_data.status = BTA_HH_ERR; hs_data.handle = p_dev_cb->hid_handle; hs_data.rsp_data.proto_mode = p_dev_cb->mode; if (p_data->status == BTA_GATT_OK && p_data->p_value) { hs_data.status = BTA_HH_OK; /* match up BTE/BTA report/boot mode def*/ hs_data.rsp_data.proto_mode = *(p_data->p_value->p_value); /* LE repot mode is the opposite value of BR/EDR report mode, flip it here */ if (hs_data.rsp_data.proto_mode == 0) hs_data.rsp_data.proto_mode = BTA_HH_PROTO_BOOT_MODE; else hs_data.rsp_data.proto_mode = BTA_HH_PROTO_RPT_MODE; p_dev_cb->mode = hs_data.rsp_data.proto_mode; } #if BTA_HH_DEBUG APPL_TRACE_DEBUG("LE GET_PROTOCOL Mode = [%s]", (hs_data.rsp_data.proto_mode == BTA_HH_PROTO_RPT_MODE)? "Report" : "Boot"); #endif p_dev_cb->w4_evt = 0; (* bta_hh_cb.p_cback)(BTA_HH_GET_PROTO_EVT, (tBTA_HH *)&hs_data); } /******************************************************************************* ** ** Function bta_hh_w4_le_read_char_cmpl ** ** Description process the GATT read complete in W4_CONN state. ** ** Parameters: ** *******************************************************************************/ void bta_hh_w4_le_read_char_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_DATA *p_buf) { tBTA_GATTC_READ * p_data = (tBTA_GATTC_READ *)p_buf; UINT8 *pp ; const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(p_dev_cb->conn_id, p_data->handle); UINT16 char_uuid = p_char->uuid.uu.uuid16; if (char_uuid == GATT_UUID_BATTERY_LEVEL) { bta_hh_read_battery_level_cmpl(p_data->status, p_dev_cb, p_data); } else if (char_uuid == GATT_UUID_GAP_PREF_CONN_PARAM) { //TODO(jpawlowski): this should be done by GAP profile, remove when GAP is fixed. UINT8 *pp = p_data->p_value->p_value; UINT16 min, max, latency, tout; STREAM_TO_UINT16 (min, pp); STREAM_TO_UINT16 (max, pp); STREAM_TO_UINT16 (latency, pp); STREAM_TO_UINT16 (tout, pp); // Make sure both min, and max are bigger than 11.25ms, lower values can introduce // audio issues if A2DP is also active. if (min < BTM_BLE_CONN_INT_MIN_LIMIT) min = BTM_BLE_CONN_INT_MIN_LIMIT; if (max < BTM_BLE_CONN_INT_MIN_LIMIT) max = BTM_BLE_CONN_INT_MIN_LIMIT; if (tout < BTM_BLE_CONN_TIMEOUT_MIN_DEF) tout = BTM_BLE_CONN_TIMEOUT_MIN_DEF; BTM_BleSetPrefConnParams (p_dev_cb->addr, min, max, latency, tout); L2CA_UpdateBleConnParams(p_dev_cb->addr, min, max, latency, tout); } else { if (p_data->status == BTA_GATT_OK && p_data->p_value) { pp = p_data->p_value->p_value; switch (char_uuid) { /* save device information */ case GATT_UUID_HID_INFORMATION: STREAM_TO_UINT16(p_dev_cb->dscp_info.version, pp); STREAM_TO_UINT8(p_dev_cb->dscp_info.ctry_code, pp); STREAM_TO_UINT8(p_dev_cb->dscp_info.flag, pp); break; case GATT_UUID_HID_REPORT_MAP: bta_hh_le_save_rpt_map(p_dev_cb, p_data); return; default: #if BTA_HH_DEBUG == TRUE APPL_TRACE_ERROR("Unexpected read %s(0x%04x)", bta_hh_uuid_to_str(char_uuid), char_uuid); #endif break; } } else { #if BTA_HH_DEBUG == TRUE APPL_TRACE_ERROR("read uuid %s[0x%04x] error: %d", bta_hh_uuid_to_str(char_uuid), char_uuid, p_data->status); #else APPL_TRACE_ERROR("read uuid [0x%04x] error: %d", char_uuid, p_data->status); #endif } } } /******************************************************************************* ** ** Function bta_hh_le_read_char_cmpl ** ** Description a characteristic value is received. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_read_char_cmpl (tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_DATA *p_buf) { tBTA_GATTC_READ * p_data = (tBTA_GATTC_READ *)p_buf; const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(p_dev_cb->conn_id, p_data->handle); UINT16 char_uuid = p_char->uuid.uu.uuid16; switch (char_uuid) { /* GET_REPORT */ case GATT_UUID_HID_REPORT: case GATT_UUID_HID_BT_KB_INPUT: case GATT_UUID_HID_BT_KB_OUTPUT: case GATT_UUID_HID_BT_MOUSE_INPUT: case GATT_UUID_BATTERY_LEVEL: /* read battery level */ bta_hh_le_proc_get_rpt_cmpl(p_dev_cb, p_data); break; case GATT_UUID_HID_PROTO_MODE: bta_hh_le_proc_read_proto_mode(p_dev_cb, p_data); break; default: APPL_TRACE_ERROR("Unexpected Read UUID: 0x%04x", char_uuid); break; } } /******************************************************************************* ** ** Function bta_hh_le_read_descr_cmpl ** ** Description read characteristic descriptor is completed in CONN st. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_read_descr_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_DATA *p_buf) { tBTA_HH_LE_RPT *p_rpt; tBTA_GATTC_READ * p_data = (tBTA_GATTC_READ *)p_buf; UINT8 *pp; const tBTA_GATTC_DESCRIPTOR *p_desc = BTA_GATTC_GetDescriptor(p_data->conn_id, p_data->handle); /* if a report client configuration */ if (p_desc->uuid.uu.uuid16 == GATT_UUID_CHAR_CLIENT_CONFIG) { if ((p_rpt = bta_hh_le_find_report_entry(p_dev_cb, p_desc->characteristic->service->handle, p_desc->characteristic->uuid.uu.uuid16, p_desc->characteristic->handle)) != NULL) { pp = p_data->p_value->p_value; STREAM_TO_UINT16(p_rpt->client_cfg_value, pp); APPL_TRACE_DEBUG("Read Client Configuration: 0x%04x", p_rpt->client_cfg_value); } } } /******************************************************************************* ** ** Function bta_hh_w4_le_read_descr_cmpl ** ** Description read characteristic descriptor is completed in W4_CONN st. ** ** Parameters: ** *******************************************************************************/ void bta_hh_w4_le_read_descr_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_DATA *p_buf) { tBTA_HH_LE_RPT *p_rpt; tBTA_GATTC_READ * p_data = (tBTA_GATTC_READ *)p_buf; UINT16 char_uuid16; if (p_data == NULL) return; const tBTA_GATTC_DESCRIPTOR *p_desc = BTA_GATTC_GetDescriptor(p_data->conn_id, p_data->handle); if (p_desc == NULL) { APPL_TRACE_ERROR("%s: p_descr is NULL %d", __func__, p_data->handle); return; } char_uuid16 = p_desc->characteristic->uuid.uu.uuid16; #if BTA_HH_DEBUG == TRUE APPL_TRACE_DEBUG("bta_hh_w4_le_read_descr_cmpl uuid: %s(0x%04x)", bta_hh_uuid_to_str(p_desc->uuid.uu.uuid16), p_desc->uuid.uu.uuid16); #endif switch (char_uuid16) { case GATT_UUID_HID_REPORT: if ((p_rpt = bta_hh_le_find_report_entry(p_dev_cb, p_desc->characteristic->service->handle, GATT_UUID_HID_REPORT, p_desc->characteristic->handle))) bta_hh_le_save_rpt_ref(p_dev_cb, p_rpt, p_data); break; case GATT_UUID_HID_REPORT_MAP: bta_hh_le_save_ext_rpt_ref(p_dev_cb, p_data); break; case GATT_UUID_BATTERY_LEVEL: if ((p_rpt = bta_hh_le_find_report_entry(p_dev_cb, p_desc->characteristic->service->handle, GATT_UUID_BATTERY_LEVEL, p_desc->characteristic->handle))) bta_hh_le_save_rpt_ref(p_dev_cb, p_rpt, p_data); break; default: APPL_TRACE_ERROR("unknown descriptor read complete for uuid: 0x%04x", char_uuid16); break; } } /******************************************************************************* ** ** Function bta_hh_w4_le_write_cmpl ** ** Description Write charactersitic complete event at W4_CONN st. ** ** Parameters: ** *******************************************************************************/ void bta_hh_w4_le_write_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_DATA *p_buf) { tBTA_GATTC_WRITE *p_data = (tBTA_GATTC_WRITE *)p_buf; if (p_data == NULL) return; const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(p_dev_cb->conn_id, p_data->handle); if (p_char->uuid.uu.uuid16 == GATT_UUID_HID_PROTO_MODE) { p_dev_cb->status = (p_data->status == BTA_GATT_OK) ? BTA_HH_OK : BTA_HH_ERR_PROTO; if ((p_dev_cb->disc_active & BTA_HH_LE_DISC_HIDS) == 0) bta_hh_le_open_cmpl(p_dev_cb); } } /******************************************************************************* ** ** Function bta_hh_le_write_cmpl ** ** Description Write charactersitic complete event at CONN st. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_write_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_DATA *p_buf) { tBTA_GATTC_WRITE *p_data = (tBTA_GATTC_WRITE *)p_buf; tBTA_HH_CBDATA cback_data ; UINT16 cb_evt = p_dev_cb->w4_evt; if (p_data == NULL || cb_evt == 0) return; const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(p_dev_cb->conn_id, p_data->handle); #if BTA_HH_DEBUG APPL_TRACE_DEBUG("bta_hh_le_write_cmpl w4_evt: %d", p_dev_cb->w4_evt); #endif switch (p_char->uuid.uu.uuid16) { /* Set protocol finished */ case GATT_UUID_HID_PROTO_MODE: cback_data.handle = p_dev_cb->hid_handle; if (p_data->status == BTA_GATT_OK) { bta_hh_le_register_input_notif(p_dev_cb, p_dev_cb->mode, FALSE); cback_data.status = BTA_HH_OK; } else cback_data.status = BTA_HH_ERR; p_dev_cb->w4_evt = 0; (* bta_hh_cb.p_cback)(cb_evt, (tBTA_HH *)&cback_data); break; /* Set Report finished */ case GATT_UUID_HID_REPORT: case GATT_UUID_HID_BT_KB_INPUT: case GATT_UUID_HID_BT_MOUSE_INPUT: case GATT_UUID_HID_BT_KB_OUTPUT: cback_data.handle = p_dev_cb->hid_handle; cback_data.status = (p_data->status == BTA_GATT_OK)? BTA_HH_OK : BTA_HH_ERR; p_dev_cb->w4_evt = 0; (* bta_hh_cb.p_cback)(cb_evt, (tBTA_HH *)&cback_data); break; case GATT_UUID_SCAN_INT_WINDOW: bta_hh_le_register_scpp_notif(p_dev_cb, p_data->status); break; default: break; } } /******************************************************************************* ** ** Function bta_hh_le_write_char_descr_cmpl ** ** Description Write charactersitic descriptor complete event ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_write_char_descr_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_DATA *p_buf) { tBTA_GATTC_WRITE *p_data = (tBTA_GATTC_WRITE *)p_buf; UINT8 srvc_inst_id, hid_inst_id; const tBTA_GATTC_DESCRIPTOR *p_desc = BTA_GATTC_GetDescriptor(p_dev_cb->conn_id, p_data->handle); UINT16 desc_uuid = p_desc->uuid.uu.uuid16; UINT16 char_uuid = p_desc->characteristic->uuid.uu.uuid16; /* only write client configuration possible */ if (desc_uuid == GATT_UUID_CHAR_CLIENT_CONFIG) { srvc_inst_id = p_desc->characteristic->service->handle; hid_inst_id = srvc_inst_id; switch (char_uuid) { case GATT_UUID_BATTERY_LEVEL: /* battery level clt cfg registered */ hid_inst_id = bta_hh_le_find_service_inst_by_battery_inst_id(p_dev_cb, srvc_inst_id); /* fall through */ case GATT_UUID_HID_BT_KB_INPUT: case GATT_UUID_HID_BT_MOUSE_INPUT: case GATT_UUID_HID_REPORT: if (p_data->status == BTA_GATT_OK) p_dev_cb->hid_srvc.report[p_dev_cb->clt_cfg_idx].client_cfg_value = BTA_GATT_CLT_CONFIG_NOTIFICATION; p_dev_cb->clt_cfg_idx ++; bta_hh_le_write_rpt_clt_cfg(p_dev_cb, hid_inst_id); break; case GATT_UUID_SCAN_REFRESH: bta_hh_le_register_scpp_notif_cmpl(p_dev_cb, p_data->status); break; default: APPL_TRACE_ERROR("Unknown char ID clt cfg: 0x%04x", char_uuid); } } else { #if BTA_HH_DEBUG == TRUE APPL_TRACE_ERROR("Unexpected write to %s(0x%04x)", bta_hh_uuid_to_str(desc_uuid), desc_uuid); #else APPL_TRACE_ERROR("Unexpected write to (0x%04x)", desc_uuid); #endif } } /******************************************************************************* ** ** Function bta_hh_le_input_rpt_notify ** ** Description process the notificaton event, most likely for input report. ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_input_rpt_notify(tBTA_GATTC_NOTIFY *p_data) { tBTA_HH_DEV_CB *p_dev_cb = bta_hh_le_find_dev_cb_by_conn_id(p_data->conn_id); UINT8 app_id; UINT8 *p_buf; tBTA_HH_LE_RPT *p_rpt; if (p_dev_cb == NULL) { APPL_TRACE_ERROR("notification received from Unknown device"); return; } const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(p_dev_cb->conn_id, p_data->handle); app_id= p_dev_cb->app_id; p_rpt = bta_hh_le_find_report_entry(p_dev_cb, p_dev_cb->hid_srvc.srvc_inst_id, p_char->uuid.uu.uuid16, p_char->handle); if (p_rpt == NULL) { APPL_TRACE_ERROR("notification received for Unknown Report"); return; } if (p_char->uuid.uu.uuid16 == GATT_UUID_HID_BT_MOUSE_INPUT) app_id = BTA_HH_APP_ID_MI; else if (p_char->uuid.uu.uuid16 == GATT_UUID_HID_BT_KB_INPUT) app_id = BTA_HH_APP_ID_KB; APPL_TRACE_DEBUG("Notification received on report ID: %d", p_rpt->rpt_id); /* need to append report ID to the head of data */ if (p_rpt->rpt_id != 0) { p_buf = (UINT8 *)osi_malloc(p_data->len + 1); p_buf[0] = p_rpt->rpt_id; memcpy(&p_buf[1], p_data->value, p_data->len); ++p_data->len; } else { p_buf = p_data->value; } bta_hh_co_data((UINT8)p_dev_cb->hid_handle, p_buf, p_data->len, p_dev_cb->mode, 0 , /* no sub class*/ p_dev_cb->dscp_info.ctry_code, p_dev_cb->addr, app_id); if (p_buf != p_data->value) osi_free(p_buf); } /******************************************************************************* ** ** Function bta_hh_gatt_open_fail ** ** Description action function to process the open fail ** ** Returns void ** *******************************************************************************/ void bta_hh_le_open_fail(tBTA_HH_DEV_CB *p_cb, tBTA_HH_DATA *p_data) { tBTA_HH_CONN conn_dat ; /* open failure in the middle of service discovery, clear all services */ if (p_cb->disc_active & BTA_HH_LE_DISC_HIDS) { bta_hh_clear_service_cache(p_cb); } p_cb->disc_active = BTA_HH_LE_DISC_NONE; /* Failure in opening connection or GATT discovery failure */ conn_dat.handle = p_cb->hid_handle; memcpy(conn_dat.bda, p_cb->addr, BD_ADDR_LEN); conn_dat.le_hid = TRUE; conn_dat.scps_supported = p_cb->scps_supported; if (p_cb->status == BTA_HH_OK) conn_dat.status = (p_data->le_close.reason == BTA_GATT_CONN_UNKNOWN) ? p_cb->status : BTA_HH_ERR; else conn_dat.status = p_cb->status; /* Report OPEN fail event */ (*bta_hh_cb.p_cback)(BTA_HH_OPEN_EVT, (tBTA_HH *)&conn_dat); } /******************************************************************************* ** ** Function bta_hh_gatt_close ** ** Description action function to process the GATT close int he state machine. ** ** Returns void ** *******************************************************************************/ void bta_hh_gatt_close(tBTA_HH_DEV_CB *p_cb, tBTA_HH_DATA *p_data) { tBTA_HH_CBDATA disc_dat = {BTA_HH_OK, 0}; /* finaliza device driver */ bta_hh_co_close(p_cb->hid_handle, p_cb->app_id); /* update total conn number */ bta_hh_cb.cnt_num --; disc_dat.handle = p_cb->hid_handle; disc_dat.status = p_cb->status; (*bta_hh_cb.p_cback)(BTA_HH_CLOSE_EVT, (tBTA_HH *)&disc_dat); /* if no connection is active and HH disable is signaled, disable service */ if (bta_hh_cb.cnt_num == 0 && bta_hh_cb.w4_disable) { bta_hh_disc_cmpl(); } else { #if (BTA_HH_LE_RECONN == TRUE) if (p_data->le_close.reason == BTA_GATT_CONN_TIMEOUT) { bta_hh_le_add_dev_bg_conn(p_cb, FALSE); } #endif } return; } /******************************************************************************* ** ** Function bta_hh_le_api_disc_act ** ** Description initaite a Close API to a remote HID device ** ** Returns void ** *******************************************************************************/ void bta_hh_le_api_disc_act(tBTA_HH_DEV_CB *p_cb) { if (p_cb->conn_id != BTA_GATT_INVALID_CONN_ID) { gatt_op_queue_clean(p_cb->conn_id); BTA_GATTC_Close(p_cb->conn_id); /* remove device from background connection if intended to disconnect, do not allow reconnection */ bta_hh_le_remove_dev_bg_conn(p_cb); } } /******************************************************************************* ** ** Function bta_hh_le_get_rpt ** ** Description GET_REPORT on a LE HID Report ** ** Returns void ** *******************************************************************************/ void bta_hh_le_get_rpt(tBTA_HH_DEV_CB *p_cb, tBTA_HH_RPT_TYPE r_type, UINT8 rpt_id) { tBTA_HH_LE_RPT *p_rpt = bta_hh_le_find_rpt_by_idtype(p_cb->hid_srvc.report, p_cb->mode, r_type, rpt_id); if (p_rpt == NULL) { APPL_TRACE_ERROR("%s: no matching report", __func__); return; } p_cb->w4_evt = BTA_HH_GET_RPT_EVT; gatt_queue_read_op(GATT_READ_CHAR, p_cb->conn_id, p_rpt->char_inst_id); } /******************************************************************************* ** ** Function bta_hh_le_write_rpt ** ** Description SET_REPORT/or DATA output on a LE HID Report ** ** Returns void ** *******************************************************************************/ void bta_hh_le_write_rpt(tBTA_HH_DEV_CB *p_cb, tBTA_HH_RPT_TYPE r_type, BT_HDR *p_buf, UINT16 w4_evt ) { tBTA_HH_LE_RPT *p_rpt; UINT8 *p_value, rpt_id; if (p_buf == NULL || p_buf->len == 0) { APPL_TRACE_ERROR("bta_hh_le_write_rpt: Illegal data"); return; } /* strip report ID from the data */ p_value = (UINT8 *)(p_buf + 1) + p_buf->offset; STREAM_TO_UINT8(rpt_id, p_value); p_buf->len -= 1; p_rpt = bta_hh_le_find_rpt_by_idtype(p_cb->hid_srvc.report, p_cb->mode, r_type, rpt_id); if (p_rpt == NULL) { APPL_TRACE_ERROR("%s: no matching report", __func__); osi_free(p_buf); return; } p_cb->w4_evt = w4_evt; const tBTA_GATTC_CHARACTERISTIC *p_char = BTA_GATTC_GetCharacteristic(p_cb->conn_id, p_rpt->char_inst_id); tBTA_GATTC_WRITE_TYPE write_type = BTA_GATTC_TYPE_WRITE; if (p_char && (p_char->properties & BTA_GATT_CHAR_PROP_BIT_WRITE_NR)) write_type = BTA_GATTC_TYPE_WRITE_NO_RSP; gatt_queue_write_op(GATT_WRITE_CHAR, p_cb->conn_id, p_rpt->char_inst_id, p_buf->len, p_value, write_type); } /******************************************************************************* ** ** Function bta_hh_le_suspend ** ** Description send LE suspend or exit suspend mode to remote device. ** ** Returns void ** *******************************************************************************/ void bta_hh_le_suspend(tBTA_HH_DEV_CB *p_cb, tBTA_HH_TRANS_CTRL_TYPE ctrl_type) { ctrl_type -= BTA_HH_CTRL_SUSPEND; gatt_queue_write_op(GATT_WRITE_CHAR, p_cb->conn_id, p_cb->hid_srvc.control_point_handle, 1, &ctrl_type, BTA_GATTC_TYPE_WRITE_NO_RSP); } /******************************************************************************* ** ** Function bta_hh_le_write_dev_act ** ** Description Write LE device action. can be SET/GET/DATA transaction. ** ** Returns void ** *******************************************************************************/ void bta_hh_le_write_dev_act(tBTA_HH_DEV_CB *p_cb, tBTA_HH_DATA *p_data) { switch(p_data->api_sndcmd.t_type) { case HID_TRANS_SET_PROTOCOL: p_cb->w4_evt = BTA_HH_SET_PROTO_EVT; bta_hh_le_set_protocol_mode(p_cb, p_data->api_sndcmd.param); break; case HID_TRANS_GET_PROTOCOL: bta_hh_le_get_protocol_mode(p_cb); break; case HID_TRANS_GET_REPORT: bta_hh_le_get_rpt(p_cb, p_data->api_sndcmd.param, p_data->api_sndcmd.rpt_id); break; case HID_TRANS_SET_REPORT: bta_hh_le_write_rpt(p_cb, p_data->api_sndcmd.param, p_data->api_sndcmd.p_data, BTA_HH_SET_RPT_EVT); break; case HID_TRANS_DATA: /* output report */ bta_hh_le_write_rpt(p_cb, p_data->api_sndcmd.param, p_data->api_sndcmd.p_data, BTA_HH_DATA_EVT); break; case HID_TRANS_CONTROL: /* no handshake event will be generated */ /* if VC_UNPLUG is issued, set flag */ if (p_data->api_sndcmd.param == BTA_HH_CTRL_SUSPEND || p_data->api_sndcmd.param == BTA_HH_CTRL_EXIT_SUSPEND) { bta_hh_le_suspend(p_cb, p_data->api_sndcmd.param); } break; default: APPL_TRACE_ERROR("%s unsupported transaction for BLE HID device: %d", __func__, p_data->api_sndcmd.t_type); break; } } /******************************************************************************* ** ** Function bta_hh_le_get_dscp_act ** ** Description Send ReportDescriptor to application for all HID services. ** ** Returns void ** *******************************************************************************/ void bta_hh_le_get_dscp_act(tBTA_HH_DEV_CB *p_cb) { if (p_cb->hid_srvc.in_use) { p_cb->dscp_info.descriptor.dl_len = p_cb->hid_srvc.descriptor.dl_len; p_cb->dscp_info.descriptor.dsc_list = p_cb->hid_srvc.descriptor.dsc_list; (*bta_hh_cb.p_cback)(BTA_HH_GET_DSCP_EVT, (tBTA_HH *)&p_cb->dscp_info); } } /******************************************************************************* ** ** Function bta_hh_le_add_dev_bg_conn ** ** Description Remove a LE HID device from back ground connection procedure. ** ** Returns void ** *******************************************************************************/ static void bta_hh_le_add_dev_bg_conn(tBTA_HH_DEV_CB *p_cb, BOOLEAN check_bond) { UINT8 sec_flag=0; BOOLEAN to_add = TRUE; if (check_bond) { /* start reconnection if remote is a bonded device */ /* verify bond */ BTM_GetSecurityFlagsByTransport(p_cb->addr, &sec_flag, BT_TRANSPORT_LE); if ((sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) == 0) to_add = FALSE; } if (/*p_cb->dscp_info.flag & BTA_HH_LE_NORMAL_CONN &&*/ !p_cb->in_bg_conn && to_add) { /* add device into BG connection to accept remote initiated connection */ BTA_GATTC_Open(bta_hh_cb.gatt_if, p_cb->addr, FALSE, BTA_GATT_TRANSPORT_LE); p_cb->in_bg_conn = TRUE; BTA_DmBleSetBgConnType(BTA_DM_BLE_CONN_AUTO, NULL); } return; } /******************************************************************************* ** ** Function bta_hh_le_add_device ** ** Description Add a LE HID device as a known device, and also add the address ** into back ground connection WL for incoming connection. ** ** Returns void ** *******************************************************************************/ UINT8 bta_hh_le_add_device(tBTA_HH_DEV_CB *p_cb, tBTA_HH_MAINT_DEV *p_dev_info) { p_cb->hid_handle = BTA_HH_GET_LE_DEV_HDL(p_cb->index); bta_hh_cb.le_cb_index[BTA_HH_GET_LE_CB_IDX(p_cb->hid_handle)] = p_cb->index; /* update DI information */ bta_hh_update_di_info(p_cb, p_dev_info->dscp_info.vendor_id, p_dev_info->dscp_info.product_id, p_dev_info->dscp_info.version, p_dev_info->dscp_info.flag); /* add to BTA device list */ bta_hh_add_device_to_list(p_cb, p_cb->hid_handle, p_dev_info->attr_mask, &p_dev_info->dscp_info.descriptor, p_dev_info->sub_class, p_dev_info->dscp_info.ssr_max_latency, p_dev_info->dscp_info.ssr_min_tout, p_dev_info->app_id); bta_hh_le_add_dev_bg_conn(p_cb, FALSE); return p_cb->hid_handle; } /******************************************************************************* ** ** Function bta_hh_le_remove_dev_bg_conn ** ** Description Remove a LE HID device from back ground connection procedure. ** ** Returns void ** *******************************************************************************/ void bta_hh_le_remove_dev_bg_conn(tBTA_HH_DEV_CB *p_dev_cb) { if (p_dev_cb->in_bg_conn) { p_dev_cb->in_bg_conn = FALSE; BTA_GATTC_CancelOpen(bta_hh_cb.gatt_if, p_dev_cb->addr, FALSE); } } /******************************************************************************* ** ** Function bta_hh_le_update_scpp ** ** Description action function to update the scan parameters on remote HID ** device ** ** Parameters: ** *******************************************************************************/ void bta_hh_le_update_scpp(tBTA_HH_DEV_CB *p_dev_cb, tBTA_HH_DATA *p_buf) { UINT8 value[4], *p = value; tBTA_HH_CBDATA cback_data; if (!p_dev_cb->is_le_device || p_dev_cb->mode != BTA_HH_PROTO_RPT_MODE || p_dev_cb->scps_supported == FALSE) { APPL_TRACE_ERROR("Can not set ScPP scan paramter as boot host, or remote does not support ScPP "); cback_data.handle = p_dev_cb->hid_handle; cback_data.status = BTA_HH_ERR; (* bta_hh_cb.p_cback)(BTA_HH_UPDATE_SCPP_EVT, (tBTA_HH *)&cback_data); return; } p_dev_cb->w4_evt = BTA_HH_UPDATE_SCPP_EVT; UINT16_TO_STREAM(p, p_buf->le_scpp_update.scan_int); UINT16_TO_STREAM(p, p_buf->le_scpp_update.scan_win); gatt_queue_write_op(GATT_WRITE_CHAR, p_dev_cb->conn_id, p_dev_cb->scan_refresh_char_handle, 2, value, BTA_GATTC_TYPE_WRITE_NO_RSP); } /******************************************************************************* ** ** Function bta_hh_gattc_callback ** ** Description This is GATT client callback function used in BTA HH. ** ** Parameters: ** *******************************************************************************/ static void bta_hh_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC *p_data) { tBTA_HH_DEV_CB *p_dev_cb; UINT16 evt; #if BTA_HH_DEBUG APPL_TRACE_DEBUG("bta_hh_gattc_callback event = %d", event); #endif if (p_data == NULL) return; switch (event) { case BTA_GATTC_REG_EVT: /* 0 */ bta_hh_le_register_cmpl(&p_data->reg_oper); break; case BTA_GATTC_DEREG_EVT: /* 1 */ bta_hh_cleanup_disable(p_data->reg_oper.status); break; case BTA_GATTC_OPEN_EVT: /* 2 */ p_dev_cb = bta_hh_le_find_dev_cb_by_bda(p_data->open.remote_bda); if (p_dev_cb) { bta_hh_sm_execute(p_dev_cb, BTA_HH_GATT_OPEN_EVT, (tBTA_HH_DATA *)&p_data->open); } break; case BTA_GATTC_READ_CHAR_EVT: /* 3 */ case BTA_GATTC_READ_DESCR_EVT: /* 8 */ mark_as_not_executing(p_data->read.conn_id); gatt_execute_next_op(p_data->read.conn_id); p_dev_cb = bta_hh_le_find_dev_cb_by_conn_id(p_data->read.conn_id); if (event == BTA_GATTC_READ_CHAR_EVT) evt = BTA_HH_GATT_READ_CHAR_CMPL_EVT; else evt = BTA_HH_GATT_READ_DESCR_CMPL_EVT; bta_hh_sm_execute(p_dev_cb, evt, (tBTA_HH_DATA *)&p_data->read); break; case BTA_GATTC_WRITE_DESCR_EVT: /* 9 */ case BTA_GATTC_WRITE_CHAR_EVT: /* 4 */ mark_as_not_executing(p_data->write.conn_id); gatt_execute_next_op(p_data->write.conn_id); p_dev_cb = bta_hh_le_find_dev_cb_by_conn_id(p_data->write.conn_id); if (event == BTA_GATTC_WRITE_CHAR_EVT) evt = BTA_HH_GATT_WRITE_CHAR_CMPL_EVT; else evt = BTA_HH_GATT_WRITE_DESCR_CMPL_EVT; bta_hh_sm_execute(p_dev_cb, evt, (tBTA_HH_DATA *)&p_data->write); break; case BTA_GATTC_CLOSE_EVT: /* 5 */ bta_hh_le_close(&p_data->close); break; case BTA_GATTC_SEARCH_CMPL_EVT: /* 6 */ bta_hh_le_srvc_search_cmpl(&p_data->search_cmpl); break; case BTA_GATTC_NOTIF_EVT: /* 10 */ bta_hh_le_input_rpt_notify(&p_data->notify); break; case BTA_GATTC_ENC_CMPL_CB_EVT: /* 17 */ p_dev_cb = bta_hh_le_find_dev_cb_by_bda(p_data->enc_cmpl.remote_bda); if (p_dev_cb) { bta_hh_sm_execute(p_dev_cb, BTA_HH_GATT_ENC_CMPL_EVT, (tBTA_HH_DATA *)&p_data->enc_cmpl); } break; default: break; } } /******************************************************************************* ** ** Function bta_hh_le_hid_read_rpt_clt_cfg ** ** Description a test command to read report descriptor client configuration ** ** Returns void ** *******************************************************************************/ void bta_hh_le_hid_read_rpt_clt_cfg(BD_ADDR bd_addr, UINT8 rpt_id) { tBTA_HH_DEV_CB *p_cb = NULL; tBTA_HH_LE_RPT *p_rpt ; UINT8 index = BTA_HH_IDX_INVALID; index = bta_hh_find_cb(bd_addr); if ((index = bta_hh_find_cb(bd_addr))== BTA_HH_IDX_INVALID) { APPL_TRACE_ERROR("unknown device"); return; } p_cb = &bta_hh_cb.kdev[index]; p_rpt = bta_hh_le_find_rpt_by_idtype(p_cb->hid_srvc.report, p_cb->mode, BTA_HH_RPTT_INPUT, rpt_id); if (p_rpt == NULL) { APPL_TRACE_ERROR("bta_hh_le_write_rpt: no matching report"); return; } bta_hh_le_read_char_dscrpt(p_cb, p_rpt->char_inst_id, GATT_UUID_CHAR_CLIENT_CONFIG); return; } /******************************************************************************* ** ** Function bta_hh_le_register_scpp_notif ** ** Description register scan parameter refresh notitication complete ** ** ** Parameters: ** *******************************************************************************/ static void bta_hh_le_register_scpp_notif(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATT_STATUS status) { UINT8 sec_flag=0; /* if write scan parameter sucessful */ /* if bonded and notification is not enabled, configure the client configuration */ if (status == BTA_GATT_OK && (p_dev_cb->scps_notify & BTA_HH_LE_SCPS_NOTIFY_SPT) != 0 && (p_dev_cb->scps_notify & BTA_HH_LE_SCPS_NOTIFY_ENB) == 0) { BTM_GetSecurityFlagsByTransport(p_dev_cb->addr, &sec_flag, BT_TRANSPORT_LE); if ((sec_flag & BTM_SEC_FLAG_LKEY_KNOWN)) { if (bta_hh_le_write_char_clt_cfg (p_dev_cb, p_dev_cb->scan_refresh_char_handle, BTA_GATT_CLT_CONFIG_NOTIFICATION)) { BTA_GATTC_RegisterForNotifications(bta_hh_cb.gatt_if, p_dev_cb->addr, p_dev_cb->scan_refresh_char_handle); return; } } } bta_hh_le_register_scpp_notif_cmpl(p_dev_cb, status); } /******************************************************************************* ** ** Function bta_hh_le_register_scpp_notif_cmpl ** ** Description action function to register scan parameter refresh notitication ** ** Parameters: ** *******************************************************************************/ static void bta_hh_le_register_scpp_notif_cmpl(tBTA_HH_DEV_CB *p_dev_cb, tBTA_GATT_STATUS status) { tBTA_HH_CBDATA cback_data ; UINT16 cb_evt = p_dev_cb->w4_evt; if (status == BTA_GATT_OK) p_dev_cb->scps_notify = (BTA_HH_LE_SCPS_NOTIFY_ENB | BTA_HH_LE_SCPS_NOTIFY_SPT); cback_data.handle = p_dev_cb->hid_handle; cback_data.status = (status == BTA_GATT_OK)? BTA_HH_OK : BTA_HH_ERR; p_dev_cb->w4_evt = 0; (* bta_hh_cb.p_cback)(cb_evt, (tBTA_HH *)&cback_data); } /******************************************************************************* ** ** Function bta_hh_process_cache_rpt ** ** Description Process the cached reports ** ** Parameters: ** *******************************************************************************/ //TODO(jpawlowski): uncomment when fixed // static void bta_hh_process_cache_rpt (tBTA_HH_DEV_CB *p_cb, // tBTA_HH_RPT_CACHE_ENTRY *p_rpt_cache, // UINT8 num_rpt) // { // UINT8 i = 0; // tBTA_HH_LE_RPT *p_rpt; // if (num_rpt != 0) /* no cache is found */ // { // p_cb->hid_srvc.in_use = TRUE; // /* set the descriptor info */ // p_cb->hid_srvc.descriptor.dl_len = // p_cb->dscp_info.descriptor.dl_len; // p_cb->hid_srvc.descriptor.dsc_list = // p_cb->dscp_info.descriptor.dsc_list; // for (; i srvc_inst_id, // p_rpt_cache->rpt_uuid, // p_rpt_cache->char_inst_id, // p_rpt_cache->prop)) == NULL) // { // APPL_TRACE_ERROR("bta_hh_process_cache_rpt: allocation report entry failure"); // break; // } // else // { // p_rpt->rpt_type = p_rpt_cache->rpt_type; // p_rpt->rpt_id = p_rpt_cache->rpt_id; // if(p_rpt->uuid == GATT_UUID_HID_BT_KB_INPUT || // p_rpt->uuid == GATT_UUID_HID_BT_MOUSE_INPUT || // (p_rpt->uuid == GATT_UUID_HID_REPORT && p_rpt->rpt_type == BTA_HH_RPTT_INPUT)) // { // p_rpt->client_cfg_value = BTA_GATT_CLT_CONFIG_NOTIFICATION; // } // } // } // } // } #endif