From ec789ba3155cd90decd252633d71535d93ae0262 Mon Sep 17 00:00:00 2001
From: Zhenhua Zhang <zhenhua.zhang@intel.com>
Date: Tue, 13 Oct 2009 23:40:04 +0800
Subject: [PATCH] handsfree profile plugin and driver framework

The implementation is according to the Bluetooth HFP spec 1.5. It
requires you create a HFP Audio Gateway TTY device(e.g. /dev/rfcomm0)
via 'rfcomm bind <port> <bdaddr> <channel>'.
---
 Makefile.am            |    8 +
 drivers/hfpmodem/hfp.c |  347 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/hfpmodem/hfp.h |   93 +++++++++++++
 plugins/hfp.c          |  224 +++++++++++++++++++++++++++++++
 plugins/modemconf.c    |    3 +-
 5 files changed, 674 insertions(+), 1 deletions(-)
 create mode 100644 drivers/hfpmodem/hfp.c
 create mode 100644 drivers/hfpmodem/hfp.h
 create mode 100644 plugins/hfp.c

diff --git a/Makefile.am b/Makefile.am
index cf84bf7..94b48dd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -116,6 +116,11 @@ builtin_sources += drivers/atmodem/atutil.h \
 			drivers/calypsomodem/calypsomodem.c \
 			drivers/calypsomodem/voicecall.c
 
+builtin_modules += hfpmodem
+builtin_sources += drivers/atmodem/atutil.h \
+			drivers/hfpmodem/hfp.h \
+			drivers/hfpmodem/hfp.c
+
 builtin_modules += modemconf
 builtin_sources += plugins/modemconf.c
 
@@ -146,6 +151,9 @@ builtin_sources += plugins/huawei.c
 
 builtin_modules += novatel
 builtin_sources += plugins/novatel.c
+
+builtin_modules += hfp
+builtin_sources += plugins/hfp.c
 endif
 
 if MAINTAINER_MODE
