| /* |
| * Copyright (C) 2014 Intel Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #include <cutils/properties.h> |
| |
| #include "hal-log.h" |
| #include "hal.h" |
| #include "hal-msg.h" |
| #include "ipc-common.h" |
| #include "hal-ipc.h" |
| |
| #define MODE_PROPERTY_NAME "persist.sys.bluetooth.handsfree" |
| |
| static const bthf_callbacks_t *cbs = NULL; |
| |
| static bool interface_ready(void) |
| { |
| return cbs != NULL; |
| } |
| |
| static void handle_conn_state(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_conn_state *ev = buf; |
| |
| if (cbs->connection_state_cb) |
| cbs->connection_state_cb(ev->state, |
| (bt_bdaddr_t *) (ev->bdaddr)); |
| } |
| |
| static void handle_audio_state(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_audio_state *ev = buf; |
| |
| if (cbs->audio_state_cb) |
| cbs->audio_state_cb(ev->state, (bt_bdaddr_t *) (ev->bdaddr)); |
| } |
| |
| static void handle_vr_state(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_vr_state *ev = buf; |
| |
| if (cbs->vr_cmd_cb) |
| cbs->vr_cmd_cb(ev->state); |
| } |
| |
| static void handle_answer(void *buf, uint16_t len, int fd) |
| { |
| if (cbs->answer_call_cmd_cb) |
| cbs->answer_call_cmd_cb(); |
| } |
| |
| static void handle_hangup(void *buf, uint16_t len, int fd) |
| { |
| if (cbs->hangup_call_cmd_cb) |
| cbs->hangup_call_cmd_cb(); |
| } |
| |
| static void handle_volume(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_volume *ev = buf; |
| |
| if (cbs->volume_cmd_cb) |
| cbs->volume_cmd_cb(ev->type, ev->volume); |
| } |
| |
| static void handle_dial(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_dial *ev = buf; |
| uint16_t num_len = ev->number_len; |
| |
| if (len != sizeof(*ev) + num_len || |
| (num_len != 0 && ev->number[num_len - 1] != '\0')) { |
| error("invalid dial event, aborting"); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (!cbs->dial_call_cmd_cb) |
| return; |
| |
| if (ev->number_len) |
| cbs->dial_call_cmd_cb((char *) ev->number); |
| else |
| cbs->dial_call_cmd_cb(NULL); |
| } |
| |
| static void handle_dtmf(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_dtmf *ev = buf; |
| |
| if (cbs->dtmf_cmd_cb) |
| cbs->dtmf_cmd_cb(ev->tone); |
| } |
| |
| static void handle_nrec(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_nrec *ev = buf; |
| |
| if (cbs->nrec_cmd_cb) |
| cbs->nrec_cmd_cb(ev->nrec); |
| } |
| |
| static void handle_chld(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_chld *ev = buf; |
| |
| if (cbs->chld_cmd_cb) |
| cbs->chld_cmd_cb(ev->chld); |
| } |
| |
| static void handle_cnum(void *buf, uint16_t len, int fd) |
| { |
| if (cbs->cnum_cmd_cb) |
| cbs->cnum_cmd_cb(); |
| } |
| |
| static void handle_cind(void *buf, uint16_t len, int fd) |
| { |
| if (cbs->cind_cmd_cb) |
| cbs->cind_cmd_cb(); |
| } |
| |
| static void handle_cops(void *buf, uint16_t len, int fd) |
| { |
| if (cbs->cops_cmd_cb) |
| cbs->cops_cmd_cb(); |
| } |
| |
| static void handle_clcc(void *buf, uint16_t len, int fd) |
| { |
| if (cbs->clcc_cmd_cb) |
| cbs->clcc_cmd_cb(); |
| } |
| |
| static void handle_unknown_at(void *buf, uint16_t len, int fd) |
| { |
| struct hal_ev_handsfree_unknown_at *ev = buf; |
| |
| if (len != sizeof(*ev) + ev->len || |
| (ev->len != 0 && ev->buf[ev->len - 1] != '\0')) { |
| error("invalid unknown command event, aborting"); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (cbs->unknown_at_cmd_cb) |
| cbs->unknown_at_cmd_cb((char *) ev->buf); |
| } |
| |
| static void handle_hsp_key_press(void *buf, uint16_t len, int fd) |
| { |
| if (cbs->key_pressed_cmd_cb) |
| cbs->key_pressed_cmd_cb(); |
| } |
| |
| /* |
| * handlers will be called from notification thread context, |
| * index in table equals to 'opcode - HAL_MINIMUM_EVENT' |
| */ |
| static const struct hal_ipc_handler ev_handlers[] = { |
| /* HAL_EV_HANDSFREE_CONN_STATE */ |
| { handle_conn_state, false, |
| sizeof(struct hal_ev_handsfree_conn_state) }, |
| /* HAL_EV_HANDSFREE_AUDIO_STATE */ |
| { handle_audio_state, false, |
| sizeof(struct hal_ev_handsfree_audio_state) }, |
| /* HAL_EV_HANDSFREE_VR */ |
| { handle_vr_state, false, sizeof(struct hal_ev_handsfree_vr_state) }, |
| /*HAL_EV_HANDSFREE_ANSWER */ |
| { handle_answer, false, 0 }, |
| /*HAL_EV_HANDSFREE_HANGUP */ |
| { handle_hangup, false, 0 }, |
| /* HAL_EV_HANDSFREE_VOLUME */ |
| { handle_volume, false, sizeof(struct hal_ev_handsfree_volume) }, |
| /* HAL_EV_HANDSFREE_DIAL */ |
| { handle_dial, true, sizeof(struct hal_ev_handsfree_dial) }, |
| /* HAL_EV_HANDSFREE_DTMF */ |
| { handle_dtmf, false, sizeof(struct hal_ev_handsfree_dtmf) }, |
| /* HAL_EV_HANDSFREE_NREC */ |
| { handle_nrec, false, sizeof(struct hal_ev_handsfree_nrec) }, |
| /* HAL_EV_HANDSFREE_CHLD */ |
| { handle_chld, false, sizeof(struct hal_ev_handsfree_chld) }, |
| /* HAL_EV_HANDSFREE_CNUM */ |
| { handle_cnum, false, 0 }, |
| /* HAL_EV_HANDSFREE_CIND */ |
| { handle_cind, false, 0 }, |
| /* HAL_EV_HANDSFREE_COPS */ |
| { handle_cops, false, 0 }, |
| /* HAL_EV_HANDSFREE_CLCC */ |
| { handle_clcc, false, 0 }, |
| /* HAL_EV_HANDSFREE_UNKNOWN_AT */ |
| { handle_unknown_at, true, sizeof(struct hal_ev_handsfree_unknown_at) }, |
| /* HAL_EV_HANDSFREE_HSP_KEY_PRESS */ |
| { handle_hsp_key_press, false, 0 }, |
| }; |
| |
| static uint8_t get_mode(void) |
| { |
| char value[PROPERTY_VALUE_MAX]; |
| |
| if (property_get(MODE_PROPERTY_NAME, value, "") > 0 && |
| (!strcasecmp(value, "hfp"))) |
| return HAL_MODE_HANDSFREE_HFP; |
| |
| if (property_get(MODE_PROPERTY_NAME, value, "") > 0 && |
| (!strcasecmp(value, "hfp_wbs"))) |
| return HAL_MODE_HANDSFREE_HFP_WBS; |
| |
| return HAL_MODE_HANDSFREE_HSP_ONLY; |
| } |
| |
| static bt_status_t init(bthf_callbacks_t *callbacks) |
| { |
| struct hal_cmd_register_module cmd; |
| int ret; |
| |
| DBG(""); |
| |
| if (interface_ready()) |
| return BT_STATUS_DONE; |
| |
| cbs = callbacks; |
| |
| hal_ipc_register(HAL_SERVICE_ID_HANDSFREE, ev_handlers, |
| sizeof(ev_handlers)/sizeof(ev_handlers[0])); |
| |
| cmd.service_id = HAL_SERVICE_ID_HANDSFREE; |
| cmd.mode = get_mode(); |
| |
| ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, |
| sizeof(cmd), &cmd, 0, NULL, NULL); |
| |
| if (ret != BT_STATUS_SUCCESS) { |
| cbs = NULL; |
| hal_ipc_unregister(HAL_SERVICE_ID_HANDSFREE); |
| } |
| |
| return ret; |
| } |
| |
| static bt_status_t handsfree_connect(bt_bdaddr_t *bd_addr) |
| { |
| struct hal_cmd_handsfree_connect cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| if (!bd_addr) |
| return BT_STATUS_PARM_INVALID; |
| |
| memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_CONNECT, |
| sizeof(cmd), &cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t disconnect(bt_bdaddr_t *bd_addr) |
| { |
| struct hal_cmd_handsfree_disconnect cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| if (!bd_addr) |
| return BT_STATUS_PARM_INVALID; |
| |
| memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_DISCONNECT, sizeof(cmd), &cmd, |
| 0, NULL, NULL); |
| } |
| |
| static bt_status_t connect_audio(bt_bdaddr_t *bd_addr) |
| { |
| struct hal_cmd_handsfree_connect_audio cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| if (!bd_addr) |
| return BT_STATUS_PARM_INVALID; |
| |
| memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_CONNECT_AUDIO, sizeof(cmd), |
| &cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t disconnect_audio(bt_bdaddr_t *bd_addr) |
| { |
| struct hal_cmd_handsfree_disconnect_audio cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| if (!bd_addr) |
| return BT_STATUS_PARM_INVALID; |
| |
| memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_DISCONNECT_AUDIO, sizeof(cmd), |
| &cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t start_voice_recognition(void) |
| { |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_START_VR, |
| 0, NULL, 0, NULL, NULL); |
| } |
| |
| static bt_status_t stop_voice_recognition(void) |
| { |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_STOP_VR, |
| 0, NULL, 0, NULL, NULL); |
| } |
| |
| static bt_status_t volume_control(bthf_volume_type_t type, int volume) |
| { |
| struct hal_cmd_handsfree_volume_control cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| cmd.type = type; |
| cmd.volume = volume; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_VOLUME_CONTROL, sizeof(cmd), |
| &cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t device_status_notification(bthf_network_state_t state, |
| bthf_service_type_t type, |
| int signal, int battery) |
| { |
| struct hal_cmd_handsfree_device_status_notif cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| cmd.state = state; |
| cmd.type = type; |
| cmd.signal = signal; |
| cmd.battery = battery; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_DEVICE_STATUS_NOTIF, |
| sizeof(cmd), &cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t cops_response(const char *cops) |
| { |
| char buf[IPC_MTU]; |
| struct hal_cmd_handsfree_cops_response *cmd = (void *) buf; |
| size_t len; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| if (!cops) |
| return BT_STATUS_PARM_INVALID; |
| |
| cmd->len = strlen(cops) + 1; |
| memcpy(cmd->buf, cops, cmd->len); |
| |
| len = sizeof(*cmd) + cmd->len; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_COPS_RESPONSE, |
| len, cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t cind_response(int svc, int num_active, int num_held, |
| bthf_call_state_t state, int signal, |
| int roam, int batt_chg) |
| { |
| struct hal_cmd_handsfree_cind_response cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| cmd.svc = svc; |
| cmd.num_active = num_active; |
| cmd.num_held = num_held; |
| cmd.state = state; |
| cmd.signal = signal; |
| cmd.roam = roam; |
| cmd.batt_chg = batt_chg; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_CIND_RESPONSE, |
| sizeof(cmd), &cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t formatted_at_response(const char *rsp) |
| { |
| char buf[IPC_MTU]; |
| struct hal_cmd_handsfree_formatted_at_response *cmd = (void *) buf; |
| size_t len; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| if (!rsp) |
| return BT_STATUS_PARM_INVALID; |
| |
| cmd->len = strlen(rsp) + 1; |
| memcpy(cmd->buf, rsp, cmd->len); |
| |
| len = sizeof(*cmd) + cmd->len; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_FORMATTED_AT_RESPONSE, |
| len, cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t at_response(bthf_at_response_t response, int error) |
| { |
| struct hal_cmd_handsfree_at_response cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| cmd.response = response; |
| cmd.error = error; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_AT_RESPONSE, |
| sizeof(cmd), &cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t clcc_response(int index, bthf_call_direction_t dir, |
| bthf_call_state_t state, |
| bthf_call_mode_t mode, |
| bthf_call_mpty_type_t mpty, |
| const char *number, |
| bthf_call_addrtype_t type) |
| { |
| char buf[IPC_MTU]; |
| struct hal_cmd_handsfree_clcc_response *cmd = (void *) buf; |
| size_t len; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| cmd->index = index; |
| cmd->dir = dir; |
| cmd->state = state; |
| cmd->mode = mode; |
| cmd->mpty = mpty; |
| cmd->type = type; |
| |
| if (number) { |
| cmd->number_len = strlen(number) + 1; |
| memcpy(cmd->number, number, cmd->number_len); |
| } else { |
| cmd->number_len = 0; |
| } |
| |
| len = sizeof(*cmd) + cmd->number_len; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_CLCC_RESPONSE, |
| len, cmd, 0, NULL, NULL); |
| } |
| |
| static bt_status_t phone_state_change(int num_active, int num_held, |
| bthf_call_state_t state, |
| const char *number, |
| bthf_call_addrtype_t type) |
| { |
| char buf[IPC_MTU]; |
| struct hal_cmd_handsfree_phone_state_change *cmd = (void *) buf; |
| size_t len; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return BT_STATUS_NOT_READY; |
| |
| cmd->num_active = num_active; |
| cmd->num_held = num_held; |
| cmd->state = state; |
| cmd->type = type; |
| |
| if (number) { |
| cmd->number_len = strlen(number) + 1; |
| memcpy(cmd->number, number, cmd->number_len); |
| } else { |
| cmd->number_len = 0; |
| } |
| |
| len = sizeof(*cmd) + cmd->number_len; |
| |
| return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, |
| HAL_OP_HANDSFREE_PHONE_STATE_CHANGE, |
| len, cmd, 0, NULL, NULL); |
| } |
| |
| static void cleanup(void) |
| { |
| struct hal_cmd_unregister_module cmd; |
| |
| DBG(""); |
| |
| if (!interface_ready()) |
| return; |
| |
| cbs = NULL; |
| |
| cmd.service_id = HAL_SERVICE_ID_HANDSFREE; |
| |
| hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, |
| sizeof(cmd), &cmd, 0, NULL, NULL); |
| |
| hal_ipc_unregister(HAL_SERVICE_ID_HANDSFREE); |
| } |
| |
| static bthf_interface_t iface = { |
| .size = sizeof(iface), |
| .init = init, |
| .connect = handsfree_connect, |
| .disconnect = disconnect, |
| .connect_audio = connect_audio, |
| .disconnect_audio = disconnect_audio, |
| .start_voice_recognition = start_voice_recognition, |
| .stop_voice_recognition = stop_voice_recognition, |
| .volume_control = volume_control, |
| .device_status_notification = device_status_notification, |
| .cops_response = cops_response, |
| .cind_response = cind_response, |
| .formatted_at_response = formatted_at_response, |
| .at_response = at_response, |
| .clcc_response = clcc_response, |
| .phone_state_change = phone_state_change, |
| .cleanup = cleanup |
| }; |
| |
| bthf_interface_t *bt_get_handsfree_interface(void) |
| { |
| return &iface; |
| } |