diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index b5e84bf..e4f5161 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
#define HFP_HF_FEATURES ( \
HFP_HF_FEAT_ECNR | \
+ HFP_HF_FEAT_3WAY | \
HFP_HF_FEAT_CLIP | \
HFP_HF_FEAT_ENHANCED_CALL_STATUS | \
HFP_HF_FEAT_ESCO_S4_T2 \
uint8_t signal;
bool roaming;
uint8_t battchg;
+ uint8_t chlds;
bool session;
bool clcc_in_progress;
return true;
}
+bool hfp_context_is_container_close(struct hfp_context *context)
+{
+ return context->data[context->offset] == ')';
+}
+
+
bool hfp_context_get_string(struct hfp_context *context, char *buf,
uint8_t len)
{
return (call->status == CALL_STATUS_ACTIVE);
}
+static bool call_waiting_match(const void *data, const void *match_data)
+{
+ const struct hf_call *call = data;
+
+ return (call->status == CALL_STATUS_WAITING);
+}
+
+static bool call_held_match(const void *data, const void *match_data)
+{
+ const struct hf_call *call = data;
+
+ return (call->status == CALL_STATUS_HELD);
+}
+
static void bsir_cb(struct hfp_context *context, void *user_data)
{
struct hfp_hf *hfp = user_data;
hfp->callbacks->update_inband_ring(!!val, hfp->callbacks_data);
}
+static void ccwa_cb(struct hfp_context *context, void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+ char number[255];
+ unsigned int type;
+ struct hf_call *call;
+ uint id;
+
+ DBG(hfp, "");
+
+ if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ send_clcc(hfp);
+ return;
+ }
+
+ if (!hfp_context_get_string(context, number, sizeof(number))) {
+ DBG(hfp, "hf: Could not get string");
+ return;
+ }
+
+ if (!hfp_context_get_number(context, &type))
+ return;
+
+ call = queue_find(hfp->calls, call_waiting_match, NULL);
+ if (call) {
+ DBG(hfp, "hf: waiting call already in progress");
+ return;
+ }
+
+ id = next_call_index(hfp);
+ if (id == 0) {
+ DBG(hfp, "hf: No new call index available");
+ return;
+ }
+ call_new(hfp, id, CALL_STATUS_WAITING, number, type, false);
+}
+
static void ciev_callsetup_cb(uint8_t val, void *user_data)
{
struct hfp_hf *hfp = user_data;
hfp->callbacks_data);
}
-static void clip_resp(enum hfp_result result, enum hfp_error cme_err,
+static void ccwa_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
struct hfp_hf *hfp = user_data;
DBG(hfp, "");
if (result != HFP_RESULT_OK) {
- DBG(hfp, "hf: CLIP error: %d", result);
+ DBG(hfp, "hf: CCWA error: %d", result);
goto failed;
}
hfp->callbacks_data);
}
+static void clip_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ DBG(hfp, "");
+
+ if (result != HFP_RESULT_OK) {
+ DBG(hfp, "hf: CLIP error: %d", result);
+ goto failed;
+ }
+
+ if (!(hfp->features & HFP_AG_FEAT_3WAY)) {
+ /* Jump to next setup state */
+ ccwa_resp(HFP_RESULT_OK, cme_err, user_data);
+ return;
+ }
+
+ if (!hfp_hf_send_command(hfp, ccwa_resp, hfp,
+ "AT+CCWA=1")) {
+ DBG(hfp, "hf: Could not send AT+CCWA=1");
+ result = HFP_RESULT_ERROR;
+ goto failed;
+ }
+
+ return;
+
+failed:
+ if (hfp->callbacks->session_ready)
+ hfp->callbacks->session_ready(result, cme_err,
+ hfp->callbacks_data);
+}
+
static void cops_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
hfp->callbacks_data);
}
-static void slc_cmer_resp(enum hfp_result result, enum hfp_error cme_err,
+static void slc_chld_cb(struct hfp_context *context, void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ if (!hfp_context_open_container(context)) {
+ DBG(hfp, "hf: Could not open container for CHLD");
+ return;
+ }
+
+ while (hfp_context_has_next(context) &&
+ !hfp_context_is_container_close(context)) {
+ char val[3];
+
+ if (!hfp_context_get_unquoted_string(context, val,
+ sizeof(val))) {
+ DBG(hfp, "hf: Could not get string");
+ goto failed;
+ }
+
+ if (strcmp(val, "0") == 0)
+ hfp->chlds |= HFP_CHLD_0;
+ else if (strcmp(val, "1") == 0)
+ hfp->chlds |= HFP_CHLD_1;
+ else if (strcmp(val, "2") == 0)
+ hfp->chlds |= HFP_CHLD_2;
+ else
+ DBG(hfp, "CHLD not supported: %s", val);
+ }
+
+ if (!hfp_context_close_container(context)) {
+ DBG(hfp, "hf: Could not close container");
+ goto failed;
+ }
+
+ return;
+failed:
+ DBG(hfp, "hf: Error on CHLD response");
+}
+
+static void slc_chld_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
struct hfp_hf *hfp = user_data;
DBG(hfp, "");
+ hfp_hf_unregister(hfp, "+CHLD");
+
if (result != HFP_RESULT_OK) {
- DBG(hfp, "hf: CMER error: %d", result);
+ DBG(hfp, "hf: CHLD=? error: %d", result);
goto failed;
}
/* Register unsolicited results handlers */
if (hfp->features & HFP_AG_FEAT_IN_BAND_RING_TONE)
hfp_hf_register(hfp, bsir_cb, "+BSIR", hfp, NULL);
+ if (hfp->features & HFP_AG_FEAT_3WAY)
+ hfp_hf_register(hfp, ccwa_cb, "+CCWA", hfp, NULL);
hfp_hf_register(hfp, ciev_cb, "+CIEV", hfp, NULL);
hfp_hf_register(hfp, clcc_cb, "+CLCC", hfp, NULL);
hfp_hf_register(hfp, clip_cb, "+CLIP", hfp, NULL);
hfp->callbacks_data);
}
+static void slc_cmer_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ DBG(hfp, "");
+
+ if (result != HFP_RESULT_OK) {
+ DBG(hfp, "hf: CMER error: %d", result);
+ goto failed;
+ }
+
+ if (!(hfp->features & HFP_AG_FEAT_3WAY)) {
+ /* Jump to next setup state */
+ slc_chld_resp(HFP_RESULT_OK, cme_err, user_data);
+ return;
+ }
+
+ if (!hfp_hf_register(hfp, slc_chld_cb, "+CHLD", hfp, NULL)) {
+ DBG(hfp, "hf: Could not register +CHLD");
+ result = HFP_RESULT_ERROR;
+ goto failed;
+ }
+
+ if (!hfp_hf_send_command(hfp, slc_chld_resp, hfp, "AT+CHLD=?")) {
+ DBG(hfp, "hf: Could not send AT+CHLD=?");
+ result = HFP_RESULT_ERROR;
+ goto failed;
+ }
+
+ return;
+
+failed:
+ if (hfp->callbacks->session_ready)
+ hfp->callbacks->session_ready(result, cme_err,
+ hfp->callbacks_data);
+}
+
static void slc_cind_status_cb(struct hfp_context *context,
void *user_data)
{
return hfp_hf_send_command(hfp, resp_cb, user_data, "ATD%s;", number);
}
+bool hfp_hf_release_and_accept(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data)
+{
+ if (!hfp)
+ return false;
+
+ DBG(hfp, "");
+
+ if (!(hfp->chlds & HFP_CHLD_1) ||
+ (!queue_find(hfp->calls, call_waiting_match, NULL) &&
+ !queue_find(hfp->calls, call_held_match, NULL)))
+ return false;
+
+ return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+CHLD=1");
+}
+
+bool hfp_hf_swap_calls(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data)
+{
+ if (!hfp)
+ return false;
+
+ DBG(hfp, "");
+
+ if (!(hfp->chlds & HFP_CHLD_2))
+ return false;
+
+ return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+CHLD=2");
+}
+
bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id,
hfp_response_func_t resp_cb,
void *user_data)
if (call_setup_match(call, NULL) || call_active_match(call, NULL)) {
return hfp_hf_send_command(hfp, resp_cb, user_data,
"AT+CHUP");
+ } else if ((call_waiting_match(call, NULL) ||
+ call_held_match(call, NULL)) &&
+ (hfp->chlds & HFP_CHLD_0)) {
+ return hfp_hf_send_command(hfp, resp_cb, user_data,
+ "AT+CHLD=0");
}
return false;
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 6e3d4c2..045e1f7 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
#define HFP_AG_FEAT_ENHANCED_VOICE_RECOGNITION_STATUS 0x00001000
#define HFP_AG_FEAT_VOICE_RECOGNITION_TEXT 0x00002000
+#define HFP_CHLD_0 1 << 0
+#define HFP_CHLD_1 1 << 1
+#define HFP_CHLD_2 1 << 2
+#define HFP_CHLD_3 1 << 3
+#define HFP_CHLD_4 1 << 4
+#define HFP_CHLD_1x 1 << 5
+#define HFP_CHLD_2x 1 << 6
+
enum hfp_result {
HFP_RESULT_OK = 0,
HFP_RESULT_CONNECT = 1,
unsigned int default_val);
bool hfp_context_open_container(struct hfp_context *context);
bool hfp_context_close_container(struct hfp_context *context);
+bool hfp_context_is_container_close(struct hfp_context *context);
bool hfp_context_get_string(struct hfp_context *context, char *buf,
uint8_t len);
bool hfp_context_get_unquoted_string(struct hfp_context *context,
bool hfp_hf_dial(struct hfp_hf *hfp, const char *number,
hfp_response_func_t resp_cb,
void *user_data);
+bool hfp_hf_release_and_accept(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data);
+bool hfp_hf_swap_calls(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data);
bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id,
hfp_response_func_t resp_cb,
void *user_data);
diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index 67a88a1..5252ed3 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
',', '5', '\r', '\n'), \
frg_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
+ raw_pdu('\r', '\n', '+', 'C', 'H', 'L', 'D', ':', '(', '0', \
+ ',', '1', ',', '1', 'x', ',', '2', ',', '2', 'x', \
+ ',', '3', ',', '4', ')', '\r', '\n'), \
+ frg_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
raw_pdu('\r', '\n', '+', 'C', 'O', 'P', 'S', ':', ' ', '0', ',', \
'0', ',', '\"', 'T', 'E', 'S', 'T', '\"', '\r', '\n'), \
frg_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
raw_pdu('\r', '\n', 'O', 'K', '\r', '\n')
static void hf_cmd_complete(enum hfp_result res, enum hfp_error cme_err,