/* 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 #include #include "stdbool.h" #include "cras_bt_device.h" #include "cras_bt_log.h" #include "cras_observer.h" #include "cras_telephony.h" #include "cras_hfp_slc.h" #include "cras_server_metrics.h" #include "cras_system_state.h" #include "cras_tm.h" #include "cras_util.h" /* Message start and end with "\r\n". refer to spec 4.33. */ #define AT_CMD(cmd) "\r\n" cmd "\r\n" /* The timeout between event reporting and HF indicator commands */ #define HF_INDICATORS_TIMEOUT_MS 2000 /* The sleep time before reading and processing the following AT commands during * codec connection setup. */ #define CODEC_CONN_SLEEP_TIME_US 2000 #define SLC_BUF_SIZE_BYTES 256 /* Indicator update command response and indicator indices. * Note that indicator index starts from '1', index 0 is used for CRAS to record * if the event report has been enabled or not. */ #define CRAS_INDICATOR_ENABLE_INDEX 0 #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 ROAM_IND_INDEX 7 #define INDICATOR_IND_MAX 8 #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. * 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_reports - Activate statuses of indicator events reporting. * ag_supported_features - Supported AG features bitmap. * hf_supported_features - Bit map of HF supported features. * hf_supports_battery_indicator - Bit map of battery indicator support of * connected HF. * hf_battery - Current battery level of HF reported by the HF. The data * range should be 0 ~ 100. Use -1 for no battery level reported. * preferred_codec - CVSD or mSBC based on the situation and strategy. This * needs not to be equal to selected_codec because codec negotiation * process may fail. * selected_codec - The codec id defaults to HFP_CODEC_UNUSED and changes * only if codec negotiation is supported and the negotiation flow * has completed. * 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 cli_active; int battery; int signal; int service; int callheld; int ind_event_reports[INDICATOR_IND_MAX]; int ag_supported_features; bool hf_codec_supported[HFP_MAX_CODECS]; int hf_supported_features; int hf_supports_battery_indicator; int hf_battery; int preferred_codec; int selected_codec; struct cras_bt_device *device; struct cras_timer *timer; 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; len = strlen(buf); written = 0; while (written < len) { err = write(handle->rfcomm_fd, buf + written, len - written); if (err < 0) return -errno; written += err; } 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_reports[CRAS_INDICATOR_ENABLE_INDEX] || !handle->ind_event_reports[ind_index]) return 0; snprintf(cmd, 64, AT_CMD("+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, AT_CMD("+CCWA: \"%s\",%d"), number, type); } else { snprintf(cmd, 64, AT_CMD("+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, AT_CMD("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, AT_CMD("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) { if (strlen(cmd) < 9) { syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd); return hfp_send(handle, AT_CMD("ERROR")); } handle->cli_active = (cmd[8] == '1'); return hfp_send(handle, AT_CMD("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_len < 4) goto error_out; 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, AT_CMD("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, AT_CMD("OK")); if (rc) return rc; handle->telephony->callsetup = 2; return hfp_send_ind_event_report(handle, CALLSETUP_IND_INDEX, 2); error_out: syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd); return hfp_send(handle, AT_CMD("ERROR")); } /* 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, AT_CMD("OK")); } /* Sends +BCS command to tell HF about our preferred codec. This shall * be called only if codec negotiation is supported. */ static void select_preferred_codec(struct hfp_slc_handle *handle) { char buf[64]; snprintf(buf, 64, AT_CMD("+BCS:%d"), handle->preferred_codec); hfp_send(handle, buf); BTLOG(btlog, BT_CODEC_SELECTION, 0, handle->preferred_codec); } /* Marks SLC handle as initialized and trigger HFP AG's init_cb. */ static void initialize_slc_handle(struct cras_timer *timer, void *arg) { struct hfp_slc_handle *handle = (struct hfp_slc_handle *)arg; if (timer) handle->timer = NULL; if (handle->init_cb) { handle->init_cb(handle); handle->init_cb = NULL; } } /* Handles the event that headset request to start a codec connection * procedure. */ static int bluetooth_codec_connection(struct hfp_slc_handle *handle, const char *cmd) { /* Reset current selected codec to force a new codec connection * procedure when the next hfp_slc_codec_connection_setup is called. */ handle->selected_codec = HFP_CODEC_UNUSED; return hfp_send(handle, AT_CMD("OK")); } /* Handles the event that headset request to select specific codec. */ static int bluetooth_codec_selection(struct hfp_slc_handle *handle, const char *cmd) { char *tokens = strdup(cmd); char *codec; int id, err; strtok(tokens, "="); codec = strtok(NULL, ","); if (!codec) goto bcs_cmd_cleanup; id = atoi(codec); if ((id <= HFP_CODEC_UNUSED) || (id >= HFP_MAX_CODECS)) { syslog(LOG_ERR, "%s: invalid codec id: '%s'", __func__, cmd); free(tokens); return hfp_send(handle, AT_CMD("ERROR")); } if (id != handle->preferred_codec) syslog(LOG_WARNING, "%s: inconsistent codec id: '%s'", __func__, cmd); BTLOG(btlog, BT_CODEC_SELECTION, 1, id); handle->selected_codec = id; bcs_cmd_cleanup: free(tokens); err = hfp_send(handle, AT_CMD("OK")); return err; } /* * AT+IPHONEACCEV command from HF to report state change.You can find details * of this command in the Accessory Design Guidelines for Apple Devices R11 * section 16.1. */ static int apple_accessory_state_change(struct hfp_slc_handle *handle, const char *cmd) { char *tokens, *num, *key, *val; int i, level; /* AT+IPHONEACCEV=Number of key/value pairs,key1,val1,key2,val2,... * Number of key/value pairs: The number of parameters coming next. * key: the type of change being reported: * 1 = Battery Level * 2 = Dock State * val: the value of the change: * Battery Level: string value between '0' and '9' * Dock State: 0 = undocked, 1 = docked */ tokens = strdup(cmd); strtok(tokens, "="); num = strtok(NULL, ","); if (!num) { free(tokens); return hfp_send(handle, AT_CMD("ERROR")); } for (i = 0; i < atoi(num); i++) { key = strtok(NULL, ","); val = strtok(NULL, ","); if (!key || !val) { syslog(LOG_WARNING, "IPHONEACCEV: Expected %d kv pairs but got %d", atoi(num), i); break; } if (atoi(key) == 1) { level = atoi(val); if (level >= 0 && level < 10) { cras_server_metrics_hfp_battery_report( CRAS_HFP_BATTERY_INDICATOR_APPLE); level = (level + 1) * 10; if (handle->hf_battery != level) { handle->hf_battery = level; cras_observer_notify_bt_battery_changed( cras_bt_device_address( handle->device), (uint32_t)(level)); } } else { syslog(LOG_ERR, "Get invalid battery status from cmd:%s", cmd); } } } free(tokens); return hfp_send(handle, AT_CMD("OK")); } /* * AT+XAPL command from HF to enable Apple custom features. You can find details * of it in the Accessory Design Guidelines for Apple Devices R11 section 15.1. */ static int apple_supported_features(struct hfp_slc_handle *handle, const char *cmd) { char *tokens, *features; int apple_features, err; char buf[64]; /* AT+XAPL=--, * Parse , the only token we care about. */ tokens = strdup(cmd); strtok(tokens, "="); strtok(NULL, ","); features = strtok(NULL, ","); if (!features) goto error_out; apple_features = atoi(features); if (apple_features & APL_BATTERY) handle->hf_supports_battery_indicator |= CRAS_HFP_BATTERY_INDICATOR_APPLE; snprintf(buf, 64, AT_CMD("+XAPL=iPhone,%d"), CRAS_APL_SUPPORTED_FEATURES); err = hfp_send(handle, buf); if (err) goto error_out; err = hfp_send(handle, AT_CMD("OK")); free(tokens); return err; error_out: syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd); free(tokens); return hfp_send(handle, AT_CMD("ERROR")); } /* Handles the event when headset reports its available codecs list. */ static int available_codecs(struct hfp_slc_handle *handle, const char *cmd) { char *tokens, *id_str; int id; for (id = 0; id < HFP_MAX_CODECS; id++) handle->hf_codec_supported[id] = false; tokens = strdup(cmd); strtok(tokens, "="); id_str = strtok(NULL, ","); while (id_str) { id = atoi(id_str); if ((id > HFP_CODEC_UNUSED) && (id < HFP_MAX_CODECS)) { handle->hf_codec_supported[id] = true; BTLOG(btlog, BT_AVAILABLE_CODECS, 0, id); } id_str = strtok(NULL, ","); } for (id = HFP_MAX_CODECS - 1; id > 0; id--) { if (handle->hf_codec_supported[id]) { handle->preferred_codec = id; break; } } free(tokens); return hfp_send(handle, AT_CMD("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=[[,[,[, [,]]]]] * Parse , 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_done; } if (atoi(mode) == FORWARD_UNSOLICIT_RESULT_CODE) handle->ind_event_reports[CRAS_INDICATOR_ENABLE_INDEX] = atoi(tmp); err = hfp_send(handle, AT_CMD("OK")); if (err) { syslog(LOG_ERR, "Error sending response for command %s", cmd); goto event_reporting_done; } /* * Wait for HF to retrieve information about HF indicators and consider * the Service Level Connection to be fully initialized, and thereby * established, if HF doesn't support HF indicators. */ if (hfp_slc_get_hf_hf_indicators_supported(handle)) handle->timer = cras_tm_create_timer(cras_system_state_get_tm(), HF_INDICATORS_TIMEOUT_MS, initialize_slc_handle, handle); /* * Otherwise, regard the Service Level Connection to be fully * initialized and ready for the potential codec negotiation. */ else initialize_slc_handle(NULL, (void *)handle); event_reporting_done: 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, AT_CMD("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, AT_CMD("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, AT_CMD("ERROR")); rc = hfp_send(handle, AT_CMD("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: ,,,, */ 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, AT_CMD("+CLCC: %d,1,1,0,0"), idx++); rc = hfp_send(handle, buf); if (rc) return rc; } if (handle->telephony->call) { snprintf(buf, 64, AT_CMD("+CLCC: %d,1,0,0,0"), idx++); rc = hfp_send(handle, buf); if (rc) return rc; } return hfp_send(handle, AT_CMD("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:,,, where * the mode=0 means automatic for network selection. If no * operator is selected, and are omitted. */ rc = hfp_send(handle, AT_CMD("+COPS: 0")); if (rc) return rc; } return hfp_send(handle, AT_CMD("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 (strlen(cmd) < 8) { syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd); return hfp_send(handle, AT_CMD("ERROR")); } if (cmd[7] == '=') { /* Indicator update test command "AT+CIND=?" */ err = hfp_send(handle, AT_CMD(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: ,,, * ,, */ snprintf(buf, 64, AT_CMD("+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, AT_CMD("OK")); } /* AT+BIA command to change the subset of indicators that shall be * sent by the AG. */ static int indicator_activation(struct hfp_slc_handle *handle, const char *cmd) { char *ptr; int idx = BATTERY_IND_INDEX; /* AT+BIA=[[][,[][,...[,[]]]]] * According to the spec: * - The indicator state can be omitted and the current reporting * states of the indicator shall not change. * Ex: AT+BIA=,1,,0 * Only the 2nd and 4th indicators may be affected. * - HF can provide fewer indicators than AG and states not provided * shall not change. * Ex: CRAS supports 7 indicators and gets AT+BIA=1,0,1 * Only the first three indicators may be affected. * - Call, Call Setup and Held Call are mandatory and should be always * on no matter what state HF set. */ ptr = strchr(cmd, '='); while (ptr && idx < INDICATOR_IND_MAX) { if (idx != CALL_IND_INDEX && idx != CALLSETUP_IND_INDEX && idx != CALLHELD_IND_INDEX) { if (*(ptr + 1) == '1') handle->ind_event_reports[idx] = 1; else if (*(ptr + 1) == '0') handle->ind_event_reports[idx] = 0; } ptr = strchr(ptr + 1, ','); idx++; } return hfp_send(handle, AT_CMD("OK")); } /* AT+BIND command to report, query and activate Generic Status Indicators. * It is sent by the HF if both AG and HF support the HF indicator feature. */ static int indicator_support(struct hfp_slc_handle *handle, const char *cmd) { char *tokens, *key; int err, cmd_len; cmd_len = strlen(cmd); if (cmd_len < 8) goto error_out; if (cmd[7] == '=') { /* AT+BIND=? (Read AG supported indicators) */ if (cmd_len > 8 && cmd[8] == '?') { /* +BIND: (,,,...,) (Response to AT+BIND=?) * ... : 0-65535, entered as decimal unsigned * integer values without leading zeros, referencing an * HF indicator assigned number. * 1 is for Enhanced Driver Status. * 2 is for Battery Level. * For the list of HF indicator assigned number, you can * check the Bluetooth SIG Assigned Numbers web page. */ BTLOG(btlog, BT_HFP_HF_INDICATOR, 1, 0); /* "2" is for HF Battery Level that we support. We don't * support "1" but this is a workaround for Pixel Buds 2 * which expects this exact combination for battery * reporting (HFP 1.7 standard) to work. This workaround * is fine since we don't enable Safety Drive with * +BIND: 1,1 (b/172680041). */ err = hfp_send(handle, AT_CMD("+BIND: (1,2)")); if (err < 0) return err; } /* AT+BIND=,,...,(List HF supported indicators) */ else { tokens = strdup(cmd); strtok(tokens, "="); key = strtok(NULL, ","); while (key != NULL) { if (atoi(key) == 2) handle->hf_supports_battery_indicator |= CRAS_HFP_BATTERY_INDICATOR_HFP; key = strtok(NULL, ","); } free(tokens); } } /* AT+BIND? (Read AG enabled/disabled status of indicators) */ else if (cmd[7] == '?') { /* +BIND: , (Unsolicited or Response to AT+BIND?) * This response enables the AG to notify the HF which HF * indicators are supported and their state, enabled or * disabled. * : 1 or 2, referencing an HF indicator assigned number. * : 0-1, entered as integer values, where * 0 = disabled, no value changes shall be sent for this * indicator * 1 = enabled, value changes may be sent for this indicator */ /* We don't support Enhanced Driver Status, so explicitly * disable it (b/172680041). */ err = hfp_send(handle, AT_CMD("+BIND: 1,0")); if (err < 0) return err; BTLOG(btlog, BT_HFP_HF_INDICATOR, 0, 0); err = hfp_send(handle, AT_CMD("+BIND: 2,1")); if (err < 0) return err; err = hfp_send(handle, AT_CMD("OK")); if (err) return err; /* * Consider the Service Level Connection to be fully initialized * and thereby established, after successfully responded with OK */ initialize_slc_handle(NULL, (void *)handle); return 0; } else { goto error_out; } /* This OK reply is required after both +BIND AT commands. It also * covers the AT+BIND= ,,..., case. */ return hfp_send(handle, AT_CMD("OK")); error_out: syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd); return hfp_send(handle, AT_CMD("ERROR")); } /* AT+BIEV command reports updated values of enabled HF indicators to the AG. */ static int indicator_state_change(struct hfp_slc_handle *handle, const char *cmd) { char *tokens, *key, *val; int level; /* AT+BIEV= , (Update value of indicator) * CRAS only supports battery level, which is with assigned number 2. * Battery level should range from 0 to 100 defined by the spec. */ tokens = strdup(cmd); strtok(tokens, "="); key = strtok(NULL, ","); if (!key) goto error_out; if (atoi(key) == 2) { val = strtok(NULL, ","); if (!val) goto error_out; level = atoi(val); if (level >= 0 && level <= 100) { cras_server_metrics_hfp_battery_report( CRAS_HFP_BATTERY_INDICATOR_HFP); if (handle->hf_battery != level) { handle->hf_battery = level; cras_observer_notify_bt_battery_changed( cras_bt_device_address(handle->device), (uint32_t)(level)); } } else { syslog(LOG_ERR, "Get invalid battery status from cmd:%s", cmd); } } else { goto error_out; } free(tokens); return hfp_send(handle, AT_CMD("OK")); error_out: syslog(LOG_WARNING, "%s: invalid command: '%s'", __func__, cmd); free(tokens); return hfp_send(handle, AT_CMD("ERROR")); } /* 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, "%s: malformed command: '%s'", __func__, cmd); return hfp_send(handle, AT_CMD("ERROR")); } /* Map 0 to the smallest non-zero scale 6/100, and 15 to * 100/100 full. */ if (cmd[5] == 'S') { gain = atoi(&cmd[7]); if (gain < 0 || gain > 15) { syslog(LOG_ERR, "signal_gain_setting: gain %d is not between 0 and 15", gain); return hfp_send(handle, AT_CMD("ERROR")); } BTLOG(btlog, BT_HFP_UPDATE_SPEAKER_GAIN, gain, 0); cras_bt_device_update_hardware_volume(handle->device, (gain + 1) * 100 / 16); } return hfp_send(handle, AT_CMD("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, AT_CMD("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]; char *tokens, *features; if (strlen(cmd) < 9) { syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd); return hfp_send(handle, AT_CMD("ERROR")); } tokens = strdup(cmd); strtok(tokens, "="); features = strtok(NULL, ","); if (!features) goto error_out; handle->hf_supported_features = atoi(features); BTLOG(btlog, BT_HFP_SUPPORTED_FEATURES, 0, handle->hf_supported_features); free(tokens); /* AT+BRSF= command received, ignore the HF supported feature * for now. Respond with +BRSF: to notify mandatory supported * features in AG(audio gateway). */ BTLOG(btlog, BT_HFP_SUPPORTED_FEATURES, 1, handle->ag_supported_features); snprintf(response, 128, AT_CMD("+BRSF: %u"), handle->ag_supported_features); err = hfp_send(handle, response); if (err < 0) return err; return hfp_send(handle, AT_CMD("OK")); error_out: free(tokens); syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd); return hfp_send(handle, AT_CMD("ERROR")); } 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; BTLOG(btlog, BT_HFP_SET_SPEAKER_GAIN, gain, 0); snprintf(command, 128, AT_CMD("+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, AT_CMD("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 meaningless "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= --> * <-- +BRSF: * <-- 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+BAC", available_codecs }, { "AT+BCC", bluetooth_codec_connection }, { "AT+BCS", bluetooth_codec_selection }, { "AT+BIA", indicator_activation }, { "AT+BIEV", indicator_state_change }, { "AT+BIND", indicator_support }, { "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+IPHONEACCEV", apple_accessory_state_change }, { "AT+VG", signal_gain_setting }, { "AT+VTS", dtmf_tone }, { "AT+XAPL", apple_supported_features }, { 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_DEBUG, "AT command %s not supported", cmd); return hfp_send(slc_handle, AT_CMD("ERROR")); } int handle_at_command_for_test(struct hfp_slc_handle *slc_handle, const char *cmd) { return handle_at_command(slc_handle, cmd); } static int process_at_commands(struct hfp_slc_handle *handle) { 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) return bytes_read; 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 0; /* 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 bytes_read; } static void slc_watch_callback(void *arg, int revents) { struct hfp_slc_handle *handle = (struct hfp_slc_handle *)arg; int err; err = process_at_commands(handle); if (err < 0) { syslog(LOG_ERR, "Error reading slc command %s", strerror(errno)); cras_system_rm_select_fd(handle->rfcomm_fd); handle->disconnect_cb(handle); } return; } /* Exported interfaces */ struct hfp_slc_handle *hfp_slc_create(int fd, int is_hsp, int ag_supported_features, struct cras_bt_device *device, hfp_slc_init_cb init_cb, hfp_slc_disconnect_cb disconnect_cb) { struct hfp_slc_handle *handle; int i; if (!disconnect_cb) return NULL; handle = (struct hfp_slc_handle *)calloc(1, sizeof(*handle)); if (!handle) return NULL; handle->rfcomm_fd = fd; handle->is_hsp = is_hsp; handle->ag_supported_features = ag_supported_features; handle->hf_supported_features = 0; 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_reports[CRAS_INDICATOR_ENABLE_INDEX] = 0; for (i = BATTERY_IND_INDEX; i < INDICATOR_IND_MAX; i++) handle->ind_event_reports[i] = 1; handle->telephony = cras_telephony_get(); handle->preferred_codec = HFP_CODEC_ID_CVSD; handle->selected_codec = HFP_CODEC_UNUSED; handle->hf_supports_battery_indicator = CRAS_HFP_BATTERY_INDICATOR_NONE; handle->hf_battery = -1; cras_system_add_select_fd(handle->rfcomm_fd, slc_watch_callback, handle, POLLIN | POLLERR | POLLHUP); return handle; } void hfp_slc_destroy(struct hfp_slc_handle *slc_handle) { cras_system_rm_select_fd(slc_handle->rfcomm_fd); if (slc_handle->timer) cras_tm_cancel_timer(cras_system_state_get_tm(), slc_handle->timer); close(slc_handle->rfcomm_fd); free(slc_handle); } int hfp_slc_is_hsp(struct hfp_slc_handle *handle) { return handle->is_hsp; } int hfp_slc_get_selected_codec(struct hfp_slc_handle *handle) { /* If codec negotiation is not supported on HF, or the negotiation * process never completed. Fallback to the preffered codec. */ if (handle->selected_codec == HFP_CODEC_UNUSED) return handle->preferred_codec; else return handle->selected_codec; } int hfp_slc_codec_connection_setup(struct hfp_slc_handle *handle) { /* The time we wait for codec selection response. */ static struct timespec timeout = { 0, 100000000 }; struct pollfd poll_fd; int rc = 0; struct timespec ts = timeout; /* * Codec negotiation is not required, if either AG or HF doesn't support * it or it has been done once. */ if (!hfp_slc_get_hf_codec_negotiation_supported(handle) || !hfp_slc_get_ag_codec_negotiation_supported(handle) || handle->selected_codec == handle->preferred_codec) return 0; redo_codec_conn: select_preferred_codec(handle); poll_fd.fd = handle->rfcomm_fd; poll_fd.events = POLLIN; ts = timeout; while (rc <= 0) { rc = cras_poll(&poll_fd, 1, &ts, NULL); if (rc == -ETIMEDOUT) { /* * Catch the case that the first initial codec * negotiation timeout. At this point we're not sure * if HF is good with the preferred codec from AG. * Fallback to CVSD doesn't help because very likely * HF won't reply that either. The best thing we can * do is just leave a warning log. */ if (handle->selected_codec == HFP_CODEC_UNUSED) { syslog(LOG_WARNING, "Proceed using codec %d without HF reply", handle->preferred_codec); } return rc; } } if (rc > 0) { do { usleep(CODEC_CONN_SLEEP_TIME_US); rc = process_at_commands(handle); } while (rc == -EAGAIN); if (rc <= 0) return rc; if (handle->selected_codec != handle->preferred_codec) goto redo_codec_conn; } return 0; } 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, AT_CMD("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); } int hfp_slc_get_ag_codec_negotiation_supported(struct hfp_slc_handle *handle) { return handle->ag_supported_features & AG_CODEC_NEGOTIATION; } int hfp_slc_get_hf_codec_negotiation_supported(struct hfp_slc_handle *handle) { return handle->hf_supported_features & HF_CODEC_NEGOTIATION; } int hfp_slc_get_hf_hf_indicators_supported(struct hfp_slc_handle *handle) { return handle->hf_supported_features & HF_HF_INDICATORS; } bool hfp_slc_get_wideband_speech_supported(struct hfp_slc_handle *handle) { return hfp_slc_get_ag_codec_negotiation_supported(handle) && hfp_slc_get_hf_codec_negotiation_supported(handle) && handle->hf_codec_supported[HFP_CODEC_ID_MSBC]; } int hfp_slc_get_hf_supports_battery_indicator(struct hfp_slc_handle *handle) { return handle->hf_supports_battery_indicator; } int hfp_slc_get_hf_battery_level(struct hfp_slc_handle *handle) { return handle->hf_battery; }