[PATCH 1/3] Add multi calls support for HFP voicecall driver

Zhenhua Zhang zhenhua.zhang at intel.com
Fri Nov 6 23:25:48 PST 2009


These 3 patches are new multi calls and multiparty call features
support for HFP voicecall driver. So I seperate them with previous
small patches.

I divide big chunk into 3 parts. The first one is multi calls support.
The second is multiparty call support and the latest is send_dtmf
and list_calls implementation.

My test device is iPhone 3G and LG KM900. If you meet problems with
the patch, please paste your log while turning on OFONO_AT_DEBUG and
let me know. Thanks.


This feature is to enable CHLD=0, 1 and 2. We have considered
following issues:

1. Some phones shift call index when a smaller index call is
released. So we use phone number to compare status in clcc_poll_cb.

2. Some phones fail to response after CHLD=0, 1, 2. Need to poll
status after 2 seconds interval. But if we have another dialing
call, we need cancel +CLCC poll in atd_cb because the new call
is not avaiable until callsetup=3 at AG side.

3. In release_specific, if user want to release a hold call, we
first swap the call and then release it.

4. When only one call left in multi calls, we reset ciev_call and
ciev_callsetup status to avoid wrong +CIEV call/callsetup indicator
from previous released call.
---
 drivers/hfpmodem/voicecall.c |  352
+++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 345 insertions(+), 7 deletions(-)

diff --git a/drivers/hfpmodem/voicecall.c b/drivers/hfpmodem/voicecall.c
index b1d144b..43b4c01 100644
--- a/drivers/hfpmodem/voicecall.c
+++ b/drivers/hfpmodem/voicecall.c
@@ -47,6 +47,8 @@
 #define AG_CHLD_3 0x20
 #define AG_CHLD_4 0x40
 
+#define POLL_CLCC_INTERVAL 2000
+
 static const char *none_prefix[] = { NULL };
 static const char *chld_prefix[] = { "+CHLD:", NULL };
 static const char *clcc_prefix[] = { "+CLCC:", NULL };
