/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/sdp.h" #include "lib/sdp_lib.h" #include "src/sdp-client.h" #include "src/uuid-helper.h" #include "src/shared/hfp.h" #include "btio/btio.h" #include "hal-msg.h" #include "ipc-common.h" #include "ipc.h" #include "handsfree.h" #include "bluetooth.h" #include "src/log.h" #include "utils.h" #include "sco-msg.h" #define HSP_AG_CHANNEL 12 #define HFP_AG_CHANNEL 13 #define HFP_AG_FEAT_3WAY 0x00000001 #define HFP_AG_FEAT_ECNR 0x00000002 #define HFP_AG_FEAT_VR 0x00000004 #define HFP_AG_FEAT_INBAND 0x00000008 #define HFP_AG_FEAT_VTAG 0x00000010 #define HFP_AG_FEAT_REJ_CALL 0x00000020 #define HFP_AG_FEAT_ECS 0x00000040 #define HFP_AG_FEAT_ECC 0x00000080 #define HFP_AG_FEAT_EXT_ERR 0x00000100 #define HFP_AG_FEAT_CODEC 0x00000200 #define HFP_HF_FEAT_ECNR 0x00000001 #define HFP_HF_FEAT_3WAY 0x00000002 #define HFP_HF_FEAT_CLI 0x00000004 #define HFP_HF_FEAT_VR 0x00000008 #define HFP_HF_FEAT_RVC 0x00000010 #define HFP_HF_FEAT_ECS 0x00000020 #define HFP_HF_FEAT_ECC 0x00000040 #define HFP_HF_FEAT_CODEC 0x00000080 #define HFP_AG_FEATURES (HFP_AG_FEAT_3WAY | HFP_AG_FEAT_ECNR |\ HFP_AG_FEAT_VR | HFP_AG_FEAT_REJ_CALL |\ HFP_AG_FEAT_ECS | HFP_AG_FEAT_EXT_ERR) #define HFP_AG_CHLD "0,1,2,3" /* offsets in indicators table, should be incremented when sending CIEV */ #define IND_SERVICE 0 #define IND_CALL 1 #define IND_CALLSETUP 2 #define IND_CALLHELD 3 #define IND_SIGNAL 4 #define IND_ROAM 5 #define IND_BATTCHG 6 #define IND_COUNT (IND_BATTCHG + 1) #define RING_TIMEOUT 2 #define CVSD_OFFSET 0 #define MSBC_OFFSET 1 #define CODECS_COUNT (MSBC_OFFSET + 1) #define CODEC_ID_CVSD 0x01 #define CODEC_ID_MSBC 0x02 struct indicator { const char *name; int min; int max; int val; bool always_active; bool active; }; struct hfp_codec { uint8_t type; bool local_supported; bool remote_supported; }; static const struct indicator inds_defaults[] = { { "service", 0, 1, 0, false, true }, { "call", 0, 1, 0, true, true }, { "callsetup", 0, 3, 0, true, true }, { "callheld", 0, 2, 0, true, true }, { "signal", 0, 5, 0, false, true }, { "roam", 0, 1, 0, false, true }, { "battchg", 0, 5, 0, false, true }, }; static const struct hfp_codec codecs_defaults[] = { { CODEC_ID_CVSD, true, false}, { CODEC_ID_MSBC, false, false}, }; static struct { bdaddr_t bdaddr; uint8_t state; uint8_t audio_state; uint32_t features; bool clip_enabled; bool cmee_enabled; bool ccwa_enabled; bool indicators_enabled; struct indicator inds[IND_COUNT]; int num_active; int num_held; int setup_state; bool call_hanging_up; uint8_t negotiated_codec; uint8_t proposed_codec; struct hfp_codec codecs[CODECS_COUNT]; guint ring; bool hsp; struct hfp_gw *gw; GIOChannel *sco; guint sco_watch; } device; static uint32_t hfp_ag_features = 0; static bdaddr_t adapter_addr; static struct ipc *hal_ipc = NULL; static struct ipc *sco_ipc = NULL; static uint32_t hfp_record_id = 0; static GIOChannel *hfp_server = NULL; static uint32_t hsp_record_id = 0; static GIOChannel *hsp_server = NULL; static GIOChannel *sco_server = NULL; static void device_set_state(uint8_t state) { struct hal_ev_handsfree_conn_state ev; char address[18]; if (device.state == state) return; device.state = state; ba2str(&device.bdaddr, address); DBG("device %s state %u", address, state); bdaddr2android(&device.bdaddr, ev.bdaddr); ev.state = state; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_CONN_STATE, sizeof(ev), &ev); } static void device_set_audio_state(uint8_t state) { struct hal_ev_handsfree_audio_state ev; char address[18]; if (device.audio_state == state) return; device.audio_state = state; ba2str(&device.bdaddr, address); DBG("device %s audio state %u", address, state); bdaddr2android(&device.bdaddr, ev.bdaddr); ev.state = state; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_AUDIO_STATE, sizeof(ev), &ev); } static void init_codecs(void) { memcpy(device.codecs, codecs_defaults, sizeof(device.codecs)); if (hfp_ag_features & HFP_AG_FEAT_CODEC) device.codecs[MSBC_OFFSET].local_supported = true; } static void device_init(const bdaddr_t *bdaddr) { bacpy(&device.bdaddr, bdaddr); device.setup_state = HAL_HANDSFREE_CALL_STATE_IDLE; memcpy(device.inds, inds_defaults, sizeof(device.inds)); init_codecs(); device_set_state(HAL_EV_HANDSFREE_CONN_STATE_CONNECTING); } static void device_cleanup(void) { if (device.gw) { hfp_gw_unref(device.gw); device.gw = NULL; } device_set_state(HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED); if (device.sco_watch) { g_source_remove(device.sco_watch); device.sco_watch = 0; } if (device.sco) { g_io_channel_shutdown(device.sco, TRUE, NULL); g_io_channel_unref(device.sco); device.sco = NULL; } if (device.ring) { g_source_remove(device.ring); device.ring = 0; } device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED); memset(&device, 0, sizeof(device)); } static void disconnect_watch(void *user_data) { DBG(""); device_cleanup(); } static void at_cmd_unknown(const char *command, void *user_data) { uint8_t buf[IPC_MTU]; struct hal_ev_handsfree_unknown_at *ev = (void *) buf; if (device.state != HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED) { hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); hfp_gw_disconnect(device.gw); return; } /* copy while string including terminating NULL */ ev->len = strlen(command) + 1; memcpy(ev->buf, command, ev->len); if (ev->len > IPC_MTU - sizeof(*ev)) { hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); return; } ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_UNKNOWN_AT, sizeof(*ev) + ev->len, ev); } static void at_cmd_vgm(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { struct hal_ev_handsfree_volume ev; unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val > 15) break; if (hfp_gw_result_has_next(result)) break; ev.type = HAL_HANDSFREE_VOLUME_TYPE_MIC; ev.volume = val; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_VOLUME, sizeof(ev), &ev); /* Framework is not replying with result for AT+VGM */ hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_vgs(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { struct hal_ev_handsfree_volume ev; unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val > 15) break; if (hfp_gw_result_has_next(result)) break; ev.type = HAL_HANDSFREE_VOLUME_TYPE_SPEAKER; ev.volume = val; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_VOLUME, sizeof(ev), &ev); /* Framework is not replying with result for AT+VGS */ hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_cops(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val; switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val != 3) break; if (!hfp_gw_result_get_number(result, &val) || val != 0) break; if (hfp_gw_result_has_next(result)) break; hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_COPS, 0, NULL); return; case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_bia(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val, i, def; bool tmp[IND_COUNT]; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: for (i = 0; i < IND_COUNT; i++) tmp[i] = device.inds[i].active; i = 0; do { def = (i < IND_COUNT) ? device.inds[i].active : 0; if (!hfp_gw_result_get_number_default(result, &val, def)) goto failed; if (val > 1) goto failed; if (i < IND_COUNT) { tmp[i] = val || device.inds[i].always_active; i++; } } while (hfp_gw_result_has_next(result)); for (i = 0; i < IND_COUNT; i++) device.inds[i].active = tmp[i]; hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_COMMAND: break; } failed: hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_a(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { DBG(""); switch (type) { case HFP_GW_CMD_TYPE_COMMAND: if (hfp_gw_result_has_next(result)) break; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_ANSWER, 0, NULL); /* Framework is not replying with result for ATA */ hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_SET: case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_d(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { char buf[IPC_MTU]; struct hal_ev_handsfree_dial *ev = (void *) buf; int cnt; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_unquoted_string(result, (char *) ev->number, 255)) break; ev->number_len = strlen((char *) ev->number); if (ev->number[ev->number_len - 1] != ';') break; if (ev->number[0] == '>') cnt = strspn((char *) ev->number + 1, "0123456789") + 1; else cnt = strspn((char *) ev->number, "0123456789ABC*#+"); if (cnt != ev->number_len - 1) break; ev->number_len++; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_DIAL, sizeof(*ev) + ev->number_len, ev); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_ccwa(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val > 1) break; if (hfp_gw_result_has_next(result)) break; device.ccwa_enabled = val; hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_chup(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { DBG(""); switch (type) { case HFP_GW_CMD_TYPE_COMMAND: if (hfp_gw_result_has_next(result)) break; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_HANGUP, 0, NULL); /* Framework is not replying with result for AT+CHUP */ hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_SET: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_clcc(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { DBG(""); switch (type) { case HFP_GW_CMD_TYPE_COMMAND: if (hfp_gw_result_has_next(result)) break; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_CLCC, 0, NULL); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_SET: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_cmee(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val > 1) break; if (hfp_gw_result_has_next(result)) break; device.cmee_enabled = val; hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_clip(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val > 1) break; if (hfp_gw_result_has_next(result)) break; device.clip_enabled = val; hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_vts(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { struct hal_ev_handsfree_dtmf ev; char str[2]; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_unquoted_string(result, str, 2)) break; if (!((str[0] >= '0' && str[0] <= '9') || (str[0] >= 'A' && str[0] <= 'D') || str[0] == '*' || str[0] == '#')) break; if (hfp_gw_result_has_next(result)) break; ev.tone = str[0]; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_DTMF, sizeof(ev), &ev); /* Framework is not replying with result for AT+VTS */ hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_cnum(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { DBG(""); switch (type) { case HFP_GW_CMD_TYPE_COMMAND: if (hfp_gw_result_has_next(result)) break; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_CNUM, 0, NULL); return; case HFP_GW_CMD_TYPE_SET: case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_binp(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { DBG(""); /* TODO */ hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_bldn(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { struct hal_ev_handsfree_dial ev; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_COMMAND: if (hfp_gw_result_has_next(result)) break; ev.number_len = 0; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_DIAL, sizeof(ev), &ev); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_SET: break; } } static void at_cmd_bvra(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { struct hal_ev_handsfree_vr_state ev; unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val > 1) break; if (hfp_gw_result_has_next(result)) break; if (val) ev.state = HAL_HANDSFREE_VR_STARTED; else ev.state = HAL_HANDSFREE_VR_STOPPED; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_VR, sizeof(ev), &ev); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_nrec(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { struct hal_ev_handsfree_nrec ev; unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: /* * Android HAL defines start and stop parameter for NREC * callback, but spec allows HF to only disable AG's NREC * feature for SLC duration. Follow spec here. */ if (!hfp_gw_result_get_number(result, &val) || val != 0) break; if (hfp_gw_result_has_next(result)) break; ev.nrec = HAL_HANDSFREE_NREC_STOP; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_NREC, sizeof(ev), &ev); /* Framework is not replying with result for AT+NREC */ hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_bsir(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { DBG(""); /* TODO */ hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_btrh(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { DBG(""); /* TODO */ hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static gboolean sco_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer user_data) { g_io_channel_shutdown(device.sco, TRUE, NULL); g_io_channel_unref(device.sco); device.sco = NULL; DBG(""); device.sco_watch = 0; device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED); return FALSE; } static void select_codec(uint8_t codec_type) { uint8_t type = CODEC_ID_CVSD; int i; if (codec_type > 0) { type = codec_type; goto done; } for (i = CODECS_COUNT - 1; i >= CVSD_OFFSET; i--) { if (!device.codecs[i].local_supported) continue; if (!device.codecs[i].remote_supported) continue; type = device.codecs[i].type; break; } done: device.proposed_codec = type; hfp_gw_send_info(device.gw, "+BCS: %u", type); } static void connect_sco_cb(GIOChannel *chan, GError *err, gpointer user_data) { if (err) { uint8_t status; error("handsfree: audio connect failed (%s)", err->message); status = HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED; device_set_audio_state(status); if (!(device.features & HFP_HF_FEAT_CODEC)) return; /* If other failed, try connecting with CVSD */ if (device.negotiated_codec != CODEC_ID_CVSD) { info("handsfree: trying fallback with CVSD"); select_codec(CODEC_ID_CVSD); } return; } g_io_channel_set_close_on_unref(chan, TRUE); device.sco = g_io_channel_ref(chan); device.sco_watch = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, sco_watch_cb, NULL); device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTED); } static bool connect_sco(void) { GIOChannel *io; GError *gerr = NULL; uint16_t voice_settings; if (device.sco) return false; if (!(device.features & HFP_HF_FEAT_CODEC)) voice_settings = 0; else if (device.negotiated_codec != CODEC_ID_CVSD) voice_settings = BT_VOICE_TRANSPARENT; else voice_settings = BT_VOICE_CVSD_16BIT; io = bt_io_connect(connect_sco_cb, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, BT_IO_OPT_DEST_BDADDR, &device.bdaddr, BT_IO_OPT_VOICE, voice_settings, BT_IO_OPT_INVALID); if (!io) { error("handsfree: unable to connect audio: %s", gerr->message); g_error_free(gerr); return false; } g_io_channel_unref(io); device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTING); return true; } static void at_cmd_bcc(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { DBG(""); switch (type) { case HFP_GW_CMD_TYPE_COMMAND: if (!(device.features & HFP_HF_FEAT_CODEC)) break; if (hfp_gw_result_has_next(result)) break; hfp_gw_send_result(device.gw, HFP_RESULT_OK); /* we haven't negotiated codec, start selection */ if (!device.negotiated_codec) { select_codec(0); return; } /* * we try connect to negotiated codec. If it fails, and it isn't * CVSD codec, try connect CVSD */ if (!connect_sco() && device.negotiated_codec != CODEC_ID_CVSD) select_codec(CODEC_ID_CVSD); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_SET: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_bcs(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val)) break; if (hfp_gw_result_has_next(result)) break; /* Remote replied with other codec. Reply with error */ if (device.proposed_codec != val) { device.proposed_codec = 0; break; } device.proposed_codec = 0; device.negotiated_codec = val; hfp_gw_send_result(device.gw, HFP_RESULT_OK); /* Connect sco with negotiated parameters */ connect_sco(); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_ckpd(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val != 200) break; if (hfp_gw_result_has_next(result)) break; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_HSP_KEY_PRESS, 0, NULL); hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void register_post_slc_at(void) { if (device.hsp) { hfp_gw_register(device.gw, at_cmd_ckpd, "+CKPD", NULL, NULL); hfp_gw_register(device.gw, at_cmd_vgs, "+VGS", NULL, NULL); hfp_gw_register(device.gw, at_cmd_vgm, "+VGM", NULL, NULL); return; } hfp_gw_register(device.gw, at_cmd_a, "A", NULL, NULL); hfp_gw_register(device.gw, at_cmd_d, "D", NULL, NULL); hfp_gw_register(device.gw, at_cmd_ccwa, "+CCWA", NULL, NULL); hfp_gw_register(device.gw, at_cmd_chup, "+CHUP", NULL, NULL); hfp_gw_register(device.gw, at_cmd_clcc, "+CLCC", NULL, NULL); hfp_gw_register(device.gw, at_cmd_cops, "+COPS", NULL, NULL); hfp_gw_register(device.gw, at_cmd_cmee, "+CMEE", NULL, NULL); hfp_gw_register(device.gw, at_cmd_clip, "+CLIP", NULL, NULL); hfp_gw_register(device.gw, at_cmd_vts, "+VTS", NULL, NULL); hfp_gw_register(device.gw, at_cmd_cnum, "+CNUM", NULL, NULL); hfp_gw_register(device.gw, at_cmd_bia, "+BIA", NULL, NULL); hfp_gw_register(device.gw, at_cmd_binp, "+BINP", NULL, NULL); hfp_gw_register(device.gw, at_cmd_bldn, "+BLDN", NULL, NULL); hfp_gw_register(device.gw, at_cmd_bvra, "+BVRA", NULL, NULL); hfp_gw_register(device.gw, at_cmd_nrec, "+NREC", NULL, NULL); hfp_gw_register(device.gw, at_cmd_vgs, "+VGS", NULL, NULL); hfp_gw_register(device.gw, at_cmd_vgm, "+VGM", NULL, NULL); hfp_gw_register(device.gw, at_cmd_bsir, "+BSIR", NULL, NULL); hfp_gw_register(device.gw, at_cmd_btrh, "+BTRH", NULL, NULL); hfp_gw_register(device.gw, at_cmd_bcc, "+BCC", NULL, NULL); hfp_gw_register(device.gw, at_cmd_bcs, "+BCS", NULL, NULL); } static void at_cmd_cmer(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val; switch (type) { case HFP_GW_CMD_TYPE_SET: /* mode must be =3 */ if (!hfp_gw_result_get_number(result, &val) || val != 3) break; /* keyp is don't care */ if (!hfp_gw_result_get_number(result, &val)) break; /* disp is don't care */ if (!hfp_gw_result_get_number(result, &val)) break; /* ind must be 0 or 1 */ if (!hfp_gw_result_get_number(result, &val) || val > 1) break; if (hfp_gw_result_has_next(result)) break; device.indicators_enabled = val; hfp_gw_send_result(device.gw, HFP_RESULT_OK); if (device.features & HFP_HF_FEAT_3WAY) return; register_post_slc_at(); device_set_state(HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED); return; case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_cind(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { char *buf, *ptr; int len; unsigned int i; switch (type) { case HFP_GW_CMD_TYPE_TEST: /* * If device supports Codec Negotiation, AT+BAC should be * received first */ if ((device.features & HFP_HF_FEAT_CODEC) && !device.codecs[CVSD_OFFSET].remote_supported) break; len = strlen("+CIND:") + 1; for (i = 0; i < IND_COUNT; i++) { len += strlen("(\"\",(X,X)),"); len += strlen(device.inds[i].name); } buf = g_malloc(len); ptr = buf + sprintf(buf, "+CIND:"); for (i = 0; i < IND_COUNT; i++) { ptr += sprintf(ptr, "(\"%s\",(%d%c%d)),", device.inds[i].name, device.inds[i].min, device.inds[i].max == 1 ? ',' : '-', device.inds[i].max); } ptr--; *ptr = '\0'; hfp_gw_send_info(device.gw, "%s", buf); hfp_gw_send_result(device.gw, HFP_RESULT_OK); g_free(buf); return; case HFP_GW_CMD_TYPE_READ: ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_CIND, 0, NULL); return; case HFP_GW_CMD_TYPE_SET: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_brsf(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int feat; switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &feat)) break; if (hfp_gw_result_has_next(result)) break; /* TODO verify features */ device.features = feat; hfp_gw_send_info(device.gw, "+BRSF: %u", hfp_ag_features); hfp_gw_send_result(device.gw, HFP_RESULT_OK); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void at_cmd_chld(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { struct hal_ev_handsfree_chld ev; unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!hfp_gw_result_get_number(result, &val) || val > 3) break; /* No ECC support */ if (hfp_gw_result_has_next(result)) break; /* value match HAL type */ ev.chld = val; ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_EV_HANDSFREE_CHLD, sizeof(ev), &ev); return; case HFP_GW_CMD_TYPE_TEST: hfp_gw_send_info(device.gw, "+CHLD: (%s)", HFP_AG_CHLD); hfp_gw_send_result(device.gw, HFP_RESULT_OK); register_post_slc_at(); device_set_state(HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED); return; case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_COMMAND: break; } hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static struct hfp_codec *find_codec_by_type(uint8_t type) { int i; for (i = 0; i < CODECS_COUNT; i++) if (type == device.codecs[i].type) return &device.codecs[i]; return NULL; } static void at_cmd_bac(struct hfp_gw_result *result, enum hfp_gw_cmd_type type, void *user_data) { unsigned int val; DBG(""); switch (type) { case HFP_GW_CMD_TYPE_SET: if (!(device.features & HFP_HF_FEAT_CODEC)) goto failed; /* set codecs to defaults */ init_codecs(); device.negotiated_codec = 0; /* * At least CVSD mandatory codec must exist * HFP V1.6 4.34.1 */ if (!hfp_gw_result_get_number(result, &val) || val != CODEC_ID_CVSD) goto failed; device.codecs[CVSD_OFFSET].remote_supported = true; if (hfp_gw_result_get_number(result, &val)) { if (val != CODEC_ID_MSBC) goto failed; device.codecs[MSBC_OFFSET].remote_supported = true; } while (hfp_gw_result_has_next(result)) { struct hfp_codec *codec; if (!hfp_gw_result_get_number(result, &val)) goto failed; codec = find_codec_by_type(val); if (!codec) continue; codec->remote_supported = true; } hfp_gw_send_result(device.gw, HFP_RESULT_OK); if (device.proposed_codec) select_codec(0); return; case HFP_GW_CMD_TYPE_TEST: case HFP_GW_CMD_TYPE_READ: case HFP_GW_CMD_TYPE_COMMAND: break; } failed: hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); } static void register_slc_at(void) { hfp_gw_register(device.gw, at_cmd_brsf, "+BRSF", NULL, NULL); hfp_gw_register(device.gw, at_cmd_cind, "+CIND", NULL, NULL); hfp_gw_register(device.gw, at_cmd_cmer, "+CMER", NULL, NULL); hfp_gw_register(device.gw, at_cmd_chld, "+CHLD", NULL, NULL); hfp_gw_register(device.gw, at_cmd_bac, "+BAC", NULL, NULL); } static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) { DBG(""); if (err) { error("handsfree: connect failed (%s)", err->message); goto failed; } device.gw = hfp_gw_new(g_io_channel_unix_get_fd(chan)); if (!device.gw) goto failed; g_io_channel_set_close_on_unref(chan, FALSE); hfp_gw_set_close_on_unref(device.gw, true); hfp_gw_set_command_handler(device.gw, at_cmd_unknown, NULL, NULL); hfp_gw_set_disconnect_handler(device.gw, disconnect_watch, NULL, NULL); if (device.hsp) { register_post_slc_at(); device_set_state(HAL_EV_HANDSFREE_CONN_STATE_CONNECTED); device_set_state(HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED); return; } register_slc_at(); device_set_state(HAL_EV_HANDSFREE_CONN_STATE_CONNECTED); return; failed: g_io_channel_shutdown(chan, TRUE, NULL); device_cleanup(); } static void confirm_cb(GIOChannel *chan, gpointer data) { char address[18]; bdaddr_t bdaddr; GError *err = NULL; bt_io_get(chan, &err, BT_IO_OPT_DEST, address, BT_IO_OPT_DEST_BDADDR, &bdaddr, BT_IO_OPT_INVALID); if (err) { error("handsfree: confirm failed (%s)", err->message); g_error_free(err); goto drop; } DBG("incoming connect from %s", address); if (device.state != HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED) { info("handsfree: refusing connection from %s", address); goto drop; } device_init(&bdaddr); if (!bt_io_accept(chan, connect_cb, NULL, NULL, NULL)) { error("handsfree: failed to accept connection"); device_cleanup(); goto drop; } device.hsp = GPOINTER_TO_INT(data); return; drop: g_io_channel_shutdown(chan, TRUE, NULL); } static void sdp_hsp_search_cb(sdp_list_t *recs, int err, gpointer data) { sdp_list_t *protos, *classes; GError *gerr = NULL; GIOChannel *io; uuid_t uuid; int channel; DBG(""); if (err < 0) { error("handsfree: unable to get SDP record: %s", strerror(-err)); goto fail; } if (!recs || !recs->data) { info("handsfree: no HSP SDP records found"); goto fail; } if (sdp_get_service_classes(recs->data, &classes) < 0 || !classes) { error("handsfree: unable to get service classes from record"); goto fail; } if (sdp_get_access_protos(recs->data, &protos) < 0) { error("handsfree: unable to get access protocols from record"); sdp_list_free(classes, free); goto fail; } /* TODO read remote version? */ /* TODO read volume control support */ memcpy(&uuid, classes->data, sizeof(uuid)); sdp_list_free(classes, free); if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 || uuid.value.uuid16 != HEADSET_SVCLASS_ID) { sdp_list_free(protos, NULL); error("handsfree: invalid service record or not HSP"); goto fail; } channel = sdp_get_proto_port(protos, RFCOMM_UUID); sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); sdp_list_free(protos, NULL); if (channel <= 0) { error("handsfree: unable to get RFCOMM channel from record"); goto fail; } io = bt_io_connect(connect_cb, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, BT_IO_OPT_DEST_BDADDR, &device.bdaddr, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_CHANNEL, channel, BT_IO_OPT_INVALID); if (!io) { error("handsfree: unable to connect: %s", gerr->message); g_error_free(gerr); goto fail; } device.hsp = true; g_io_channel_unref(io); return; fail: device_cleanup(); } static int sdp_search_hsp(void) { uuid_t uuid; sdp_uuid16_create(&uuid, HEADSET_SVCLASS_ID); return bt_search_service(&adapter_addr, &device.bdaddr, &uuid, sdp_hsp_search_cb, NULL, NULL, 0); } static void sdp_hfp_search_cb(sdp_list_t *recs, int err, gpointer data) { sdp_list_t *protos, *classes; GError *gerr = NULL; GIOChannel *io; uuid_t uuid; int channel; DBG(""); if (err < 0) { error("handsfree: unable to get SDP record: %s", strerror(-err)); goto fail; } if (!recs || !recs->data) { info("handsfree: no HFP SDP records found, trying HSP"); if (sdp_search_hsp() < 0) { error("handsfree: HSP SDP search failed"); goto fail; } return; } if (sdp_get_service_classes(recs->data, &classes) < 0 || !classes) { error("handsfree: unable to get service classes from record"); goto fail; } if (sdp_get_access_protos(recs->data, &protos) < 0) { error("handsfree: unable to get access protocols from record"); sdp_list_free(classes, free); goto fail; } /* TODO read remote version? */ memcpy(&uuid, classes->data, sizeof(uuid)); sdp_list_free(classes, free); if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 || uuid.value.uuid16 != HANDSFREE_SVCLASS_ID) { sdp_list_free(protos, NULL); error("handsfree: invalid service record or not HFP"); goto fail; } channel = sdp_get_proto_port(protos, RFCOMM_UUID); sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); sdp_list_free(protos, NULL); if (channel <= 0) { error("handsfree: unable to get RFCOMM channel from record"); goto fail; } io = bt_io_connect(connect_cb, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, BT_IO_OPT_DEST_BDADDR, &device.bdaddr, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_CHANNEL, channel, BT_IO_OPT_INVALID); if (!io) { error("handsfree: unable to connect: %s", gerr->message); g_error_free(gerr); goto fail; } g_io_channel_unref(io); return; fail: device_cleanup(); } static int sdp_search_hfp(void) { uuid_t uuid; sdp_uuid16_create(&uuid, HANDSFREE_SVCLASS_ID); return bt_search_service(&adapter_addr, &device.bdaddr, &uuid, sdp_hfp_search_cb, NULL, NULL, 0); } static void handle_connect(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_connect *cmd = buf; char addr[18]; uint8_t status; bdaddr_t bdaddr; int ret; DBG(""); if (device.state != HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED) { status = HAL_STATUS_FAILED; goto failed; } android2bdaddr(&cmd->bdaddr, &bdaddr); ba2str(&bdaddr, addr); DBG("connecting to %s", addr); device_init(&bdaddr); /* prefer HFP over HSP */ ret = hfp_server ? sdp_search_hfp() : sdp_search_hsp(); if (ret < 0) { error("handsfree: SDP search failed"); device_cleanup(); status = HAL_STATUS_FAILED; goto failed; } status = HAL_STATUS_SUCCESS; failed: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_CONNECT, status); } static void handle_disconnect(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_disconnect *cmd = buf; bdaddr_t bdaddr; uint8_t status; DBG(""); android2bdaddr(cmd->bdaddr, &bdaddr); if (device.state == HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED || bacmp(&device.bdaddr, &bdaddr)) { status = HAL_STATUS_FAILED; goto failed; } if (device.state == HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTING) { status = HAL_STATUS_SUCCESS; goto failed; } if (device.state == HAL_EV_HANDSFREE_CONN_STATE_CONNECTING) { device_cleanup(); } else { device_set_state(HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTING); hfp_gw_disconnect(device.gw); } status = HAL_STATUS_SUCCESS; failed: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_DISCONNECT, status); } static bool disconnect_sco(void) { if (!device.sco) return false; device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTING); if (device.sco_watch) { g_source_remove(device.sco_watch); device.sco_watch = 0; } g_io_channel_shutdown(device.sco, TRUE, NULL); g_io_channel_unref(device.sco); device.sco = NULL; device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED); return true; } static bool connect_audio(void) { if (device.audio_state != HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED) return false; /* we haven't negotiated codec, start selection */ if ((device.features & HFP_HF_FEAT_CODEC) && !device.negotiated_codec) { select_codec(0); return true; } return connect_sco(); } static void handle_connect_audio(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_connect_audio *cmd = buf; bdaddr_t bdaddr; uint8_t status; DBG(""); android2bdaddr(cmd->bdaddr, &bdaddr); if (device.audio_state != HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED || bacmp(&device.bdaddr, &bdaddr)) { status = HAL_STATUS_FAILED; goto done; } status = connect_audio() ? HAL_STATUS_SUCCESS : HAL_STATUS_FAILED; done: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_CONNECT_AUDIO, status); } static void handle_disconnect_audio(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_disconnect_audio *cmd = buf; bdaddr_t bdaddr; uint8_t status; DBG(""); android2bdaddr(cmd->bdaddr, &bdaddr); if (device.audio_state != HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTED || bacmp(&device.bdaddr, &bdaddr)) { status = HAL_STATUS_FAILED; goto done; } status = disconnect_sco() ? HAL_STATUS_SUCCESS : HAL_STATUS_FAILED; done: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_DISCONNECT_AUDIO, status); } static void handle_start_vr(const void *buf, uint16_t len) { uint8_t status; DBG(""); if (device.features & HFP_HF_FEAT_VR) { hfp_gw_send_info(device.gw, "+BVRA: 1"); status = HAL_STATUS_SUCCESS; } else { status = HAL_STATUS_FAILED; } ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_START_VR, status); } static void handle_stop_vr(const void *buf, uint16_t len) { uint8_t status; DBG(""); if (device.features & HFP_HF_FEAT_VR) { hfp_gw_send_info(device.gw, "+BVRA: 0"); status = HAL_STATUS_SUCCESS; } else { status = HAL_STATUS_FAILED; } ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_STOP_VR, status); } static void handle_volume_control(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_volume_control *cmd = buf; uint8_t status, volume; DBG("type=%u volume=%u", cmd->type, cmd->volume); volume = cmd->volume > 15 ? 15 : cmd->volume; switch (cmd->type) { case HAL_HANDSFREE_VOLUME_TYPE_MIC: hfp_gw_send_info(device.gw, "+VGM: %u", volume); status = HAL_STATUS_SUCCESS; break; case HAL_HANDSFREE_VOLUME_TYPE_SPEAKER: hfp_gw_send_info(device.gw, "+VGS: %u", volume); status = HAL_STATUS_SUCCESS; break; default: status = HAL_STATUS_FAILED; break; } ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_VOLUME_CONTROL, status); } static void update_indicator(int ind, uint8_t val) { DBG("ind=%u new=%u old=%u", ind, val, device.inds[ind].val); if (device.inds[ind].val == val) return; device.inds[ind].val = val; if (!device.indicators_enabled) return; if (!device.inds[ind].active) return; /* indicator numbers in CIEV start from 1 */ hfp_gw_send_info(device.gw, "+CIEV: %u,%u", ind + 1, val); } static void handle_device_status_notif(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_device_status_notif *cmd = buf; DBG(""); update_indicator(IND_SERVICE, cmd->state); update_indicator(IND_ROAM, cmd->type); update_indicator(IND_SIGNAL, cmd->signal); update_indicator(IND_BATTCHG, cmd->battery); ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_DEVICE_STATUS_NOTIF, HAL_STATUS_SUCCESS); } static void handle_cops(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_cops_response *cmd = buf; if (len != sizeof(*cmd) + cmd->len || (cmd->len != 0 && cmd->buf[cmd->len - 1] != '\0')) { error("Invalid cops response command, terminating"); raise(SIGTERM); return; } DBG(""); hfp_gw_send_info(device.gw, "+COPS: 0,0,\"%.16s\"", cmd->len ? (char *) cmd->buf : ""); hfp_gw_send_result(device.gw, HFP_RESULT_OK); ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_COPS_RESPONSE, HAL_STATUS_SUCCESS); } static unsigned int get_callsetup(uint8_t state) { switch (state) { case HAL_HANDSFREE_CALL_STATE_INCOMING: return 1; case HAL_HANDSFREE_CALL_STATE_DIALING: return 2; case HAL_HANDSFREE_CALL_STATE_ALERTING: return 3; default: return 0; } } static void handle_cind(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_cind_response *cmd = buf; DBG(""); /* HAL doesn't provide indicators values so need to convert here */ device.inds[IND_SERVICE].val = cmd->svc; device.inds[IND_CALL].val = !!(cmd->num_active + cmd->num_held); device.inds[IND_CALLSETUP].val = get_callsetup(cmd->state); device.inds[IND_CALLHELD].val = cmd->num_held ? (cmd->num_active ? 1 : 2) : 0; device.inds[IND_SIGNAL].val = cmd->signal; device.inds[IND_ROAM].val = cmd->roam; device.inds[IND_BATTCHG].val = cmd->batt_chg; /* Order must match indicators_defaults table */ hfp_gw_send_info(device.gw, "+CIND: %u,%u,%u,%u,%u,%u,%u", device.inds[IND_SERVICE].val, device.inds[IND_CALL].val, device.inds[IND_CALLSETUP].val, device.inds[IND_CALLHELD].val, device.inds[IND_SIGNAL].val, device.inds[IND_ROAM].val, device.inds[IND_BATTCHG].val); hfp_gw_send_result(device.gw, HFP_RESULT_OK); ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_CIND_RESPONSE, HAL_STATUS_SUCCESS); } static void handle_formatted_at_resp(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_formatted_at_response *cmd = buf; DBG(""); if (len != sizeof(*cmd) + cmd->len || (cmd->len != 0 && cmd->buf[cmd->len - 1] != '\0')) { error("Invalid formatted AT response command, terminating"); raise(SIGTERM); return; } DBG(""); hfp_gw_send_info(device.gw, "%s", cmd->len ? (char *) cmd->buf : ""); ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_FORMATTED_AT_RESPONSE, HAL_STATUS_SUCCESS); } static void handle_at_resp(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_at_response *cmd = buf; DBG(""); if (cmd->response == HAL_HANDSFREE_AT_RESPONSE_OK) hfp_gw_send_result(device.gw, HFP_RESULT_OK); else if (device.cmee_enabled) hfp_gw_send_error(device.gw, cmd->error); else hfp_gw_send_result(device.gw, HFP_RESULT_ERROR); ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_AT_RESPONSE, HAL_STATUS_SUCCESS); } static void handle_clcc_resp(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_clcc_response *cmd = buf; uint8_t status; char *number; if (len != sizeof(*cmd) + cmd->number_len || (cmd->number_len != 0 && cmd->number[cmd->number_len - 1] != '\0')) { error("Invalid CLCC response command, terminating"); raise(SIGTERM); return; } DBG(""); if (!cmd->index) { hfp_gw_send_result(device.gw, HFP_RESULT_OK); status = HAL_STATUS_SUCCESS; goto done; } number = cmd->number_len ? (char *) cmd->number : ""; switch (cmd->state) { case HAL_HANDSFREE_CALL_STATE_INCOMING: case HAL_HANDSFREE_CALL_STATE_WAITING: case HAL_HANDSFREE_CALL_STATE_ACTIVE: case HAL_HANDSFREE_CALL_STATE_HELD: case HAL_HANDSFREE_CALL_STATE_DIALING: case HAL_HANDSFREE_CALL_STATE_ALERTING: if (cmd->type == HAL_HANDSFREE_CALL_ADDRTYPE_INTERNATIONAL && number[0] != '+') hfp_gw_send_info(device.gw, "+CLCC: %u,%u,%u,%u,%u,\"+%s\",%u", cmd->index, cmd->dir, cmd->state, cmd->mode, cmd->mpty, number, cmd->type); else hfp_gw_send_info(device.gw, "+CLCC: %u,%u,%u,%u,%u,\"%s\",%u", cmd->index, cmd->dir, cmd->state, cmd->mode, cmd->mpty, number, cmd->type); status = HAL_STATUS_SUCCESS; break; case HAL_HANDSFREE_CALL_STATE_IDLE: default: status = HAL_STATUS_FAILED; break; } done: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_CLCC_RESPONSE, status); } static gboolean ring_cb(gpointer user_data) { char *clip = user_data; hfp_gw_send_info(device.gw, "RING"); if (device.clip_enabled && clip) hfp_gw_send_info(device.gw, "%s", clip); return TRUE; } static void phone_state_dialing(int num_active, int num_held) { update_indicator(IND_CALLSETUP, 2); if (num_active == 0 && num_held > 0) update_indicator(IND_CALLHELD, 2); if (device.num_active == 0 && device.num_held == 0) connect_audio(); } static void phone_state_alerting(int num_active, int num_held) { update_indicator(IND_CALLSETUP, 3); } static void phone_state_waiting(int num_active, int num_held, uint8_t type, const uint8_t *number, int number_len) { char *num; if (!device.ccwa_enabled) return; num = number_len ? (char *) number : ""; if (type == HAL_HANDSFREE_CALL_ADDRTYPE_INTERNATIONAL && num[0] != '+') hfp_gw_send_info(device.gw, "+CCWA: \"+%s\",%u", num, type); else hfp_gw_send_info(device.gw, "+CCWA: \"%s\",%u", num, type); update_indicator(IND_CALLSETUP, 1); } static void phone_state_incoming(int num_active, int num_held, uint8_t type, const uint8_t *number, int number_len) { char *clip, *num; if (device.setup_state == HAL_HANDSFREE_CALL_STATE_INCOMING) { if (device.num_active != num_active || device.num_held != num_held) { /* * calls changed while waiting call ie. due to * termination of active call */ update_indicator(IND_CALLHELD, num_held ? (num_active ? 1 : 2) : 0); update_indicator(IND_CALL, !!(num_active + num_held)); } return; } if (device.call_hanging_up) return; if (num_active > 0 || num_held > 0) { phone_state_waiting(num_active, num_held, type, number, number_len); return; } update_indicator(IND_CALLSETUP, 1); num = number_len ? (char *) number : ""; if (type == HAL_HANDSFREE_CALL_ADDRTYPE_INTERNATIONAL && num[0] != '+') clip = g_strdup_printf("+CLIP: \"+%s\",%u", num, type); else clip = g_strdup_printf("+CLIP: \"%s\",%u", num, type); /* send first RING */ ring_cb(clip); device.ring = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, RING_TIMEOUT, ring_cb, clip, g_free); if (!device.ring) g_free(clip); } static void phone_state_idle(int num_active, int num_held) { if (device.ring) { g_source_remove(device.ring); device.ring = 0; } switch (device.setup_state) { case HAL_HANDSFREE_CALL_STATE_INCOMING: if (num_active > device.num_active) { update_indicator(IND_CALL, 1); if (device.num_active == 0 && device.num_held == 0) connect_audio(); } if (num_held > device.num_held) update_indicator(IND_CALLHELD, 1); update_indicator(IND_CALLSETUP, 0); if (num_active == device.num_active && num_held == device.num_held) device.call_hanging_up = true; break; case HAL_HANDSFREE_CALL_STATE_DIALING: case HAL_HANDSFREE_CALL_STATE_ALERTING: if (num_active > device.num_active) update_indicator(IND_CALL, 1); update_indicator(IND_CALLHELD, num_held ? (num_active ? 1 : 2) : 0); update_indicator(IND_CALLSETUP, 0); break; case HAL_HANDSFREE_CALL_STATE_IDLE: if (device.call_hanging_up) { device.call_hanging_up = false; return; } /* check if calls swapped */ if (num_held != 0 && num_active != 0 && device.num_active == num_held && device.num_held == num_active) { /* TODO better way for forcing indicator */ device.inds[IND_CALLHELD].val = 0; } else if ((num_active > 0 || num_held > 0) && device.num_active == 0 && device.num_held == 0) { /* * If number of active or held calls change but there * was no call setup change this means that there were * calls present when headset was connected. */ connect_audio(); } else if (num_active == 0 && num_held == 0) { disconnect_sco(); } update_indicator(IND_CALLHELD, num_held ? (num_active ? 1 : 2) : 0); update_indicator(IND_CALL, !!(num_active + num_held)); update_indicator(IND_CALLSETUP, 0); /* If call was terminated due to carrier lost send NO CARRIER */ if (num_active == 0 && num_held == 0 && device.inds[IND_SERVICE].val == 0 && (device.num_active > 0 || device.num_held > 0)) hfp_gw_send_info(device.gw, "NO CARRIER"); break; default: DBG("unhandled state %u", device.setup_state); break; } } static void handle_phone_state_change(const void *buf, uint16_t len) { const struct hal_cmd_handsfree_phone_state_change *cmd = buf; uint8_t status; if (len != sizeof(*cmd) + cmd->number_len || (cmd->number_len != 0 && cmd->number[cmd->number_len - 1] != '\0')) { error("Invalid phone state change command, terminating"); raise(SIGTERM); return; } DBG("active=%u hold=%u state=%u", cmd->num_active, cmd->num_held, cmd->state); switch (cmd->state) { case HAL_HANDSFREE_CALL_STATE_DIALING: phone_state_dialing(cmd->num_active, cmd->num_held); break; case HAL_HANDSFREE_CALL_STATE_ALERTING: phone_state_alerting(cmd->num_active, cmd->num_held); break; case HAL_HANDSFREE_CALL_STATE_INCOMING: phone_state_incoming(cmd->num_active, cmd->num_held, cmd->type, cmd->number, cmd->number_len); break; case HAL_HANDSFREE_CALL_STATE_IDLE: phone_state_idle(cmd->num_active, cmd->num_held); break; default: DBG("unhandled new state %u (current state %u)", cmd->state, device.setup_state); status = HAL_STATUS_FAILED; goto failed; } device.num_active = cmd->num_active; device.num_held = cmd->num_held; device.setup_state = cmd->state; status = HAL_STATUS_SUCCESS; failed: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_PHONE_STATE_CHANGE, status); } static const struct ipc_handler cmd_handlers[] = { /* HAL_OP_HANDSFREE_CONNECT */ { handle_connect, false, sizeof(struct hal_cmd_handsfree_connect) }, /* HAL_OP_HANDSFREE_DISCONNECT */ { handle_disconnect, false, sizeof(struct hal_cmd_handsfree_disconnect) }, /* HAL_OP_HANDSFREE_CONNECT_AUDIO */ { handle_connect_audio, false, sizeof(struct hal_cmd_handsfree_connect_audio) }, /* HAL_OP_HANDSFREE_DISCONNECT_AUDIO */ { handle_disconnect_audio, false, sizeof(struct hal_cmd_handsfree_disconnect_audio) }, /* define HAL_OP_HANDSFREE_START_VR */ { handle_start_vr, false, 0 }, /* define HAL_OP_HANDSFREE_STOP_VR */ { handle_stop_vr, false, 0 }, /* HAL_OP_HANDSFREE_VOLUME_CONTROL */ { handle_volume_control, false, sizeof(struct hal_cmd_handsfree_volume_control) }, /* HAL_OP_HANDSFREE_DEVICE_STATUS_NOTIF */ { handle_device_status_notif, false, sizeof(struct hal_cmd_handsfree_device_status_notif) }, /* HAL_OP_HANDSFREE_COPS_RESPONSE */ { handle_cops, true, sizeof(struct hal_cmd_handsfree_cops_response) }, /* HAL_OP_HANDSFREE_CIND_RESPONSE */ { handle_cind, false, sizeof(struct hal_cmd_handsfree_cind_response) }, /* HAL_OP_HANDSFREE_FORMATTED_AT_RESPONSE */ { handle_formatted_at_resp, true, sizeof(struct hal_cmd_handsfree_formatted_at_response) }, /* HAL_OP_HANDSFREE_AT_RESPONSE */ { handle_at_resp, false, sizeof(struct hal_cmd_handsfree_at_response) }, /* HAL_OP_HANDSFREE_CLCC_RESPONSE */ { handle_clcc_resp, true, sizeof(struct hal_cmd_handsfree_clcc_response) }, /* HAL_OP_HANDSFREE_PHONE_STATE_CHANGE */ { handle_phone_state_change, true, sizeof(struct hal_cmd_handsfree_phone_state_change) }, }; static sdp_record_t *headset_ag_record(void) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; uuid_t l2cap_uuid, rfcomm_uuid; sdp_profile_desc_t profile; sdp_list_t *aproto, *proto[2]; sdp_record_t *record; sdp_data_t *channel; uint8_t netid = 0x01; sdp_data_t *network; uint8_t ch = HSP_AG_CHANNEL; record = sdp_record_alloc(); if (!record) return NULL; network = sdp_data_alloc(SDP_UINT8, &netid); if (!network) { sdp_record_free(record); return NULL; } sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(record, root); sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID); svclass_id = sdp_list_append(0, &svclass_uuid); sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); sdp_set_service_classes(record, svclass_id); sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID); profile.version = 0x0102; pfseq = sdp_list_append(0, &profile); sdp_set_profile_descs(record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &ch); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(record, aproto); sdp_set_info_attr(record, "Voice Gateway", 0, 0); sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network); sdp_data_free(channel); sdp_list_free(proto[0], NULL); sdp_list_free(proto[1], NULL); sdp_list_free(apseq, NULL); sdp_list_free(pfseq, NULL); sdp_list_free(aproto, NULL); sdp_list_free(root, NULL); sdp_list_free(svclass_id, NULL); return record; } static void confirm_sco_cb(GIOChannel *chan, gpointer user_data) { char address[18]; bdaddr_t bdaddr; GError *err = NULL; if (device.sco) goto drop; bt_io_get(chan, &err, BT_IO_OPT_DEST, address, BT_IO_OPT_DEST_BDADDR, &bdaddr, BT_IO_OPT_INVALID); if (err) { error("handsfree: audio confirm failed (%s)", err->message); g_error_free(err); goto drop; } DBG("incoming SCO connection from %s", address); if (device.state != HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED || bacmp(&device.bdaddr, &bdaddr)) { error("handsfree: audio connection from %s rejected", address); goto drop; } if (!bt_io_accept(chan, connect_sco_cb, NULL, NULL, NULL)) { error("handsfree: failed to accept audio connection"); goto drop; } device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTING); return; drop: g_io_channel_shutdown(chan, TRUE, NULL); } static bool enable_hsp_ag(void) { sdp_record_t *rec; GError *err = NULL; DBG(""); hsp_server = bt_io_listen(NULL, confirm_cb, GINT_TO_POINTER(true), NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, BT_IO_OPT_CHANNEL, HSP_AG_CHANNEL, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_INVALID); if (!hsp_server) { error("Failed to listen on Headset rfcomm: %s", err->message); g_error_free(err); return false; } rec = headset_ag_record(); if (!rec) { error("Failed to allocate Headset record"); goto failed; } if (bt_adapter_add_record(rec, 0) < 0) { error("Failed to register Headset record"); sdp_record_free(rec); goto failed; } hsp_record_id = rec->handle; return true; failed: g_io_channel_shutdown(hsp_server, TRUE, NULL); g_io_channel_unref(hsp_server); hsp_server = NULL; return false; } static void cleanup_hsp_ag(void) { if (hsp_server) { g_io_channel_shutdown(hsp_server, TRUE, NULL); g_io_channel_unref(hsp_server); hsp_server = NULL; } if (hsp_record_id > 0) { bt_adapter_remove_record(hsp_record_id); hsp_record_id = 0; } } static sdp_record_t *hfp_ag_record(void) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; uuid_t l2cap_uuid, rfcomm_uuid; sdp_profile_desc_t profile; sdp_list_t *aproto, *proto[2]; sdp_record_t *record; sdp_data_t *channel, *features; uint8_t netid = 0x01; uint16_t sdpfeat; sdp_data_t *network; uint8_t ch = HFP_AG_CHANNEL; record = sdp_record_alloc(); if (!record) return NULL; network = sdp_data_alloc(SDP_UINT8, &netid); if (!network) { sdp_record_free(record); return NULL; } sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(record, root); sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID); svclass_id = sdp_list_append(NULL, &svclass_uuid); sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); sdp_set_service_classes(record, svclass_id); sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID); profile.version = 0x0106; pfseq = sdp_list_append(NULL, &profile); sdp_set_profile_descs(record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(NULL, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(NULL, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &ch); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); /* Codec Negotiation bit in SDP feature is different then in BRSF */ sdpfeat = hfp_ag_features & 0x0000003F; if (hfp_ag_features & HFP_AG_FEAT_CODEC) sdpfeat |= 0x00000020; else sdpfeat &= ~0x00000020; features = sdp_data_alloc(SDP_UINT16, &sdpfeat); sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); aproto = sdp_list_append(NULL, apseq); sdp_set_access_protos(record, aproto); sdp_set_info_attr(record, "Hands-Free Audio Gateway", NULL, NULL); sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network); sdp_data_free(channel); sdp_list_free(proto[0], NULL); sdp_list_free(proto[1], NULL); sdp_list_free(apseq, NULL); sdp_list_free(pfseq, NULL); sdp_list_free(aproto, NULL); sdp_list_free(root, NULL); sdp_list_free(svclass_id, NULL); return record; } static bool enable_hfp_ag(void) { sdp_record_t *rec; GError *err = NULL; DBG(""); if (hfp_server) return false; hfp_server = bt_io_listen(NULL, confirm_cb, GINT_TO_POINTER(false), NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, BT_IO_OPT_CHANNEL, HFP_AG_CHANNEL, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_INVALID); if (!hfp_server) { error("Failed to listen on Handsfree rfcomm: %s", err->message); g_error_free(err); return false; } rec = hfp_ag_record(); if (!rec) { error("Failed to allocate Handsfree record"); goto failed; } if (bt_adapter_add_record(rec, 0) < 0) { error("Failed to register Handsfree record"); sdp_record_free(rec); goto failed; } hfp_record_id = rec->handle; return true; failed: g_io_channel_shutdown(hfp_server, TRUE, NULL); g_io_channel_unref(hfp_server); hfp_server = NULL; return false; } static void cleanup_hfp_ag(void) { if (hfp_server) { g_io_channel_shutdown(hfp_server, TRUE, NULL); g_io_channel_unref(hfp_server); hfp_server = NULL; } if (hfp_record_id > 0) { bt_adapter_remove_record(hfp_record_id); hfp_record_id = 0; } } static bool enable_sco_server(void) { GError *err = NULL; sco_server = bt_io_listen(NULL, confirm_sco_cb, NULL, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, BT_IO_OPT_INVALID); if (!sco_server) { error("handsfree: Failed to listen on SCO: %s", err->message); g_error_free(err); cleanup_hsp_ag(); cleanup_hfp_ag(); return false; } return true; } static void disable_sco_server(void) { if (sco_server) { g_io_channel_shutdown(sco_server, TRUE, NULL); g_io_channel_unref(sco_server); sco_server = NULL; } } static void bt_sco_connect(const void *buf, uint16_t len) { int fd; GError *err; struct sco_rsp_connect rsp; DBG(""); if (!device.sco) goto failed; err = NULL; if (!bt_io_get(device.sco, &err, BT_IO_OPT_MTU, &rsp.mtu, BT_IO_OPT_INVALID)) { error("Unable to get MTU: %s\n", err->message); g_clear_error(&err); goto failed; } fd = g_io_channel_unix_get_fd(device.sco); DBG("fd %d mtu %u", fd, rsp.mtu); ipc_send_rsp_full(sco_ipc, SCO_SERVICE_ID, SCO_OP_CONNECT, sizeof(rsp), &rsp, fd); return; failed: ipc_send_rsp(sco_ipc, SCO_SERVICE_ID, SCO_OP_STATUS, SCO_STATUS_FAILED); } static const struct ipc_handler sco_handlers[] = { /* SCO_OP_CONNECT */ { bt_sco_connect, false, 0 } }; static void bt_sco_unregister(void) { DBG(""); ipc_cleanup(sco_ipc); sco_ipc = NULL; } static bool bt_sco_register(ipc_disconnect_cb disconnect) { DBG(""); sco_ipc = ipc_init(BLUEZ_SCO_SK_PATH, sizeof(BLUEZ_SCO_SK_PATH), SCO_SERVICE_ID, false, disconnect, NULL); if (!sco_ipc) return false; ipc_register(sco_ipc, SCO_SERVICE_ID, sco_handlers, G_N_ELEMENTS(sco_handlers)); return true; } bool bt_handsfree_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) { DBG("mode 0x%x", mode); bacpy(&adapter_addr, addr); if (!enable_hsp_ag()) return false; if (!enable_sco_server()) { cleanup_hsp_ag(); return false; } if (mode == HAL_MODE_HANDSFREE_HSP_ONLY) goto done; hfp_ag_features = HFP_AG_FEATURES; if (mode == HAL_MODE_HANDSFREE_HFP_WBS) hfp_ag_features |= HFP_AG_FEAT_CODEC; if (enable_hfp_ag()) goto done; cleanup_hsp_ag(); disable_sco_server(); hfp_ag_features = 0; return false; done: hal_ipc = ipc; ipc_register(hal_ipc, HAL_SERVICE_ID_HANDSFREE, cmd_handlers, G_N_ELEMENTS(cmd_handlers)); bt_sco_register(NULL); return true; } void bt_handsfree_unregister(void) { DBG(""); bt_sco_unregister(); ipc_unregister(hal_ipc, HAL_SERVICE_ID_HANDSFREE); hal_ipc = NULL; cleanup_hfp_ag(); cleanup_hsp_ag(); disable_sco_server(); hfp_ag_features = 0; }