Diff between 5e41d1e1d361e7288964e4c2c5ed90736025662f and e9b4bf2917a33537ab8251349f4bec5d1c69ae8c

Changed Files

File Additions Deletions Status
src/shared/hfp.c +214 -4 modified
src/shared/hfp.h +15 -0 modified
unit/test-hfp.c +5 -0 modified

Full Patch

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
@@ -32,6 +32,7 @@
 
 #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 \
@@ -105,6 +106,7 @@ struct hfp_hf {
 	uint8_t signal;
 	bool roaming;
 	uint8_t battchg;
+	uint8_t chlds;
 
 	bool session;
 	bool clcc_in_progress;
@@ -396,6 +398,12 @@ bool hfp_context_close_container(struct hfp_context *context)
 	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)
 {
@@ -1852,6 +1860,20 @@ static bool call_active_match(const void *data, const void *match_data)
 	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;
@@ -1866,6 +1888,43 @@ static void bsir_cb(struct hfp_context *context, void *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;
@@ -2240,7 +2299,7 @@ failed:
 						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;
@@ -2248,7 +2307,7 @@ static void clip_resp(enum hfp_result result, enum hfp_error cme_err,
 	DBG(hfp, "");
 
 	if (result != HFP_RESULT_OK) {
-		DBG(hfp, "hf: CLIP error: %d", result);
+		DBG(hfp, "hf: CCWA error: %d", result);
 		goto failed;
 	}
 
@@ -2272,6 +2331,39 @@ 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)
 {
@@ -2328,15 +2420,56 @@ failed:
 						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;
 	}
 
@@ -2351,6 +2484,8 @@ static void slc_cmer_resp(enum hfp_result result, enum hfp_error cme_err,
 	/* 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);
@@ -2364,6 +2499,44 @@ failed:
 						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)
 {
@@ -2745,6 +2918,38 @@ bool hfp_hf_dial(struct hfp_hf *hfp, const char *number,
 	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)
@@ -2791,6 +2996,11 @@ bool hfp_hf_call_hangup(struct hfp_hf *hfp, uint id,
 	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
@@ -38,6 +38,14 @@
 #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,
@@ -177,6 +185,7 @@ bool hfp_context_get_number_default(struct hfp_context *context,
 						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,
@@ -242,6 +251,12 @@ const char *hfp_hf_call_get_number(struct hfp_hf *hfp, uint id);
 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
@@ -758,12 +758,17 @@ static void test_hf_robustness(gconstpointer data)
 		',', '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,