/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include <sys/socket.h> #include <syslog.h> #include "cras_bt_device.h" #include "cras_telephony.h" #include "cras_hfp_ag_profile.h" #include "cras_hfp_slc.h" #include "cras_system_state.h" #define SLC_BUF_SIZE_BYTES 256 /* Indicator update command response and indicator indices. * Note that indicator index starts from '1'. */ #define BATTERY_IND_INDEX 1 #define SIGNAL_IND_INDEX 2 #define SERVICE_IND_INDEX 3 #define CALL_IND_INDEX 4 #define CALLSETUP_IND_INDEX 5 #define CALLHELD_IND_INDEX 6 #define INDICATOR_UPDATE_RSP \ "+CIND: " \ "(\"battchg\",(0-5))," \ "(\"signal\",(0-5))," \ "(\"service\",(0,1))," \ "(\"call\",(0,1))," \ "(\"callsetup\",(0-3))," \ "(\"callheld\",(0-2))," \ "(\"roam\",(0,1))" \ "" /* Mode values for standard event reporting activation/deactivation AT * command AT+CMER. Used for indicator events reporting in HFP. */ #define FORWARD_UNSOLICIT_RESULT_CODE 3 /* Handle object to hold required info to initialize and maintain * an HFP service level connection. * Args: * buf - Buffer hold received commands. * buf_read_idx - Read index for buf. * buf_write_idx - Write index for buf. * rfcomm_fd - File descriptor for the established RFCOMM connection. * init_cb - Callback to be triggered when an SLC is initialized. * initialized - The service level connection is fully initilized of not. * cli_active - Calling line identification notification is enabled or not. * battery - Current battery level of AG stored in SLC. * signal - Current signal strength of AG stored in SLC. * service - Current service availability of AG stored in SLC. * callheld - Current callheld status of AG stored in SLC. * ind_event_report - Activate status of indicator events reporting. * telephony - A reference of current telephony handle. * device - The associated bt device. */ struct hfp_slc_handle { char buf[SLC_BUF_SIZE_BYTES]; int buf_read_idx; int buf_write_idx; int is_hsp; int rfcomm_fd; hfp_slc_init_cb init_cb; hfp_slc_disconnect_cb disconnect_cb; int initialized; int cli_active; int battery; int signal; int service; int callheld; int ind_event_report; struct cras_bt_device *device; struct cras_telephony_handle *telephony; }; /* AT command exchanges between AG(Audio gateway) and HF(Hands-free device) */ struct at_command { const char *cmd; int (*callback) (struct hfp_slc_handle *handle, const char *cmd); }; /* Sends a response or command to HF */ static int hfp_send(struct hfp_slc_handle *handle, const char *buf) { int written, err, len; if (handle->rfcomm_fd < 0) return -EIO; /* Message start and end with "\r\n". refer to spec 4.33. */ err = write(handle->rfcomm_fd, "\r\n", 2); if (err < 0) return -errno; len = strlen(buf); written = 0; while (written < len) { err = write(handle->rfcomm_fd, buf + written, len - written); if (err < 0) return -errno; written += err; } err = write(handle->rfcomm_fd, "\r\n", 2); if (err < 0) return -errno; return 0; } /* Sends a response for indicator event reporting. */ static int hfp_send_ind_event_report(struct hfp_slc_handle *handle, int ind_index, int value) { char cmd[64]; if (handle->is_hsp || !handle->ind_event_report) return 0; snprintf(cmd, 64, "+CIEV: %d,%d", ind_index, value); return hfp_send(handle, cmd); } /* Sends calling line identification unsolicited result code and * standard call waiting notification. */ static int hfp_send_calling_line_identification(struct hfp_slc_handle *handle, const char *number, int type) { char cmd[64]; if (handle->is_hsp) return 0; if (handle->telephony->call) { snprintf(cmd, 64, "+CCWA: \"%s\",%d", number, type); } else { snprintf(cmd, 64, "+CLIP: \"%s\",%d", number, type); } return hfp_send(handle, cmd); } /* ATA command to accept an incoming call. Mandatory support per spec 4.13. */ static int answer_call(struct hfp_slc_handle *handle, const char *cmd) { int rc; rc = hfp_send(handle, "OK"); if (rc) return rc; return cras_telephony_event_answer_call(); } /* AT+CCWA command to enable the "Call Waiting notification" function. * Mandatory support per spec 4.21. */ static int call_waiting_notify(struct hfp_slc_handle *handle, const char *buf) { return hfp_send(handle, "OK"); } /* AT+CLIP command to enable the "Calling Line Identification notification" * function. Mandatory per spec 4.23. */ static int cli_notification(struct hfp_slc_handle *handle, const char *cmd) { handle->cli_active = (cmd[8] == '1'); return hfp_send(handle, "OK"); } /* ATDdd...dd command to place call with supplied number, or ATD>nnn... * command to dial the number stored at memory location. Mandatory per * spec 4.18 and 4.19. */ static int dial_number(struct hfp_slc_handle *handle, const char *cmd) { int rc, cmd_len; cmd_len = strlen(cmd); if (cmd[3] == '>') { /* Handle memory dial. Extract memory location from command * ATD>nnn...; and lookup. */ int memory_location; memory_location = strtol(cmd + 4, NULL, 0); if (handle->telephony->dial_number == NULL || memory_location != 1) return hfp_send(handle, "ERROR"); } else { /* ATDddddd; Store dial number to the only memory slot. */ cras_telephony_store_dial_number(cmd_len - 3 - 1, cmd + 3); } rc = hfp_send(handle, "OK"); if (rc) return rc; handle->telephony->callsetup = 2; return hfp_send_ind_event_report(handle, CALLSETUP_IND_INDEX, 2); } /* AT+VTS command to generate a DTMF code. Mandatory per spec 4.27. */ static int dtmf_tone(struct hfp_slc_handle *handle, const char *buf) { return hfp_send(handle, "OK"); } /* AT+CMER command enables the registration status update function in AG. * The service level connection is consider initialized when successfully * responded OK to the AT+CMER command. Mandatory support per spec 4.4. */ static int event_reporting(struct hfp_slc_handle *handle, const char *cmd) { char *tokens, *mode, *tmp; int err = 0; /* AT+CMER=[<mode>[,<keyp>[,<disp>[,<ind> [,<bfr>]]]]] * Parse <ind>, the only token we care about. */ tokens = strdup(cmd); strtok(tokens, "="); mode = strtok(NULL, ","); tmp = strtok(NULL, ","); tmp = strtok(NULL, ","); tmp = strtok(NULL, ","); /* mode = 3 for forward unsolicited result codes. * AT+CMER=3,0,0,1 activates “indicator events reporting”. * The service level connection is considered established after * successfully responded with OK, regardless of the indicator * events reporting status. */ if (!mode || !tmp) { syslog(LOG_ERR, "Invalid event reporting” cmd %s", cmd); err = -EINVAL; goto event_reporting_err; } if (atoi(mode) == FORWARD_UNSOLICIT_RESULT_CODE) handle->ind_event_report = atoi(tmp); err = hfp_send(handle, "OK"); if (err) { syslog(LOG_ERR, "Error sending response for command %s", cmd); goto event_reporting_err; } /* Consider the Service Level Connection to be fully initialized, * and thereby established, after successfully responded with OK. */ if (!handle->initialized) { handle->initialized = 1; if (handle->init_cb) handle->init_cb(handle); } event_reporting_err: free(tokens); return err; } /* AT+CMEE command to set the "Extended Audio Gateway Error Result Code". * Mandatory per spec 4.9. */ static int extended_errors(struct hfp_slc_handle *handle, const char *buf) { return hfp_send(handle, "OK"); } /* AT+CKPD command to handle the user initiated action from headset profile * device. */ static int key_press(struct hfp_slc_handle *handle, const char *buf) { hfp_send(handle, "OK"); /* Release the call and connection. */ if (handle->telephony->call || handle->telephony->callsetup) { cras_telephony_event_terminate_call(); handle->disconnect_cb(handle); return -EIO; } return 0; } /* AT+BLDN command to re-dial the last number. Mandatory support * per spec 4.20. */ static int last_dialed_number(struct hfp_slc_handle *handle, const char *buf) { int rc; if (!handle->telephony->dial_number) return hfp_send(handle, "ERROR"); rc = hfp_send(handle, "OK"); if (rc) return rc; handle->telephony->callsetup = 2; return hfp_send_ind_event_report(handle, CALLSETUP_IND_INDEX, 2); } /* AT+CLCC command to query list of current calls. Mandatory support * per spec 4.31. * * +CLCC: <idx>,<direction>,<status>,<mode>,<multiparty> */ static int list_current_calls(struct hfp_slc_handle *handle, const char *cmd) { char buf[64]; int idx = 1; int rc; /* Fake the call list base on callheld and call status * since we have no API exposed to manage call list. * This is a hack to pass qualification test which ask us to * handle the basic case that one call is active and * the other is on hold. */ if (handle->telephony->callheld) { snprintf(buf, 64, "+CLCC: %d,1,1,0,0", idx++); rc = hfp_send(handle, buf); if (rc) return rc; } if (handle->telephony->call) { snprintf(buf, 64, "+CLCC: %d,1,0,0,0", idx++); rc = hfp_send(handle, buf); if (rc) return rc; } return hfp_send(handle, "OK"); } /* AT+COPS command to query currently selected operator or set name format. * Mandatory support per spec 4.8. */ static int operator_selection(struct hfp_slc_handle *handle, const char *buf) { int rc; if (buf[7] == '?') { /* HF sends AT+COPS? command to find current network operator. * AG responds with +COPS:<mode>,<format>,<operator>, where * the mode=0 means automatic for network selection. If no * operator is selected, <format> and <operator> are omitted. */ rc = hfp_send(handle, "+COPS: 0"); if (rc) return rc; } return hfp_send(handle, "OK"); } /* AT+CIND command retrieves the supported indicator and its corresponding * range and order index or read current status of indicators. Mandatory * support per spec 4.2. */ static int report_indicators(struct hfp_slc_handle *handle, const char *cmd) { int err; char buf[64]; if (cmd[7] == '=') { /* Indicator update test command "AT+CIND=?" */ err = hfp_send(handle, INDICATOR_UPDATE_RSP); } else { /* Indicator update read command "AT+CIND?". * Respond with current status of AG indicators, * the values must be listed in the indicator order declared * in INDICATOR_UPDATE_RSP. * +CIND: <signal>,<service>,<call>, * <callsetup>,<callheld>,<roam> */ snprintf(buf, 64, "+CIND: %d,%d,%d,%d,%d,%d,0", handle->battery, handle->signal, handle->service, handle->telephony->call, handle->telephony->callsetup, handle->telephony->callheld ); err = hfp_send(handle, buf); } if (err < 0) return err; return hfp_send(handle, "OK"); } /* AT+BIA command to change the subset of indicators that shall be * sent by the AG. It is okay to ignore this command here since we * don't do event reporting(CMER). */ static int indicator_activation(struct hfp_slc_handle *handle, const char *cmd) { /* AT+BIA=[[<indrep 1>][,[<indrep 2>][,...[,[<indrep n>]]]]] */ syslog(LOG_ERR, "Bluetooth indicator activation command %s", cmd); return hfp_send(handle, "OK"); } /* AT+VGM and AT+VGS command reports the current mic and speaker gain * level respectively. Optional support per spec 4.28. */ static int signal_gain_setting(struct hfp_slc_handle *handle, const char *cmd) { int gain; if (strlen(cmd) < 8) { syslog(LOG_ERR, "Invalid gain setting command %s", cmd); return -EINVAL; } /* Map 0 to the smallest non-zero scale 6/100, and 15 to * 100/100 full. */ if (cmd[5] == 'S') { gain = atoi(&cmd[7]); cras_bt_device_update_hardware_volume(handle->device, (gain + 1) * 100 / 16); } return hfp_send(handle, "OK"); } /* AT+CNUM command to query the subscriber number. Mandatory support * per spec 4.30. */ static int subscriber_number(struct hfp_slc_handle *handle, const char *buf) { return hfp_send(handle, "OK"); } /* AT+BRSF command notifies the HF(Hands-free device) supported features * and retrieves the AG(Audio gateway) supported features. Mandatory * support per spec 4.2. */ static int supported_features(struct hfp_slc_handle *handle, const char *cmd) { int err; char response[128]; if (strlen(cmd) < 9) return -EINVAL; /* AT+BRSF=<feature> command received, ignore the HF supported feature * for now. Respond with +BRSF:<feature> to notify mandatory supported * features in AG(audio gateway). */ snprintf(response, 128, "+BRSF: %u", HFP_SUPPORTED_FEATURE); err = hfp_send(handle, response); if (err < 0) return err; return hfp_send(handle, "OK"); } int hfp_event_speaker_gain(struct hfp_slc_handle *handle, int gain) { char command[128]; /* Normailize gain value to 0-15 */ gain = gain * 15 / 100; snprintf(command, 128, "+VGS=%d", gain); return hfp_send(handle, command); } /* AT+CHUP command to terminate current call. Mandatory support * per spec 4.15. */ static int terminate_call(struct hfp_slc_handle *handle, const char *cmd) { int rc; rc = hfp_send(handle, "OK"); if (rc) return rc; return cras_telephony_event_terminate_call(); } /* AT commands to support in order to conform HFP specification. * * An initialized service level connection is the pre-condition for all * call related procedures. Note that for the call related commands, * we are good to just respond with a dummy "OK". * * The procedure to establish a service level connection is described below: * * 1. HF notifies AG about its own supported features and AG responds * with its supported feature. * * HF(hands-free) AG(audio gateway) * AT+BRSF=<HF supported feature> --> * <-- +BRSF:<AG supported feature> * <-- OK * * 2. HF retrieves the information about the indicators supported in AG. * * HF(hands-free) AG(audio gateway) * AT+CIND=? --> * <-- +CIND:... * <-- OK * * 3. The HF requests the current status of the indicators in AG. * * HF(hands-free) AG(audio gateway) * AT+CIND --> * <-- +CIND:... * <-- OK * * 4. HF requests enabling indicator status update in the AG. * * HF(hands-free) AG(audio gateway) * AT+CMER= --> * <-- OK */ static struct at_command at_commands[] = { { "ATA", answer_call }, { "ATD", dial_number }, { "AT+BIA", indicator_activation }, { "AT+BLDN", last_dialed_number }, { "AT+BRSF", supported_features }, { "AT+CCWA", call_waiting_notify }, { "AT+CHUP", terminate_call }, { "AT+CIND", report_indicators }, { "AT+CKPD", key_press }, { "AT+CLCC", list_current_calls }, { "AT+CLIP", cli_notification }, { "AT+CMEE", extended_errors }, { "AT+CMER", event_reporting }, { "AT+CNUM", subscriber_number }, { "AT+COPS", operator_selection }, { "AT+VG", signal_gain_setting }, { "AT+VTS", dtmf_tone }, { 0 } }; static int handle_at_command(struct hfp_slc_handle *slc_handle, const char *cmd) { struct at_command *atc; for (atc = at_commands; atc->cmd; atc++) if (!strncmp(cmd, atc->cmd, strlen(atc->cmd))) return atc->callback(slc_handle, cmd); syslog(LOG_ERR, "AT command %s not supported", cmd); return hfp_send(slc_handle, "ERROR"); } static void slc_watch_callback(void *arg) { struct hfp_slc_handle *handle = (struct hfp_slc_handle *)arg; ssize_t bytes_read; int err; bytes_read = read(handle->rfcomm_fd, &handle->buf[handle->buf_write_idx], SLC_BUF_SIZE_BYTES - handle->buf_write_idx - 1); if (bytes_read < 0) { syslog(LOG_ERR, "Error reading slc command %s", strerror(errno)); handle->disconnect_cb(handle); return; } handle->buf_write_idx += bytes_read; handle->buf[handle->buf_write_idx] = '\0'; while (handle->buf_read_idx != handle->buf_write_idx) { char *end_char; end_char = strchr(&handle->buf[handle->buf_read_idx], '\r'); if (end_char == NULL) break; *end_char = '\0'; err = handle_at_command(handle, &handle->buf[handle->buf_read_idx]); if (err < 0) return; /* Shift the read index */ handle->buf_read_idx = 1 + end_char - handle->buf; if (handle->buf_read_idx == handle->buf_write_idx) { handle->buf_read_idx = 0; handle->buf_write_idx = 0; } } /* Handle the case when buffer is full and no command found. */ if (handle->buf_write_idx == SLC_BUF_SIZE_BYTES - 1) { if (handle->buf_read_idx) { memmove(handle->buf, &handle->buf[handle->buf_read_idx], handle->buf_write_idx - handle->buf_read_idx); handle->buf_write_idx -= handle->buf_read_idx; handle->buf_read_idx = 0; } else { syslog(LOG_ERR, "Parse SLC command error, clean up buffer"); handle->buf_write_idx = 0; } } return; } /* Exported interfaces */ struct hfp_slc_handle *hfp_slc_create(int fd, int is_hsp, struct cras_bt_device *device, hfp_slc_init_cb init_cb, hfp_slc_disconnect_cb disconnect_cb) { struct hfp_slc_handle *handle; handle = (struct hfp_slc_handle*) calloc(1, sizeof(*handle)); if (!handle) return NULL; handle->rfcomm_fd = fd; handle->is_hsp = is_hsp; handle->device = device; handle->init_cb = init_cb; handle->disconnect_cb = disconnect_cb; handle->cli_active = 0; handle->battery = 5; handle->signal = 5; handle->service = 1; handle->ind_event_report = 0; handle->telephony = cras_telephony_get(); cras_system_add_select_fd(handle->rfcomm_fd, slc_watch_callback, handle); return handle; } void hfp_slc_destroy(struct hfp_slc_handle *slc_handle) { cras_system_rm_select_fd(slc_handle->rfcomm_fd); close(slc_handle->rfcomm_fd); free(slc_handle); } int hfp_set_call_status(struct hfp_slc_handle *handle, int call) { int old_call = handle->telephony->call; if (old_call == call) return 0; handle->telephony->call = call; return hfp_event_update_call(handle); } /* Procedure to setup a call when AG sees incoming call. * * HF(hands-free) AG(audio gateway) * <-- Incoming call * <-- +CIEV: (callsetup = 1) * <-- RING (ALERT) */ int hfp_event_incoming_call(struct hfp_slc_handle *handle, const char *number, int type) { int rc; if (handle->is_hsp) return 0; if (handle->cli_active) { rc = hfp_send_calling_line_identification(handle, number, type); if (rc) return rc; } if (handle->telephony->call) return 0; else return hfp_send(handle, "RING"); } int hfp_event_update_call(struct hfp_slc_handle *handle) { return hfp_send_ind_event_report(handle, CALL_IND_INDEX, handle->telephony->call); } int hfp_event_update_callsetup(struct hfp_slc_handle *handle) { return hfp_send_ind_event_report(handle, CALLSETUP_IND_INDEX, handle->telephony->callsetup); } int hfp_event_update_callheld(struct hfp_slc_handle *handle) { return hfp_send_ind_event_report(handle, CALLHELD_IND_INDEX, handle->telephony->callheld); } int hfp_event_set_battery(struct hfp_slc_handle *handle, int level) { handle->battery = level; return hfp_send_ind_event_report(handle, BATTERY_IND_INDEX, level); } int hfp_event_set_signal(struct hfp_slc_handle *handle, int level) { handle->signal = level; return hfp_send_ind_event_report(handle, SIGNAL_IND_INDEX, level); } int hfp_event_set_service(struct hfp_slc_handle *handle, int avail) { /* Convert to 0 or 1. * Since the value must be either 1 or 0. (service presence or not) */ handle->service = !!avail; return hfp_send_ind_event_report(handle, SERVICE_IND_INDEX, avail); }