diff --git a/drivers/hfpmodem/hfp.c b/drivers/hfpmodem/hfp.c
new file mode 100644
index 0000000..941e7b1
--- /dev/null
+++ b/drivers/hfpmodem/hfp.c
@@ -0,0 +1,347 @@
+/*
+ *
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2008-2009  Intel Corporation. All rights reserved.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+
+#define OFONO_API_SUBJECT_TO_CHANGE
+#include <ofono/plugin.h>
+#include <ofono/log.h>
+#include <ofono/modem.h>
+#include <dbus.h>
+
+#include <gatchat.h>
+#include <gatresult.h>
+
+#include "hfp.h"
+
+static const char *none_prefix[] = { NULL };
+static const char *brsf_prefix[] = { "+BRSF:", NULL };
+static const char *cind_prefix[] = { "+CIND:", NULL };
+static const char *cmer_prefix[] = { "+CMER:", NULL };
+static const char *chld_prefix[] = { "+CHLD:", NULL };
+
+static void cind_status_cb(gboolean ok, GAtResult *result,
+				gpointer user_data)
+{
+	struct gateway *gateway = user_data;
+	GSList *l = gateway->indies;
+	struct indicator *ind;
+	struct ofono_error error;
+	GAtResultIter iter;
+	int value;
+
+	dump_response("cind_status_cb", ok, result);
+	decode_at_error(&error, g_at_result_final_response(result));
+
+	if (!ok)
+		goto fail;
+
+	g_at_result_iter_init(&iter, result);
+	if (!g_at_result_iter_next(&iter, "+CIND:"))
+		goto fail;
+
+	ofono_debug("cind_status_cb: cind:");
+
+	while (g_at_result_iter_next_number(&iter, &value)) {
+		ofono_debug("%d", value);
+		ind = g_slist_nth_data(l, 0);
+		ind->value = value;
+		l = g_slist_next(l);
+	}
+
+	ofono_debug("Service layer connection successfully established!");
+	g_at_chat_send(gateway->chat, "AT+CMEE=1", NULL, NULL, NULL, NULL);
+
+	gateway->state = GATEWAY_STATE_CONNECTED;
+
+	gateway->conn_cb(gateway->cb_data);
+	return;
+
+fail:
+	ofono_error("CIND status handshake with AudioGateway failed");
+}
+
+/* get phone capability about multiparty handling feature, may use in future */
+static void get_mpty_features(unsigned int *result, char *features)
+{
+	if (!strcmp(features, "0"))
+		*result |= AG_CHLD_0;
+
+	if (!strcmp(features, "1"))
+		*result |= AG_CHLD_1;
+
+	if (!strcmp(features, "1x"))
+		*result |= AG_CHLD_1x;
+
+	if (!strcmp(features, "2"))
+		*result |= AG_CHLD_2;
+
+	if (!strcmp(features, "2x"))
+		*result |= AG_CHLD_2x;
+
+	if (!strcmp(features, "3"))
+		*result |= AG_CHLD_3;
+
+	if (!strcmp(features, "4"))
+		*result |= AG_CHLD_4;
+}
+
+static void chld_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct gateway *gateway = user_data;
+	unsigned int mpty_feature;
+	struct ofono_error error;
+	GAtResultIter iter;
+	const char *str;
+	char value[3];
+	int index = 0, i = 0;
+
+	dump_response("chld_cb", ok, result);
+	decode_at_error(&error, g_at_result_final_response(result));
+
+	if (!ok)
+		goto fail;
+
+	g_at_result_iter_init(&iter, result);
+	if (!g_at_result_iter_next(&iter, "+CHLD:"))
+		goto fail;
+
+	if (!g_at_result_iter_open_list(&iter))
+		goto fail;
+
+	ofono_debug("chld_cb: chld:");
+
+	str = g_at_result_iter_raw_line(&iter);
+	while (str[i] != ')') {
+		if (str[i] == ',') {
+			value[index] = '\0';
+			ofono_debug("%s", value);
+			get_mpty_features(&mpty_feature, value);
+			index = 0;
+		}
+
+		if (((str[i] >= '0') && (str[i] <= '4')) || str[i] == 'x') {
+			value[index] = str[i];
+			index++;
+		}
+		i++;
+	}
+	value[index] = '\0';
+	ofono_debug("%s", value);
+	get_mpty_features(&mpty_feature, value);
+
+	gateway->mpty_features = mpty_feature;
+
+	g_at_chat_send(gateway->chat, "AT+CIND?", cind_prefix,
+				cind_status_cb, gateway, NULL);
+	return;
+
+fail:
+	ofono_error("CHLD handshake with AudioGateway failed");
+}
+
+static void cmer_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct gateway *gateway = user_data;
+	struct ofono_error error;
+
+	dump_response("cmer_cb", ok, result);
+	decode_at_error(&error, g_at_result_final_response(result));
+
+	if (!ok) {
+		ofono_error("CMER Handshake with AudioGateway failed");
+		return;
+	}
+	if ((gateway->ag_features & AG_FEATURE_3WAY) != 0)
+		g_at_chat_send(gateway->chat, "AT+CHLD=?", chld_prefix,
+				chld_cb, gateway, NULL);
+	else {
+		gateway->mpty_features = 0;
+		g_at_chat_send(gateway->chat, "AT+CIND?", cind_prefix,
+				cind_status_cb, gateway, NULL);
+	}
+}
+
+static void cind_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct gateway *gateway = user_data;
+	GSList *l = NULL;
+	struct ofono_error error;
+	GAtResultIter iter;
+	const char *str;
+	struct indicator *ind;
+	int index_min;
+	int index_max;
+
+	if (!ok)
+		goto fail;
+
+	dump_response("cind_cb", ok, result);
+	decode_at_error(&error, g_at_result_final_response(result));
+
+	g_at_result_iter_init(&iter, result);
+	if (!g_at_result_iter_next(&iter, "+CIND:"))
+		goto fail;
+
+	while (g_at_result_iter_open_list(&iter)) {
+		if (!g_at_result_iter_next_string(&iter, &str))
+			goto fail;
+
+		ind  = g_try_new0(struct indicator, 1);
+		strncpy(ind->descr, str, 20);
+		ind->descr[strlen(str)] = '\0';
+		l = g_slist_append(l, (gpointer) ind);
+
+		if (!g_at_result_iter_open_list(&iter))
+			goto fail;
+
+		while (g_at_result_iter_next_range(&iter,
+				&index_min, &index_max)) {
+			ofono_debug("cind_cb: cind: %s (%d-%d)", str,
+				index_min, index_max);
+		}
+
+		if (!g_at_result_iter_close_list(&iter))
+			goto fail;
+
+		if (!g_at_result_iter_close_list(&iter))
+			goto fail;
+	}
+
+	gateway->indies = l;
+	g_at_chat_send(gateway->chat, "AT+CMER=3,0,0,1", cmer_prefix,
+				cmer_cb, gateway, NULL);
+	return;
+fail:
+	ofono_error("CIND handshake with AudioGateway failed");
+}
+
+static void brsf_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct gateway *gateway = user_data;
+	struct ofono_error error;
+	GAtResultIter iter;
+
+	if (!ok) {
+		ofono_error("BRSF Handshake with AudioGateway failed");
+		return;
+	}
+
+	dump_response("brsf_cb", ok, result);
+	decode_at_error(&error, g_at_result_final_response(result));
+
+	g_at_result_iter_init(&iter, result);
+
+	if (!g_at_result_iter_next(&iter, "+BRSF:")) {
+		ofono_error("BRSF handshake with AudioGateway failed");
+		return;
+	}
+
+	g_at_result_iter_next_number(&iter, &(gateway->ag_features));
+
+	ofono_debug("brsf_cb: brsf: %d", gateway->ag_features);
+
+	g_at_chat_send(gateway->chat, "AT+CIND=?", cind_prefix,
+				cind_cb, gateway, NULL);
+
+}
+
+struct gateway *hfp_service_level_connection(GAtChat *chat,
+			HfpConnectFunc conn_cb, gpointer cb_data)
+{
+	struct gateway *gateway;
+	int ret = 0;
+
+	gateway = g_try_new0(struct gateway, 1);
+	if (!gateway)
+		return NULL;
+
+	gateway->state = GATEWAY_STATE_DISCONNECTED;
+	gateway->chat = chat;
+	gateway->conn_cb = conn_cb;
+	gateway->cb_data = cb_data;
+
+	/* = 0x76, support multiparty calling, enhanced call status and */
+	/* enhanced call control */
+	ret = g_at_chat_send(gateway->chat, "AT+BRSF=118", brsf_prefix,
+				brsf_cb, gateway, NULL);
+	if (ret < 0)
+		return NULL;
+
+	return gateway;
+}
+
+gboolean is_rfcomm_tty(const char *tty)
+{
+	return ((tty != NULL) && strstr(tty, "/dev/rfcomm"));
+}
+
+static void indicator_slice_free(gpointer mem)
+{
+	g_slice_free(struct indicator, mem);
+}
+
+void hfp_cleanup(gpointer user_data)
+{
+	struct gateway *gateway = user_data;
+
+	if (!gateway)
+		return;
+
+	g_at_chat_unref(gateway->chat);
+	gateway->chat = NULL;
+
+	g_slist_foreach(gateway->indies, (GFunc) indicator_slice_free, NULL);
+	g_slist_free(gateway->indies);
+	gateway->indies = NULL;
+
+	g_free(gateway);
+	gateway = NULL;
+}
+
+GAtChat *hfp_get_chat(gpointer user_data)
+{
+	struct gateway *gateway = user_data;
+
+	if (gateway && gateway->chat)
+		return gateway->chat;
+
+	return NULL;
+}
+
+static int hfpmodem_init(void)
+{
+}
+
+static void hfpmodem_exit(void)
+{
+}
+
+OFONO_PLUGIN_DEFINE(hfpmodem, "Hands-Free Profile Driver", VERSION,
+		OFONO_PLUGIN_PRIORITY_DEFAULT, hfpmodem_init, hfpmodem_exit)
diff --git a/drivers/hfpmodem/hfp.h b/drivers/hfpmodem/hfp.h
new file mode 100644
index 0000000..fa5641f
--- /dev/null
+++ b/drivers/hfpmodem/hfp.h
@@ -0,0 +1,93 @@
+/*
+ *
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2008-2009  Intel Corporation. All rights reserved.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#ifndef __BLUETOOTH_H__
+#define __BLUETOOTH_H__
+
+#include <drivers/atmodem/atutil.h>
+
+/* AG supported features bitmap. Bluetooth HFP 1.5 spec page 77 */
+#define AG_FEATURE_3WAY 0x1
+#define AG_FEATURE_ECNR 0x2
+#define AG_FEATURE_VOICE_RECOG 0x4
+#define AG_FEATURE_IN_BAND_RING_TONE 0x8
+#define AG_FEATURE_ATTACH_VOICE_TAG 0x10
+#define AG_FEATURE_REJECT_CALL 0x20
+#define AG_FEATURE_ENHANCED_CALL_STATUS 0x40
+#define AG_FEATURE_ENHANCED_CALL_CONTROL 0x80
+#define AG_FEATURE_EXTENDED_RES_CODE 0x100
+
+/* Hold and multipary AG features. Bluetooth HFP 1.5 spec page 69  */
+
+/* Releases all held calls or sets User Determined User Busy (UDUB)
+ * for a waiting call */
+#define AG_CHLD_0 0x01
+/* Releases all active calls (if any exist) and accepts the other
+ * (held or waiting) call */
+#define AG_CHLD_1 0x02
+/* Releases specified active call only <x> */
+#define AG_CHLD_1x 0x04
+/* Places all active calls (if any exist) on hold and accepts the other
+ * (held or waiting) call */
+#define AG_CHLD_2 0x08
+/* Request private consultation mode with specified call <x> (Place all
+ * calls on hold EXCEPT the call <x>) */
+#define AG_CHLD_2x 0x10
+/* Adds a held call to the conversation */
+#define AG_CHLD_3 0x20
+/* Connects the two calls and disconnects the subscriber from both calls
+ * (Explicit Call Transfer). Support for this value and its associated
+ * functionality is optional for the HF. */
+#define AG_CHLD_4 0x40
+
+typedef void (*HfpConnectFunc)(gpointer user_data);
+
+enum gateway_state {
+	GATEWAY_STATE_DISCONNECTED = 0,
+	GATEWAY_STATE_CONNECTED
+};
+
+struct gateway {
+	enum gateway_state state;
+	GAtChat *chat;
+
+	HfpConnectFunc conn_cb;
+	gpointer cb_data;
+
+	unsigned int ag_features;
+	unsigned int mpty_features;
+	GSList *indies;
+};
+
+struct indicator {
+    char descr[20];
+    int value;
+};
+
+struct gateway *hfp_service_level_connection(GAtChat *chat,
+			HfpConnectFunc conn_cb, gpointer cb_data);
+void hfp_cleanup(gpointer user_data);
+GAtChat *hfp_get_chat(gpointer user_data);
+gboolean is_rfcomm_tty(const char *tty);
+
+extern void hfp_voicecall_init();
+extern void hfp_voicecall_exit();
+
+#endif
diff --git a/plugins/hfp.c b/plugins/hfp.c
new file mode 100644
index 0000000..c73a032
--- /dev/null
+++ b/plugins/hfp.c
@@ -0,0 +1,224 @@
+/*
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2008-2009  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2009  Collabora Ltd. All rights reserved.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <glib.h>
+#include <gatchat.h>
+#include <gattty.h>
+#include <dbus.h>
+
+#define OFONO_API_SUBJECT_TO_CHANGE
+#include <ofono/plugin.h>
+#include <ofono/log.h>
+#include <ofono/modem.h>
+#include <ofono/call-barring.h>
+#include <ofono/call-forwarding.h>
+#include <ofono/call-meter.h>
+#include <ofono/call-settings.h>
+#include <ofono/devinfo.h>
+#include <ofono/message-waiting.h>
+#include <ofono/netreg.h>
+#include <ofono/phonebook.h>
+#include <ofono/sim.h>
+#include <ofono/sms.h>
+#include <ofono/ssn.h>
+#include <ofono/ussd.h>
+#include <ofono/voicecall.h>
+
+#include <drivers/hfpmodem/hfp.h>
+
+struct hfp_data {
+	char *tty;			/* tty created through rfcomm */
+	gpointer gateway;
+};
+
+static int timeout = 0;
+
+static int hfp_disable(struct ofono_modem *modem);
+
+static void hfp_debug(const char *str, void *data)
+{
+	DBG("%s", str);
+}
+
+static gboolean hfp_enable_timeout(gpointer user)
+{
+	struct ofono_modem *modem = user;
+	if (ofono_modem_get_powered(modem))
+		return FALSE;
+
+	hfp_disable(modem);
+	return FALSE;
+}
+
+/* either Ofono or Phone could request HFP connection */
+static void hfp_connect_cb(gpointer user_data)
+{
+	struct ofono_modem *modem = user_data;
+
+	if (timeout)
+		g_source_remove(timeout);
+
+	ofono_modem_set_powered(modem, TRUE);
+}
+
+/* try service_level_connection */
+static int hfp_connect_tty(struct ofono_modem *modem, const char *tty)
+{
+	struct hfp_data *hfp_data = ofono_modem_get_data(modem);
+	GIOChannel *io;
+	GAtSyntax *syntax;
+	GAtChat *chat;
+
+	if (!is_rfcomm_tty(tty))
+		return -EINVAL;
+
+	io = g_at_tty_open(tty, NULL);
+	if (!io)
+		return -EIO;
+
+	syntax = g_at_syntax_new_gsmv1();
+	chat = g_at_chat_new(io, syntax);
+	g_at_syntax_unref(syntax);
+	g_io_channel_unref(io);
+
+	if (!chat) {
+		ofono_error("failed to create chat from %s: %s (%d)", tty,
+			strerror(errno), errno);
+		return -ENOMEM;
+	}
+
+	if (getenv("OFONO_AT_DEBUG"))
+		g_at_chat_set_debug(chat, hfp_debug, NULL);
+
+	hfp_data->tty = g_strdup(tty);
+	hfp_data->gateway = hfp_service_level_connection(chat,
+				hfp_connect_cb, modem);
+
+	timeout = g_timeout_add_seconds(10, hfp_enable_timeout, modem);
+
+	/* wait util device is created or timeout */
+	return -EINPROGRESS;
+}
+
+static int hfp_probe(struct ofono_modem *modem)
+{
+	struct hfp_data *hfp_data;
+
+	hfp_data = g_try_new0(struct hfp_data, 1);
+	if (!hfp_data)
+		return -ENOMEM;
+	hfp_data->tty = NULL;
+
+	ofono_modem_set_data(modem, hfp_data);
+
+	return 0;
+}
+
+static void hfp_remove(struct ofono_modem *modem)
+{
+	gpointer data = ofono_modem_get_data(modem);
+
+	if (data)
+		g_free(data);
+
+	ofono_modem_set_data(modem, NULL);
+}
+
+/* power up hardware */
+static int hfp_enable(struct ofono_modem *modem)
+{
+	const char *tty;
+	int ret;
+
+	DBG("%p", modem);
+
+	tty = ofono_modem_get_string(modem, "Device");
+	if (tty == NULL)
+		return -EINVAL;
+
+	ret = hfp_connect_tty(modem, tty);
+
+	return ret;
+}
+
+static int hfp_disable(struct ofono_modem *modem)
+{
+	struct hfp_data *hfp_data = ofono_modem_get_data(modem);
+
+	DBG("%p", modem);
+
+	if (hfp_data->tty) {
+		g_free(hfp_data->tty);
+		hfp_data->tty = NULL;
+	}
+
+	hfp_cleanup(hfp_data->gateway);
+	hfp_data->gateway = NULL;
+
+	ofono_modem_set_powered(modem, FALSE);
+
+	return 0;
+}
+
+static void hfp_pre_sim(struct ofono_modem *modem)
+{
+	struct hfp_data *hfp_data = ofono_modem_get_data(modem);
+
+	DBG("%p", modem);
+}
+
+static void hfp_post_sim(struct ofono_modem *modem)
+{
+	struct hfp_data *hfp_data = ofono_modem_get_data(modem);
+
+	DBG("%p", modem);
+}
+
+static struct ofono_modem_driver hfp_driver = {
+	.name		= "hfp",
+	.probe		= hfp_probe,
+	.remove		= hfp_remove,
+	.enable		= hfp_enable,
+	.disable	= hfp_disable,
+	.pre_sim	= hfp_pre_sim,
+	.post_sim	= hfp_post_sim,
+};
+
+static int hfp_init(void)
+{
+	DBG("");
+	return ofono_modem_driver_register(&hfp_driver);
+}
+
+static void hfp_exit(void)
+{
+	ofono_modem_driver_unregister(&hfp_driver);
+}
+
+OFONO_PLUGIN_DEFINE(hfp, "Hands-Free Profile Plugins", VERSION,
+			OFONO_PLUGIN_PRIORITY_DEFAULT, hfp_init, hfp_exit)
diff --git a/plugins/modemconf.c b/plugins/modemconf.c
index 4795749..192faa6 100644
--- a/plugins/modemconf.c
+++ b/plugins/modemconf.c
@@ -100,7 +100,8 @@ static struct ofono_modem *create_modem(GKeyFile *keyfile, const char *group)
 		set_address(modem, keyfile, group);
 
 	if (!g_strcmp0(driver, "atgen") || !g_strcmp0(driver, "g1") ||
-						!g_strcmp0(driver, "calypso"))
+						!g_strcmp0(driver, "calypso") ||
+						!g_strcmp0(driver, "hfp"))
 		set_device(modem, keyfile, group);
 
 	g_free(driver);
-- 
1.6.1.3

