blob: 28f73edc51f1e17fce89740ce24606eefb182f54 [file] [log] [blame]
/* 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 "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=<vendorID>-<productID>-<version>,<features>
* Parse <features>, 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, ",");
}
if (hfp_slc_get_wideband_speech_supported(handle))
handle->preferred_codec = HFP_CODEC_ID_MSBC;
else
handle->preferred_codec = HFP_CODEC_ID_CVSD;
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=[<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_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: <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, 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:<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, AT_CMD("+COPS: 0"));
if (rc)
return rc;
}
return hfp_send(handle, AT_CMD("OK"));
}
/* The AT+CHLD command is used to control call hold, release, and multiparty
* states.
*/
static int call_hold(struct hfp_slc_handle *handle, const char *buf)
{
int rc;
// Chrome OS doesn't yet support CHLD features but we need to reply
// the query with an empty feature list rather than "ERROR" to increase
// interoperability with certain devices (b/172413440).
if (strlen(buf) > 8 && buf[7] == '=' && buf[8] == '?') {
rc = hfp_send(handle, AT_CMD("+CHLD:"));
if (rc)
return rc;
return hfp_send(handle, AT_CMD("OK"));
}
return hfp_send(handle, AT_CMD("ERROR"));
}
/* 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: <signal>,<service>,<call>,
* <callsetup>,<callheld>,<roam>
*/
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=[[<indrep 1>][,[<indrep 2>][,...[,[<indrep n>]]]]]
* 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: (<a>,<b>,<c>,...,<n>) (Response to AT+BIND=?)
* <a> ... <n>: 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=<a>,<b>,...,<n>(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: <a>,<state> (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.
* <a>: 1 or 2, referencing an HF indicator assigned number.
* <state>: 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= <a>,<b>,...,<n> 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= <assigned number>,<value> (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=<feature> command received, ignore the HF supported feature
* for now. Respond with +BRSF:<feature> 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+XEVENT is defined by Android to support vendor specific features.
* Currently, the only known supported case for CrOS is the battery event sent
* by some Plantronics headsets.
*/
static int vendor_specific_features(struct hfp_slc_handle *handle,
const char *cmd)
{
char *tokens, *event, *level_str, *num_of_level_str;
int level, num_of_level;
tokens = strdup(cmd);
strtok(tokens, "=");
event = strtok(NULL, ",");
if (!event)
goto error_out;
/* AT+XEVENT=BATTERY,Level,NumberOfLevel,MinutesOfTalkTime,IsCharging
* Level: The charge level with a zero-based integer.
* NumberOfLevel: How many charging levels there are.
* MinuteOfTalkTime: The estimated number of talk minutes remaining.
* IsCharging: A 0 or 1 value.
*
* We only support the battery level and thus only care about the first
* 3 arguments.
*/
if (!strncmp(event, "BATTERY", 7)) {
level_str = strtok(NULL, ",");
num_of_level_str = strtok(NULL, ",");
if (!level_str || !num_of_level_str)
goto error_out;
level = atoi(level_str);
num_of_level = atoi(num_of_level_str);
if (level < 0 || num_of_level <= 1 || level >= num_of_level)
goto error_out;
level = (int64_t)level * 100 / (num_of_level - 1);
if (handle->hf_battery != level) {
handle->hf_supports_battery_indicator |=
CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS;
cras_server_metrics_hfp_battery_report(
CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS);
handle->hf_battery = level;
cras_observer_notify_bt_battery_changed(
cras_bt_device_address(handle->device),
(uint32_t)(level));
}
}
free(tokens);
/* For Plantronic headsets, it is required to reply "OK" for the first
* AT+XEVENT=USER-AGENT... command to tell the headset our support of
* the xevent protocol. Otherwise, all following events including
* BATTERY won't be sent.
*/
return hfp_send(handle, AT_CMD("OK"));
error_out:
syslog(LOG_ERR, "%s: malformed vendor specific command: '%s'", __func__,
cmd);
free(tokens);
return hfp_send(handle, AT_CMD("ERROR"));
}
/* 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=<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+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 },
{ "AT+XEVENT", vendor_specific_features },
{ "AT+CHLD", call_hold },
{ 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;
}