@@ -61,6 +63,7 @@ struct voicecall_data {
 	int cind_val[HFP_INDICATOR_LAST];
 	unsigned int id_list;
 	unsigned int local_release;
+	unsigned int clcc_source;
 };
 
 struct release_id_req {
@@ -252,6 +255,13 @@ static void atd_cb(gboolean ok, GAtResult *result,
gpointer user_data)
 	if (!ok)
 		goto out;
 
+	/* Disable multiparty call if phone does not support +CLCC */
+	if (vd->call && !(vd->ag_features & AG_FEATURE_ENHANCED_CALL_STATUS))
{
+		ofono_error("Phone does NOT support enhanced "
+				" call status feature, fail to create call");
+		goto out;
+	}
+
 	call = create_call(vd, 0, 0, CALL_STATUS_DIALING,
 					ph.number, ph.type, 0);
 
@@ -276,6 +286,14 @@ static void hfp_dial(struct ofono_voicecall *vc,
 	struct atd_cb_data *cbd;
 	char buf[256];
 
+	/* Remove +CLCC request if we swap the calls before. See comments in
+	 * hfp_hold_all_active.
+	 */
+	if (vd->clcc_source) {
+		g_source_remove(vd->clcc_source);
+		vd->clcc_source = 0;
+	}
+
 	cbd = g_try_new0(struct atd_cb_data, 1);
 
 	if (!cbd)
@@ -348,6 +366,253 @@ static void hfp_hangup(struct ofono_voicecall *vc,
 	hfp_template("AT+CHUP", vc, generic_cb, 0x3f, cb, data);
 }
 
+static void reset_to_single_call(struct voicecall_data *vd)
+{
+	struct ofono_call *oc = vd->calls->data;
+
+	vd->call = oc;
+	vd->cind_val[HFP_INDICATOR_CALL] = 1;
+
+	if (oc->direction == 1)
+		vd->cind_val[HFP_INDICATOR_CALLSETUP] = 1;
+	else
+		vd->cind_val[HFP_INDICATOR_CALLSETUP] = 3;
+}
+
+static void clcc_poll_cb(gboolean ok, GAtResult *result, gpointer
user_data)
+{
+	struct ofono_voicecall *vc = user_data;
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	GSList *n;
+	GSList *c;
+	GSList *o = vd->calls;
+	struct ofono_call *nc, *oc;
+	struct ofono_error error;
+
+	dump_response("clcc_poll_cb", ok, result);
+	decode_at_error(&error, g_at_result_final_response(result));
+
+	if (!ok)
+		return;
+
+	n = parse_clcc(result);
+
+	while (o) {
+		oc = o->data;
+
+		/* Call index may be shift when another call is released.
+		 * So we can only find call by phone number.
+		 */
+		c = g_slist_find_custom(n, &oc->phone_number,
+					at_util_call_compare_by_phone_number);
+
+		if (c) {
+			nc = c->data;
+
+			if (memcmp(nc, oc, sizeof(struct ofono_call))) {
+				oc->status = nc->status;
+
+				/* If phone does not support multiparty call,
+				 * there will have only one active call at one
+				 * time. +CHUP only hang up current active call.
+				 */
+				if (oc->status == CALL_STATUS_ACTIVE)
+					vd->call = oc;
+
+				ofono_voicecall_notify(vc, oc);
+			}
+
+			o = o->next;
+		} else {
+			release_call(vc, oc);
+			o = vd->calls;
+		}
+	}
+
+	g_slist_foreach(n, (GFunc) g_free, NULL);
+	g_slist_free(n);
+
+	/* Reset ciev_call and ciev_callsetup based on call info when only one
+	 * call is left
+	 */
+	if (g_slist_length(vd->calls) == 1)
+		reset_to_single_call(vd);
+}
+
+static gboolean poll_clcc(gpointer user_data)
+{
+	struct ofono_voicecall *vc = user_data;
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+
+	if (vd->ag_features & AG_FEATURE_ENHANCED_CALL_STATUS) {
+		g_at_chat_send(vd->chat, "AT+CLCC", clcc_prefix,
+					clcc_poll_cb, vc, NULL);
+
+		vd->clcc_source = 0;
+	}
+
+	return FALSE;
+}
+
+static gboolean check_mpty_feature(struct ofono_voicecall *vc,
+				unsigned int feature,
+				ofono_voicecall_cb_t cb, void *data)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+
+	if (vd->ag_mpty_features & feature)
+		return TRUE;
+
+	CALLBACK_WITH_FAILURE(cb, data);
+	return FALSE;
+}
+
+static void hfp_hold_all_active(struct ofono_voicecall *vc,
+				ofono_voicecall_cb_t cb, void *data)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+
+	if (check_mpty_feature(vc, AG_CHLD_2, cb, data)) {
+		hfp_template("AT+CHLD=2", vc, generic_cb, 0, cb, data);
+
+		/* Some phones fail to send response back after CHLD=2.
+		 * But if we have another dialing out call, we should hold
+		 * +CLCC until AG creates the new call.
+		 */
+		vd->clcc_source = g_timeout_add(POLL_CLCC_INTERVAL,
+						poll_clcc, vc);
+	}
+}
+
+static void hfp_release_all_held(struct ofono_voicecall *vc,
+				ofono_voicecall_cb_t cb, void *data)
+{
+	if (check_mpty_feature(vc, AG_CHLD_0, cb, data))
+		hfp_template("AT+CHLD=0", vc, generic_cb, 0x10, cb, data);
+}
+
+static void release_id_cb(gboolean ok, GAtResult *result,
+				gpointer user_data)
+{
+	struct release_id_req *req = user_data;
+	struct voicecall_data *vd = ofono_voicecall_get_data(req->vc);
+	struct ofono_error error;
+
+	dump_response("release_id_cb", ok, result);
+	decode_at_error(&error, g_at_result_final_response(result));
+
+	if (ok)
+		vd->local_release |= (1 << req->id);
+
+	req->cb(&error, req->data);
+
+	/* Some phones fail to send response back after CHLD=1x */
+	vd->clcc_source = g_timeout_add(POLL_CLCC_INTERVAL, poll_clcc,
req->vc);
+}
+
+static void hfp_set_udub(struct ofono_voicecall *vc,
+				ofono_voicecall_cb_t cb, void *data)
+{
+	unsigned int incoming_or_waiting = (1 << 4) | (1 << 5);
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	struct ofono_call *call = vd->call;
+	struct release_id_req *req;
+
+	if (vd->ag_mpty_features & AG_CHLD_0)
+		hfp_template("AT+CHLD=0", vc, generic_cb, incoming_or_waiting,
+					cb, data);
+	else {
+		req = g_try_new0(struct release_id_req, 1);
+
+		if (!req || !call) {
+			CALLBACK_WITH_FAILURE(cb, data);
+			return;
+		}
+
+		req->vc = vc;
+		req->cb = cb;
+		req->data = data;
+		req->id = call->id;
+
+		g_at_chat_send(vd->chat, "AT+CHUP", none_prefix,
+					release_id_cb, req, g_free);
+	}
+}
+
+static void hfp_release_all_active(struct ofono_voicecall *vc,
+					ofono_voicecall_cb_t cb, void *data)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+
+	if (check_mpty_feature(vc, AG_CHLD_1, cb, data)) {
+		hfp_template("AT+CHLD=1", vc, generic_cb, 0x1, cb, data);
+
+		vd->clcc_source = g_timeout_add(POLL_CLCC_INTERVAL, poll_clcc,
+						vc);
+	}
+}
+
+static gboolean release_hold_cb(gpointer user_data)
+{
+	struct release_id_req *req = user_data;
+	struct voicecall_data *vd = ofono_voicecall_get_data(req->vc);
+
+	vd->local_release |= (1 << req->id);
+
+	hfp_release_all_active(req->vc, req->cb, req->data);
+
+	g_free(req);
+
+	return FALSE;
+}
+
+static void swap_hold_cb(gboolean ok, GAtResult *result, gpointer
user_data)
+{
+	struct release_id_req *req = user_data;
+	struct ofono_error error;
+
+	dump_response("generic_cb", ok, result);
+	decode_at_error(&error, g_at_result_final_response(result));
+
+	if (!ok) {
+		CALLBACK_WITH_FAILURE(req->cb, req->data);
+		return;
+	}
+
+	g_timeout_add(POLL_CLCC_INTERVAL, release_hold_cb, req);
+}
+
+static void hfp_release_specific(struct ofono_voicecall *vc, int id,
+				ofono_voicecall_cb_t cb, void *data)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	struct release_id_req *req = g_try_new0(struct release_id_req, 1);
+	char buf[32];
+
+	if (!req) {
+		CALLBACK_WITH_FAILURE(cb, data);
+		return;
+	}
+
+	req->vc = vc;
+	req->cb = cb;
+	req->data = data;
+	req->id = id;
+
+	if (vd->ag_features & AG_FEATURE_ENHANCED_CALL_CONTROL) {
+		sprintf(buf, "AT+CHLD=1%d", id);
+
+		g_at_chat_send(vd->chat, buf, none_prefix,
+					release_id_cb, req, g_free);
+	} else if (id == (int) vd->call->id)
+		g_at_chat_send(vd->chat, "AT+CHUP", none_prefix,
+					release_id_cb, req, g_free);
+	else
+		/* If the call is held, swap and then release it */
+		g_at_chat_send(vd->chat, "AT+CHLD=2", none_prefix,
+					swap_hold_cb, req, NULL);
+}
+
 static void ring_notify(GAtResult *result, gpointer user_data)
 {
 	struct ofono_voicecall *vc = user_data;
@@ -439,7 +704,10 @@ static void ciev_call_notify(struct ofono_voicecall
*vc,
 
 	switch (value) {
 	case 0:
-		release_call(vc, call);
+		if (g_slist_length(vd->calls) > 1 && !vd->clcc_source)
+			poll_clcc(vc);
+		else
+			release_call(vc, call);
 		break;
 	case 1:
 		call->status = CALL_STATUS_ACTIVE;
@@ -515,7 +783,9 @@ static void ciev_callsetup_notify(struct
ofono_voicecall *vc,
 		/* call=0 and callsetup=1: reject an incoming call
 		 * call=0 and callsetup=2,3: interrupt an outgoing call
 		 */
-		if ((ciev_call == 0) && (ciev_callsetup > 0))
+		if (g_slist_length(vd->calls) > 1 && !vd->clcc_source)
+			poll_clcc(vc);
+		else if ((ciev_call == 0) && (ciev_callsetup > 0))
 			release_call(vc, call);
 		break;
 	case 1:
@@ -538,6 +808,7 @@ static void ciev_callsetup_notify(struct
ofono_voicecall *vc,
 	case 3:
 		call->status = CALL_STATUS_ALERTING;
 		ofono_voicecall_notify(vc, call);
+		break;
 	default:
 		break;
 	}
@@ -545,6 +816,18 @@ static void ciev_callsetup_notify(struct
ofono_voicecall *vc,
 	vd->cind_val[HFP_INDICATOR_CALLSETUP] = value;
 }
 
+static void ciev_callheld_notify(struct ofono_voicecall *vc,
+				struct ofono_call *call,
+				unsigned int value)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+
+	if (!vd->clcc_source)
+		poll_clcc(vc);
+
+	vd->cind_val[HFP_INDICATOR_CALLHELD] = value;
+}
+
 static void ciev_notify(GAtResult *result, gpointer user_data)
 {
 	struct ofono_voicecall *vc = user_data;
@@ -569,6 +852,57 @@ static void ciev_notify(GAtResult *result, gpointer
user_data)
 		ciev_call_notify(vc, call, value);
 	else if (index == vd->cind_pos[HFP_INDICATOR_CALLSETUP])
 		ciev_callsetup_notify(vc, call, value);
+	else if (index == vd->cind_pos[HFP_INDICATOR_CALLHELD])
+		ciev_callheld_notify(vc, call, value);
+}
+
+static void ccwa_notify(GAtResult *result, gpointer user_data)
+{
+	struct ofono_voicecall *vc = user_data;
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	GAtResultIter iter;
+	const char *num;
+	int type;
+	struct ofono_call *call;
+
+	dump_response("ccwa_notify", TRUE, result);
+
+	/* CCWA can be repeat, ignore if we already have a waiting call */
+	if (g_slist_find_custom(vd->calls,
+				GINT_TO_POINTER(CALL_STATUS_WAITING),
+				at_util_call_compare_by_status))
+		return;
+
+	g_at_result_iter_init(&iter, result);
+
+	if (!g_at_result_iter_next(&iter, "+CCWA:"))
+		return;
+
+	if (!g_at_result_iter_next_string(&iter, &num))
+		return;
+
+	if (!g_at_result_iter_next_number(&iter, &type))
+		return;
+
+	/* Skip class field */
+	g_at_result_iter_skip_next(&iter);
+
+	/* Skip alpha field */
+	g_at_result_iter_skip_next(&iter);
+
+	/* Skip validity field */
+	g_at_result_iter_skip_next(&iter);
+
+	call = create_call(vd, 0, 1, CALL_STATUS_WAITING,
+				num, type, 0);
+
+	if (!call) {
+		ofono_error("malloc call structfailed."
+				"Call management is fubar");
+		return;
+	}
+
+	ofono_voicecall_notify(vc, call);
 }
 
 static void chld_cb(gboolean ok, GAtResult *result, gpointer user_data)
@@ -624,6 +958,10 @@ static void hfp_voicecall_initialized(gboolean ok,
GAtResult *result,
 	g_at_chat_register(vd->chat, "+CLIP:", clip_notify, FALSE, vc, NULL);
 	g_at_chat_register(vd->chat, "+CIEV:", ciev_notify, FALSE, vc, NULL);
 
+	if (vd->ag_features & AG_FEATURE_ENHANCED_CALL_STATUS)
+		g_at_chat_register(vd->chat, "+CCWA:", ccwa_notify, FALSE, vc,
+					NULL);
+
 	ofono_voicecall_register(vc);
 }
 
@@ -671,11 +1009,11 @@ static struct ofono_voicecall_driver driver = {
 	.answer			= hfp_answer,
 	.hangup			= hfp_hangup,
 	.list_calls		= NULL,
-	.hold_all_active	= NULL,
-	.release_all_held	= NULL,
-	.set_udub		= NULL,
-	.release_all_active	= NULL,
-	.release_specific	= NULL,
+	.hold_all_active	= hfp_hold_all_active,
+	.release_all_held	= hfp_release_all_held,
+	.set_udub		= hfp_set_udub,
+	.release_all_active	= hfp_release_all_active,
+	.release_specific	= hfp_release_specific,
 	.private_chat		= NULL,
 	.create_multiparty	= NULL,
 	.transfer		= NULL,
-- 
1.6.2.5





More information about the ofono mailing list