public inbox for linux-bluetooth@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Gateway profile
       [not found] <664d43d60812151512y6631ebf2j9665e1473193077d@mail.gmail.com>
@ 2008-12-15 23:14 ` event
  2008-12-17  2:48   ` Marcel Holtmann
  0 siblings, 1 reply; 10+ messages in thread
From: event @ 2008-12-15 23:14 UTC (permalink / raw)
  To: linux-bluetooth

[-- Attachment #1: Type: text/plain, Size: 275 bytes --]

Hi all,

I've implemented gateway profile. I've tested basic things, like
place/cancel/answer call. Others are in development. Some could not be
tested as my carrier doesn't provide corresponding services (like
3-way call, etc.) so any help welcome.

Vale,
Leonid Movshovich

[-- Attachment #2: gateway.patch --]
[-- Type: application/octet-stream, Size: 48580 bytes --]

Index: audio/audio-api.txt
===================================================================
RCS file: /cvsroot/bluez/utils/audio/audio-api.txt,v
retrieving revision 1.30
diff -w -u -r1.30 audio-api.txt
--- audio/audio-api.txt	29 May 2008 10:41:44 -0000	1.30
+++ audio/audio-api.txt	15 Dec 2008 23:04:41 -0000
@@ -1,6 +1,7 @@
 Bluetooth audio service API description
 ***************************************
 
+Copyright (C) 2008  Leonid Movshovich <event.riga@gmail.com>
 Copyright (C) 2004-2007  Marcel Holtmann <marcel@holtmann.org>
 Copyright (C) 2005-2007  Johan Hedberg <johan.hedberg@nokia.com>
 Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
@@ -233,13 +234,102 @@
 org.bluez.audio.Gateway interface
 =================================
 
-[not yet implemented]
-
 This interface is available for remote devices which can function in the Audio
 Gateway role of the HSP and/or HFP profiles.
 
 Object path(s)	/org/bluez/audio/device*
 
+Methods		
+    void Connect()
+	Connect to the AG service on the remote device.
+
+    void Disconnect()
+    boolean IsConnected()
+
+    void AnswerCall()
+         It has to called only after Ring signal received.
+    
+    void CancelCurrentCall()
+         Cancel call which is running now. This one does nothing with any 3-way
+         situation incl. RaH. Just plain old PDH.
+         
+    void Call(string number)
+         Dial a number 'number'. No number processing is done thus if AG would 
+         reject to dial it don't blame me :)
+    
+    string GetOperatorName()
+    
+    void VoiceRecognitionActivate()
+    	in development
+    void VoiceRecognitionDeactivate()
+       	in development
+
+    string GetNumberForLastVoiceTag()
+         Ask AG to provide a phone number. It looks like AG shell in turn 
+         ask user to choose one. Idea behind this is VR on the HF side.
+
+    void SendDTMF(string digits)
+         Will send each digit in the 'digits' sequentially. Would send 
+         nothing if there is non-dtmf digit.
+
+    void SendMicrophoneGain(uint8 gain)
+    void SendSpeakerGain(uint8 gain)
+         Feel them useless but really easy to implement :)
+    
+    string[] GetSubscriberNumbers()
+         Get all the numbers of AG
+
+    void SendAudioData(uint8[] data)
+         Would only be effective when call or VR is running
+         (i.e. SCO link aka Audio Connection exists). Data should be 
+         plain 16 bit LE PCM (e.g. directly from ALSA)
+
+Signals
+    void Connected(uint32 ag_features) 
+         Connection successfully esteblished. 'ag_features' are
+         ORed features:
+              0x001 Three-way calling
+              0x002 Echo cancelling and/or noice reduction function
+              0x004 Voice recognition function
+              0x008 In-band ring tone capability
+              0x010 Attach a number to a voice tag
+              0x020 Ability to reject a call
+              0x040 Enhanced call status
+              0x080 Enhanced call control
+              0x100 Extended Error Result Codes
+
+    void Disconnected()
+
+    void Ring(string number)
+         Someone's calling from 'number'. 
+         Caller number is provided as received from AG.
+
+    void StatusUpdate(string indicator_name, uint32 indicator_value)
+         Indicator 'indicator_name' changed its value to 'indicator_value'. 
+         System (call, callsetup, etc.) statuses are not signalled.
+
+    void CallCancelled()
+         Call failed to set up. It means that we tried to call someone 
+         or someone tried to call us but call was not accepted.
+
+    void CallStart()
+         Call set up successfully.
+
+    void CallEnd()
+         Call was started and now ended. In contrast with CallCancelled 
+         where call didn't started
+
+    void VoiceRecognitionActive()
+    void VoiceRecognitionInactive()
+    	in development
+
+    void MicrophoneGain(uint8 gain) 
+    void SpeakerGain(uint8 gain)
+         AG sent us new gain.
+
+    void AudioData(uint8[] data) 
+         Array of audio data from AG. It is plain 16 bit PCM ready for ALSA.
+         
 
 org.bluez.audio.Sink interface
 ==============================
Index: audio/device.h
===================================================================
RCS file: /cvsroot/bluez/utils/audio/device.h,v
retrieving revision 1.18
diff -w -u -r1.18 device.h
--- audio/device.h	29 May 2008 08:05:16 -0000	1.18
+++ audio/device.h	15 Dec 2008 23:04:41 -0000
@@ -22,6 +22,11 @@
  *
  */
 
+#ifndef __DEVICE_H__
+#define __DEVICE_H__
+
+#include <bluetooth/bluetooth.h>
+
 #define AUDIO_DEVICE_INTERFACE	"org.bluez.audio.Device"
 
 #define GENERIC_AUDIO_UUID	"00001203-0000-1000-8000-00805F9B34FB"
@@ -81,3 +86,5 @@
 uint8_t device_get_state(struct audio_device *dev);
 
 gboolean device_is_connected(struct audio_device *dev, const char *interface);
+
+#endif /*__DEVICE_H__*/
Index: audio/gateway.c
===================================================================
RCS file: /cvsroot/bluez/utils/audio/gateway.c,v
retrieving revision 1.12
diff -w -u -r1.12 gateway.c
--- audio/gateway.c	2 Feb 2008 03:37:05 -0000	1.12
+++ audio/gateway.c	15 Dec 2008 23:04:46 -0000
@@ -4,6 +4,7 @@
  *
  *  Copyright (C) 2006-2007  Nokia Corporation
  *  Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2008  Leonid Movshovich <event.riga@gmail.org>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -27,8 +28,945 @@
 #endif
 
 #include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
 
+#include <fcntl.h>
 #include <glib.h>
 #include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
 
 #include "gateway.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "logging.h"
+
+#define SCO_BUF_SIZE 1024
+#define RFCOMM_BUF_SIZE 256
+#define AG_INDICATOR_DESCR_SIZE 20 /* not-more-then-16 defined by GSM + 1 for NULL + padding */
+#define AG_CALLER_NUM_SIZE 64 /* size of number + type */
+
+/* commands */
+#define AG_FEATURES "AT+BRSF=26\r" // = 0x7F = All features supported
+#define AG_INDICATORS_SUPP "AT+CIND=?\r"
+#define AG_INDICATORS_VAL "AT+CIND?\r"
+#define AG_INDICATORS_ENABLE "AT+CMER=3,0,0,1\r"
+#define AG_HOLD_MPTY_SUPP "AT+CHLD=?\r"
+#define AG_CALLER_IDENT_ENABLE "AT+CLIP=1\r"
+#define AG_CARRIER_FORMAT "AT+COPS=3,0\r"
+#define AG_EXTENDED_RESULT_CODE "AT+CMEE=1\r"
+
+#define AG_FEATURE_3WAY 0x1
+#define AG_FEATURE_EXTENDED_RES_CODE 0x100
+/* Hold and multipary AG features. Comments below are copied from hands-free spec for reference */
+#define AG_CHLD_0 0x01 /* Releases all held calls or sets User Determined User Busy (UDUB) for a waiting call */
+#define AG_CHLD_1 0x02 /* Releases all active calls (if any exist) and accepts the other (held or waiting) call */
+#define AG_CHLD_1x 0x04 /* Releases specified active call only <x> */
+#define AG_CHLD_2 0x08 /* Places all active calls (if any exist) on hold and accepts the other (held or waiting) call */
+#define AG_CHLD_2x 0x10 /* Request private consultation mode with specified call <x> (Place all calls on hold EXCEPT the call <x>) */
+#define AG_CHLD_3 0x20 /* Adds a held call to the conversation */
+#define AG_CHLD_4 0x40 /* 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 OK_RESPONSE "\r\nOK\r\n"
+#define ERROR_RESPONSE "\r\nERROR\r\n"
+
+
+struct indicator {
+	gchar descr[AG_INDICATOR_DESCR_SIZE]; 
+	gint value;
+};
+
+struct gateway {
+	GIOChannel* rfcomm;
+	guint rfcomm_watch_id;
+	GIOChannel* sco;
+	GIOChannel* sco_server;
+	DBusMessage* connect_message;
+	guint ag_features;
+	guint hold_multiparty_features;
+	GSList* indies;
+	gboolean is_dialing;
+};
+
+
+static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond, struct audio_device *device);
+
+static void rfcomm_start_watch(struct audio_device* dev) 
+{
+	struct gateway* gw = dev->gateway;
+	gw->rfcomm_watch_id = g_io_add_watch(gw->rfcomm, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL
+					     , (GIOFunc) rfcomm_ag_data_cb, dev);
+}
+
+static void rfcomm_stop_watch(struct audio_device* dev) 
+{
+	struct gateway* gw = dev->gateway;
+	g_source_remove(gw->rfcomm_watch_id);
+}
+
+static gboolean io_channel_write_all(GIOChannel* io, gchar* data, gsize count) 
+{
+	gsize written = 0;
+	GIOStatus status;
+	while (0 < count) {
+		status =  g_io_channel_write_chars(io, data, count, &written, NULL);
+		if (G_IO_STATUS_NORMAL != status) {
+			return FALSE;
+		}
+		data += written;
+		count -= written;
+	}
+	return TRUE;
+}
+
+/* it's worth to mention that data and response could be the same pointers */
+static gboolean rfcomm_send_and_read(struct gateway* gw, gchar* data, gchar* response, gsize count) 
+{
+	GIOChannel* rfcomm = gw->rfcomm;
+
+	if (!io_channel_write_all(rfcomm, data, count)) {
+		return FALSE;
+	}
+
+	gsize read = 0;
+	gboolean got_ok = FALSE;
+	gboolean got_error = FALSE;
+	gchar* resp_buf = response;
+	gsize toread = RFCOMM_BUF_SIZE - 1;
+	GIOStatus status;
+	while (! (got_ok || got_error)) {
+		status = g_io_channel_read_chars(rfcomm, resp_buf, toread, &read, NULL);
+		if (G_IO_STATUS_NORMAL == status) {
+			resp_buf[read] = '\0';
+		} else {
+			debug("rfcomm_send_and_read(): %m");
+			return FALSE;
+		}
+		got_ok = NULL != strstr(resp_buf, OK_RESPONSE);
+		got_error = NULL != strstr(resp_buf, ERROR_RESPONSE);
+		resp_buf += read;
+		toread -= read;
+	}
+	debug("!!! Data is '%s'", response);
+	return TRUE;
+}
+
+/* get <descr> from the names: (<descr>, (<values>)), (<descr>, (<values>)) ... */
+GSList* parse_indicator_names(gchar* names, GSList* indies) 
+{
+	gchar* current = names - 1;
+	GSList* result = indies;
+	gchar* next;
+	while (NULL != current) {
+		current += 2;
+		next = strstr(current, ",(");
+		struct indicator* ind = (struct indicator*) g_slice_new(struct indicator); 
+		strncpy(ind->descr, current, 20);
+		ind->descr[(gint)next - (gint)current] = '\0';
+		debug("found indicator '%s'", ind->descr);
+		result = g_slist_append(result, (gpointer)ind);
+		debug("parse_indicator_names(): append success");
+		current = strstr(next + 1, ",(");
+		debug("parse_indicator_names(): strstr() success");
+	}
+	debug("parse_indicator_names(): success");
+	return result;
+}
+
+/* get values from <val0>,<val1>,... */
+GSList* parse_indicator_values(gchar* values, GSList* indies) 
+{
+	gint val;
+	gchar* current = values - 1;
+	GSList* runner = indies;
+	while (NULL != current) {
+		current += 1;
+		sscanf(current, "%d", &val);
+		current = strchr(current, ',');
+		struct indicator* ind = (struct indicator*) g_slist_nth_data(runner, 0);
+		ind->value = val;
+		runner = g_slist_next(runner);
+	}
+	return indies;
+}
+
+/* get values from <val0>,<val1>,... */
+static guint get_hold_mpty_features(gchar* features) 
+{
+	guint result = 0;
+	if (NULL != strstr(features, "0")) {
+		result |= AG_CHLD_0;
+	}
+	if (NULL != strstr(features, "1")) {
+		result |= AG_CHLD_1;
+	}
+	if (NULL != strstr(features, "1x")) {
+		result |= AG_CHLD_1x;
+	}
+	if (NULL != strstr(features, "2")) {
+		result |= AG_CHLD_2;
+	}
+	if (NULL != strstr(features, "2x")) {
+		result |= AG_CHLD_2x;
+	}
+	if (NULL != strstr(features, "3")) {
+		result |= AG_CHLD_3;
+	}
+	if (NULL != strstr(features, "4")) {
+		result |= AG_CHLD_4;
+	}
+	return result;
+}
+
+static gboolean establish_service_level_conn(struct gateway* gw) 
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	gint read;
+	gboolean res;
+	res = rfcomm_send_and_read(gw, AG_FEATURES, buf, sizeof(AG_FEATURES) - 1);
+	if (! res 
+	    || 1 != sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features)) {
+		return FALSE;
+	}
+	debug("features are 0x%X", gw->ag_features);
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_SUPP, buf, sizeof(AG_INDICATORS_SUPP) - 1);
+	if (! res || !strstr(buf, "+CIND:")) {
+		return FALSE;
+	}
+	debug("Got indicators '%s'", buf);
+	gw->indies = parse_indicator_names(strchr(buf, '('), NULL);
+
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_VAL, buf, sizeof(AG_INDICATORS_VAL) - 1);
+	if (! res || !strstr(buf, "+CIND:")) {
+		return FALSE;
+	}
+	debug("Got values for them '%s'", buf);
+	gw->indies = parse_indicator_values(strchr(buf, ':') + 1, gw->indies);
+    
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_ENABLE, buf, sizeof(AG_INDICATORS_ENABLE) - 1);
+	if (! res || !strstr(buf, "OK")) {
+		return FALSE;
+	}
+	if (0 != gw->ag_features & AG_FEATURE_3WAY) {
+		res = rfcomm_send_and_read(gw, AG_HOLD_MPTY_SUPP, buf, sizeof(AG_HOLD_MPTY_SUPP) - 1);
+		if (! res || !strstr(buf, "+CHLD:")) {
+			g_slice_free1(RFCOMM_BUF_SIZE, buf);
+			return FALSE;
+		}
+		gw->hold_multiparty_features = get_hold_mpty_features(strchr(buf, '('));
+	} else {
+		gw->hold_multiparty_features = 0;
+	}
+	debug("CHLD is 0x%X", gw->hold_multiparty_features);
+	debug("Service layer connection successfully established!");
+	rfcomm_send_and_read(gw, AG_CALLER_IDENT_ENABLE, buf, sizeof(AG_CALLER_IDENT_ENABLE) - 1);
+	debug("Caller identification enabled");
+	rfcomm_send_and_read(gw, AG_CARRIER_FORMAT, buf, sizeof(AG_CARRIER_FORMAT) - 1);
+	debug("Carrier format set");
+	if (0 != gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) {
+		rfcomm_send_and_read(gw, AG_EXTENDED_RESULT_CODE, buf, sizeof(AG_EXTENDED_RESULT_CODE) - 1);
+		debug("Extended result code enabled");
+	}
+	return TRUE;
+}
+
+static void process_ind_change(struct audio_device* dev, gchar* name, gint value) 
+{
+	struct gateway* gw = dev->gateway;
+	if (0 == strcmp(name, "call")) {
+		if (0 < value) {
+			g_dbus_emit_signal(dev->conn, dev->path, AUDIO_GATEWAY_INTERFACE, "CallStart", DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+		} else {
+			g_dbus_emit_signal(dev->conn, dev->path, AUDIO_GATEWAY_INTERFACE, "CallEnd", DBUS_TYPE_INVALID);
+		}
+	} else if (0 == strcmp(name, "callstatus") 
+		   && gw->is_dialing) {
+		if (0 == value) {
+			g_dbus_emit_signal(dev->conn, dev->path, AUDIO_GATEWAY_INTERFACE, "CallCancelled", DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+		} else {
+			gw->is_dialing = TRUE;
+		}
+	} else if(0 == strcmp(name, "callheld")) {
+		/* FIXME: The following code is based on assumptions only. Has to be tested for interoperability
+		   I assume that callheld=2 would be sent when dial from HF failed in case of 3-way call 
+		   Unfortunately this path is not covered by the HF spec so the code has to be tested for interop
+		*/
+		if (2 == value) { /* '2' means: all calls held, no active calls */
+			if (gw->is_dialing) {
+				g_dbus_emit_signal(dev->conn, dev->path, AUDIO_GATEWAY_INTERFACE, "CallCancelled", DBUS_TYPE_INVALID);
+				gw->is_dialing == FALSE;
+			}
+		}
+	} else {
+		g_dbus_emit_signal(dev->conn, dev->path, AUDIO_GATEWAY_INTERFACE, "StatusUpdate"
+				   , DBUS_TYPE_STRING, name, DBUS_TYPE_INT32, value, DBUS_TYPE_INVALID);
+	}
+}
+
+static void process_ring(struct audio_device* device, GIOChannel* chan, gchar* buf) 
+{
+	gchar number[AG_CALLER_NUM_SIZE];
+	gchar* sep;
+	gint read;
+    
+	rfcomm_stop_watch(device);
+	g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read, NULL);
+	if (1 == sscanf(buf, "\r\n+CLIP:%s\r\n", number)) {
+		sep = strchr(number, ',');
+		sep[0] = '\0';
+		/* FIXME: signal will be emitted on each RING+CLIP. That's bad */
+		g_dbus_emit_signal(device->conn, device->path, AUDIO_GATEWAY_INTERFACE, "Ring"
+				   , DBUS_TYPE_STRING, number, DBUS_TYPE_INVALID);
+	} else {
+		debug("rfcomm_ag_data_cb(): '%s' in place of +CLIP after RING", buf);
+	}
+	rfcomm_start_watch(device);
+}
+
+static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond, struct audio_device *device)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	struct gateway* gw;
+	gint read;
+	gchar indicator[AG_INDICATOR_DESCR_SIZE + 4]; /* some space for value */
+	gint value;
+	gint result;
+	gchar* sep;
+    
+	if (cond & G_IO_NVAL)
+		return FALSE;
+    
+	gw = device->gateway;
+    
+	if (cond & (G_IO_ERR | G_IO_HUP))
+		return FALSE;
+    
+    
+	if (G_IO_ERROR_NONE != g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read, NULL))
+		return TRUE;
+
+	buf[read] = '\0';
+	debug("!!! Callback received: %s", buf);
+	if (1 == sscanf(buf, "\r\n+CIEV:%s\r\n", indicator)) {
+		sep = strchr(indicator, ',');
+		sep[0] = '\0';
+		sep += 1;
+		value = atoi(sep);
+		process_ind_change(device, indicator, value);
+	} else if (NULL != strstr(buf, "RING")){
+		process_ring(device, chan, buf);
+	} else if (1 == sscanf(buf, "\r\n+BVRA:%d\r\n", value)){
+		if (0 == value) {
+			g_dbus_emit_signal(device->conn, device->path, AUDIO_GATEWAY_INTERFACE
+					   , "VoiceRecognitionActive", DBUS_TYPE_INVALID);
+		} else {
+			g_dbus_emit_signal(device->conn, device->path, AUDIO_GATEWAY_INTERFACE
+					   , "VoiceRecognitionInactive", DBUS_TYPE_INVALID);
+		}
+	} else if (1 == sscanf(buf, "\r\n+VGS:%d\r\n", value)){
+		g_dbus_emit_signal(device->conn, device->path, AUDIO_GATEWAY_INTERFACE
+				   , "SpeakerGain", DBUS_TYPE_INT32, value, DBUS_TYPE_INVALID);
+        
+	} else if (1 == sscanf(buf, "\r\n+VGM:%d\r\n", value)){
+		g_dbus_emit_signal(device->conn, device->path, AUDIO_GATEWAY_INTERFACE
+				   , "MicGain", DBUS_TYPE_INT32, value, DBUS_TYPE_INVALID);
+	} else {
+		debug("rfcomm_ag_data_cb(): read wrong data '%s'", buf);
+	}
+	return TRUE;
+}
+
+static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
+                          struct audio_device *dev) 
+{
+	gchar buf[SCO_BUF_SIZE];
+	gint read;
+	struct gateway* gw = dev->gateway;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+    
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		GIOChannel* chan = gw->sco;
+		debug("SCO connection finished");
+		g_io_channel_unref(chan);
+		g_io_channel_close(chan);
+		gw->sco = NULL;
+		return FALSE;
+	}
+    
+	/* May be here some more data have to be read. Depends on result performance. 
+	   For Nokia E51 volume is 48 bytes per call*/
+	if (G_IO_ERROR_NONE == g_io_channel_read(chan, buf, sizeof(buf) , &read)) {
+//    if (G_IO_ERROR_NONE == g_io_channel_read_chars(chan, buf, sizeof(buf) , &read, NULL)) {
+		gchar* p_buf = buf;
+		g_dbus_emit_signal(dev->conn, dev->path, AUDIO_GATEWAY_INTERFACE, "AudioData"
+				   , DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &p_buf, read, DBUS_TYPE_INVALID);
+	}
+	return TRUE;
+}
+
+
+static void sco_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+                           const bdaddr_t *dst, gpointer user_data)
+{
+	struct audio_device *dev = (struct audio_device*) user_data;
+	struct gateway* gw = dev->gateway;
+
+	if (0 > err) {
+		error("sco_connect_cb(): %s (%d)", strerror(-err), -err);
+		/* not sure, but from other point of view, 
+		   what is the reason to have headset which cannot play audio? */
+		gateway_close(gw); 
+		return;
+	}
+
+	debug("SCO socket opened for gateway %s", dev->path);
+	gw->sco = chan;
+	fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0); /* why is this here? */
+	g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN,
+		       (GIOFunc) sco_io_cb, dev);
+}
+
+
+static void rfcomm_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+                              const bdaddr_t *dst, gpointer user_data)
+{
+	struct audio_device* dev = user_data;
+	struct gateway* gw = dev->gateway;
+	DBusMessage* conn_mes = gw->connect_message;
+	gchar gw_addr[18];
+    
+	if (err < 0) {
+		error("connect(): %s (%d)", strerror(-err), -err);
+		return;
+	}
+    
+	ba2str(&dev->dst, gw_addr);
+	/* Blocking mode should be default, but just in case: */
+	GIOFlags flags = g_io_channel_get_flags(chan); 
+	flags &= ~G_IO_FLAG_NONBLOCK; 
+	flags &= G_IO_FLAG_MASK;
+	g_io_channel_set_flags(chan, flags, NULL); 
+	g_io_channel_set_encoding(chan, NULL, NULL);
+	g_io_channel_set_buffered(chan, FALSE);
+	gw->rfcomm = chan;
+	if (establish_service_level_conn(dev->gateway)){
+		GIOChannel* sco_server = bt_sco_listen(&dev->src, sco_connect_cb, dev);
+		if (NULL != sco_server) {
+			debug("%s: Connected to %s", dev->path, gw_addr);
+			rfcomm_start_watch(dev);
+			gw->sco_server = sco_server;
+			DBusMessage* reply = dbus_message_new_method_return(conn_mes); 
+			dbus_connection_send(dev->conn, reply, NULL);
+			dbus_message_unref(reply); 
+			dbus_message_unref(conn_mes); 
+			gw->connect_message = NULL; 
+
+			g_dbus_emit_signal(dev->conn, dev->path, AUDIO_GATEWAY_INTERFACE, "Connected"
+					   , DBUS_TYPE_UINT32, &gw->ag_features, DBUS_TYPE_INVALID);
+			return;
+		} else {
+			debug("%s: Failed to setup SCO server socket", dev->path);
+		}
+	} else {
+		debug("%s: Failed to establish service layer connection to %s", dev->path, gw_addr);
+	}
+	gateway_close(gw);
+}
+
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	DBusMessage* conn_mes = dev->gateway->connect_message;
+	int ch = -1;
+	sdp_list_t *protos, *classes = NULL;
+	sdp_record_t *record = NULL;
+	uuid_t uuid;
+
+	if (err < 0) {
+		error("Unable to get service record: %s (%d)", strerror(-err), -err);
+	} else if (NULL == recs || NULL == recs->data) {
+		error("No records found");
+	} else if (sdp_get_service_classes(recs->data, &classes) < 0) {
+		error("Unable to get service classes from record");
+	} else {
+		memcpy(&uuid, classes->data, sizeof(uuid));
+            
+		if (0 == sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16) {
+			error("Not a 16 bit UUID");
+		} else if (uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
+			error("Service record didn't contain the HFP UUID");
+		} else if (0 == sdp_get_access_protos(recs->data, &protos)) {
+			ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+			sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
+					 NULL);
+			sdp_list_free(protos, NULL);
+			if (ch == -1) {
+				error("Unable to extract RFCOMM channel from service record");
+			}else { 
+				debug("Connecting on channel %d", ch);
+				if (bt_rfcomm_connect(&dev->src, &dev->dst, ch,
+						      rfcomm_connect_cb, dev) < 0){
+					error("Unable to connect: %s (%s)", strerror(-err), -err);
+					error_connection_attempt_failed(dev->conn, conn_mes, -err); 
+					gateway_close(dev->gateway);
+				} 
+				sdp_list_free(classes, free);
+				sdp_record_free(recs->data);
+				return; 
+			}                
+		}
+	}
+	sdp_list_free(classes, free);
+	sdp_record_free(recs->data);
+	error_not_supported(dev->conn, conn_mes);
+	dbus_message_unref(conn_mes);
+	dev->gateway->connect_message = NULL; 
+}
+
+static int get_records(struct audio_device *device)
+{
+	uuid_t uuid;
+
+	sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID);
+	return bt_search_service(&device->src, &device->dst, &uuid,
+				 get_record_cb, device, NULL);
+}
+
+
+static DBusMessage* ag_connect(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	struct audio_device* au_dev = (struct audio_device*) data;
+	struct gateway* gw = au_dev->gateway;
+
+	if (NULL != gw->rfcomm) {
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".AlreadyConnected",
+					   "Already Connected");
+	} 
+    
+	gw->connect_message = dbus_message_ref(msg); 
+	if (get_records(au_dev) < 0){
+		dbus_message_unref(gw->connect_message);
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".ConnectAttemptFailed",
+					   "Connect Attempt Failed");
+	}
+	debug("!!! Connection established");
+	return NULL;
+}
+
+static DBusMessage* ag_disconnect(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	DBusMessage* reply = NULL;
+	char gw_addr[18];
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (NULL == gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".NotConnected",
+					   "Device not Connected");
+
+	gateway_close(gw);
+	ba2str(&device->dst, gw_addr);
+	debug("Disconnected from %s, %s", gw_addr, device->path);
+
+	return reply;
+
+}
+
+static DBusMessage* ag_is_connected(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	gboolean res = gateway_is_connected((struct audio_device*)data);
+	DBusMessage* reply = dbus_message_new_method_return(msg);
+	if (NULL != reply) {
+		dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &res,
+					 DBUS_TYPE_INVALID);
+	}
+	return reply;
+}
+
+static DBusMessage* process_ag_reponse(DBusMessage* msg, gchar* response) 
+{
+	DBusMessage* reply;
+	if (NULL != strstr(response, OK_RESPONSE)) {
+		reply = dbus_message_new_method_return(msg);
+	} else {
+		/* FIXME: some code should be here to processes errors in better fasion */
+		debug("AG responded with '%s' to %s method call", response, dbus_message_get_member(msg));
+		reply = dbus_message_new_error(msg, ERROR_INTERFACE".OperationFailed", "Operation failed. See log for details");
+	}
+	return reply;
+}
+
+static DBusMessage* process_simple(DBusMessage* msg, struct audio_device* dev, gchar* data) 
+{
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+
+	rfcomm_stop_watch(dev);
+	rfcomm_send_and_read(gw, data, buf, strlen(data));
+	rfcomm_start_watch(dev);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_ANSWER "ATA\r"
+
+static DBusMessage* ag_answer(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	return process_simple(msg, (struct audio_device*) data, AG_ANSWER);
+}
+
+#define AG_CANCEL_CUR "AT+CHUP\r"
+
+static DBusMessage* ag_cancel_cur(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	return process_simple(msg, (struct audio_device*) data, AG_CANCEL_CUR);
+}
+
+#define ALLOWED_NUMBER_SYMBOLS "1234567890*#+ABC" /* according to GSM spec */
+#define AG_PLACE_CALL "ATD%s\r"
+/* dialing from memory is not supported as headset spec doesn't define a way
+   to retreive phone memory entries.
+*/
+static DBusMessage* ag_call(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar* number;
+	gint atd_len;
+    
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,	DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS)) {
+		return dbus_message_new_error(msg, ERROR_INTERFACE".BadNumber"
+					      , "Number contains characters which are not allowed");
+	}
+	atd_len = sprintf(buf, AG_PLACE_CALL, number);
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, buf, buf, atd_len);
+	rfcomm_start_watch(device);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_GET_CARRIER "AT+COPS?\r"
+
+static DBusMessage* ag_get_operator(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	struct audio_device* dev = (struct audio_device*) data;
+	struct gateway* gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	GIOChannel* rfcomm = gw->rfcomm;
+	gsize read;
+	gchar* result;
+	DBusMessage* reply;
+	GIOStatus status;
+
+	rfcomm_stop_watch(dev);
+	io_channel_write_all(rfcomm, AG_GET_CARRIER, strlen(AG_GET_CARRIER));
+    
+	status = g_io_channel_read_chars(rfcomm, buf, RFCOMM_BUF_SIZE - 1, &read, NULL);
+	rfcomm_start_watch(dev);
+	if (G_IO_STATUS_NORMAL == status) {
+		buf[read] = '\0';
+		if (NULL != strstr(buf, "+COPS")) {
+			result = strrchr(buf, ',') + 1;
+			reply = dbus_message_new_method_return(msg);
+			dbus_message_append_args(reply, DBUS_TYPE_STRING, &result, DBUS_TYPE_INVALID);
+		} else {
+			debug("ag_get_operator(): '+COPS' expected but '%s' received", buf);
+			reply = dbus_message_new_error(msg, ERROR_INTERFACE".Failed", "Unexpected response from AG");
+		}
+	} else {
+		debug("ag_get_operator(): %m");
+		reply = dbus_message_new_error(msg, ERROR_INTERFACE".ConnectionFailed", "Failed to receive response from AG");
+	}
+	return reply;
+}
+
+#define AG_VR_ACTIVATE "AT+BVRA=1\r"
+static DBusMessage* ag_vr_activate(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	return process_simple(msg, (struct audio_device*)data, AG_VR_ACTIVATE);
+}
+
+#define AG_VR_DEACTIVATE "AT+BVRA=0\r"
+static DBusMessage* ag_vr_deactivate(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	return process_simple(msg, (struct audio_device*)data, AG_VR_DEACTIVATE);
+}
+
+#define AG_REQUEST_NUMBER "AT+BINP=1\r"
+static DBusMessage* ag_get_number_for_voice(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar number[AG_CALLER_NUM_SIZE];
+    
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, AG_REQUEST_NUMBER, buf, strlen(AG_REQUEST_NUMBER));
+	rfcomm_start_watch(device);
+	if (1 == sscanf(buf, "\r\n+BINP:%s\r\n", number)) {
+		DBusMessage* reply = dbus_message_new_method_return(msg);
+		dbus_message_append_args(reply, DBUS_TYPE_STRING, number, DBUS_TYPE_INVALID);
+		return reply;
+	} else {
+		debug("ag_get_number_for_voice(): AG returned '%s' in place of number", buf);
+		return dbus_message_new_error(msg, ERROR_INTERFACE".Failed", "AG didn't return a number");
+	}
+}
+
+#define AG_SEND_DTMF "AT+VTS:%c\r"
+static DBusMessage* ag_send_dtmf(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar* number;
+	gint com_len;
+    
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,	DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS)) {
+		return dbus_message_new_error(msg, ERROR_INTERFACE".BadNumber"
+					      , "Number contains characters which are not allowed");
+	}
+	gboolean got_ok = TRUE;
+	gint num_len = strlen(number);
+	gint i = 0;
+	rfcomm_stop_watch(device);
+	while (i < num_len && got_ok) {
+		com_len = sprintf(buf, AG_SEND_DTMF, number[i]);
+		rfcomm_send_and_read(gw, buf, buf, com_len);
+		got_ok = NULL != strstr(buf, OK_RESPONSE);
+		i += 1;
+	}
+	rfcomm_start_watch(device);
+	return process_ag_reponse(msg, buf);
+    
+}
+
+#define AG_SET_MIC_GAIN "AT+VGM:%d\r"
+static DBusMessage* ag_send_mic_gain(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	gint value;
+	gchar buf[RFCOMM_BUF_SIZE];
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &value, DBUS_TYPE_INVALID);
+	sprintf(buf, AG_SET_MIC_GAIN, value);
+	return process_simple(msg, (struct audio_device*)data, buf);
+}
+
+
+#define AG_SET_SPEAKER_GAIN "AT+VGS:%d\r"
+static DBusMessage* ag_send_speaker_gain(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	gint value;
+	gchar buf[RFCOMM_BUF_SIZE];
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &value, DBUS_TYPE_INVALID);
+	sprintf(buf, AG_SET_SPEAKER_GAIN, value);
+	return process_simple(msg, (struct audio_device*)data, buf);
+}
+
+#define AG_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
+#define CNUM_LEN 5 /* length of "+CNUM" string */
+#define MAX_NUMBER_CNT 16
+static DBusMessage* ag_get_subscriber_nums(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar* cur_cnum;
+	gchar numbers[MAX_NUMBER_CNT][AG_CALLER_NUM_SIZE];
+	int number_cnt = 0;
+
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf, strlen(AG_GET_SUBSCRIBER_NUMS));
+	rfcomm_start_watch(device);
+	cur_cnum = strstr(buf, "+CNUM");
+	while (NULL != cur_cnum && number_cnt < MAX_NUMBER_CNT) {
+		sscanf(cur_cnum, "+CNUM:%s,", numbers[number_cnt]);
+		number_cnt += 1;
+		cur_cnum = strstr(cur_cnum + CNUM_LEN, "+CNUM");
+	}
+	DBusMessage* reply = dbus_message_new_method_return(msg);
+	dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &numbers, number_cnt, DBUS_TYPE_INVALID);
+	return reply;
+}
+
+static DBusMessage* ag_send_audio_data(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+	gchar* audio_data;
+	gint data_byte_cnt;
+	struct audio_device* dev = (struct audio_device*) data;
+	struct gateway* gw = dev->gateway;
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE
+			      , &audio_data, &data_byte_cnt, DBUS_TYPE_INVALID);
+    
+	if (io_channel_write_all(gw->sco, audio_data, data_byte_cnt)) {
+		return dbus_message_new_method_return(msg);
+	} else {
+		debug("ag_send_audio_data(): %m");
+		return dbus_message_new_error(msg, ERROR_INTERFACE".WriteFailed", "SCO data send failed");
+	}
+}
+
+static DBusMessage* ag_hold_all_but_this(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+
+}
+
+static DBusMessage* ag_cancel_all(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+
+}
+
+static DBusMessage* ag_cancel_spec(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+
+}
+
+static DBusMessage* ag_rah_put_on_hold(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+
+}
+
+static DBusMessage* ag_rah_answer_held_call(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+
+}
+
+static DBusMessage* ag_rah_cancel_held_call(DBusConnection* conn, DBusMessage* msg, void* data) 
+{
+
+}
+
+static GDBusMethodTable gateway_methods[] = {
+	{"Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC}
+	, {"Disconnect", "", "", ag_disconnect}
+	, {"IsConnected", "", "b", ag_is_connected}
+	, {"AnswerCall", "", "", ag_answer}
+	, {"CancelCurrentCall", "", "", ag_cancel_cur}
+	, {"Call", "s", "", ag_call}
+	, {"GetOperatorName", "", "s", ag_get_operator}
+	, {"VoiceRecognitionActvate", "", "", ag_vr_activate}
+	, {"VoiceRecognitionDeactvate", "", "", ag_vr_deactivate}
+	, {"GetNumberForLastVoiceTag", "", "s", ag_get_number_for_voice}
+	, {"SendDTMF", "s", "", ag_send_dtmf}
+	, {"SendMicrophoneGain", "y", "", ag_send_mic_gain}
+	, {"SendSpeakerGain", "y", "", ag_send_speaker_gain}
+	, {"GetSubscriberNumbers", "", "as", ag_get_subscriber_nums}
+
+	, {"RAHPutOnHold", "", "", ag_rah_put_on_hold}
+	, {"RAHAnswerHeldCall", "", "", ag_rah_answer_held_call}
+	, {"RAHCancelHeldCall", "", "", ag_rah_cancel_held_call}
+
+	, {"CancelCallFromNumber", "s", "", ag_cancel_spec}
+	, {"CancelAllCalls", "", "", ag_cancel_all}
+	, {"HoldAllButThis", "s", "", ag_hold_all_but_this}
+
+	, {"SendAudioData", "ay", "", ag_send_audio_data}
+	, {NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable gateway_signals[] = {
+	{"Connected", "u"}
+	, {"Disconnected", ""}
+	, {"Ring", "s"}
+	, {"StatusUpdate", "si"}
+	, {"CallCancelled", ""}
+	, {"CallStart", ""}
+	, {"CallEnd", ""}
+	, {"VoiceRecognitionActive", ""}
+	, {"VoiceRecognitionInactive", ""}
+	, {"MicrophoneGain", "y"}
+	, {"SpeakerGain", "y"}
+	, {"RAHCallOnHold", ""}
+	, {"NoCallsHeld", ""}
+	, {"HoldCalls", ""}
+	, {"AllCallsHeld", ""}
+	, {"AudioData", "ay"}
+	, {NULL, NULL }
+};
+
+gboolean gateway_is_connected(struct audio_device* dev)
+{
+	return (NULL == dev) || (NULL == dev->gateway) || (NULL == dev->gateway->rfcomm);   
+}
+
+struct gateway* gateway_init(struct audio_device* dev)
+{
+	debug("!!! Initializing gateway");
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+				       AUDIO_GATEWAY_INTERFACE,
+				       gateway_methods, gateway_signals, NULL,
+				       dev, NULL)) {
+		return NULL;
+	}
+	struct gateway* gw = g_new0(struct gateway, 1);
+	gw->indies = NULL;
+	gw->is_dialing = FALSE;
+	debug("!!! GW init success");
+	return gw;
+}
+
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
+{
+	if (!io)
+		return -EINVAL;
+
+	dev->gateway->rfcomm = io;
+
+	return 0;
+}
+
+static void indicator_slice_free(gpointer mem) 
+{
+	g_slice_free(struct indicator, mem);
+}
+
+int gateway_close(struct gateway* gw)
+{
+	GIOChannel *rfcomm = gw->rfcomm;
+	GIOChannel *sco = gw->sco;
+	GIOChannel *sco_server = gw->sco_server;
+	g_slist_foreach(gw->indies, (GFunc)indicator_slice_free, NULL);
+	g_slist_free(gw->indies);
+	if (rfcomm) {
+		g_io_channel_close(rfcomm);
+		g_io_channel_unref(rfcomm);
+		gw->rfcomm = NULL;
+	}
+  
+	if (sco) {
+		g_io_channel_close(sco);
+		g_io_channel_unref(sco);
+		gw->sco = NULL;
+	}
+
+	if (sco_server) {
+		g_io_channel_close(sco_server);
+		g_io_channel_unref(sco_server);
+		gw->sco_server = NULL;
+	}
+	return 0;
+}
+
Index: audio/gateway.h
===================================================================
RCS file: /cvsroot/bluez/utils/audio/gateway.h,v
retrieving revision 1.9
diff -w -u -r1.9 gateway.h
--- audio/gateway.h	21 Apr 2008 12:49:16 -0000	1.9
+++ audio/gateway.h	15 Dec 2008 23:04:46 -0000
@@ -21,14 +21,19 @@
  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
+#ifndef __GATEWAY_H__
+#define __GATEWAY_H__
 
+#include "device.h"
 #define AUDIO_GATEWAY_INTERFACE "org.bluez.audio.Gateway"
 
 #define DEFAULT_HSP_HS_CHANNEL 6
 #define DEFAULT_HFP_HS_CHANNEL 7
 
-int gateway_init(DBusConnection *conn, gboolean disable_hfp, gboolean sco_hci);
+struct gateway* gateway_init(struct audio_device *device);
 
-void gateway_exit(void);
+gboolean gateway_is_connected(struct audio_device* dev);
 
-gboolean gateway_is_enabled(uint16_t svc);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+
+#endif /*__GATEWAY_H__*/
Index: audio/manager.c
===================================================================
RCS file: /cvsroot/bluez/utils/audio/manager.c,v
retrieving revision 1.115
diff -w -u -r1.115 manager.c
--- audio/manager.c	17 Jun 2008 19:37:36 -0000	1.115
+++ audio/manager.c	15 Dec 2008 23:04:52 -0000
@@ -105,12 +105,12 @@
 static uint32_t hsp_ag_record_id = 0;
 static uint32_t hfp_ag_record_id = 0;
 
-static uint32_t hsp_hs_record_id = 0;
+static uint32_t hfp_hs_record_id = 0;
 
 static GIOChannel *hsp_ag_server = NULL;
 static GIOChannel *hfp_ag_server = NULL;
 
-static GIOChannel *hsp_hs_server = NULL;
+static GIOChannel *hfp_hs_server = NULL;
 
 static struct enabled_interfaces enabled = {
 	.headset	= TRUE,
@@ -231,13 +231,13 @@
 		ret = (hsp_ag_server != NULL);
 		break;
 	case HEADSET_AGW_SVCLASS_ID:
-		ret = (hsp_hs_server != NULL);
+		ret = FALSE;
 		break;
 	case HANDSFREE_SVCLASS_ID:
 		ret = (hfp_ag_server != NULL);
 		break;
 	case HANDSFREE_AGW_SVCLASS_ID:
-		ret = FALSE;
+		ret = (hfp_hs_server != NULL);
 		break;
 	case AUDIO_SINK_SVCLASS_ID:
 		return enabled.sink;
@@ -284,6 +284,9 @@
 		break;
 	case HANDSFREE_AGW_SVCLASS_ID:
 		debug("Found Handsfree AG record");
+		if (device->gateway == NULL) {
+			device->gateway = gateway_init(device);
+		}
 		break;
 	case AUDIO_SINK_SVCLASS_ID:
 		debug("Found Audio Sink");
@@ -500,6 +503,15 @@
 			return NULL;
 
 		headset = TRUE;
+	} else if (!strcmp(uuid, HFP_HS_UUID)) {
+		if (device->gateway) {
+			return device;
+		}
+
+		device->gateway = gateway_init(device);
+		if (NULL == device->gateway) {
+			return NULL;
+		}
 	} else if (!strcmp(uuid, A2DP_SOURCE_UUID)) {
 		if (device->sink)
 			return device;
@@ -516,9 +528,9 @@
 
 		if (!device->control)
 			return NULL;
-	} else
+	} else {
 		return NULL;
-
+	}
 	path = device->path;
 
 	if (created) {
@@ -886,6 +898,8 @@
 
 	if (enabled.headset && strstr(value, "headset"))
 		device->headset = headset_init(device, NULL, 0);
+	if (enabled.gateway && strstr(value, "gateway"))
+		device->gateway = gateway_init(device);
 	if (enabled.sink && strstr(value, "sink"))
 		device->sink = sink_init(device);
 	if (enabled.control && strstr(value, "control"))
@@ -1029,7 +1043,7 @@
 	return record;
 }
 
-static sdp_record_t *hsp_hs_record(uint8_t ch)
+static sdp_record_t *hfp_hs_record(uint8_t ch)
 {
 	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
 	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
@@ -1047,13 +1061,13 @@
 	root = sdp_list_append(0, &root_uuid);
 	sdp_set_browse_groups(record, root);
 
-	sdp_uuid16_create(&svclass_uuid, HEADSET_SVCLASS_ID);
+	sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID);
 	svclass_id = sdp_list_append(0, &svclass_uuid);
 	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
 	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
 	sdp_set_service_classes(record, svclass_id);
 
-	sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
+	sdp_uuid16_create(&profile.uuid, HANDSFREE_SVCLASS_ID);
 	profile.version = 0x0100;
 	pfseq = sdp_list_append(0, &profile);
 	sdp_set_profile_descs(record, pfseq);
@@ -1150,7 +1164,7 @@
 	return record;
 }
 
-static void auth_cb(DBusError *derr, void *user_data)
+static void headset_auth_cb(DBusError *derr, void *user_data)
 {
 	struct audio_device *device = user_data;
 	const char *uuid;
@@ -1217,7 +1231,7 @@
 		goto drop;
 	}
 
-	err = service_req_auth(&device->src, &device->dst, uuid, auth_cb,
+	err = service_req_auth(&device->src, &device->dst, uuid, headset_auth_cb,
 				device);
 	if (err < 0) {
 		debug("Authorization denied: %s", strerror(-err));
@@ -1235,10 +1249,57 @@
 	return;
 }
 
-static void hs_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+static void gateway_auth_cb(DBusError *derr, void *user_data)
+{
+	struct audio_device *device = user_data;
+
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+		if (dbus_error_has_name(derr, DBUS_ERROR_NO_REPLY)) {
+			debug("Canceling authorization request");
+			service_cancel_auth(&device->src, &device->dst);
+		}
+	} else {
+		char hs_address[18];
+
+		headset_set_authorized(device);
+
+		ba2str(&device->dst, hs_address);
+
+		debug("Accepted headset connection from %s for %s", hs_address, device->path);
+	}
+}
+
+static void hf_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
 		const bdaddr_t *dst, void *data)
 {
-	/*Stub*/
+	const char *uuid;
+	struct audio_device *device;
+	gboolean hfp_active;
+
+	if (err < 0) {
+		error("accept: %s (%d)", strerror(-err), -err);
+		return;
+	}
+
+	uuid = HFP_HS_UUID;
+
+	device = manager_device_connected(dst, uuid);
+	if (!device) {
+		debug("Device creation failed");
+	} else if (gateway_is_connected(device)) {
+		debug("Refusing new connection since one already exists");
+	} else if (service_req_auth(&device->src, 
+				    &device->dst, uuid, gateway_auth_cb, device) < 0) {
+		debug("Authorization denied: %s", strerror(-err));
+	} else if (gateway_connect_rfcomm(device, chan) < 0){
+		error("Allocating new GIOChannel failed!");
+	} else {
+		return;
+	}
+
+	g_io_channel_close(chan);
+	g_io_channel_unref(chan);
 	return;
 }
 
@@ -1332,22 +1393,19 @@
 
 static int gateway_server_init(DBusConnection *conn, GKeyFile *config)
 {
-	uint8_t chan = DEFAULT_HSP_HS_CHANNEL;
+	uint8_t chan = DEFAULT_HFP_HS_CHANNEL;
 	sdp_record_t *record;
 	gboolean master = TRUE;
 	GError *err = NULL;
 	uint32_t flags;
 
-	if (!enabled.gateway)
-		return 0;
-
 	if (config) {
 		gboolean tmp;
 
 		tmp = g_key_file_get_boolean(config, "General", "Master",
 						&err);
 		if (err) {
-			debug("audio.conf: %s", err->message);
+			debug("Gateway: audio.conf: %s", err->message);
 			g_error_free(err);
 			err = NULL;
 		} else
@@ -1358,13 +1416,13 @@
 
 	if (master)
 		flags |= RFCOMM_LM_MASTER;
-
-	hsp_hs_server = bt_rfcomm_listen(BDADDR_ANY, chan, flags, hs_io_cb,
+	debug("!!! Start listen for rfcomm");
+	hfp_hs_server = bt_rfcomm_listen(BDADDR_ANY, chan, flags, hf_io_cb,
 				NULL);
-	if (!hsp_hs_server)
+	if (!hfp_hs_server)
 		return -1;
 
-	record = hsp_hs_record(chan);
+	record = hfp_hs_record(chan);
 	if (!record) {
 		error("Unable to allocate new service record");
 		return -1;
@@ -1373,12 +1431,12 @@
 	if (add_record_to_server(BDADDR_ANY, record) < 0) {
 		error("Unable to register HSP HS service record");
 		sdp_record_free(record);
-		g_io_channel_unref(hsp_hs_server);
-		hsp_hs_server = NULL;
+		g_io_channel_unref(hfp_hs_server);
+		hfp_hs_server = NULL;
 		return -1;
 	}
 
-	hsp_hs_record_id = record->handle;
+	hfp_hs_record_id = record->handle;
 
 	return 0;
 }
@@ -1395,14 +1453,14 @@
 		hsp_ag_server = NULL;
 	}
 
-	if (hsp_hs_record_id) {
-		remove_record_from_server(hsp_hs_record_id);
-		hsp_hs_record_id = 0;
+	if (hfp_hs_record_id) {
+		remove_record_from_server(hfp_hs_record_id);
+		hfp_hs_record_id = 0;
 	}
 
-	if (hsp_hs_server) {
-		g_io_channel_unref(hsp_hs_server);
-		hsp_hs_server = NULL;
+	if (hfp_hs_server) {
+		g_io_channel_unref(hfp_hs_server);
+		hfp_hs_server = NULL;
 	}
 
 	if (hfp_ag_record_id) {
@@ -1423,8 +1481,9 @@
 
 	connection = dbus_connection_ref(conn);
 
-	if (!config)
+	if (!config) {
 		goto proceed;
+	}
 
 	list = g_key_file_get_string_list(config, "General", "Enable",
 						NULL, NULL);
@@ -1463,7 +1522,6 @@
 		if (headset_server_init(conn, config) < 0)
 			goto failed;
 	}
-
 	if (enabled.gateway) {
 		if (gateway_server_init(conn, config) < 0)
 			goto failed;
Index: common/glib-helper.c
===================================================================
RCS file: /cvsroot/bluez/utils/common/glib-helper.c,v
retrieving revision 1.39
diff -w -u -r1.39 glib-helper.c
--- common/glib-helper.c	30 Jun 2008 14:05:38 -0000	1.39
+++ common/glib-helper.c	15 Dec 2008 23:04:57 -0000
@@ -41,6 +41,7 @@
 #include <glib.h>
 
 #include "glib-helper.h"
+#include "logging.h"
 
 typedef int (*resolver_t) (int fd, char *src, char *dst);
 typedef BtIOError (*connect_t) (BtIO *io, BtIOFunc func);
@@ -727,38 +728,68 @@
 	return 0;
 }
 
-static BtIOError sco_connect(BtIO *io, BtIOFunc func)
-{
-	struct io_context *io_ctxt = io->io_ctxt;
+static BtIOError sco_bind(struct io_context *io_ctxt, const char *address) {
 	struct sockaddr_sco addr;
-	int sk, err;
-
-	io_ctxt->func = func;
-
-	sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
-	if (sk < 0)
-		return -errno;
+	BtIOError err;
+        int sco_fd;
 
-	memset(&addr, 0, sizeof(addr));
+	sco_fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+	if (sco_fd < 0) {
+                return BT_IO_FAILED;
+        }
+        debug("sco_bind(): socket %d created", sco_fd);
 	addr.sco_family = AF_BLUETOOTH;
-	str2ba(io->src, &addr.sco_bdaddr);
+	str2ba(address, &addr.sco_bdaddr);
 
-	err = bind(sk, (struct sockaddr *) &addr, sizeof(addr));
+	err = bind(sco_fd, (struct sockaddr *) &addr, sizeof(addr));
 	if (err < 0) {
-		close(sk);
+                close(sco_fd);
 		return BT_IO_FAILED;
 	}
+        debug("sco_bind(): socket %d bind", sco_fd);
+        io_ctxt->fd = sco_fd;
+	return BT_IO_SUCCESS;
+}
+
+static BtIOError sco_listen(BtIO *io, BtIOFunc func) {
+	struct io_context *io_ctxt = io->io_ctxt;
+	BtIOError err;
+
+	io_ctxt->func = func;
+	err = sco_bind(io_ctxt, io->src);
+	if (err != BT_IO_SUCCESS) {
+                debug("sco_listen(): %m");
+                return err;
+        }
 
-	io_ctxt->fd = sk;
+	err = transport_listen(io);
+        debug("sco_listen(): %s", strerror(-err));
+	if (err < 0) {
+                close(io_ctxt->fd);
+                return err;
+	}
+	return BT_IO_SUCCESS;
+}
+
+static BtIOError sco_connect(BtIO *io, BtIOFunc func)
+{
+	struct io_context *io_ctxt = io->io_ctxt;
+	struct sockaddr_sco addr;
+	int sk, err;
+
+	io_ctxt->func = func;
+
+	err = sco_bind(io_ctxt, io->src);
+	if (err < 0)
+		return err;
 
-	memset(&addr, 0, sizeof(addr));
 	addr.sco_family = AF_BLUETOOTH;
 	str2ba(io->dst, &addr.sco_bdaddr);
 
 	err = transport_connect(io, (struct sockaddr *) &addr,
 				sizeof(addr));
 	if (err < 0) {
-		close(sk);
+		close(io_ctxt->fd);
 		return BT_IO_FAILED;
 	}
 
@@ -1020,6 +1051,25 @@
 
 }
 
+GIOChannel* bt_sco_listen(const bdaddr_t *src, bt_io_callback_t cb, void *user_data)
+{
+	BtIO *io;
+	BtIOError err;
+        
+	io = bt_io_create(BT_IO_SCO, user_data, NULL);
+	if (!io)
+                return NULL;
+	ba2str(src, io->src);
+	io->io_ctxt->cb = cb;
+	err = bt_io_listen(io, NULL, NULL);
+	if (err != BT_IO_SUCCESS) {
+                bt_io_unref(io);
+                return NULL;
+	}
+        debug("Returning %p", io->io_ctxt->io);
+	return io->io_ctxt->io;
+}
+
 int bt_rfcomm_connect(const bdaddr_t *src, const bdaddr_t *dst,
 			uint8_t channel, bt_io_callback_t cb, void *user_data)
 {
@@ -1144,6 +1194,7 @@
 		err = create_io_context(&io->io_ctxt, NULL, NULL,
 				sco_resolver, user_data);
 		io->connect = sco_connect;
+		io->listen = sco_listen;
 		break;
 	default:
 		return NULL;
Index: common/glib-helper.h
===================================================================
RCS file: /cvsroot/bluez/utils/common/glib-helper.h,v
retrieving revision 1.21
diff -w -u -r1.21 glib-helper.h
--- common/glib-helper.h	30 Jun 2008 14:05:38 -0000	1.21
+++ common/glib-helper.h	15 Dec 2008 23:04:58 -0000
@@ -57,6 +57,7 @@
 int bt_sco_connect(const bdaddr_t *src, const bdaddr_t *dst,
 			bt_io_callback_t cb, void *user_data);
 
+GIOChannel* bt_sco_listen(const bdaddr_t *src, bt_io_callback_t cb, void *user_data);
 /* Experiemental bt_io API */
 
 typedef struct bt_io BtIO;

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
  2008-12-15 23:14 ` [PATCH] Gateway profile event
@ 2008-12-17  2:48   ` Marcel Holtmann
  2008-12-19  7:36     ` event
  2009-01-16 13:22     ` event
  0 siblings, 2 replies; 10+ messages in thread
From: Marcel Holtmann @ 2008-12-17  2:48 UTC (permalink / raw)
  To: event; +Cc: linux-bluetooth

Hi Leonid,

> I've implemented gateway profile. I've tested basic things, like
> place/cancel/answer call. Others are in development. Some could not be
> tested as my carrier doesn't provide corresponding services (like
> 3-way call, etc.) so any help welcome.

thanks for the works, but can you please base the patch against the
latest GIT tree. It is kinda hard to review things that might already
have been implemented like sco_listen.

 audio/audio-api.txt  |   94 +++++
 audio/device.h       |    7 
 audio/gateway.c      |  938 +++++++++++++++++++++++++++++++++++++++++++++++++++
 audio/gateway.h      |   11 
 audio/manager.c      |  124 ++++--
 common/glib-helper.c |   85 +++-
 common/glib-helper.h |    1 
 7 files changed, 1205 insertions(+), 55 deletions(-)

So any changes to glib-helper.[ch] have to be in a separate patch and
need to be discussed independent from the gateway implementation.

Any audio-api.txt stuff should also go separately since that has to be
discussed. Also we can't send PCM data over D-Bus. It just doesn't work
like that. We do have the internal IPC for that and plugins for ALSA,
GStreamer and PulseAudio that should be used.

However the most important part is that you follow the coding style and
that is the kernel coding style. You make it really hard for us to
review the code like this and it can't be applied. I really want you to
add support for the gateway role to BlueZ, but the overall code in the
project needs to follow the same rules.

So please fix these issues first and then we do a deep review of it.

Regards

Marcel



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
  2008-12-17  2:48   ` Marcel Holtmann
@ 2008-12-19  7:36     ` event
  2009-01-16 13:22     ` event
  1 sibling, 0 replies; 10+ messages in thread
From: event @ 2008-12-19  7:36 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

[-- Attachment #1: Type: text/plain, Size: 2036 bytes --]

Hello Marcel,

Thank you for your comments. Here are reworked fixed patches attached.
I've missed when you changed repo :).

I've noticed IPC infrastructure and going to make use of it in future version.

I fully agree for code style. Sorry for that. Was too stupid to setup
my editor correctly :).

Vale,
event



On Wed, Dec 17, 2008 at 04:48, Marcel Holtmann <marcel@holtmann.org> wrote:
> Hi Leonid,
>
>> I've implemented gateway profile. I've tested basic things, like
>> place/cancel/answer call. Others are in development. Some could not be
>> tested as my carrier doesn't provide corresponding services (like
>> 3-way call, etc.) so any help welcome.
>
> thanks for the works, but can you please base the patch against the
> latest GIT tree. It is kinda hard to review things that might already
> have been implemented like sco_listen.
>
>  audio/audio-api.txt  |   94 +++++
>  audio/device.h       |    7
>  audio/gateway.c      |  938 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  audio/gateway.h      |   11
>  audio/manager.c      |  124 ++++--
>  common/glib-helper.c |   85 +++-
>  common/glib-helper.h |    1
>  7 files changed, 1205 insertions(+), 55 deletions(-)
>
> So any changes to glib-helper.[ch] have to be in a separate patch and
> need to be discussed independent from the gateway implementation.
>
> Any audio-api.txt stuff should also go separately since that has to be
> discussed. Also we can't send PCM data over D-Bus. It just doesn't work
> like that. We do have the internal IPC for that and plugins for ALSA,
> GStreamer and PulseAudio that should be used.
>
> However the most important part is that you follow the coding style and
> that is the kernel coding style. You make it really hard for us to
> review the code like this and it can't be applied. I really want you to
> add support for the gateway role to BlueZ, but the overall code in the
> project needs to follow the same rules.
>
> So please fix these issues first and then we do a deep review of it.
>
> Regards
>
> Marcel
>
>
>

[-- Attachment #2: gateway.patch --]
[-- Type: application/octet-stream, Size: 38936 bytes --]

diff --git a/audio/device.h b/audio/device.h
index 80e1b10..60dbc76 100644
--- a/audio/device.h
+++ b/audio/device.h
@@ -22,6 +22,11 @@
  *
  */
 
+#ifndef __DEVICE_H__
+#define __DEVICE_H__
+
+#include <bluetooth/bluetooth.h>
+
 #define GENERIC_AUDIO_UUID	"00001203-0000-1000-8000-00805F9B34FB"
 
 #define HSP_HS_UUID		"00001108-0000-1000-8000-00805F9B34FB"
@@ -72,3 +77,6 @@ struct audio_device *device_register(DBusConnection *conn,
 void device_unregister(struct audio_device *device);
 
 gboolean device_is_connected(struct audio_device *dev, const char *interface);
+
+#endif /*__DEVICE_H__*/
+
diff --git a/audio/gateway.c b/audio/gateway.c
index a299d28..4f0297c 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -4,6 +4,7 @@
  *
  *  Copyright (C) 2006-2007  Nokia Corporation
  *  Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2008  Leonid Movshovich <event.riga@gmail.org>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -27,8 +28,1068 @@
 #endif
 
 #include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
 
+#include <fcntl.h>
 #include <glib.h>
 #include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
 
 #include "gateway.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "logging.h"
+
+#define SCO_BUF_SIZE 1024
+#define RFCOMM_BUF_SIZE 256
+#define AG_INDICATOR_DESCR_SIZE 20	/* not-more-then-16 defined by GSM + 1 for NULL + padding */
+#define AG_CALLER_NUM_SIZE 64	/* size of number + type */
+
+/* commands */
+#define AG_FEATURES "AT+BRSF=26\r"	// = 0x7F = All features supported
+#define AG_INDICATORS_SUPP "AT+CIND=?\r"
+#define AG_INDICATORS_VAL "AT+CIND?\r"
+#define AG_INDICATORS_ENABLE "AT+CMER=3,0,0,1\r"
+#define AG_HOLD_MPTY_SUPP "AT+CHLD=?\r"
+#define AG_CALLER_IDENT_ENABLE "AT+CLIP=1\r"
+#define AG_CARRIER_FORMAT "AT+COPS=3,0\r"
+#define AG_EXTENDED_RESULT_CODE "AT+CMEE=1\r"
+
+#define AG_FEATURE_3WAY 0x1
+#define AG_FEATURE_EXTENDED_RES_CODE 0x100
+/* Hold and multipary AG features. Comments below are copied from hands-free spec for reference */
+#define AG_CHLD_0 0x01		/* Releases all held calls or sets User Determined User Busy (UDUB) for a waiting call */
+#define AG_CHLD_1 0x02		/* Releases all active calls (if any exist) and accepts the other (held or waiting) call */
+#define AG_CHLD_1x 0x04		/* Releases specified active call only <x> */
+#define AG_CHLD_2 0x08		/* Places all active calls (if any exist) on hold and accepts the other (held or waiting) call */
+#define AG_CHLD_2x 0x10		/* Request private consultation mode with specified call <x> (Place all calls on hold EXCEPT the call <x>) */
+#define AG_CHLD_3 0x20		/* Adds a held call to the conversation */
+#define AG_CHLD_4 0x40		/* 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 OK_RESPONSE "\r\nOK\r\n"
+#define ERROR_RESPONSE "\r\nERROR\r\n"
+
+
+struct indicator {
+	gchar descr[AG_INDICATOR_DESCR_SIZE];
+	gint value;
+};
+
+struct gateway {
+	GIOChannel *rfcomm;
+	guint rfcomm_watch_id;
+	GIOChannel *sco;
+	GIOChannel *sco_server;
+	DBusMessage *connect_message;
+	guint ag_features;
+	guint hold_multiparty_features;
+	GSList *indies;
+	gboolean is_dialing;
+};
+
+
+static gboolean rfcomm_ag_data_cb(GIOChannel * chan, GIOCondition cond,
+				  struct audio_device *device);
+
+static void rfcomm_start_watch(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+	gw->rfcomm_watch_id =
+	    g_io_add_watch(gw->rfcomm,
+			   G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+			   (GIOFunc) rfcomm_ag_data_cb, dev);
+}
+
+static void rfcomm_stop_watch(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+	g_source_remove(gw->rfcomm_watch_id);
+}
+
+static gboolean io_channel_write_all(GIOChannel * io, gchar * data,
+				     gsize count)
+{
+	gsize written = 0;
+	GIOStatus status;
+	while (0 < count) {
+		status =
+		    g_io_channel_write_chars(io, data, count, &written,
+					     NULL);
+		if (G_IO_STATUS_NORMAL != status) {
+			return FALSE;
+		}
+		data += written;
+		count -= written;
+	}
+	return TRUE;
+}
+
+/* it's worth to mention that data and response could be the same pointers */
+static gboolean rfcomm_send_and_read(struct gateway *gw, gchar * data,
+				     gchar * response, gsize count)
+{
+	GIOChannel *rfcomm = gw->rfcomm;
+
+	if (!io_channel_write_all(rfcomm, data, count)) {
+		return FALSE;
+	}
+
+	gsize read = 0;
+	gboolean got_ok = FALSE;
+	gboolean got_error = FALSE;
+	gchar *resp_buf = response;
+	gsize toread = RFCOMM_BUF_SIZE - 1;
+	GIOStatus status;
+	while (!(got_ok || got_error)) {
+		status =
+		    g_io_channel_read_chars(rfcomm, resp_buf, toread,
+					    &read, NULL);
+		if (G_IO_STATUS_NORMAL == status) {
+			resp_buf[read] = '\0';
+		} else {
+			debug("rfcomm_send_and_read(): %m");
+			return FALSE;
+		}
+		got_ok = NULL != strstr(resp_buf, OK_RESPONSE);
+		got_error = NULL != strstr(resp_buf, ERROR_RESPONSE);
+		resp_buf += read;
+		toread -= read;
+	}
+	return TRUE;
+}
+
+/* get <descr> from the names: (<descr>, (<values>)), (<descr>, (<values>)) ... */
+GSList *parse_indicator_names(gchar * names, GSList * indies)
+{
+	gchar *current = names - 1;
+	GSList *result = indies;
+	gchar *next;
+	while (NULL != current) {
+		current += 2;
+		next = strstr(current, ",(");
+		struct indicator *ind =
+		    (struct indicator *) g_slice_new(struct indicator);
+		strncpy(ind->descr, current, 20);
+		ind->descr[(gint) next - (gint) current] = '\0';
+		result = g_slist_append(result, (gpointer) ind);
+		current = strstr(next + 1, ",(");
+	}
+	return result;
+}
+
+/* get values from <val0>,<val1>,... */
+GSList *parse_indicator_values(gchar * values, GSList * indies)
+{
+	gint val;
+	gchar *current = values - 1;
+	GSList *runner = indies;
+	while (NULL != current) {
+		current += 1;
+		sscanf(current, "%d", &val);
+		current = strchr(current, ',');
+		struct indicator *ind =
+		    (struct indicator *) g_slist_nth_data(runner, 0);
+		ind->value = val;
+		runner = g_slist_next(runner);
+	}
+	return indies;
+}
+
+/* get values from <val0>,<val1>,... */
+static guint get_hold_mpty_features(gchar * features)
+{
+	guint result = 0;
+	if (NULL != strstr(features, "0")) {
+		result |= AG_CHLD_0;
+	}
+	if (NULL != strstr(features, "1")) {
+		result |= AG_CHLD_1;
+	}
+	if (NULL != strstr(features, "1x")) {
+		result |= AG_CHLD_1x;
+	}
+	if (NULL != strstr(features, "2")) {
+		result |= AG_CHLD_2;
+	}
+	if (NULL != strstr(features, "2x")) {
+		result |= AG_CHLD_2x;
+	}
+	if (NULL != strstr(features, "3")) {
+		result |= AG_CHLD_3;
+	}
+	if (NULL != strstr(features, "4")) {
+		result |= AG_CHLD_4;
+	}
+	return result;
+}
+
+static gboolean establish_service_level_conn(struct gateway *gw)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	gint read;
+	gboolean res;
+	res =
+	    rfcomm_send_and_read(gw, AG_FEATURES, buf,
+				 sizeof(AG_FEATURES) - 1);
+	if (!res || 1 != sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features)) {
+		return FALSE;
+	}
+	debug("features are 0x%X", gw->ag_features);
+	res =
+	    rfcomm_send_and_read(gw, AG_INDICATORS_SUPP, buf,
+				 sizeof(AG_INDICATORS_SUPP) - 1);
+	if (!res || !strstr(buf, "+CIND:")) {
+		return FALSE;
+	}
+	gw->indies = parse_indicator_names(strchr(buf, '('), NULL);
+
+	res =
+	    rfcomm_send_and_read(gw, AG_INDICATORS_VAL, buf,
+				 sizeof(AG_INDICATORS_VAL) - 1);
+	if (!res || !strstr(buf, "+CIND:")) {
+		return FALSE;
+	}
+	gw->indies =
+	    parse_indicator_values(strchr(buf, ':') + 1, gw->indies);
+
+	res =
+	    rfcomm_send_and_read(gw, AG_INDICATORS_ENABLE, buf,
+				 sizeof(AG_INDICATORS_ENABLE) - 1);
+	if (!res || !strstr(buf, "OK")) {
+		return FALSE;
+	}
+	if (0 != gw->ag_features & AG_FEATURE_3WAY) {
+		res =
+		    rfcomm_send_and_read(gw, AG_HOLD_MPTY_SUPP, buf,
+					 sizeof(AG_HOLD_MPTY_SUPP) - 1);
+		if (!res || !strstr(buf, "+CHLD:")) {
+			g_slice_free1(RFCOMM_BUF_SIZE, buf);
+			return FALSE;
+		}
+		gw->hold_multiparty_features =
+		    get_hold_mpty_features(strchr(buf, '('));
+	} else {
+		gw->hold_multiparty_features = 0;
+	}
+	debug("Service layer connection successfully established!");
+	rfcomm_send_and_read(gw, AG_CALLER_IDENT_ENABLE, buf,
+			     sizeof(AG_CALLER_IDENT_ENABLE) - 1);
+	rfcomm_send_and_read(gw, AG_CARRIER_FORMAT, buf,
+			     sizeof(AG_CARRIER_FORMAT) - 1);
+	if (0 != gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) {
+		rfcomm_send_and_read(gw, AG_EXTENDED_RESULT_CODE, buf,
+				     sizeof(AG_EXTENDED_RESULT_CODE) - 1);
+	}
+	return TRUE;
+}
+
+static void process_ind_change(struct audio_device *dev, gchar * name,
+			       gint value)
+{
+	struct gateway *gw = dev->gateway;
+	if (0 == strcmp(name, "call")) {
+		if (0 < value) {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "CallStart", DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+		} else {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "CallEnd", DBUS_TYPE_INVALID);
+		}
+	} else if (0 == strcmp(name, "callstatus")
+		   && gw->is_dialing) {
+		if (0 == value) {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "CallCancelled",
+					   DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+		} else {
+			gw->is_dialing = TRUE;
+		}
+	} else if (0 == strcmp(name, "callheld")) {
+		/* FIXME: The following code is based on assumptions only. Has to be tested for interoperability
+		   I assume that callheld=2 would be sent when dial from HF failed in case of 3-way call 
+		   Unfortunately this path is not covered by the HF spec so the code has to be tested for interop
+		 */
+		if (2 == value) {	/* '2' means: all calls held, no active calls */
+			if (gw->is_dialing) {
+				g_dbus_emit_signal(dev->conn, dev->path,
+						   AUDIO_GATEWAY_INTERFACE,
+						   "CallCancelled",
+						   DBUS_TYPE_INVALID);
+				gw->is_dialing == FALSE;
+			}
+		}
+	} else {
+		g_dbus_emit_signal(dev->conn, dev->path,
+				   AUDIO_GATEWAY_INTERFACE, "StatusUpdate",
+				   DBUS_TYPE_STRING, name, DBUS_TYPE_INT32,
+				   value, DBUS_TYPE_INVALID);
+	}
+}
+
+static void process_ring(struct audio_device *device, GIOChannel * chan,
+			 gchar * buf)
+{
+	gchar number[AG_CALLER_NUM_SIZE];
+	gchar *sep;
+	gint read;
+
+	rfcomm_stop_watch(device);
+	g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read,
+				NULL);
+	if (1 == sscanf(buf, "\r\n+CLIP:%s\r\n", number)) {
+		sep = strchr(number, ',');
+		sep[0] = '\0';
+		/* FIXME: signal will be emitted on each RING+CLIP. That's bad */
+		g_dbus_emit_signal(device->conn, device->path,
+				   AUDIO_GATEWAY_INTERFACE, "Ring",
+				   DBUS_TYPE_STRING, number,
+				   DBUS_TYPE_INVALID);
+	} else {
+		warn
+		    ("rfcomm_ag_data_cb(): '%s' in place of +CLIP after RING",
+		     buf);
+	}
+	rfcomm_start_watch(device);
+}
+
+static gboolean rfcomm_ag_data_cb(GIOChannel * chan, GIOCondition cond,
+				  struct audio_device *device)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	struct gateway *gw;
+	gint read;
+	gchar indicator[AG_INDICATOR_DESCR_SIZE + 4];	/* some space for value */
+	gint value;
+	gint result;
+	gchar *sep;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	gw = device->gateway;
+
+	if (cond & (G_IO_ERR | G_IO_HUP))
+		return FALSE;
+
+
+	if (G_IO_ERROR_NONE !=
+	    g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read,
+				    NULL))
+		return TRUE;
+
+	buf[read] = '\0';
+	if (1 == sscanf(buf, "\r\n+CIEV:%s\r\n", indicator)) {
+		sep = strchr(indicator, ',');
+		sep[0] = '\0';
+		sep += 1;
+		value = atoi(sep);
+		process_ind_change(device, indicator, value);
+	} else if (NULL != strstr(buf, "RING")) {
+		process_ring(device, chan, buf);
+	} else if (1 == sscanf(buf, "\r\n+BVRA:%d\r\n", value)) {
+		if (0 == value) {
+			g_dbus_emit_signal(device->conn, device->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "VoiceRecognitionActive",
+					   DBUS_TYPE_INVALID);
+		} else {
+			g_dbus_emit_signal(device->conn, device->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "VoiceRecognitionInactive",
+					   DBUS_TYPE_INVALID);
+		}
+	} else if (1 == sscanf(buf, "\r\n+VGS:%d\r\n", value)) {
+		g_dbus_emit_signal(device->conn, device->path,
+				   AUDIO_GATEWAY_INTERFACE, "SpeakerGain",
+				   DBUS_TYPE_INT32, value,
+				   DBUS_TYPE_INVALID);
+
+	} else if (1 == sscanf(buf, "\r\n+VGM:%d\r\n", value)) {
+		g_dbus_emit_signal(device->conn, device->path,
+				   AUDIO_GATEWAY_INTERFACE, "MicGain",
+				   DBUS_TYPE_INT32, value,
+				   DBUS_TYPE_INVALID);
+	} else {
+		error("rfcomm_ag_data_cb(): read wrong data '%s'", buf);
+	}
+	return TRUE;
+}
+
+static gboolean sco_io_cb(GIOChannel * chan, GIOCondition cond,
+			  struct audio_device *dev)
+{
+	gchar buf[SCO_BUF_SIZE];
+	gint read;
+	struct gateway *gw = dev->gateway;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		GIOChannel *chan = gw->sco;
+		g_io_channel_unref(chan);
+		g_io_channel_close(chan);
+		gw->sco = NULL;
+		return FALSE;
+	}
+
+	/* May be here some more data have to be read. Depends on result performance. 
+	   For Nokia E51 volume is 48 bytes per call */
+	if (G_IO_ERROR_NONE ==
+	    g_io_channel_read(chan, buf, sizeof(buf), &read)) {
+//    if (G_IO_ERROR_NONE == g_io_channel_read_chars(chan, buf, sizeof(buf) , &read, NULL)) {
+		gchar *p_buf = buf;
+		g_dbus_emit_signal(dev->conn, dev->path,
+				   AUDIO_GATEWAY_INTERFACE, "AudioData",
+				   DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &p_buf,
+				   read, DBUS_TYPE_INVALID);
+	}
+	return TRUE;
+}
+
+
+static void sco_connect_cb(GIOChannel * chan, int err,
+			   const bdaddr_t * src, const bdaddr_t * dst,
+			   gpointer user_data)
+{
+	struct audio_device *dev = (struct audio_device *) user_data;
+	struct gateway *gw = dev->gateway;
+
+	if (0 > err) {
+		error("sco_connect_cb(): %s (%d)", strerror(-err), -err);
+		/* not sure, but from other point of view, 
+		   what is the reason to have headset which cannot play audio? */
+		gateway_close(gw);
+		return;
+	}
+
+	gw->sco = chan;
+	fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);	/* why is this here? */
+	g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN,
+		       (GIOFunc) sco_io_cb, dev);
+}
+
+
+static void rfcomm_connect_cb(GIOChannel * chan, int err,
+			      const bdaddr_t * src, const bdaddr_t * dst,
+			      gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct gateway *gw = dev->gateway;
+	DBusMessage *conn_mes = gw->connect_message;
+	gchar gw_addr[18];
+
+	if (err < 0) {
+		error("connect(): %s (%d)", strerror(-err), -err);
+		return;
+	}
+
+	ba2str(&dev->dst, gw_addr);
+	/* Blocking mode should be default, but just in case: */
+	GIOFlags flags = g_io_channel_get_flags(chan);
+	flags &= ~G_IO_FLAG_NONBLOCK;
+	flags &= G_IO_FLAG_MASK;
+	g_io_channel_set_flags(chan, flags, NULL);
+	g_io_channel_set_encoding(chan, NULL, NULL);
+	g_io_channel_set_buffered(chan, FALSE);
+	gw->rfcomm = chan;
+	if (establish_service_level_conn(dev->gateway)) {
+		GIOChannel *sco_server =
+		    bt_sco_listen(&dev->src, 0, sco_connect_cb, dev);
+		if (NULL != sco_server) {
+			debug("%s: Connected to %s", dev->path, gw_addr);
+			rfcomm_start_watch(dev);
+			gw->sco_server = sco_server;
+			DBusMessage *reply =
+			    dbus_message_new_method_return(conn_mes);
+			dbus_connection_send(dev->conn, reply, NULL);
+			dbus_message_unref(reply);
+			dbus_message_unref(conn_mes);
+			gw->connect_message = NULL;
+
+			g_dbus_emit_signal(dev->conn, dev->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "Connected", DBUS_TYPE_UINT32,
+					   &gw->ag_features,
+					   DBUS_TYPE_INVALID);
+			return;
+		} else {
+			error("%s: Failed to setup SCO server socket",
+			      dev->path);
+		}
+	} else {
+		error
+		    ("%s: Failed to establish service layer connection to %s",
+		     dev->path, gw_addr);
+	}
+	gateway_close(gw);
+}
+
+static void get_record_cb(sdp_list_t * recs, int err, gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	DBusMessage *conn_mes = dev->gateway->connect_message;
+	int ch = -1;
+	sdp_list_t *protos, *classes = NULL;
+	sdp_record_t *record = NULL;
+	uuid_t uuid;
+
+	if (err < 0) {
+		error("Unable to get service record: %s (%d)",
+		      strerror(-err), -err);
+	} else if (NULL == recs || NULL == recs->data) {
+		error("No records found");
+	} else if (sdp_get_service_classes(recs->data, &classes) < 0) {
+		error("Unable to get service classes from record");
+	} else {
+		memcpy(&uuid, classes->data, sizeof(uuid));
+
+		if (0 == sdp_uuid128_to_uuid(&uuid)
+		    || uuid.type != SDP_UUID16) {
+			error("Not a 16 bit UUID");
+		} else if (uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
+			error
+			    ("Service record didn't contain the HFP UUID");
+		} else if (0 == sdp_get_access_protos(recs->data, &protos)) {
+			ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+			sdp_list_foreach(protos,
+					 (sdp_list_func_t) sdp_list_free,
+					 NULL);
+			sdp_list_free(protos, NULL);
+			if (ch == -1) {
+				error
+				    ("Unable to extract RFCOMM channel from service record");
+			} else {
+				debug("Connecting on channel %d", ch);
+				if (bt_rfcomm_connect
+				    (&dev->src, &dev->dst, ch,
+				     rfcomm_connect_cb, dev) < 0) {
+					error("Unable to connect: %s (%s)",
+					      strerror(-err), -err);
+					error_connection_attempt_failed
+					    (dev->conn, conn_mes, -err);
+					gateway_close(dev->gateway);
+				}
+				sdp_list_free(classes, free);
+				sdp_record_free(recs->data);
+				return;
+			}
+		}
+	}
+	sdp_list_free(classes, free);
+	sdp_record_free(recs->data);
+	error_not_supported(dev->conn, conn_mes);
+	dbus_message_unref(conn_mes);
+	dev->gateway->connect_message = NULL;
+}
+
+static int get_records(struct audio_device *device)
+{
+	uuid_t uuid;
+
+	sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID);
+	return bt_search_service(&device->src, &device->dst, &uuid,
+				 get_record_cb, device, NULL);
+}
+
+
+static DBusMessage *ag_connect(DBusConnection * conn, DBusMessage * msg,
+			       void *data)
+{
+	struct audio_device *au_dev = (struct audio_device *) data;
+	struct gateway *gw = au_dev->gateway;
+
+	if (NULL != gw->rfcomm) {
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".AlreadyConnected",
+					   "Already Connected");
+	}
+
+	gw->connect_message = dbus_message_ref(msg);
+	if (get_records(au_dev) < 0) {
+		dbus_message_unref(gw->connect_message);
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".ConnectAttemptFailed",
+					   "Connect Attempt Failed");
+	}
+	return NULL;
+}
+
+static DBusMessage *ag_disconnect(DBusConnection * conn, DBusMessage * msg,
+				  void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	DBusMessage *reply = NULL;
+	char gw_addr[18];
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (NULL == gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".NotConnected",
+					   "Device not Connected");
+
+	gateway_close(gw);
+	ba2str(&device->dst, gw_addr);
+	debug("Disconnected from %s, %s", gw_addr, device->path);
+
+	return reply;
+
+}
+
+static DBusMessage *ag_is_connected(DBusConnection * conn,
+				    DBusMessage * msg, void *data)
+{
+	gboolean res = gateway_is_connected((struct audio_device *) data);
+	DBusMessage *reply = dbus_message_new_method_return(msg);
+	if (NULL != reply) {
+		dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &res,
+					 DBUS_TYPE_INVALID);
+	}
+	return reply;
+}
+
+static DBusMessage *process_ag_reponse(DBusMessage * msg, gchar * response)
+{
+	DBusMessage *reply;
+	if (NULL != strstr(response, OK_RESPONSE)) {
+		reply = dbus_message_new_method_return(msg);
+	} else {
+		/* FIXME: some code should be here to processes errors in better fasion */
+		debug("AG responded with '%s' to %s method call", response,
+		      dbus_message_get_member(msg));
+		reply =
+		    dbus_message_new_error(msg,
+					   ERROR_INTERFACE
+					   ".OperationFailed",
+					   "Operation failed. See log for details");
+	}
+	return reply;
+}
+
+static DBusMessage *process_simple(DBusMessage * msg,
+				   struct audio_device *dev, gchar * data)
+{
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+
+	rfcomm_stop_watch(dev);
+	rfcomm_send_and_read(gw, data, buf, strlen(data));
+	rfcomm_start_watch(dev);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_ANSWER "ATA\r"
+
+static DBusMessage *ag_answer(DBusConnection * conn, DBusMessage * msg,
+			      void *data)
+{
+	return process_simple(msg, (struct audio_device *) data,
+			      AG_ANSWER);
+}
+
+#define AG_CANCEL_CUR "AT+CHUP\r"
+
+static DBusMessage *ag_cancel_cur(DBusConnection * conn, DBusMessage * msg,
+				  void *data)
+{
+	return process_simple(msg, (struct audio_device *) data,
+			      AG_CANCEL_CUR);
+}
+
+#define ALLOWED_NUMBER_SYMBOLS "1234567890*#+ABC"	/* according to GSM spec */
+#define AG_PLACE_CALL "ATD%s\r"
+/* dialing from memory is not supported as headset spec doesn't define a way
+   to retreive phone memory entries.
+*/
+static DBusMessage *ag_call(DBusConnection * conn, DBusMessage * msg,
+			    void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number;
+	gint atd_len;
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+			      DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS)) {
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE ".BadNumber",
+					      "Number contains characters which are not allowed");
+	}
+	atd_len = sprintf(buf, AG_PLACE_CALL, number);
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, buf, buf, atd_len);
+	rfcomm_start_watch(device);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_GET_CARRIER "AT+COPS?\r"
+
+static DBusMessage *ag_get_operator(DBusConnection * conn,
+				    DBusMessage * msg, void *data)
+{
+	struct audio_device *dev = (struct audio_device *) data;
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	GIOChannel *rfcomm = gw->rfcomm;
+	gsize read;
+	gchar *result;
+	DBusMessage *reply;
+	GIOStatus status;
+
+	rfcomm_stop_watch(dev);
+	io_channel_write_all(rfcomm, AG_GET_CARRIER,
+			     strlen(AG_GET_CARRIER));
+
+	status =
+	    g_io_channel_read_chars(rfcomm, buf, RFCOMM_BUF_SIZE - 1,
+				    &read, NULL);
+	rfcomm_start_watch(dev);
+	if (G_IO_STATUS_NORMAL == status) {
+		buf[read] = '\0';
+		if (NULL != strstr(buf, "+COPS")) {
+			result = strrchr(buf, ',') + 1;
+			reply = dbus_message_new_method_return(msg);
+			dbus_message_append_args(reply, DBUS_TYPE_STRING,
+						 &result,
+						 DBUS_TYPE_INVALID);
+		} else {
+			warn
+			    ("ag_get_operator(): '+COPS' expected but '%s' received",
+			     buf);
+			reply =
+			    dbus_message_new_error(msg,
+						   ERROR_INTERFACE
+						   ".Failed",
+						   "Unexpected response from AG");
+		}
+	} else {
+		error("ag_get_operator(): %m");
+		reply =
+		    dbus_message_new_error(msg,
+					   ERROR_INTERFACE
+					   ".ConnectionFailed",
+					   "Failed to receive response from AG");
+	}
+	return reply;
+}
+
+#define AG_VR_ACTIVATE "AT+BVRA=1\r"
+static DBusMessage *ag_vr_activate(DBusConnection * conn,
+				   DBusMessage * msg, void *data)
+{
+	return process_simple(msg, (struct audio_device *) data,
+			      AG_VR_ACTIVATE);
+}
+
+#define AG_VR_DEACTIVATE "AT+BVRA=0\r"
+static DBusMessage *ag_vr_deactivate(DBusConnection * conn,
+				     DBusMessage * msg, void *data)
+{
+	return process_simple(msg, (struct audio_device *) data,
+			      AG_VR_DEACTIVATE);
+}
+
+#define AG_REQUEST_NUMBER "AT+BINP=1\r"
+static DBusMessage *ag_get_number_for_voice(DBusConnection * conn,
+					    DBusMessage * msg, void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar number[AG_CALLER_NUM_SIZE];
+
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, AG_REQUEST_NUMBER, buf,
+			     strlen(AG_REQUEST_NUMBER));
+	rfcomm_start_watch(device);
+	if (1 == sscanf(buf, "\r\n+BINP:%s\r\n", number)) {
+		DBusMessage *reply = dbus_message_new_method_return(msg);
+		dbus_message_append_args(reply, DBUS_TYPE_STRING, number,
+					 DBUS_TYPE_INVALID);
+		return reply;
+	} else {
+		warn
+		    ("ag_get_number_for_voice(): AG returned '%s' in place of number",
+		     buf);
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE ".Failed",
+					      "AG didn't return a number");
+	}
+}
+
+#define AG_SEND_DTMF "AT+VTS:%c\r"
+static DBusMessage *ag_send_dtmf(DBusConnection * conn, DBusMessage * msg,
+				 void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number;
+	gint com_len;
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+			      DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS)) {
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE ".BadNumber",
+					      "Number contains characters which are not allowed");
+	}
+	gboolean got_ok = TRUE;
+	gint num_len = strlen(number);
+	gint i = 0;
+	rfcomm_stop_watch(device);
+	while (i < num_len && got_ok) {
+		com_len = sprintf(buf, AG_SEND_DTMF, number[i]);
+		rfcomm_send_and_read(gw, buf, buf, com_len);
+		got_ok = NULL != strstr(buf, OK_RESPONSE);
+		i += 1;
+	}
+	rfcomm_start_watch(device);
+	return process_ag_reponse(msg, buf);
+
+}
+
+#define AG_SET_MIC_GAIN "AT+VGM:%d\r"
+static DBusMessage *ag_send_mic_gain(DBusConnection * conn,
+				     DBusMessage * msg, void *data)
+{
+	gint value;
+	gchar buf[RFCOMM_BUF_SIZE];
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &value,
+			      DBUS_TYPE_INVALID);
+	sprintf(buf, AG_SET_MIC_GAIN, value);
+	return process_simple(msg, (struct audio_device *) data, buf);
+}
+
+
+#define AG_SET_SPEAKER_GAIN "AT+VGS:%d\r"
+static DBusMessage *ag_send_speaker_gain(DBusConnection * conn,
+					 DBusMessage * msg, void *data)
+{
+	gint value;
+	gchar buf[RFCOMM_BUF_SIZE];
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &value,
+			      DBUS_TYPE_INVALID);
+	sprintf(buf, AG_SET_SPEAKER_GAIN, value);
+	return process_simple(msg, (struct audio_device *) data, buf);
+}
+
+#define AG_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
+#define CNUM_LEN 5		/* length of "+CNUM" string */
+#define MAX_NUMBER_CNT 16
+static DBusMessage *ag_get_subscriber_nums(DBusConnection * conn,
+					   DBusMessage * msg, void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *cur_cnum;
+	gchar numbers[MAX_NUMBER_CNT][AG_CALLER_NUM_SIZE];
+	int number_cnt = 0;
+
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf,
+			     strlen(AG_GET_SUBSCRIBER_NUMS));
+	rfcomm_start_watch(device);
+	cur_cnum = strstr(buf, "+CNUM");
+	while (NULL != cur_cnum && number_cnt < MAX_NUMBER_CNT) {
+		sscanf(cur_cnum, "+CNUM:%s,", numbers[number_cnt]);
+		number_cnt += 1;
+		cur_cnum = strstr(cur_cnum + CNUM_LEN, "+CNUM");
+	}
+	DBusMessage *reply = dbus_message_new_method_return(msg);
+	dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+				 &numbers, number_cnt, DBUS_TYPE_INVALID);
+	return reply;
+}
+
+static DBusMessage *ag_send_audio_data(DBusConnection * conn,
+				       DBusMessage * msg, void *data)
+{
+	gchar *audio_data;
+	gint data_byte_cnt;
+	struct audio_device *dev = (struct audio_device *) data;
+	struct gateway *gw = dev->gateway;
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+			      &audio_data, &data_byte_cnt,
+			      DBUS_TYPE_INVALID);
+
+	if (io_channel_write_all(gw->sco, audio_data, data_byte_cnt)) {
+		return dbus_message_new_method_return(msg);
+	} else {
+		error("ag_send_audio_data(): %m");
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE
+					      ".WriteFailed",
+					      "SCO data send failed");
+	}
+}
+
+static DBusMessage *ag_hold_all_but_this(DBusConnection * conn,
+					 DBusMessage * msg, void *data)
+{
+
+}
+
+static DBusMessage *ag_cancel_all(DBusConnection * conn, DBusMessage * msg,
+				  void *data)
+{
+
+}
+
+static DBusMessage *ag_cancel_spec(DBusConnection * conn,
+				   DBusMessage * msg, void *data)
+{
+
+}
+
+static DBusMessage *ag_rah_put_on_hold(DBusConnection * conn,
+				       DBusMessage * msg, void *data)
+{
+
+}
+
+static DBusMessage *ag_rah_answer_held_call(DBusConnection * conn,
+					    DBusMessage * msg, void *data)
+{
+
+}
+
+static DBusMessage *ag_rah_cancel_held_call(DBusConnection * conn,
+					    DBusMessage * msg, void *data)
+{
+
+}
+
+static GDBusMethodTable gateway_methods[] = {
+	{"Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC}
+	, {"Disconnect", "", "", ag_disconnect}
+	, {"IsConnected", "", "b", ag_is_connected}
+	, {"AnswerCall", "", "", ag_answer}
+	, {"CancelCurrentCall", "", "", ag_cancel_cur}
+	, {"Call", "s", "", ag_call}
+	, {"GetOperatorName", "", "s", ag_get_operator}
+	, {"VoiceRecognitionActvate", "", "", ag_vr_activate}
+	, {"VoiceRecognitionDeactvate", "", "", ag_vr_deactivate}
+	, {"GetNumberForLastVoiceTag", "", "s", ag_get_number_for_voice}
+	, {"SendDTMF", "s", "", ag_send_dtmf}
+	, {"SendMicrophoneGain", "y", "", ag_send_mic_gain}
+	, {"SendSpeakerGain", "y", "", ag_send_speaker_gain}
+	, {"GetSubscriberNumbers", "", "as", ag_get_subscriber_nums}
+
+	, {"RAHPutOnHold", "", "", ag_rah_put_on_hold}
+	, {"RAHAnswerHeldCall", "", "", ag_rah_answer_held_call}
+	, {"RAHCancelHeldCall", "", "", ag_rah_cancel_held_call}
+
+	, {"CancelCallFromNumber", "s", "", ag_cancel_spec}
+	, {"CancelAllCalls", "", "", ag_cancel_all}
+	, {"HoldAllButThis", "s", "", ag_hold_all_but_this}
+
+	, {"SendAudioData", "ay", "", ag_send_audio_data}
+	, {NULL, NULL, NULL, NULL}
+};
+
+static GDBusSignalTable gateway_signals[] = {
+	{"Connected", "u"}
+	, {"Disconnected", ""}
+	, {"Ring", "s"}
+	, {"StatusUpdate", "si"}
+	, {"CallCancelled", ""}
+	, {"CallStart", ""}
+	, {"CallEnd", ""}
+	, {"VoiceRecognitionActive", ""}
+	, {"VoiceRecognitionInactive", ""}
+	, {"MicrophoneGain", "y"}
+	, {"SpeakerGain", "y"}
+	, {"RAHCallOnHold", ""}
+	, {"NoCallsHeld", ""}
+	, {"HoldCalls", ""}
+	, {"AllCallsHeld", ""}
+	, {"AudioData", "ay"}
+	, {NULL, NULL}
+};
+
+gboolean gateway_is_connected(struct audio_device *dev)
+{
+	return (NULL == dev) || (NULL == dev->gateway)
+	    || (NULL == dev->gateway->rfcomm);
+}
+
+struct gateway *gateway_init(struct audio_device *dev)
+{
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+				       AUDIO_GATEWAY_INTERFACE,
+				       gateway_methods, gateway_signals,
+				       NULL, dev, NULL)) {
+		return NULL;
+	}
+	struct gateway *gw = g_new0(struct gateway, 1);
+	gw->indies = NULL;
+	gw->is_dialing = FALSE;
+	return gw;
+}
+
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel * io)
+{
+	if (!io)
+		return -EINVAL;
+
+	dev->gateway->rfcomm = io;
+
+	return 0;
+}
+
+static void indicator_slice_free(gpointer mem)
+{
+	g_slice_free(struct indicator, mem);
+}
+
+int gateway_close(struct gateway *gw)
+{
+	GIOChannel *rfcomm = gw->rfcomm;
+	GIOChannel *sco = gw->sco;
+	GIOChannel *sco_server = gw->sco_server;
+	g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL);
+	g_slist_free(gw->indies);
+	if (rfcomm) {
+		g_io_channel_close(rfcomm);
+		g_io_channel_unref(rfcomm);
+		gw->rfcomm = NULL;
+	}
+
+	if (sco) {
+		g_io_channel_close(sco);
+		g_io_channel_unref(sco);
+		gw->sco = NULL;
+	}
+
+	if (sco_server) {
+		g_io_channel_close(sco_server);
+		g_io_channel_unref(sco_server);
+		gw->sco_server = NULL;
+	}
+	return 0;
+}
diff --git a/audio/gateway.h b/audio/gateway.h
index 3e44937..68e34a0 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -22,13 +22,23 @@
  *
  */
 
+
+
+#ifndef __GATEWAY_H__
+#define __GATEWAY_H__
+
+#include "device.h"
+
 #define AUDIO_GATEWAY_INTERFACE "org.bluez.Gateway"
 
 #define DEFAULT_HSP_HS_CHANNEL 6
 #define DEFAULT_HFP_HS_CHANNEL 7
 
-int gateway_init(DBusConnection *conn, gboolean disable_hfp, gboolean sco_hci);
+struct gateway* gateway_init(struct audio_device *device);
+
+gboolean gateway_is_connected(struct audio_device* dev);
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
 
-void gateway_exit(void);
+#endif /*__GATEWAY_H__*/
 
-gboolean gateway_is_enabled(uint16_t svc);
diff --git a/audio/manager.c b/audio/manager.c
index 453e273..ad13670 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -91,10 +91,10 @@ struct audio_adapter {
 	char	*path;
 	uint32_t hsp_ag_record_id;
 	uint32_t hfp_ag_record_id;
-	uint32_t hsp_hs_record_id;
+	uint32_t hfp_hs_record_id;
 	GIOChannel *hsp_ag_server;
 	GIOChannel *hfp_ag_server;
-	GIOChannel *hsp_hs_server;
+	GIOChannel *hfp_hs_server;
 };
 
 static int max_connected_headsets = 1;
@@ -132,11 +132,11 @@ gboolean server_is_enabled(bdaddr_t *src, uint16_t svc)
 	case HEADSET_SVCLASS_ID:
 		return enabled.headset;
 	case HEADSET_AGW_SVCLASS_ID:
-		return enabled.gateway;
+		return  FALSE;
 	case HANDSFREE_SVCLASS_ID:
 		return enabled.headset && enabled.hfp;
 	case HANDSFREE_AGW_SVCLASS_ID:
-		return  FALSE;
+		return enabled.gateway;
 	case AUDIO_SINK_SVCLASS_ID:
 		return enabled.sink;
 	case AV_REMOTE_TARGET_SVCLASS_ID:
@@ -192,6 +192,10 @@ static void handle_uuid(const char *uuidstr, struct audio_device *device)
 		break;
 	case HANDSFREE_AGW_SVCLASS_ID:
 		debug("Found Handsfree AG record");
+		if (device->gateway == NULL) {
+			device->gateway = gateway_init(device);
+		}
+
 		break;
 	case AUDIO_SINK_SVCLASS_ID:
 		debug("Found Audio Sink");
@@ -277,7 +281,7 @@ static sdp_record_t *hsp_ag_record(uint8_t ch)
 	return record;
 }
 
-static sdp_record_t *hsp_hs_record(uint8_t ch)
+static sdp_record_t *hfp_hs_record(uint8_t ch)
 {
 	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
 	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
@@ -295,13 +299,13 @@ static sdp_record_t *hsp_hs_record(uint8_t ch)
 	root = sdp_list_append(0, &root_uuid);
 	sdp_set_browse_groups(record, root);
 
-	sdp_uuid16_create(&svclass_uuid, HEADSET_SVCLASS_ID);
+	sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID);
 	svclass_id = sdp_list_append(0, &svclass_uuid);
 	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
 	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
 	sdp_set_service_classes(record, svclass_id);
 
-	sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
+	sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
 	profile.version = 0x0100;
 	pfseq = sdp_list_append(0, &profile);
 	sdp_set_profile_descs(record, pfseq);
@@ -319,7 +323,7 @@ static sdp_record_t *hsp_hs_record(uint8_t ch)
 	aproto = sdp_list_append(0, apseq);
 	sdp_set_access_protos(record, aproto);
 
-	sdp_set_info_attr(record, "Headset", 0, 0);
+	sdp_set_info_attr(record, "Hands-Free", 0, 0);
 
 	sdp_data_free(channel);
 	sdp_list_free(proto[0], 0);
@@ -398,7 +402,7 @@ static sdp_record_t *hfp_ag_record(uint8_t ch, uint32_t feat)
 	return record;
 }
 
-static void auth_cb(DBusError *derr, void *user_data)
+static void headset_auth_cb(DBusError *derr, void *user_data)
 {
 	struct audio_device *device = user_data;
 	const char *uuid;
@@ -479,7 +483,7 @@ static void ag_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
 	headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS);
 
 	err = btd_request_authorization(&device->src, &device->dst,
-					server_uuid, auth_cb, device);
+					server_uuid, headset_auth_cb, device);
 	if (err < 0) {
 		debug("Authorization denied: %s", strerror(-err));
 		headset_set_state(device, HEADSET_STATE_DISCONNECTED);
@@ -493,10 +497,49 @@ drop:
 	g_io_channel_unref(chan);
 }
 
-static void hs_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+
+static void gateway_auth_cb(DBusError *derr, void *user_data)
+{
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+	} 
+}
+
+static void hf_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
 		const bdaddr_t *dst, void *data)
 {
-	/*Stub*/
+	struct audio_adapter *adapter = data;
+	const char *server_uuid, *remote_uuid;
+	uint16_t svclass;
+	struct audio_device *device;
+
+	if (err < 0) {
+		error("accept: %s (%d)", strerror(-err), -err);
+		return;
+	}
+
+	server_uuid = HSP_HS_UUID;
+	remote_uuid = HSP_AG_UUID;
+	svclass = HEADSET_AGW_SVCLASS_ID;
+
+	device = manager_get_device(src, dst);
+	if (!device) {
+		debug("Failed to get device");
+	} else if (!device->gateway) {
+		btd_device_add_uuid(device->btd_dev, remote_uuid);
+	} else if (gateway_is_connected(device)) {
+		debug("Refusing new connection since one already exists");
+	} else if (0 > btd_request_authorization(&device->src, &device->dst,
+					     server_uuid, gateway_auth_cb, device)) {
+		debug("Authorization denied!");
+	} else if (gateway_connect_rfcomm(device, chan) < 0) {
+		error("Allocating new GIOChannel failed!");
+	} else {
+		return;
+	}
+
+	g_io_channel_close(chan);
+	g_io_channel_unref(chan);
 	return;
 }
 
@@ -609,26 +652,26 @@ static int gateway_server_init(struct audio_adapter *adapter)
 	if (master)
 		flags |= RFCOMM_LM_MASTER;
 
-	adapter->hsp_hs_server = bt_rfcomm_listen(&adapter->src, chan, flags,
-				hs_io_cb, adapter);
-	if (!adapter->hsp_hs_server)
+	adapter->hfp_hs_server = bt_rfcomm_listen(&adapter->src, chan, flags,
+				hf_io_cb, adapter);
+	if (!adapter->hfp_hs_server)
 		return -1;
 
-	record = hsp_hs_record(chan);
+	record = hfp_hs_record(chan);
 	if (!record) {
 		error("Unable to allocate new service record");
 		return -1;
 	}
 
 	if (add_record_to_server(&adapter->src, record) < 0) {
-		error("Unable to register HSP HS service record");
+		error("Unable to register HFP HS service record");
 		sdp_record_free(record);
-		g_io_channel_unref(adapter->hsp_hs_server);
-		adapter->hsp_hs_server = NULL;
+		g_io_channel_unref(adapter->hfp_hs_server);
+		adapter->hfp_hs_server = NULL;
 		return -1;
 	}
 
-	adapter->hsp_hs_record_id = record->handle;
+	adapter->hfp_hs_record_id = record->handle;
 
 	return 0;
 }
@@ -770,14 +813,14 @@ static void gateway_server_remove(struct btd_adapter *adapter)
 	if (!adp)
 		return;
 
-	if (adp->hsp_hs_record_id) {
-		remove_record_from_server(adp->hsp_hs_record_id);
-		adp->hsp_hs_record_id = 0;
+	if (adp->hfp_hs_record_id) {
+		remove_record_from_server(adp->hfp_hs_record_id);
+		adp->hfp_hs_record_id = 0;
 	}
 
-	if (adp->hsp_hs_server) {
-		g_io_channel_unref(adp->hsp_hs_server);
-		adp->hsp_hs_server = NULL;
+	if (adp->hfp_hs_server) {
+		g_io_channel_unref(adp->hfp_hs_server);
+		adp->hfp_hs_server = NULL;
 	}
 }
 

[-- Attachment #3: audio-api.patch --]
[-- Type: application/octet-stream, Size: 3716 bytes --]

diff --git a/doc/audio-api.txt b/doc/audio-api.txt
index a73ba20..cdb6de9 100644
--- a/doc/audio-api.txt
+++ b/doc/audio-api.txt
@@ -195,3 +195,120 @@ properties	boolean Connected [readonly]
 
 			Indicates if a stream is active to a A2DP sink on
 			the remote device.
+
+
+AudioSink hierarchy
+===================
+
+Service		org.bluez
+Interface	org.bluez.Gateway
+Object path	[variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+This interface is available for remote devices which can function in the Audio
+Gateway role of the HFP profiles.
+
+Methods		void Connect()
+
+		     Connect to the AG service on the remote device.
+
+		void Disconnect()
+		boolean IsConnected()
+
+		void AnswerCall()
+		
+		      It has to called only after Ring signal received.
+    
+    		void CancelCurrentCall()
+
+		      Cancel call which is running now. This one does nothing with any 3-way
+		      situation incl. RaH. Just plain old PDH.
+         
+	 	void Call(string number)
+
+		      Dial a number 'number'. No number processing is done thus if AG would 
+		      reject to dial it don't blame me :)
+    
+    		string GetOperatorName()
+    
+    		void VoiceRecognitionActivate()
+		      in development
+		void VoiceRecognitionDeactivate()
+		      in development
+
+		string GetNumberForLastVoiceTag()
+
+		      Ask AG to provide a phone number. It looks like AG shell in turn 
+		      ask user to choose one. Idea behind this is VR on the HF side.
+
+    		void SendDTMF(string digits)
+
+		      Will send each digit in the 'digits' sequentially. Would send 
+		      nothing if there is non-dtmf digit.
+
+		void SendMicrophoneGain(uint8 gain)
+		void SendSpeakerGain(uint8 gain)
+
+		      Feel them useless but really easy to implement :)
+    
+    		string[] GetSubscriberNumbers()
+
+		      Get all the numbers of AG
+
+		void SendAudioData(uint8[] data)
+
+		      Would only be effective when call or VR is running
+		      (i.e. SCO link aka Audio Connection exists). Data should be 
+		      plain 16 bit LE PCM (e.g. directly from ALSA)
+
+Signals
+		void Connected(uint32 ag_features) 
+
+		      Connection successfully esteblished. 'ag_features' are
+		      ORed features:
+		                    0x001 Three-way calling
+				    0x002 Echo cancelling and/or noice reduction function
+				    0x004 Voice recognition function
+				    0x008 In-band ring tone capability
+              			    0x010 Attach a number to a voice tag
+              			    0x020 Ability to reject a call
+              			    0x040 Enhanced call status
+              			    0x080 Enhanced call control
+              			    0x100 Extended Error Result Cod
+
+    		void Disconnected()
+
+		void Ring(string number)
+
+		      Someone's calling from 'number'. 
+         	      Caller number is provided as received from AG.
+
+		void StatusUpdate(string indicator_name, uint32 indicator_value)
+
+		      Indicator 'indicator_name' changed its value to 'indicator_value'. 
+		      System (call, callsetup, etc.) statuses are not signalled.
+
+		void CallCancelled()
+		
+		      Call failed to set up. It means that we tried to call someone 
+		      or someone tried to call us but call was not accepted.
+
+		void CallStart()
+
+		      Call set up successfully.
+
+		void CallEnd()
+
+		      Call was started and now ended. In contrast with CallCancelled 
+		      where call didn't started
+
+		void VoiceRecognitionActive()
+		void VoiceRecognitionInactive()
+		      in development
+
+		void MicrophoneGain(uint8 gain) 
+		void SpeakerGain(uint8 gain)
+		      AG sent us new gain.
+
+		void AudioData(uint8[] data) 
+
+		      Array of audio data from AG. It is plain 16 bit PCM ready for ALSA.

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
  2008-12-17  2:48   ` Marcel Holtmann
  2008-12-19  7:36     ` event
@ 2009-01-16 13:22     ` event
  2009-01-18 16:07       ` Marcel Holtmann
  1 sibling, 1 reply; 10+ messages in thread
From: event @ 2009-01-16 13:22 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

[-- Attachment #1: Type: text/plain, Size: 2712 bytes --]

On Wed, Dec 17, 2008 at 04:48, Marcel Holtmann <marcel@holtmann.org> wrote:
> Hi Leonid,
>
>> I've implemented gateway profile. I've tested basic things, like
>> place/cancel/answer call. Others are in development. Some could not be
>> tested as my carrier doesn't provide corresponding services (like
>> 3-way call, etc.) so any help welcome.
>
> thanks for the works, but can you please base the patch against the
> latest GIT tree. It is kinda hard to review things that might already
> have been implemented like sco_listen.
>
>  audio/audio-api.txt  |   94 +++++
>  audio/device.h       |    7
>  audio/gateway.c      |  938 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  audio/gateway.h      |   11
>  audio/manager.c      |  124 ++++--
>  common/glib-helper.c |   85 +++-
>  common/glib-helper.h |    1
>  7 files changed, 1205 insertions(+), 55 deletions(-)
>
> So any changes to glib-helper.[ch] have to be in a separate patch and
> need to be discussed independent from the gateway implementation.
>
> Any audio-api.txt stuff should also go separately since that has to be
> discussed. Also we can't send PCM data over D-Bus. It just doesn't work
> like that. We do have the internal IPC for that and plugins for ALSA,
> GStreamer and PulseAudio that should be used.
>
> However the most important part is that you follow the coding style and
> that is the kernel coding style. You make it really hard for us to
> review the code like this and it can't be applied. I really want you to
> add support for the gateway role to BlueZ, but the overall code in the
> project needs to follow the same rules.
>
> So please fix these issues first and then we do a deep review of it.
>
> Regards
>
> Marcel
>
>
>

Hello Marcel,

Here is reworked and improved patches as you suggested with IPC support.

But I have some doubts and questions:
1. to distinguish between headset and gateway I've added one more alsa
config option "role" which could be master (for gateway and probably
a2dp source) or slave (which is default and works for headset and a2dp
sink). I don't really like this approach so if you have any other idea
it would be great.
2. my cell phone closes SCO connection when it doesn't need one,
probably others act like this as well. SCO close results in
bluetooth_hsp_write returning an error. What would be the best way to
overcome this?
3. I've noticed that ipc interface duplicate dbus interface to some
extent. Why can't pcm_bluetooth work over dbus directly?
4. Aren't there any legal issues with implementing bluez? As I noticed
in the spec that all the rights belong to Bluetooth SIG so it does
look neither "free as in free beer" nor "free as in freedom" :)

Vale,
Leonid Movshovich

[-- Attachment #2: audio_api.txt.patch --]
[-- Type: application/octet-stream, Size: 3381 bytes --]

diff --git a/doc/audio-api.txt b/doc/audio-api.txt
index a73ba20..f6e35a9 100644
--- a/doc/audio-api.txt
+++ b/doc/audio-api.txt
@@ -195,3 +195,112 @@ properties	boolean Connected [readonly]
 
 			Indicates if a stream is active to a A2DP sink on
 			the remote device.
+
+
+AudioSink hierarchy
+===================
+
+Service		org.bluez
+Interface	org.bluez.Gateway
+Object path	[variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+This interface is available for remote devices which can function in the Audio
+Gateway role of the HFP profiles.
+
+Methods		void Connect()
+
+		     Connect to the AG service on the remote device.
+
+		void Disconnect()
+		boolean IsConnected()
+
+		void AnswerCall()
+		
+		      It has to called only after Ring signal received.
+    
+    		void CancelCurrentCall()
+
+		      Cancel call which is running now. This one does nothing with any 3-way
+		      situation incl. RaH. Just plain old PDH.
+         
+	 	void Call(string number)
+
+		      Dial a number 'number'. No number processing is done thus if AG would 
+		      reject to dial it don't blame me :)
+    
+    		string GetOperatorName()
+    
+    		void VoiceRecognitionActivate()
+		      in development
+		void VoiceRecognitionDeactivate()
+		      in development
+
+		string GetNumberForLastVoiceTag()
+
+		      Ask AG to provide a phone number. It looks like AG shell in turn 
+		      ask user to choose one. Idea behind this is VR on the HF side.
+
+    		void SendDTMF(string digits)
+
+		      Will send each digit in the 'digits' sequentially. Would send 
+		      nothing if there is non-dtmf digit.
+
+		void SendMicrophoneGain(uint8 gain)
+		void SendSpeakerGain(uint8 gain)
+
+		      Feel them useless but really easy to implement :)
+    
+    		string[] GetSubscriberNumbers()
+
+		      Get all the numbers of AG
+
+
+Signals
+		void Connected(uint32 ag_features) 
+
+		      Connection successfully esteblished. 'ag_features' are
+		      ORed features:
+		                    0x001 Three-way calling
+				    0x002 Echo cancelling and/or noice reduction function
+				    0x004 Voice recognition function
+				    0x008 In-band ring tone capability
+              			    0x010 Attach a number to a voice tag
+              			    0x020 Ability to reject a call
+              			    0x040 Enhanced call status
+              			    0x080 Enhanced call control
+              			    0x100 Extended Error Result Cod
+
+    		void Disconnected()
+
+		void Ring(string number)
+
+		      Someone's calling from 'number'. 
+         	      Caller number is provided as received from AG.
+
+		void StatusUpdate(string indicator_name, uint32 indicator_value)
+
+		      Indicator 'indicator_name' changed its value to 'indicator_value'. 
+		      System (call, callsetup, etc.) statuses are not signalled.
+
+		void CallCancelled()
+		
+		      Call failed to set up. It means that we tried to call someone 
+		      or someone tried to call us but call was not accepted.
+
+		void CallStart()
+
+		      Call set up successfully.
+
+		void CallEnd()
+
+		      Call was started and now ended. In contrast with CallCancelled 
+		      where call didn't started
+
+		void VoiceRecognitionActive()
+		void VoiceRecognitionInactive()
+		      in development
+
+		void MicrophoneGain(uint8 gain) 
+		void SpeakerGain(uint8 gain)
+		      AG sent us new gain.
+

[-- Attachment #3: gateway_profile.patch --]
[-- Type: application/octet-stream, Size: 53279 bytes --]

diff --git a/audio/device.c b/audio/device.c
index 6f59746..18cc1de 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -52,6 +52,7 @@
 #include "avdtp.h"
 #include "control.h"
 #include "headset.h"
+#include "gateway.h"
 #include "sink.h"
 
 static void device_free(struct audio_device *dev)
@@ -102,6 +103,9 @@ gboolean audio_device_is_connected(struct audio_device *dev,
 	else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset &&
 			headset_is_active(dev))
 		return TRUE;
+	else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway &&
+			gateway_is_connected(dev))
+		return TRUE;
 	else if (!strcmp(interface, AUDIO_CONTROL_INTERFACE) && dev->headset &&
 			control_is_active(dev))
 		return TRUE;
diff --git a/audio/device.h b/audio/device.h
index 2f626a5..4dd2b26 100644
--- a/audio/device.h
+++ b/audio/device.h
@@ -22,6 +22,11 @@
  *
  */
 
+#ifndef __DEVICE_H__
+#define __DEVICE_H__
+
+#include <bluetooth/bluetooth.h>
+
 #define GENERIC_AUDIO_UUID	"00001203-0000-1000-8000-00805F9B34FB"
 
 #define HSP_HS_UUID		"00001108-0000-1000-8000-00805F9B34FB"
@@ -73,3 +78,5 @@ void audio_device_unregister(struct audio_device *device);
 
 gboolean audio_device_is_connected(struct audio_device *dev,
 						const char *interface);
+#endif /*__DEVICE_H__*/
+
diff --git a/audio/gateway.c b/audio/gateway.c
index edf38de..d15861b 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -4,6 +4,7 @@
  *
  *  Copyright (C) 2006-2007  Nokia Corporation
  *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2008-2009  Leonid Movshovich <event.riga@gmail.org>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -27,8 +28,1150 @@
 #endif
 
 #include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
 
+#include <fcntl.h>
 #include <glib.h>
 #include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
 
 #include "gateway.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "logging.h"
+#include "../src/error.h"
+
+#define RFCOMM_BUF_SIZE 256
+#define AG_INDICATOR_DESCR_SIZE 20	/* not-more-then-16 defined by GSM + 1 for NULL + padding */
+#define AG_CALLER_NUM_SIZE 64	/* size of number + type */
+
+/* commands */
+#define AG_FEATURES "AT+BRSF=26\r"	// = 0x7F = All features supported
+#define AG_INDICATORS_SUPP "AT+CIND=?\r"
+#define AG_INDICATORS_VAL "AT+CIND?\r"
+#define AG_INDICATORS_ENABLE "AT+CMER=3,0,0,1\r"
+#define AG_HOLD_MPTY_SUPP "AT+CHLD=?\r"
+#define AG_CALLER_IDENT_ENABLE "AT+CLIP=1\r"
+#define AG_CARRIER_FORMAT "AT+COPS=3,0\r"
+#define AG_EXTENDED_RESULT_CODE "AT+CMEE=1\r"
+
+#define AG_FEATURE_3WAY 0x1
+#define AG_FEATURE_EXTENDED_RES_CODE 0x100
+/* Hold and multipary AG features. Comments below are copied from hands-free spec for reference */
+#define AG_CHLD_0 0x01		/* Releases all held calls or sets User Determined User Busy (UDUB) for a waiting call */
+#define AG_CHLD_1 0x02		/* Releases all active calls (if any exist) and accepts the other (held or waiting) call */
+#define AG_CHLD_1x 0x04		/* Releases specified active call only <x> */
+#define AG_CHLD_2 0x08		/* Places all active calls (if any exist) on hold and accepts the other (held or waiting) call */
+#define AG_CHLD_2x 0x10		/* Request private consultation mode with specified call <x> (Place all calls on hold EXCEPT the call <x>) */
+#define AG_CHLD_3 0x20		/* Adds a held call to the conversation */
+#define AG_CHLD_4 0x40		/* 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 OK_RESPONSE "\r\nOK\r\n"
+#define ERROR_RESPONSE "\r\nERROR\r\n"
+
+
+struct indicator {
+	gchar descr[AG_INDICATOR_DESCR_SIZE];
+	gint value;
+};
+
+struct gateway {
+	GIOChannel* rfcomm;
+	guint rfcomm_watch_id;
+	GIOChannel* sco;
+	GIOChannel* sco_server;
+	gateway_stream_cb_t sco_start_cb;
+	void* sco_start_cb_data;
+	DBusMessage* connect_message;
+	guint ag_features;
+	guint hold_multiparty_features;
+	GSList* indies;
+	gboolean is_dialing;
+};
+
+
+static gboolean rfcomm_ag_data_cb(GIOChannel * chan, GIOCondition cond,
+				  struct audio_device *device);
+
+int gateway_close(struct gateway *gw);
+
+static void rfcomm_start_watch(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+	gw->rfcomm_watch_id =
+	    g_io_add_watch(gw->rfcomm,
+			   G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+			   (GIOFunc) rfcomm_ag_data_cb, dev);
+}
+
+static void rfcomm_stop_watch(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+	g_source_remove(gw->rfcomm_watch_id);
+}
+
+static gboolean io_channel_write_all(GIOChannel * io, gchar * data,
+				     gsize count)
+{
+	gsize written = 0;
+	GIOStatus status;
+	while (0 < count) {
+		status =
+		    g_io_channel_write_chars(io, data, count, &written,
+					     NULL);
+		if (G_IO_STATUS_NORMAL != status) {
+			return FALSE;
+		}
+		data += written;
+		count -= written;
+	}
+	return TRUE;
+}
+
+/* it's worth to mention that data and response could be the same pointers */
+static gboolean rfcomm_send_and_read(struct gateway *gw, gchar * data,
+				     gchar * response, gsize count)
+{
+	GIOChannel *rfcomm = gw->rfcomm;
+
+	if (!io_channel_write_all(rfcomm, data, count)) {
+		return FALSE;
+	}
+
+	gsize read = 0;
+	gboolean got_ok = FALSE;
+	gboolean got_error = FALSE;
+	gchar *resp_buf = response;
+	gsize toread = RFCOMM_BUF_SIZE - 1;
+	GIOStatus status;
+	while (!(got_ok || got_error)) {
+		status =
+		    g_io_channel_read_chars(rfcomm, resp_buf, toread,
+					    &read, NULL);
+		if (G_IO_STATUS_NORMAL == status) {
+			resp_buf[read] = '\0';
+		} else {
+			debug("rfcomm_send_and_read(): %m");
+			return FALSE;
+		}
+		got_ok = NULL != strstr(resp_buf, OK_RESPONSE);
+		got_error = NULL != strstr(resp_buf, ERROR_RESPONSE);
+		resp_buf += read;
+		toread -= read;
+	}
+	return TRUE;
+}
+
+/* get <descr> from the names: (<descr>, (<values>)), (<descr>, (<values>)) ... */
+GSList *parse_indicator_names(gchar * names, GSList * indies)
+{
+	gchar *current = names - 1;
+	GSList *result = indies;
+	gchar *next;
+	while (NULL != current) {
+		current += 2;
+		next = strstr(current, ",(");
+		struct indicator *ind =
+		    (struct indicator *) g_slice_new(struct indicator);
+		strncpy(ind->descr, current, 20);
+		ind->descr[(gint) next - (gint) current] = '\0';
+		result = g_slist_append(result, (gpointer) ind);
+		current = strstr(next + 1, ",(");
+	}
+	return result;
+}
+
+/* get values from <val0>,<val1>,... */
+GSList *parse_indicator_values(gchar * values, GSList * indies)
+{
+	gint val;
+	gchar *current = values - 1;
+	GSList *runner = indies;
+	while (NULL != current) {
+		current += 1;
+		sscanf(current, "%d", &val);
+		current = strchr(current, ',');
+		struct indicator *ind =
+		    (struct indicator *) g_slist_nth_data(runner, 0);
+		ind->value = val;
+		runner = g_slist_next(runner);
+	}
+	return indies;
+}
+
+/* get values from <val0>,<val1>,... */
+static guint get_hold_mpty_features(gchar * features)
+{
+	guint result = 0;
+	if (NULL != strstr(features, "0")) {
+		result |= AG_CHLD_0;
+	}
+	if (NULL != strstr(features, "1")) {
+		result |= AG_CHLD_1;
+	}
+	if (NULL != strstr(features, "1x")) {
+		result |= AG_CHLD_1x;
+	}
+	if (NULL != strstr(features, "2")) {
+		result |= AG_CHLD_2;
+	}
+	if (NULL != strstr(features, "2x")) {
+		result |= AG_CHLD_2x;
+	}
+	if (NULL != strstr(features, "3")) {
+		result |= AG_CHLD_3;
+	}
+	if (NULL != strstr(features, "4")) {
+		result |= AG_CHLD_4;
+	}
+	return result;
+}
+
+static gboolean establish_service_level_conn(struct gateway *gw)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	gboolean res;
+	res =
+	    rfcomm_send_and_read(gw, AG_FEATURES, buf,
+				 sizeof(AG_FEATURES) - 1);
+	if (!res || 1 != sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features)) {
+		return FALSE;
+	}
+	debug("features are 0x%X", gw->ag_features);
+	res =
+	    rfcomm_send_and_read(gw, AG_INDICATORS_SUPP, buf,
+				 sizeof(AG_INDICATORS_SUPP) - 1);
+	if (!res || !strstr(buf, "+CIND:")) {
+		return FALSE;
+	}
+	gw->indies = parse_indicator_names(strchr(buf, '('), NULL);
+
+	res =
+	    rfcomm_send_and_read(gw, AG_INDICATORS_VAL, buf,
+				 sizeof(AG_INDICATORS_VAL) - 1);
+	if (!res || !strstr(buf, "+CIND:")) {
+		return FALSE;
+	}
+	gw->indies =
+	    parse_indicator_values(strchr(buf, ':') + 1, gw->indies);
+
+	res =
+	    rfcomm_send_and_read(gw, AG_INDICATORS_ENABLE, buf,
+				 sizeof(AG_INDICATORS_ENABLE) - 1);
+	if (!res || !strstr(buf, "OK")) {
+		return FALSE;
+	}
+	if (0 != (gw->ag_features & AG_FEATURE_3WAY)) {
+		res =
+		    rfcomm_send_and_read(gw, AG_HOLD_MPTY_SUPP, buf,
+					 sizeof(AG_HOLD_MPTY_SUPP) - 1);
+		if (!res || !strstr(buf, "+CHLD:")) {
+			g_slice_free1(RFCOMM_BUF_SIZE, buf);
+			return FALSE;
+		}
+		gw->hold_multiparty_features =
+		    get_hold_mpty_features(strchr(buf, '('));
+	} else {
+		gw->hold_multiparty_features = 0;
+	}
+	debug("Service layer connection successfully established!");
+	rfcomm_send_and_read(gw, AG_CALLER_IDENT_ENABLE, buf,
+			     sizeof(AG_CALLER_IDENT_ENABLE) - 1);
+	rfcomm_send_and_read(gw, AG_CARRIER_FORMAT, buf,
+			     sizeof(AG_CARRIER_FORMAT) - 1);
+	if (0 != (gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE)) {
+		rfcomm_send_and_read(gw, AG_EXTENDED_RESULT_CODE, buf,
+				     sizeof(AG_EXTENDED_RESULT_CODE) - 1);
+	}
+	return TRUE;
+}
+
+static void process_ind_change(struct audio_device *dev, gchar * name,
+			       gint value)
+{
+	struct gateway *gw = dev->gateway;
+	if (0 == strcmp(name, "call")) {
+		if (0 < value) {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "CallStart", DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+		} else {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "CallEnd", DBUS_TYPE_INVALID);
+		}
+	} else if (0 == strcmp(name, "callstatus")
+		   && gw->is_dialing) {
+		if (0 == value) {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "CallCancelled",
+					   DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+		} else {
+			gw->is_dialing = TRUE;
+		}
+	} else if (0 == strcmp(name, "callheld")) {
+		/* FIXME: The following code is based on assumptions only. Has to be tested for interoperability
+		   I assume that callheld=2 would be sent when dial from HF failed in case of 3-way call 
+		   Unfortunately this path is not covered by the HF spec so the code has to be tested for interop
+		 */
+		if (2 == value) {	/* '2' means: all calls held, no active calls */
+			if (gw->is_dialing) {
+				g_dbus_emit_signal(dev->conn, dev->path,
+						   AUDIO_GATEWAY_INTERFACE,
+						   "CallCancelled",
+						   DBUS_TYPE_INVALID);
+				gw->is_dialing = FALSE;
+			}
+		}
+	} else {
+		g_dbus_emit_signal(dev->conn, dev->path,
+				   AUDIO_GATEWAY_INTERFACE, "StatusUpdate",
+				   DBUS_TYPE_STRING, name, DBUS_TYPE_INT32,
+				   value, DBUS_TYPE_INVALID);
+	}
+}
+
+static void process_ring(struct audio_device *device, GIOChannel * chan,
+			 gchar * buf)
+{
+	gchar number[AG_CALLER_NUM_SIZE];
+	gchar *sep;
+	guint read;
+
+	rfcomm_stop_watch(device);
+	g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read,
+				NULL);
+	if (1 == sscanf(buf, "\r\n+CLIP:%s\r\n", number)) {
+		sep = strchr(number, ',');
+		sep[0] = '\0';
+		/* FIXME: signal will be emitted on each RING+CLIP. That's bad */
+		g_dbus_emit_signal(device->conn, device->path,
+				   AUDIO_GATEWAY_INTERFACE, "Ring",
+				   DBUS_TYPE_STRING, number,
+				   DBUS_TYPE_INVALID);
+	} else {
+		info("rfcomm_ag_data_cb(): '%s' in place of +CLIP after RING"
+		     , buf);
+	}
+	rfcomm_start_watch(device);
+}
+
+static gboolean rfcomm_ag_data_cb(GIOChannel * chan, GIOCondition cond,
+				  struct audio_device *device)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	struct gateway *gw;
+	guint read;
+	gchar indicator[AG_INDICATOR_DESCR_SIZE + 4];	/* some space for value */
+	gint value;
+	gchar *sep;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	gw = device->gateway;
+
+	if (cond & (G_IO_ERR | G_IO_HUP))
+		return FALSE;
+
+
+	if (G_IO_ERROR_NONE !=
+	    g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read,
+				    NULL))
+		return TRUE;
+
+	buf[read] = '\0';
+	if (1 == sscanf(buf, "\r\n+CIEV:%s\r\n", indicator)) {
+		sep = strchr(indicator, ',');
+		sep[0] = '\0';
+		sep += 1;
+		value = atoi(sep);
+		process_ind_change(device, indicator, value);
+	} else if (NULL != strstr(buf, "RING")) {
+		process_ring(device, chan, buf);
+	} else if (1 == sscanf(buf, "\r\n+BVRA:%d\r\n", &value)) {
+		if (0 == value) {
+			g_dbus_emit_signal(device->conn, device->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "VoiceRecognitionActive",
+					   DBUS_TYPE_INVALID);
+		} else {
+			g_dbus_emit_signal(device->conn, device->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "VoiceRecognitionInactive",
+					   DBUS_TYPE_INVALID);
+		}
+	} else if (1 == sscanf(buf, "\r\n+VGS:%d\r\n", &value)) {
+		g_dbus_emit_signal(device->conn, device->path,
+				   AUDIO_GATEWAY_INTERFACE, "SpeakerGain",
+				   DBUS_TYPE_INT32, value,
+				   DBUS_TYPE_INVALID);
+
+	} else if (1 == sscanf(buf, "\r\n+VGM:%d\r\n", &value)) {
+		g_dbus_emit_signal(device->conn, device->path,
+				   AUDIO_GATEWAY_INTERFACE, "MicGain",
+				   DBUS_TYPE_INT32, value,
+				   DBUS_TYPE_INVALID);
+	} else {
+		error("rfcomm_ag_data_cb(): read wrong data '%s'", buf);
+	}
+	return TRUE;
+}
+
+static gboolean sco_io_cb(GIOChannel * chan, GIOCondition cond,
+			  struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		GIOChannel *chan = gw->sco;
+		g_io_channel_unref(chan);
+		g_io_channel_close(chan);
+		gw->sco = NULL;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+static void sco_connect_cb(GIOChannel * chan, int err,
+			   const bdaddr_t * src, const bdaddr_t * dst,
+			   gpointer user_data)
+{
+	struct audio_device *dev = (struct audio_device *) user_data;
+	struct gateway *gw = dev->gateway;
+	if (0 > err) {
+		error("sco_connect_cb(): %s (%d)", strerror(-err), -err);
+		/* not sure, but from other point of view, 
+		   what is the reason to have headset which cannot play audio? */
+		gateway_close(gw);
+		return;
+	}
+	
+	gw->sco = chan;
+	if (NULL != gw->sco_start_cb) {
+		gw->sco_start_cb(dev, gw->sco_start_cb_data);
+	}
+	fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);	/* why is this here? */
+	g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+		       (GIOFunc) sco_io_cb, dev);
+}
+
+
+static void rfcomm_connect_cb(GIOChannel * chan, int err,
+			      const bdaddr_t * src, const bdaddr_t * dst,
+			      gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct gateway *gw = dev->gateway;
+	DBusMessage *conn_mes = gw->connect_message;
+	gchar gw_addr[18];
+
+	if (err < 0) {
+		error("connect(): %s (%d)", strerror(-err), -err);
+		if (NULL != gw->sco_start_cb) {
+			gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+		}
+		return;
+	}
+	
+	ba2str(&dev->dst, gw_addr);
+	/* Blocking mode should be default, but just in case: */
+	GIOFlags flags = g_io_channel_get_flags(chan);
+	flags &= ~G_IO_FLAG_NONBLOCK;
+	flags &= G_IO_FLAG_MASK;
+	g_io_channel_set_flags(chan, flags, NULL);
+	g_io_channel_set_encoding(chan, NULL, NULL);
+	g_io_channel_set_buffered(chan, FALSE);
+	gw->rfcomm = chan;
+	if (establish_service_level_conn(dev->gateway)) {
+		GIOChannel *sco_server =
+		    bt_sco_listen(&dev->src, 0, sco_connect_cb, dev);
+
+		if (NULL != sco_server) {
+			debug("%s: Connected to %s", dev->path, gw_addr);
+			rfcomm_start_watch(dev);
+			gw->sco_server = sco_server;
+			if (NULL != conn_mes) {
+				DBusMessage *reply =
+					dbus_message_new_method_return(conn_mes);
+				dbus_connection_send(dev->conn, reply, NULL);
+				dbus_message_unref(reply);
+				dbus_message_unref(conn_mes);
+				gw->connect_message = NULL;
+			}
+
+			g_dbus_emit_signal(dev->conn, dev->path,
+					   AUDIO_GATEWAY_INTERFACE,
+					   "Connected", DBUS_TYPE_UINT32,
+					   &gw->ag_features,
+					   DBUS_TYPE_INVALID);
+			return;
+		} else {
+			error("%s: Failed to setup SCO server socket",
+			      dev->path);
+		}
+	} else {
+		error
+		    ("%s: Failed to establish service layer connection to %s",
+		     dev->path, gw_addr);
+	}
+	if (NULL != gw->sco_start_cb) {
+		gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+	}
+	gateway_close(gw);
+}
+
+static void get_record_cb(sdp_list_t * recs, int err, gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	DBusMessage *conn_mes = dev->gateway->connect_message;
+	int ch = -1;
+	sdp_list_t *protos, *classes = NULL;
+	uuid_t uuid;
+
+	if (err < 0) {
+		error("Unable to get service record: %s (%d)",
+		      strerror(-err), -err);
+	} else if (NULL == recs || NULL == recs->data) {
+		error("No records found");
+	} else if (sdp_get_service_classes(recs->data, &classes) < 0) {
+		error("Unable to get service classes from record");
+	} else {
+		memcpy(&uuid, classes->data, sizeof(uuid));
+
+		if (0 == sdp_uuid128_to_uuid(&uuid)
+		    || uuid.type != SDP_UUID16) {
+			error("Not a 16 bit UUID");
+		} else if (uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
+			error("Service record didn't contain the HFP UUID");
+		} else if (0 == sdp_get_access_protos(recs->data, &protos)) {
+			ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+			sdp_list_foreach(protos,
+					 (sdp_list_func_t) sdp_list_free,
+					 NULL);
+			sdp_list_free(protos, NULL);
+			if (ch == -1) {
+				error("Unable to extract RFCOMM channel from service record");
+			} else {
+				err = bt_rfcomm_connect(&dev->src, &dev->dst, ch
+							, rfcomm_connect_cb, dev);
+				if (err < 0) {
+					gchar* strerr = strerror(err);
+					error("Unable to connect: %s (%s)",
+					      strerr, err);
+					if (NULL != conn_mes) {
+						error_common_reply(dev->conn, conn_mes
+								   , ERROR_INTERFACE".ConnectionAttemptFailed"
+								   , strerr);
+					}
+					gateway_close(dev->gateway);
+				}
+				sdp_list_free(classes, free);
+				return;
+			}
+		}
+	}
+
+	if (NULL != classes) {
+		sdp_list_free(classes, free);
+	}
+
+	if (NULL != recs && NULL != recs->data) {	
+		sdp_record_free(recs->data);
+	}
+
+	if (NULL != conn_mes) {
+		error_common_reply(dev->conn, conn_mes
+				   , ERROR_INTERFACE".NotSupported"
+				   , "Not supported");
+		dbus_message_unref(conn_mes);
+		dev->gateway->connect_message = NULL;
+	}
+	gateway_stream_cb_t sco_cb = dev->gateway->sco_start_cb;
+	if (NULL != sco_cb) {
+		sco_cb(NULL, dev->gateway->sco_start_cb_data);
+	}
+}
+
+static int get_records(struct audio_device *device)
+{
+	uuid_t uuid;
+	sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID);
+	return bt_search_service(&device->src, &device->dst, &uuid
+				    , get_record_cb, device, NULL);
+}
+
+
+static DBusMessage *ag_connect(DBusConnection * conn, DBusMessage * msg,
+			       void *data)
+{
+	struct audio_device *au_dev = (struct audio_device *) data;
+	struct gateway *gw = au_dev->gateway;
+
+	if (NULL != gw->rfcomm) {
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".AlreadyConnected",
+					   "Already Connected");
+	}
+
+	gw->connect_message = dbus_message_ref(msg);
+	if (get_records(au_dev) < 0) {
+		dbus_message_unref(gw->connect_message);
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".ConnectAttemptFailed",
+					   "Connect Attempt Failed");
+	}
+	return NULL;
+}
+
+static DBusMessage *ag_disconnect(DBusConnection * conn, DBusMessage * msg,
+				  void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	DBusMessage *reply = NULL;
+	char gw_addr[18];
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (NULL == gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					   ".NotConnected",
+					   "Device not Connected");
+
+	gateway_close(gw);
+	ba2str(&device->dst, gw_addr);
+	debug("Disconnected from %s, %s", gw_addr, device->path);
+
+	return reply;
+
+}
+
+static DBusMessage *ag_is_connected(DBusConnection * conn,
+				    DBusMessage * msg, void *data)
+{
+	gboolean res = gateway_is_connected((struct audio_device *) data);
+	DBusMessage *reply = dbus_message_new_method_return(msg);
+	if (NULL != reply) {
+		dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &res,
+					 DBUS_TYPE_INVALID);
+	}
+	return reply;
+}
+
+static DBusMessage *process_ag_reponse(DBusMessage * msg, gchar * response)
+{
+	DBusMessage *reply;
+	if (NULL != strstr(response, OK_RESPONSE)) {
+		reply = dbus_message_new_method_return(msg);
+	} else {
+		/* FIXME: some code should be here to processes errors in better fasion */
+		debug("AG responded with '%s' to %s method call", response,
+		      dbus_message_get_member(msg));
+		reply =
+		    dbus_message_new_error(msg,
+					   ERROR_INTERFACE
+					   ".OperationFailed",
+					   "Operation failed. See log for details");
+	}
+	return reply;
+}
+
+static DBusMessage *process_simple(DBusMessage * msg,
+				   struct audio_device *dev, gchar * data)
+{
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+
+	rfcomm_stop_watch(dev);
+	rfcomm_send_and_read(gw, data, buf, strlen(data));
+	rfcomm_start_watch(dev);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_ANSWER "ATA\r"
+
+static DBusMessage *ag_answer(DBusConnection * conn, DBusMessage * msg,
+			      void *data)
+{
+	return process_simple(msg, (struct audio_device *) data,
+			      AG_ANSWER);
+}
+
+#define AG_CANCEL_CUR "AT+CHUP\r"
+
+static DBusMessage *ag_cancel_cur(DBusConnection * conn, DBusMessage * msg,
+				  void *data)
+{
+	return process_simple(msg, (struct audio_device *) data,
+			      AG_CANCEL_CUR);
+}
+
+#define ALLOWED_NUMBER_SYMBOLS "1234567890*#+ABC"	/* according to GSM spec */
+#define AG_PLACE_CALL "ATD%s\r"
+/* dialing from memory is not supported as headset spec doesn't define a way
+   to retreive phone memory entries.
+*/
+static DBusMessage *ag_call(DBusConnection * conn, DBusMessage * msg,
+			    void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number;
+	gint atd_len;
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+			      DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS)) {
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE ".BadNumber",
+					      "Number contains characters which are not allowed");
+	}
+	atd_len = sprintf(buf, AG_PLACE_CALL, number);
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, buf, buf, atd_len);
+	rfcomm_start_watch(device);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_GET_CARRIER "AT+COPS?\r"
+
+static DBusMessage *ag_get_operator(DBusConnection * conn,
+				    DBusMessage * msg, void *data)
+{
+	struct audio_device *dev = (struct audio_device *) data;
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	GIOChannel *rfcomm = gw->rfcomm;
+	gsize read;
+	gchar *result;
+	DBusMessage *reply;
+	GIOStatus status;
+
+	rfcomm_stop_watch(dev);
+	io_channel_write_all(rfcomm, AG_GET_CARRIER,
+			     strlen(AG_GET_CARRIER));
+
+	status =
+	    g_io_channel_read_chars(rfcomm, buf, RFCOMM_BUF_SIZE - 1,
+				    &read, NULL);
+	rfcomm_start_watch(dev);
+	if (G_IO_STATUS_NORMAL == status) {
+		buf[read] = '\0';
+		if (NULL != strstr(buf, "+COPS")) {
+			result = strrchr(buf, ',') + 1;
+			reply = dbus_message_new_method_return(msg);
+			dbus_message_append_args(reply, DBUS_TYPE_STRING,
+						 &result,
+						 DBUS_TYPE_INVALID);
+		} else {
+			info("ag_get_operator(): '+COPS' expected but '%s' received"
+			     , buf);
+			reply =
+			    dbus_message_new_error(msg,
+						   ERROR_INTERFACE
+						   ".Failed",
+						   "Unexpected response from AG");
+		}
+	} else {
+		error("ag_get_operator(): %m");
+		reply =
+		    dbus_message_new_error(msg,
+					   ERROR_INTERFACE
+					   ".ConnectionFailed",
+					   "Failed to receive response from AG");
+	}
+	return reply;
+}
+
+#define AG_VR_ACTIVATE "AT+BVRA=1\r"
+static DBusMessage *ag_vr_activate(DBusConnection * conn,
+				   DBusMessage * msg, void *data)
+{
+	return process_simple(msg, (struct audio_device *) data,
+			      AG_VR_ACTIVATE);
+}
+
+#define AG_VR_DEACTIVATE "AT+BVRA=0\r"
+static DBusMessage *ag_vr_deactivate(DBusConnection * conn,
+				     DBusMessage * msg, void *data)
+{
+	return process_simple(msg, (struct audio_device *) data,
+			      AG_VR_DEACTIVATE);
+}
+
+#define AG_REQUEST_NUMBER "AT+BINP=1\r"
+static DBusMessage *ag_get_number_for_voice(DBusConnection * conn,
+					    DBusMessage * msg, void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar number[AG_CALLER_NUM_SIZE];
+
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, AG_REQUEST_NUMBER, buf,
+			     strlen(AG_REQUEST_NUMBER));
+	rfcomm_start_watch(device);
+	if (1 == sscanf(buf, "\r\n+BINP:%s\r\n", number)) {
+		DBusMessage *reply = dbus_message_new_method_return(msg);
+		dbus_message_append_args(reply, DBUS_TYPE_STRING, number,
+					 DBUS_TYPE_INVALID);
+		return reply;
+	} else {
+		info("ag_get_number_for_voice(): AG returned '%s' in place of number"
+		     , buf);
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE ".Failed",
+					      "AG didn't return a number");
+	}
+}
+
+#define AG_SEND_DTMF "AT+VTS:%c\r"
+static DBusMessage *ag_send_dtmf(DBusConnection * conn, DBusMessage * msg,
+				 void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number;
+	gint com_len;
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+			      DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS)) {
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE ".BadNumber",
+					      "Number contains characters which are not allowed");
+	}
+	gboolean got_ok = TRUE;
+	gint num_len = strlen(number);
+	gint i = 0;
+	rfcomm_stop_watch(device);
+	while (i < num_len && got_ok) {
+		com_len = sprintf(buf, AG_SEND_DTMF, number[i]);
+		rfcomm_send_and_read(gw, buf, buf, com_len);
+		got_ok = NULL != strstr(buf, OK_RESPONSE);
+		i += 1;
+	}
+	rfcomm_start_watch(device);
+	return process_ag_reponse(msg, buf);
+
+}
+
+#define AG_SET_MIC_GAIN "AT+VGM:%d\r"
+static DBusMessage *ag_send_mic_gain(DBusConnection * conn,
+				     DBusMessage * msg, void *data)
+{
+	gint value;
+	gchar buf[RFCOMM_BUF_SIZE];
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &value,
+			      DBUS_TYPE_INVALID);
+	sprintf(buf, AG_SET_MIC_GAIN, value);
+	return process_simple(msg, (struct audio_device *) data, buf);
+}
+
+
+#define AG_SET_SPEAKER_GAIN "AT+VGS:%d\r"
+static DBusMessage *ag_send_speaker_gain(DBusConnection * conn,
+					 DBusMessage * msg, void *data)
+{
+	gint value;
+	gchar buf[RFCOMM_BUF_SIZE];
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &value,
+			      DBUS_TYPE_INVALID);
+	sprintf(buf, AG_SET_SPEAKER_GAIN, value);
+	return process_simple(msg, (struct audio_device *) data, buf);
+}
+
+#define AG_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
+#define CNUM_LEN 5		/* length of "+CNUM" string */
+#define MAX_NUMBER_CNT 16
+static DBusMessage *ag_get_subscriber_nums(DBusConnection * conn,
+					   DBusMessage * msg, void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *cur_cnum;
+	gchar numbers[MAX_NUMBER_CNT][AG_CALLER_NUM_SIZE];
+	int number_cnt = 0;
+
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf,
+			     strlen(AG_GET_SUBSCRIBER_NUMS));
+	rfcomm_start_watch(device);
+	cur_cnum = strstr(buf, "+CNUM");
+	while (NULL != cur_cnum && number_cnt < MAX_NUMBER_CNT) {
+		sscanf(cur_cnum, "+CNUM:%s,", numbers[number_cnt]);
+		number_cnt += 1;
+		cur_cnum = strstr(cur_cnum + CNUM_LEN, "+CNUM");
+	}
+	DBusMessage *reply = dbus_message_new_method_return(msg);
+	dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+				 &numbers, number_cnt, DBUS_TYPE_INVALID);
+	return reply;
+}
+
+static DBusMessage *ag_hold_all_but_this(DBusConnection * conn,
+					 DBusMessage * msg, void *data)
+{
+	return dbus_message_new_error(
+		msg
+		, ERROR_INTERFACE".NotImplemented"
+		, "Not implemented");
+	
+}
+
+static DBusMessage *ag_cancel_all(DBusConnection * conn, DBusMessage * msg,
+				  void *data)
+{
+	return dbus_message_new_error(
+		msg
+		, ERROR_INTERFACE".NotImplemented"
+		, "Not implemented");
+
+}
+
+static DBusMessage *ag_cancel_spec(DBusConnection * conn,
+				   DBusMessage * msg, void *data)
+{
+	return dbus_message_new_error(
+		msg
+		, ERROR_INTERFACE".NotImplemented"
+		, "Not implemented");
+
+}
+
+static DBusMessage *ag_rah_put_on_hold(DBusConnection * conn,
+				       DBusMessage * msg, void *data)
+{
+	return dbus_message_new_error(
+		msg
+		, ERROR_INTERFACE".NotImplemented"
+		, "Not implemented");
+
+}
+
+static DBusMessage *ag_rah_answer_held_call(DBusConnection * conn,
+					    DBusMessage * msg, void *data)
+{
+	return dbus_message_new_error(
+		msg
+		, ERROR_INTERFACE".NotImplemented"
+		, "Not implemented");
+
+}
+
+static DBusMessage *ag_rah_cancel_held_call(DBusConnection * conn,
+					    DBusMessage * msg, void *data)
+{
+	return dbus_message_new_error(
+		msg
+		, ERROR_INTERFACE".NotImplemented"
+		, "Not implemented");
+
+}
+
+static GDBusMethodTable gateway_methods[] = {
+	{"Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC}
+	, {"Disconnect", "", "", ag_disconnect}
+	, {"IsConnected", "", "b", ag_is_connected}
+	, {"AnswerCall", "", "", ag_answer}
+	, {"CancelCurrentCall", "", "", ag_cancel_cur}
+	, {"Call", "s", "", ag_call}
+	, {"GetOperatorName", "", "s", ag_get_operator}
+	, {"VoiceRecognitionActvate", "", "", ag_vr_activate}
+	, {"VoiceRecognitionDeactvate", "", "", ag_vr_deactivate}
+	, {"GetNumberForLastVoiceTag", "", "s", ag_get_number_for_voice}
+	, {"SendDTMF", "s", "", ag_send_dtmf}
+	, {"SendMicrophoneGain", "y", "", ag_send_mic_gain}
+	, {"SendSpeakerGain", "y", "", ag_send_speaker_gain}
+	, {"GetSubscriberNumbers", "", "as", ag_get_subscriber_nums}
+
+	, {"RAHPutOnHold", "", "", ag_rah_put_on_hold}
+	, {"RAHAnswerHeldCall", "", "", ag_rah_answer_held_call}
+	, {"RAHCancelHeldCall", "", "", ag_rah_cancel_held_call}
+
+	, {"CancelCallFromNumber", "s", "", ag_cancel_spec}
+	, {"CancelAllCalls", "", "", ag_cancel_all}
+	, {"HoldAllButThis", "s", "", ag_hold_all_but_this}
+
+	, {NULL, NULL, NULL, NULL}
+};
+
+static GDBusSignalTable gateway_signals[] = {
+	{"Connected", "u"}
+	, {"Disconnected", ""}
+	, {"Ring", "s"}
+	, {"StatusUpdate", "si"}
+	, {"CallCancelled", ""}
+	, {"CallStart", ""}
+	, {"CallEnd", ""}
+	, {"VoiceRecognitionActive", ""}
+	, {"VoiceRecognitionInactive", ""}
+	, {"MicrophoneGain", "y"}
+	, {"SpeakerGain", "y"}
+	, {"RAHCallOnHold", ""}
+	, {"NoCallsHeld", ""}
+	, {"HoldCalls", ""}
+	, {"AllCallsHeld", ""}
+	, {NULL, NULL}
+};
+
+gboolean gateway_is_connected(struct audio_device *dev)
+{
+	return (NULL != dev) && (NULL != dev->gateway)
+	    && (NULL != dev->gateway->rfcomm);
+}
+
+struct gateway *gateway_init(struct audio_device *dev)
+{
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+				       AUDIO_GATEWAY_INTERFACE,
+				       gateway_methods, gateway_signals,
+				       NULL, dev, NULL)) {
+		return NULL;
+	}
+	struct gateway *gw = g_new0(struct gateway, 1);
+	gw->indies = NULL;
+	gw->is_dialing = FALSE;
+	return gw;
+}
+
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel * io)
+{
+	if (!io)
+		return -EINVAL;
+
+	dev->gateway->rfcomm = io;
+
+	return 0;
+}
+
+static void indicator_slice_free(gpointer mem)
+{
+	g_slice_free(struct indicator, mem);
+}
+
+int gateway_close(struct gateway *gw)
+{
+	GIOChannel *rfcomm = gw->rfcomm;
+	GIOChannel *sco = gw->sco;
+	GIOChannel *sco_server = gw->sco_server;
+	g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL);
+	g_slist_free(gw->indies);
+	if (rfcomm) {
+		g_io_channel_close(rfcomm);
+		g_io_channel_unref(rfcomm);
+		gw->rfcomm = NULL;
+	}
+
+	if (sco) {
+		g_io_channel_close(sco);
+		g_io_channel_unref(sco);
+		gw->sco = NULL;
+		gw->sco_start_cb = NULL;
+		gw->sco_start_cb_data = NULL;
+	}
+
+	if (sco_server) {
+		g_io_channel_close(sco_server);
+		g_io_channel_unref(sco_server);
+		gw->sco_server = NULL;
+	}
+	return 0;
+}
+
+
+/* These are functions to be called from unix.c for audio system ifaces (alsa, gstreamer, etc.) */
+
+gboolean gateway_request_stream(struct audio_device *dev
+			   , gateway_stream_cb_t cb
+			   , void *user_data)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (NULL == gw->sco) { 
+		if (NULL == gw->rfcomm) {
+			return FALSE;
+		}
+		gw->sco_start_cb = cb;
+		gw->sco_start_cb_data = user_data;
+		bt_sco_connect(&dev->src, &dev->dst, sco_connect_cb, dev);
+	} else {
+		if (NULL != cb) {
+			cb(dev, user_data);
+		}
+	}
+	return TRUE;
+}
+
+int gateway_config_stream(struct audio_device *dev
+			  , gateway_stream_cb_t sco_cb
+			  , void *user_data)
+{
+	struct gateway *gw = dev->gateway;
+	if (NULL == gw->rfcomm) {
+		gw->sco_start_cb = sco_cb;
+		gw->sco_start_cb_data = user_data;
+		return get_records(dev);
+	} else {
+		if (NULL != sco_cb) {
+			sco_cb(dev, user_data);
+		}
+		return 0; 
+	}
+}
+
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id)
+{
+	struct gateway *gw = dev->gateway;
+	gateway_close(gw);
+	return TRUE;
+}
+
+int gateway_get_sco_fd(struct audio_device *dev)
+{
+	GIOChannel* sco_chan = dev->gateway->sco;
+
+	if (NULL == sco_chan)
+		return -1;
+
+	return g_io_channel_unix_get_fd(sco_chan);
+}
+
+void gateway_suspend_stream(struct audio_device *dev)
+{
+	struct gateway* gw = dev->gateway;
+	
+	if (gw->sco) {
+		g_io_channel_close(gw->sco);
+		g_io_channel_unref(gw->sco);
+		gw->sco = NULL;
+		gw->sco_start_cb = NULL;
+		gw->sco_start_cb_data = NULL;
+	}
+}
diff --git a/audio/gateway.h b/audio/gateway.h
index e93406b..a6a5310 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -22,13 +22,40 @@
  *
  */
 
+
+
+#ifndef __GATEWAY_H__
+#define __GATEWAY_H__
+
+#include "device.h"
+
 #define AUDIO_GATEWAY_INTERFACE "org.bluez.Gateway"
 
 #define DEFAULT_HSP_HS_CHANNEL 6
 #define DEFAULT_HFP_HS_CHANNEL 7
 
-int gateway_init(DBusConnection *conn, gboolean disable_hfp, gboolean sco_hci);
+typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+struct gateway* gateway_init(struct audio_device *device);
+
+gboolean gateway_is_connected(struct audio_device* dev);
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+
+gboolean gateway_request_stream(struct audio_device *dev
+			   , gateway_stream_cb_t cb
+			   , void *user_data);
+
+int gateway_config_stream(struct audio_device *dev
+			   , gateway_stream_cb_t cb
+			   , void *user_data);
+
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id);
+
+int gateway_get_sco_fd(struct audio_device *dev);
+
+void gateway_suspend_stream(struct audio_device *dev);
+
 
-void gateway_exit(void);
+#endif /*__GATEWAY_H__*/
 
-gboolean gateway_is_enabled(uint16_t svc);
diff --git a/audio/ipc.h b/audio/ipc.h
index 6aa6486..4597f3a 100644
--- a/audio/ipc.h
+++ b/audio/ipc.h
@@ -110,7 +110,11 @@ typedef struct {
 #define BT_CAPABILITIES_ACCESS_MODE_WRITE	2
 #define BT_CAPABILITIES_ACCESS_MODE_READWRITE	3
 
+#define BT_CAPABILITIES_ROLE_SLAVE 0
+#define BT_CAPABILITIES_ROLE_MASTER 1
+
 #define BT_FLAG_AUTOCONNECT	1
+#define BT_FLAG_MASTER 0x2 /* choose between gateway/source (set bit) and headset/sink (clear bit aka default) */
 
 struct bt_get_capabilities_req {
 	bt_audio_msg_header_t	h;
@@ -165,8 +169,8 @@ struct bt_get_capabilities_req {
 #define BT_PCM_FLAG_NREC			1
 
 typedef struct {
-	uint8_t transport;
-	uint8_t type;
+	uint8_t transport; /* sco | a2dp */
+	uint8_t type; /* sbc | mpeg12 | mpeg24 | atrac */
 	uint8_t length;
 	uint8_t data[0];
 } __attribute__ ((packed)) codec_capabilities_t;
diff --git a/audio/manager.c b/audio/manager.c
index 4c9248f..99a49ad 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -91,10 +91,10 @@ struct audio_adapter {
 	char	*path;
 	uint32_t hsp_ag_record_id;
 	uint32_t hfp_ag_record_id;
-	uint32_t hsp_hs_record_id;
+	uint32_t hfp_hs_record_id;
 	GIOChannel *hsp_ag_server;
 	GIOChannel *hfp_ag_server;
-	GIOChannel *hsp_hs_server;
+	GIOChannel *hfp_hs_server;
 };
 
 static int max_connected_headsets = 1;
@@ -106,7 +106,7 @@ static GSList *devices = NULL;
 static struct enabled_interfaces enabled = {
 	.hfp		= TRUE,
 	.headset	= TRUE,
-	.gateway	= FALSE,
+	.gateway	= TRUE,
 	.sink		= TRUE,
 	.source		= FALSE,
 	.control	= TRUE,
@@ -132,11 +132,11 @@ gboolean server_is_enabled(bdaddr_t *src, uint16_t svc)
 	case HEADSET_SVCLASS_ID:
 		return enabled.headset;
 	case HEADSET_AGW_SVCLASS_ID:
-		return enabled.gateway;
+		return  FALSE;
 	case HANDSFREE_SVCLASS_ID:
 		return enabled.headset && enabled.hfp;
 	case HANDSFREE_AGW_SVCLASS_ID:
-		return  FALSE;
+		return enabled.gateway;
 	case AUDIO_SINK_SVCLASS_ID:
 		return enabled.sink;
 	case AV_REMOTE_TARGET_SVCLASS_ID:
@@ -192,6 +192,10 @@ static void handle_uuid(const char *uuidstr, struct audio_device *device)
 		break;
 	case HANDSFREE_AGW_SVCLASS_ID:
 		debug("Found Handsfree AG record");
+		if (device->gateway == NULL) {
+			device->gateway = gateway_init(device);
+		}
+
 		break;
 	case AUDIO_SINK_SVCLASS_ID:
 		debug("Found Audio Sink");
@@ -277,7 +281,7 @@ static sdp_record_t *hsp_ag_record(uint8_t ch)
 	return record;
 }
 
-static sdp_record_t *hsp_hs_record(uint8_t ch)
+static sdp_record_t *hfp_hs_record(uint8_t ch)
 {
 	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
 	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
@@ -295,13 +299,13 @@ static sdp_record_t *hsp_hs_record(uint8_t ch)
 	root = sdp_list_append(0, &root_uuid);
 	sdp_set_browse_groups(record, root);
 
-	sdp_uuid16_create(&svclass_uuid, HEADSET_SVCLASS_ID);
+	sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID);
 	svclass_id = sdp_list_append(0, &svclass_uuid);
 	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
 	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
 	sdp_set_service_classes(record, svclass_id);
 
-	sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
+	sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
 	profile.version = 0x0100;
 	pfseq = sdp_list_append(0, &profile);
 	sdp_set_profile_descs(record, pfseq);
@@ -319,7 +323,7 @@ static sdp_record_t *hsp_hs_record(uint8_t ch)
 	aproto = sdp_list_append(0, apseq);
 	sdp_set_access_protos(record, aproto);
 
-	sdp_set_info_attr(record, "Headset", 0, 0);
+	sdp_set_info_attr(record, "Hands-Free", 0, 0);
 
 	sdp_data_free(channel);
 	sdp_list_free(proto[0], 0);
@@ -398,7 +402,7 @@ static sdp_record_t *hfp_ag_record(uint8_t ch, uint32_t feat)
 	return record;
 }
 
-static void auth_cb(DBusError *derr, void *user_data)
+static void headset_auth_cb(DBusError *derr, void *user_data)
 {
 	struct audio_device *device = user_data;
 	const char *uuid;
@@ -479,7 +483,7 @@ static void ag_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
 	headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS);
 
 	err = btd_request_authorization(&device->src, &device->dst,
-					server_uuid, auth_cb, device);
+					server_uuid, headset_auth_cb, device);
 	if (err < 0) {
 		debug("Authorization denied: %s", strerror(-err));
 		headset_set_state(device, HEADSET_STATE_DISCONNECTED);
@@ -493,10 +497,49 @@ drop:
 	g_io_channel_unref(chan);
 }
 
-static void hs_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+
+static void gateway_auth_cb(DBusError *derr, void *user_data)
+{
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+	} 
+}
+
+static void hf_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
 		const bdaddr_t *dst, void *data)
 {
-	/*Stub*/
+	struct audio_adapter *adapter = data;
+	const char *server_uuid, *remote_uuid;
+	uint16_t svclass;
+	struct audio_device *device;
+
+	if (err < 0) {
+		error("accept: %s (%d)", strerror(-err), -err);
+		return;
+	}
+
+	server_uuid = HSP_HS_UUID;
+	remote_uuid = HSP_AG_UUID;
+	svclass = HEADSET_AGW_SVCLASS_ID;
+
+	device = manager_get_device(src, dst);
+	if (!device) {
+		debug("Failed to get device");
+	} else if (!device->gateway) {
+		btd_device_add_uuid(device->btd_dev, remote_uuid);
+	} else if (gateway_is_connected(device)) {
+		debug("Refusing new connection since one already exists");
+	} else if (0 > btd_request_authorization(&device->src, &device->dst,
+					     server_uuid, gateway_auth_cb, device)) {
+		debug("Authorization denied!");
+	} else if (gateway_connect_rfcomm(device, chan) < 0) {
+		error("Allocating new GIOChannel failed!");
+	} else {
+		return;
+	}
+
+	g_io_channel_close(chan);
+	g_io_channel_unref(chan);
 	return;
 }
 
@@ -609,26 +652,26 @@ static int gateway_server_init(struct audio_adapter *adapter)
 	if (master)
 		flags |= RFCOMM_LM_MASTER;
 
-	adapter->hsp_hs_server = bt_rfcomm_listen(&adapter->src, chan, flags,
-				hs_io_cb, adapter);
-	if (!adapter->hsp_hs_server)
+	adapter->hfp_hs_server = bt_rfcomm_listen(&adapter->src, chan, flags,
+				hf_io_cb, adapter);
+	if (!adapter->hfp_hs_server)
 		return -1;
 
-	record = hsp_hs_record(chan);
+	record = hfp_hs_record(chan);
 	if (!record) {
 		error("Unable to allocate new service record");
 		return -1;
 	}
 
 	if (add_record_to_server(&adapter->src, record) < 0) {
-		error("Unable to register HSP HS service record");
+		error("Unable to register HFP HS service record");
 		sdp_record_free(record);
-		g_io_channel_unref(adapter->hsp_hs_server);
-		adapter->hsp_hs_server = NULL;
+		g_io_channel_unref(adapter->hfp_hs_server);
+		adapter->hfp_hs_server = NULL;
 		return -1;
 	}
 
-	adapter->hsp_hs_record_id = record->handle;
+	adapter->hfp_hs_record_id = record->handle;
 
 	return 0;
 }
@@ -770,14 +813,14 @@ static void gateway_server_remove(struct btd_adapter *adapter)
 	if (!adp)
 		return;
 
-	if (adp->hsp_hs_record_id) {
-		remove_record_from_server(adp->hsp_hs_record_id);
-		adp->hsp_hs_record_id = 0;
+	if (adp->hfp_hs_record_id) {
+		remove_record_from_server(adp->hfp_hs_record_id);
+		adp->hfp_hs_record_id = 0;
 	}
 
-	if (adp->hsp_hs_server) {
-		g_io_channel_unref(adp->hsp_hs_server);
-		adp->hsp_hs_server = NULL;
+	if (adp->hfp_hs_server) {
+		g_io_channel_unref(adp->hfp_hs_server);
+		adp->hfp_hs_server = NULL;
 	}
 }
 
@@ -991,19 +1034,23 @@ struct audio_device *manager_find_device(const bdaddr_t *bda, const char *interf
 		if (bacmp(bda, BDADDR_ANY) && bacmp(&dev->dst, bda))
 			continue;
 
-		if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface)
+		if (interface && 0  == strcmp(AUDIO_HEADSET_INTERFACE, interface)
 				&& !dev->headset)
 			continue;
+	
+		if (interface && 0 == strcmp(AUDIO_GATEWAY_INTERFACE, interface)
+				&& !dev->gateway)
+			continue;
 
-		if (interface && !strcmp(AUDIO_SINK_INTERFACE, interface)
+		if (interface && 0 == strcmp(AUDIO_SINK_INTERFACE, interface)
 				&& !dev->sink)
 			continue;
 
-		if (interface && !strcmp(AUDIO_SOURCE_INTERFACE, interface)
+		if (interface && 0 == strcmp(AUDIO_SOURCE_INTERFACE, interface)
 				&& !dev->source)
 			continue;
 
-		if (interface && !strcmp(AUDIO_CONTROL_INTERFACE, interface)
+		if (interface && 0 == strcmp(AUDIO_CONTROL_INTERFACE, interface)
 				&& !dev->control)
 			continue;
 
diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c
index bf24206..937efca 100644
--- a/audio/pcm_bluetooth.c
+++ b/audio/pcm_bluetooth.c
@@ -109,8 +109,10 @@ struct bluetooth_a2dp {
 struct bluetooth_alsa_config {
 	char device[18];		/* Address of the remote Device */
 	int has_device;
-	uint8_t transport;		/* Requested transport */
+	uint8_t transport;		/* SCO or A2DP */
 	int has_transport;
+	uint8_t role;		        /* Master (gateway|a2dp source) or Slave (headset|a2dp sink) */
+	int has_role;
 	uint16_t rate;
 	int has_rate;
 	uint8_t channel_mode;		/* A2DP only */
@@ -1398,6 +1400,21 @@ static int bluetooth_parse_config(snd_config_t *conf,
 			}
 			continue;
 		}
+		if (strcmp(id, "role") == 0){
+			if (snd_config_get_string(n, &value) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+
+			if (strcmp(value, "master") == 0) {
+				bt_config->role = BT_CAPABILITIES_ROLE_MASTER;
+				bt_config->has_role = 1;
+			} else {
+				bt_config->role = BT_CAPABILITIES_ROLE_SLAVE;
+				bt_config->has_role = 1;
+			}
+			continue;
+		}
 
 		if (strcmp(id, "rate") == 0) {
 			if (snd_config_get_string(n, &value) < 0) {
@@ -1643,6 +1660,9 @@ static int bluetooth_init(struct bluetooth_data *data, snd_pcm_stream_t stream,
 
 	if (alsa_conf->autoconnect)
 		req->flags |= BT_FLAG_AUTOCONNECT;
+	if (alsa_conf->has_role && alsa_conf->role == BT_CAPABILITIES_ROLE_MASTER) {
+		req->flags |= BT_FLAG_MASTER;
+	}
 	strncpy(req->device, alsa_conf->device, 18);
 	if (alsa_conf->has_transport)
 		req->transport = alsa_conf->transport;
diff --git a/audio/unix.c b/audio/unix.c
index ad31181..1d83836 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -47,12 +47,14 @@
 #include "a2dp.h"
 #include "headset.h"
 #include "sink.h"
+#include "gateway.h"
 #include "unix.h"
 #include "glib-helper.h"
 
 typedef enum {
 	TYPE_NONE,
 	TYPE_HEADSET,
+	TYPE_GATEWAY,
 	TYPE_SINK,
 	TYPE_SOURCE
 } service_type_t;
@@ -93,7 +95,6 @@ static int unix_sock = -1;
 static void client_free(struct unix_client *client)
 {
 	struct a2dp_data *a2dp;
-
 	switch (client->type) {
 	case TYPE_SINK:
 	case TYPE_SOURCE:
@@ -190,10 +191,13 @@ static service_type_t select_service(struct audio_device *dev, const char *inter
 			return TYPE_SINK;
 		else if (dev->headset)
 			return TYPE_HEADSET;
-	} else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink)
+	} else if (0 == strcmp(interface, AUDIO_SINK_INTERFACE) && NULL != dev->sink) {
 		return TYPE_SINK;
-	else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset)
+	} else if (0 == strcmp(interface, AUDIO_HEADSET_INTERFACE) && NULL != dev->headset) {
 		return TYPE_HEADSET;
+	} else if (0 == strcmp(interface, AUDIO_GATEWAY_INTERFACE) && NULL != dev->gateway) {
+		return TYPE_GATEWAY;
+	}
 
 	return TYPE_NONE;
 }
@@ -233,7 +237,6 @@ static void headset_discovery_complete(struct audio_device *dev, void *user_data
 	struct bt_get_capabilities_rsp *rsp = (void *) buf;
 	codec_capabilities_t *codec;
 	pcm_capabilities_t *pcm;
-
 	client->req_id = 0;
 
 	if (!dev)
@@ -248,7 +251,7 @@ static void headset_discovery_complete(struct audio_device *dev, void *user_data
 
 	pcm = (void *) codec;
 	pcm->sampling_rate = 8000;
-	if (headset_get_nrec(dev))
+	if (NULL != dev->headset && headset_get_nrec(dev))
 		pcm->flags |= BT_PCM_FLAG_NREC;
 
 	rsp->h.type = BT_RESPONSE;
@@ -296,6 +299,34 @@ failed:
 	unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
 }
 
+static void gateway_setup_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+
+	if (NULL == dev) {
+		unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+		return;
+	}
+
+	client->req_id = 0;
+
+	memset(buf, 0, sizeof(buf));
+
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_SET_CONFIGURATION;
+	rsp->h.length = sizeof(*rsp);
+
+	rsp->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+	rsp->access_mode = client->access_mode;
+	rsp->link_mtu = 48;
+
+	client->data_fd = gateway_get_sco_fd(dev);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+}
+
 static void headset_resume_complete(struct audio_device *dev, void *user_data)
 {
 	struct unix_client *client = user_data;
@@ -343,6 +374,35 @@ failed:
 	unix_ipc_error(client, BT_START_STREAM, EIO);
 }
 
+static void gateway_resume_complete(struct audio_device *dev, void *user_data)
+{
+	struct unix_client *client = user_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_start_stream_rsp *rsp = (void *) buf;
+	struct bt_new_stream_ind *ind = (void *) buf;
+
+	memset(buf, 0, sizeof(buf));
+	rsp->h.type = BT_RESPONSE;
+	rsp->h.name = BT_START_STREAM;
+	rsp->h.length = sizeof(*rsp);
+
+	unix_ipc_sendmsg(client, &rsp->h);
+
+	memset(buf, 0, sizeof(buf));
+	ind->h.type = BT_INDICATION;
+	ind->h.name = BT_NEW_STREAM;
+	ind->h.length = sizeof(*ind);
+
+	unix_ipc_sendmsg(client, &ind->h);
+
+	client->data_fd = gateway_get_sco_fd(dev);
+	if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) {
+		error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno);
+		unix_ipc_error(client, BT_START_STREAM, EIO);
+	}
+	client->req_id = 0;
+}
+
 static void headset_suspend_complete(struct audio_device *dev, void *user_data)
 {
 	struct unix_client *client = user_data;
@@ -642,7 +702,6 @@ static void start_discovery(struct audio_device *dev, struct unix_client *client
 {
 	struct a2dp_data *a2dp;
 	int err = 0;
-
 	client->type = select_service(dev, client->interface);
 
 	switch (client->type) {
@@ -664,6 +723,7 @@ static void start_discovery(struct audio_device *dev, struct unix_client *client
 		break;
 
 	case TYPE_HEADSET:
+	case TYPE_GATEWAY:
 		headset_discovery_complete(dev, client);
 		break;
 
@@ -677,7 +737,7 @@ static void start_discovery(struct audio_device *dev, struct unix_client *client
 	return;
 
 failed:
-	unix_ipc_error(client, BT_GET_CAPABILITIES, err ? : EIO);
+	unix_ipc_error(client, BT_GET_CAPABILITIES, EIO);
 }
 
 static void start_config(struct audio_device *dev, struct unix_client *client)
@@ -727,6 +787,14 @@ static void start_config(struct audio_device *dev, struct unix_client *client)
 					hs->lock, client);
 		client->cancel = headset_cancel_stream;
 		break;
+	case TYPE_GATEWAY:
+		if (gateway_config_stream(dev, gateway_setup_complete, client) >= 0) {
+			client->cancel = gateway_cancel_stream;
+			id = 1;
+		} else {
+			id = 0;
+		}
+		break;
 
 	default:
 		error("No known services for device");
@@ -786,6 +854,15 @@ static void start_resume(struct audio_device *dev, struct unix_client *client)
 		client->cancel = headset_cancel_stream;
 		break;
 
+	case TYPE_GATEWAY:
+		if (gateway_request_stream(dev, gateway_resume_complete, client)){
+			id = 1;
+		} else {
+			id = 0;
+		}
+		client->cancel = gateway_cancel_stream;
+		break;
+
 	default:
 		error("No known services for device");
 		goto failed;
@@ -795,7 +872,6 @@ static void start_resume(struct audio_device *dev, struct unix_client *client)
 		error("start_resume: resume failed");
 		goto failed;
 	}
-
 	return;
 
 failed:
@@ -840,6 +916,13 @@ static void start_suspend(struct audio_device *dev, struct unix_client *client)
 		client->cancel = headset_cancel_stream;
 		break;
 
+	case TYPE_GATEWAY:
+		gateway_suspend_stream(dev);
+		client->cancel = gateway_cancel_stream;
+		headset_suspend_complete(dev, client);
+		id = 1;
+		break;
+
 	default:
 		error("No known services for device");
 		goto failed;
@@ -869,19 +952,25 @@ static void handle_getcapabilities_req(struct unix_client *client,
 		client->interface = NULL;
 	}
 
-	if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)
-		client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
-	else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+	if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO) {
+		if (req->flags & BT_FLAG_MASTER) {
+			client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE);
+		} else {
+			client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
+		}
+	} else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
 		client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+	}
 
-	if (!manager_find_device(&bdaddr, NULL, FALSE))
+	if (!manager_find_device(&bdaddr, NULL, FALSE)) {
 		goto failed;
+	}
 
 	dev = manager_find_device(&bdaddr, client->interface, TRUE);
 	if (!dev) {
-		if (req->flags & BT_FLAG_AUTOCONNECT)
+		if (req->flags & BT_FLAG_AUTOCONNECT) {
 			dev = manager_find_device(&bdaddr, client->interface, FALSE);
-		else
+		} else
 			goto failed;
 	}
 
@@ -998,18 +1087,7 @@ static void handle_setconfiguration_req(struct unix_client *client,
 
 	str2ba(req->device, &bdaddr);
 
-	if (client->interface) {
-		g_free(client->interface);
-		client->interface = NULL;
-	}
-
-	if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_SCO) {
-		err = handle_sco_transport(client, req);
-		if (err < 0) {
-			err = -err;
-			goto failed;
-		}
-	} else if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+	if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
 		err = handle_a2dp_transport(client, req);
 		if (err < 0) {
 			err = -err;
@@ -1110,7 +1188,6 @@ static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
 		default:
 			break;
 		}
-
 		if (client->cancel && client->req_id > 0)
 			client->cancel(client->dev, client->req_id);
 		goto failed;
@@ -1159,7 +1236,6 @@ static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
 		error("Audio API: received unexpected message name %d",
 				msghdr->name);
 	}
-
 	return TRUE;
 
 failed:

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
  2009-01-16 13:22     ` event
@ 2009-01-18 16:07       ` Marcel Holtmann
  2009-01-19  8:37         ` event
  0 siblings, 1 reply; 10+ messages in thread
From: Marcel Holtmann @ 2009-01-18 16:07 UTC (permalink / raw)
  To: event; +Cc: linux-bluetooth

Hi,

> >> I've implemented gateway profile. I've tested basic things, like
> >> place/cancel/answer call. Others are in development. Some could not be
> >> tested as my carrier doesn't provide corresponding services (like
> >> 3-way call, etc.) so any help welcome.
> >
> > thanks for the works, but can you please base the patch against the
> > latest GIT tree. It is kinda hard to review things that might already
> > have been implemented like sco_listen.
> >
> >  audio/audio-api.txt  |   94 +++++
> >  audio/device.h       |    7
> >  audio/gateway.c      |  938 +++++++++++++++++++++++++++++++++++++++++++++++++++
> >  audio/gateway.h      |   11
> >  audio/manager.c      |  124 ++++--
> >  common/glib-helper.c |   85 +++-
> >  common/glib-helper.h |    1
> >  7 files changed, 1205 insertions(+), 55 deletions(-)
> >
> > So any changes to glib-helper.[ch] have to be in a separate patch and
> > need to be discussed independent from the gateway implementation.
> >
> > Any audio-api.txt stuff should also go separately since that has to be
> > discussed. Also we can't send PCM data over D-Bus. It just doesn't work
> > like that. We do have the internal IPC for that and plugins for ALSA,
> > GStreamer and PulseAudio that should be used.
> >
> > However the most important part is that you follow the coding style and
> > that is the kernel coding style. You make it really hard for us to
> > review the code like this and it can't be applied. I really want you to
> > add support for the gateway role to BlueZ, but the overall code in the
> > project needs to follow the same rules.
> >
> > So please fix these issues first and then we do a deep review of it.
>
> Here is reworked and improved patches as you suggested with IPC support.
> 
> But I have some doubts and questions:
> 1. to distinguish between headset and gateway I've added one more alsa
> config option "role" which could be master (for gateway and probably
> a2dp source) or slave (which is default and works for headset and a2dp
> sink). I don't really like this approach so if you have any other idea
> it would be great.

we should use the terms "headset", "gateway", "sink" and "source" as
these are used through the specs.

> 2. my cell phone closes SCO connection when it doesn't need one,
> probably others act like this as well. SCO close results in
> bluetooth_hsp_write returning an error. What would be the best way to
> overcome this?

No idea what's the problem here. You should already get a notice of the
IPC that the channel is closed. On closed channels we have to discard
any kind of PCM data from the PC.

> 3. I've noticed that ipc interface duplicate dbus interface to some
> extent. Why can't pcm_bluetooth work over dbus directly?

D-Bus can't handle massive amount of PCM data payload. Also the ALSA
plugins don't really like dealing with a D-Bus mainloop. Hence we do
have the IPC as an alternate way of dealing with audio. We don't like to
do it, but we have to.

> 4. Aren't there any legal issues with implementing bluez? As I noticed
> in the spec that all the rights belong to Bluetooth SIG so it does
> look neither "free as in free beer" nor "free as in freedom" :)

BlueZ is a fully qualified host stack that complies with the SIG's
requirements. We do have to update the listing with a headset gateway
role at some point, but only once we have it fully working and actually
used in a product.

Regards

Marcel



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
  2009-01-18 16:07       ` Marcel Holtmann
@ 2009-01-19  8:37         ` event
  2009-01-19  9:20           ` Marcel Holtmann
  0 siblings, 1 reply; 10+ messages in thread
From: event @ 2009-01-19  8:37 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

On Sun, Jan 18, 2009 at 18:07, Marcel Holtmann <marcel@holtmann.org> wrote:
> Hi,
>
>> >> I've implemented gateway profile. I've tested basic things, like
>> >> place/cancel/answer call. Others are in development. Some could not be
>> >> tested as my carrier doesn't provide corresponding services (like
>> >> 3-way call, etc.) so any help welcome.
>> >
>> > thanks for the works, but can you please base the patch against the
>> > latest GIT tree. It is kinda hard to review things that might already
>> > have been implemented like sco_listen.
>> >
>> >  audio/audio-api.txt  |   94 +++++
>> >  audio/device.h       |    7
>> >  audio/gateway.c      |  938 +++++++++++++++++++++++++++++++++++++++++++++++++++
>> >  audio/gateway.h      |   11
>> >  audio/manager.c      |  124 ++++--
>> >  common/glib-helper.c |   85 +++-
>> >  common/glib-helper.h |    1
>> >  7 files changed, 1205 insertions(+), 55 deletions(-)
>> >
>> > So any changes to glib-helper.[ch] have to be in a separate patch and
>> > need to be discussed independent from the gateway implementation.
>> >
>> > Any audio-api.txt stuff should also go separately since that has to be
>> > discussed. Also we can't send PCM data over D-Bus. It just doesn't work
>> > like that. We do have the internal IPC for that and plugins for ALSA,
>> > GStreamer and PulseAudio that should be used.
>> >
>> > However the most important part is that you follow the coding style and
>> > that is the kernel coding style. You make it really hard for us to
>> > review the code like this and it can't be applied. I really want you to
>> > add support for the gateway role to BlueZ, but the overall code in the
>> > project needs to follow the same rules.
>> >
>> > So please fix these issues first and then we do a deep review of it.
>>
>> Here is reworked and improved patches as you suggested with IPC support.
>>
>> But I have some doubts and questions:
>> 1. to distinguish between headset and gateway I've added one more alsa
>> config option "role" which could be master (for gateway and probably
>> a2dp source) or slave (which is default and works for headset and a2dp
>> sink). I don't really like this approach so if you have any other idea
>> it would be great.
>
> we should use the terms "headset", "gateway", "sink" and "source" as
> these are used through the specs.
>

But current configuration contains "type" which is either voice or
hifi. So should I set 4 mentioned above for type field? I mean this
will break backward compatibility.

>> 2. my cell phone closes SCO connection when it doesn't need one,
>> probably others act like this as well. SCO close results in
>> bluetooth_hsp_write returning an error. What would be the best way to
>> overcome this?
>
> No idea what's the problem here. You should already get a notice of the
> IPC that the channel is closed. On closed channels we have to discard
> any kind of PCM data from the PC.
>

That is how it should work with current implementation but it would
not be very nice for application developers as e.g. pause of the
player on the phone will result in snd_pcm_read (or how is it named)
returning an error. I've tested using pyalsaaudio which raises an
exception in this case.
If I would develop an application over such api I would say several bad words :)

>> 3. I've noticed that ipc interface duplicate dbus interface to some
>> extent. Why can't pcm_bluetooth work over dbus directly?
>
> D-Bus can't handle massive amount of PCM data payload. Also the ALSA
> plugins don't really like dealing with a D-Bus mainloop. Hence we do
> have the IPC as an alternate way of dealing with audio. We don't like to
> do it, but we have to.
>

You can add one more DBus call which will create socket and send audio
connection over it.

>> 4. Aren't there any legal issues with implementing bluez? As I noticed
>> in the spec that all the rights belong to Bluetooth SIG so it does
>> look neither "free as in free beer" nor "free as in freedom" :)
>
> BlueZ is a fully qualified host stack that complies with the SIG's
> requirements. We do have to update the listing with a headset gateway
> role at some point, but only once we have it fully working and actually
> used in a product.
>
> Regards
>
> Marcel
>
>
>


Vale,
Leonid Movshovich

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
  2009-01-19  8:37         ` event
@ 2009-01-19  9:20           ` Marcel Holtmann
  2009-01-19 10:02             ` event
  0 siblings, 1 reply; 10+ messages in thread
From: Marcel Holtmann @ 2009-01-19  9:20 UTC (permalink / raw)
  To: event; +Cc: linux-bluetooth

Hi,

> >> >> I've implemented gateway profile. I've tested basic things, like
> >> >> place/cancel/answer call. Others are in development. Some could not be
> >> >> tested as my carrier doesn't provide corresponding services (like
> >> >> 3-way call, etc.) so any help welcome.
> >> >
> >> > thanks for the works, but can you please base the patch against the
> >> > latest GIT tree. It is kinda hard to review things that might already
> >> > have been implemented like sco_listen.
> >> >
> >> >  audio/audio-api.txt  |   94 +++++
> >> >  audio/device.h       |    7
> >> >  audio/gateway.c      |  938 +++++++++++++++++++++++++++++++++++++++++++++++++++
> >> >  audio/gateway.h      |   11
> >> >  audio/manager.c      |  124 ++++--
> >> >  common/glib-helper.c |   85 +++-
> >> >  common/glib-helper.h |    1
> >> >  7 files changed, 1205 insertions(+), 55 deletions(-)
> >> >
> >> > So any changes to glib-helper.[ch] have to be in a separate patch and
> >> > need to be discussed independent from the gateway implementation.
> >> >
> >> > Any audio-api.txt stuff should also go separately since that has to be
> >> > discussed. Also we can't send PCM data over D-Bus. It just doesn't work
> >> > like that. We do have the internal IPC for that and plugins for ALSA,
> >> > GStreamer and PulseAudio that should be used.
> >> >
> >> > However the most important part is that you follow the coding style and
> >> > that is the kernel coding style. You make it really hard for us to
> >> > review the code like this and it can't be applied. I really want you to
> >> > add support for the gateway role to BlueZ, but the overall code in the
> >> > project needs to follow the same rules.
> >> >
> >> > So please fix these issues first and then we do a deep review of it.
> >>
> >> Here is reworked and improved patches as you suggested with IPC support.
> >>
> >> But I have some doubts and questions:
> >> 1. to distinguish between headset and gateway I've added one more alsa
> >> config option "role" which could be master (for gateway and probably
> >> a2dp source) or slave (which is default and works for headset and a2dp
> >> sink). I don't really like this approach so if you have any other idea
> >> it would be great.
> >
> > we should use the terms "headset", "gateway", "sink" and "source" as
> > these are used through the specs.
> >
> 
> But current configuration contains "type" which is either voice or
> hifi. So should I set 4 mentioned above for type field? I mean this
> will break backward compatibility.

you could actually and still have "sink" == "hifi" and then also
"headset" == "voice" for example.

> >> 2. my cell phone closes SCO connection when it doesn't need one,
> >> probably others act like this as well. SCO close results in
> >> bluetooth_hsp_write returning an error. What would be the best way to
> >> overcome this?
> >
> > No idea what's the problem here. You should already get a notice of the
> > IPC that the channel is closed. On closed channels we have to discard
> > any kind of PCM data from the PC.
> >
> 
> That is how it should work with current implementation but it would
> not be very nice for application developers as e.g. pause of the
> player on the phone will result in snd_pcm_read (or how is it named)
> returning an error. I've tested using pyalsaaudio which raises an
> exception in this case.
> If I would develop an application over such api I would say several bad words :)

Obviously the ALSA plugin (or GStreamer or PluseAudio for that matter)
need to hide that fact and make it return a proper value instead of an
error. While for playback we can just discard the audio data, for
capture we might have to produce silence.

> >> 3. I've noticed that ipc interface duplicate dbus interface to some
> >> extent. Why can't pcm_bluetooth work over dbus directly?
> >
> > D-Bus can't handle massive amount of PCM data payload. Also the ALSA
> > plugins don't really like dealing with a D-Bus mainloop. Hence we do
> > have the IPC as an alternate way of dealing with audio. We don't like to
> > do it, but we have to.
> >
> 
> You can add one more DBus call which will create socket and send audio
> connection over it.

It just doesn't work that way. You will be killing your performance and
creating memory pressure. Especially on small and embedded systems. Also
the latency is pretty bad. Trust me here.

Regards

Marcel



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
  2009-01-19  9:20           ` Marcel Holtmann
@ 2009-01-19 10:02             ` event
  2009-01-19 10:11               ` Marcel Holtmann
  0 siblings, 1 reply; 10+ messages in thread
From: event @ 2009-01-19 10:02 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

On Mon, Jan 19, 2009 at 11:20, Marcel Holtmann <marcel@holtmann.org> wrote:
> Hi,
>
>> >> >> I've implemented gateway profile. I've tested basic things, like
>> >> >> place/cancel/answer call. Others are in development. Some could not be
>> >> >> tested as my carrier doesn't provide corresponding services (like
>> >> >> 3-way call, etc.) so any help welcome.
>> >> >
>> >> > thanks for the works, but can you please base the patch against the
>> >> > latest GIT tree. It is kinda hard to review things that might already
>> >> > have been implemented like sco_listen.
>> >> >
>> >> >  audio/audio-api.txt  |   94 +++++
>> >> >  audio/device.h       |    7
>> >> >  audio/gateway.c      |  938 +++++++++++++++++++++++++++++++++++++++++++++++++++
>> >> >  audio/gateway.h      |   11
>> >> >  audio/manager.c      |  124 ++++--
>> >> >  common/glib-helper.c |   85 +++-
>> >> >  common/glib-helper.h |    1
>> >> >  7 files changed, 1205 insertions(+), 55 deletions(-)
>> >> >
>> >> > So any changes to glib-helper.[ch] have to be in a separate patch and
>> >> > need to be discussed independent from the gateway implementation.
>> >> >
>> >> > Any audio-api.txt stuff should also go separately since that has to be
>> >> > discussed. Also we can't send PCM data over D-Bus. It just doesn't work
>> >> > like that. We do have the internal IPC for that and plugins for ALSA,
>> >> > GStreamer and PulseAudio that should be used.
>> >> >
>> >> > However the most important part is that you follow the coding style and
>> >> > that is the kernel coding style. You make it really hard for us to
>> >> > review the code like this and it can't be applied. I really want you to
>> >> > add support for the gateway role to BlueZ, but the overall code in the
>> >> > project needs to follow the same rules.
>> >> >
>> >> > So please fix these issues first and then we do a deep review of it.
>> >>
>> >> Here is reworked and improved patches as you suggested with IPC support.
>> >>
>> >> But I have some doubts and questions:
>> >> 1. to distinguish between headset and gateway I've added one more alsa
>> >> config option "role" which could be master (for gateway and probably
>> >> a2dp source) or slave (which is default and works for headset and a2dp
>> >> sink). I don't really like this approach so if you have any other idea
>> >> it would be great.
>> >
>> > we should use the terms "headset", "gateway", "sink" and "source" as
>> > these are used through the specs.
>> >
>>
>> But current configuration contains "type" which is either voice or
>> hifi. So should I set 4 mentioned above for type field? I mean this
>> will break backward compatibility.
>
> you could actually and still have "sink" == "hifi" and then also
> "headset" == "voice" for example.
>
>> >> 2. my cell phone closes SCO connection when it doesn't need one,
>> >> probably others act like this as well. SCO close results in
>> >> bluetooth_hsp_write returning an error. What would be the best way to
>> >> overcome this?
>> >
>> > No idea what's the problem here. You should already get a notice of the
>> > IPC that the channel is closed. On closed channels we have to discard
>> > any kind of PCM data from the PC.
>> >
>>
>> That is how it should work with current implementation but it would
>> not be very nice for application developers as e.g. pause of the
>> player on the phone will result in snd_pcm_read (or how is it named)
>> returning an error. I've tested using pyalsaaudio which raises an
>> exception in this case.
>> If I would develop an application over such api I would say several bad words :)
>
> Obviously the ALSA plugin (or GStreamer or PluseAudio for that matter)
> need to hide that fact and make it return a proper value instead of an
> error. While for playback we can just discard the audio data, for
> capture we might have to produce silence.
>
>> >> 3. I've noticed that ipc interface duplicate dbus interface to some
>> >> extent. Why can't pcm_bluetooth work over dbus directly?
>> >
>> > D-Bus can't handle massive amount of PCM data payload. Also the ALSA
>> > plugins don't really like dealing with a D-Bus mainloop. Hence we do
>> > have the IPC as an alternate way of dealing with audio. We don't like to
>> > do it, but we have to.
>> >
>>
>> You can add one more DBus call which will create socket and send audio
>> connection over it.
>
> It just doesn't work that way. You will be killing your performance and
> creating memory pressure. Especially on small and embedded systems. Also
> the latency is pretty bad. Trust me here.

I didn't meant to send audio data over dbus. My idea was like this:

     +-------+            +------+
     | bluez |            | alsa |
     +-------+            +------+
         |  get unix socket  |
         | <-----------------|
      create                 |
    unix socket              |
         |                   |
         |    send unix      |
         |   socket name     |
         |------------------>|
         |                 listen
         |                 for fd
         |                   |
         | send sco fd       |
         |------------------>|

first call is over Dbus and socket is sent over domain socket.
>
> Regards
>
> Marcel
>
>
>


Vale,
Leonid Movshovich

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
  2009-01-19 10:02             ` event
@ 2009-01-19 10:11               ` Marcel Holtmann
  0 siblings, 0 replies; 10+ messages in thread
From: Marcel Holtmann @ 2009-01-19 10:11 UTC (permalink / raw)
  To: event; +Cc: linux-bluetooth

Hi,

> >> >> >> I've implemented gateway profile. I've tested basic things, like
> >> >> >> place/cancel/answer call. Others are in development. Some could not be
> >> >> >> tested as my carrier doesn't provide corresponding services (like
> >> >> >> 3-way call, etc.) so any help welcome.
> >> >> >
> >> >> > thanks for the works, but can you please base the patch against the
> >> >> > latest GIT tree. It is kinda hard to review things that might already
> >> >> > have been implemented like sco_listen.
> >> >> >
> >> >> >  audio/audio-api.txt  |   94 +++++
> >> >> >  audio/device.h       |    7
> >> >> >  audio/gateway.c      |  938 +++++++++++++++++++++++++++++++++++++++++++++++++++
> >> >> >  audio/gateway.h      |   11
> >> >> >  audio/manager.c      |  124 ++++--
> >> >> >  common/glib-helper.c |   85 +++-
> >> >> >  common/glib-helper.h |    1
> >> >> >  7 files changed, 1205 insertions(+), 55 deletions(-)
> >> >> >
> >> >> > So any changes to glib-helper.[ch] have to be in a separate patch and
> >> >> > need to be discussed independent from the gateway implementation.
> >> >> >
> >> >> > Any audio-api.txt stuff should also go separately since that has to be
> >> >> > discussed. Also we can't send PCM data over D-Bus. It just doesn't work
> >> >> > like that. We do have the internal IPC for that and plugins for ALSA,
> >> >> > GStreamer and PulseAudio that should be used.
> >> >> >
> >> >> > However the most important part is that you follow the coding style and
> >> >> > that is the kernel coding style. You make it really hard for us to
> >> >> > review the code like this and it can't be applied. I really want you to
> >> >> > add support for the gateway role to BlueZ, but the overall code in the
> >> >> > project needs to follow the same rules.
> >> >> >
> >> >> > So please fix these issues first and then we do a deep review of it.
> >> >>
> >> >> Here is reworked and improved patches as you suggested with IPC support.
> >> >>
> >> >> But I have some doubts and questions:
> >> >> 1. to distinguish between headset and gateway I've added one more alsa
> >> >> config option "role" which could be master (for gateway and probably
> >> >> a2dp source) or slave (which is default and works for headset and a2dp
> >> >> sink). I don't really like this approach so if you have any other idea
> >> >> it would be great.
> >> >
> >> > we should use the terms "headset", "gateway", "sink" and "source" as
> >> > these are used through the specs.
> >> >
> >>
> >> But current configuration contains "type" which is either voice or
> >> hifi. So should I set 4 mentioned above for type field? I mean this
> >> will break backward compatibility.
> >
> > you could actually and still have "sink" == "hifi" and then also
> > "headset" == "voice" for example.
> >
> >> >> 2. my cell phone closes SCO connection when it doesn't need one,
> >> >> probably others act like this as well. SCO close results in
> >> >> bluetooth_hsp_write returning an error. What would be the best way to
> >> >> overcome this?
> >> >
> >> > No idea what's the problem here. You should already get a notice of the
> >> > IPC that the channel is closed. On closed channels we have to discard
> >> > any kind of PCM data from the PC.
> >> >
> >>
> >> That is how it should work with current implementation but it would
> >> not be very nice for application developers as e.g. pause of the
> >> player on the phone will result in snd_pcm_read (or how is it named)
> >> returning an error. I've tested using pyalsaaudio which raises an
> >> exception in this case.
> >> If I would develop an application over such api I would say several bad words :)
> >
> > Obviously the ALSA plugin (or GStreamer or PluseAudio for that matter)
> > need to hide that fact and make it return a proper value instead of an
> > error. While for playback we can just discard the audio data, for
> > capture we might have to produce silence.
> >
> >> >> 3. I've noticed that ipc interface duplicate dbus interface to some
> >> >> extent. Why can't pcm_bluetooth work over dbus directly?
> >> >
> >> > D-Bus can't handle massive amount of PCM data payload. Also the ALSA
> >> > plugins don't really like dealing with a D-Bus mainloop. Hence we do
> >> > have the IPC as an alternate way of dealing with audio. We don't like to
> >> > do it, but we have to.
> >> >
> >>
> >> You can add one more DBus call which will create socket and send audio
> >> connection over it.
> >
> > It just doesn't work that way. You will be killing your performance and
> > creating memory pressure. Especially on small and embedded systems. Also
> > the latency is pretty bad. Trust me here.
> 
> I didn't meant to send audio data over dbus. My idea was like this:
> 
>      +-------+            +------+
>      | bluez |            | alsa |
>      +-------+            +------+
>          |  get unix socket  |
>          | <-----------------|
>       create                 |
>     unix socket              |
>          |                   |
>          |    send unix      |
>          |   socket name     |
>          |------------------>|
>          |                 listen
>          |                 for fd
>          |                   |
>          | send sco fd       |
>          |------------------>|
> 
> first call is over Dbus and socket is sent over domain socket.

then you still have to handle the integration of D-Bus mainloop (or call
it message parsing/handling) into an ALSA plugin. ALSA is just a total
broken concept when it comes to virtual sound cards. It works great for
actual physical devices, but that is it.

Regards

Marcel



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Gateway profile
@ 2009-02-06 12:08 Thierry Pierret
  0 siblings, 0 replies; 10+ messages in thread
From: Thierry Pierret @ 2009-02-06 12:08 UTC (permalink / raw)
  To: linux-bluetooth

Hi Leonid, hi to all,

I know this patch is not perfect and still has some issues. But since 
there is currently no alternative, I would like to use it on my iMX31 
platform. And if afterwards I can help with some improvements, it's 
worthy.
In the meantime, I have to dive in your code, the bluez code and in the 
buetooth specifications. Since I'm not an expert, neither for bluetooth 
and bluez, neither for sound, I would earn some time if I could get some 
answers already.

I installed the "Gateway Profile" patch on BlueZ 4.28 (with some minor 
corrections for the compilation).

1. First issue, only the "Handsfree" service is exported :

$ sdptool browse local
Browsing FF:FF:FF:00:00:00 ...
Service Name: Hands-free
Service RecHandle: 0x10000
Service Class ID List:
  "Handsfree" (0x111e)
  "Generic Audio" (0x1203)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 6
Profile Descriptor List:
  "Handsfree" (0x111e)
    Version: 0x0100

Here is the audio.conf file I use :

[General]
Master=true
Enable=Gateway
Disable=Headset;Sink;Source;Control
SCORouting=PCM

[Headset]
HFP=true
MaxConnected=1

I certainly miss something in my configuration in order to also get the 
Headset service exported.

2. Second issue, any attempt to connect the handsfree service from a 
mobile failed. The mobile ends in the paired state, but the handsfree 
device connection failed. Here is the full (annotated) ouput of the 
bluetoothd daemon.

bluetoothd[1772]: Bluetooth daemon
bluetoothd[1772]: Enabling debug information
bluetoothd[1772]: parsing main.conf
bluetoothd[1772]: discovto=0
bluetoothd[1772]: pairto=0
bluetoothd[1772]: pageto=8192
bluetoothd[1772]: name=%h-%d
bluetoothd[1772]: class=0x000100
bluetoothd[1772]: inqmode=0
bluetoothd[1772]: Key file does not have key 'DeviceID'
bluetoothd[1772]: Starting SDP server
bluetoothd[1772]: Loading plugins /usr/lib/bluetooth/plugins
bluetoothd[1772]: register_interface: path /org/bluez/1772/any
bluetoothd[1772]: Registered interface org.bluez.Service on path 
/org/bluez/1772/any
bluetoothd[1772]: Unix socket created: 10
bluetoothd[1772]: HCI dev 0 registered
bluetoothd[1772]: child 1773 forked
bluetoothd[1772]: HCI dev 0 already up
bluetoothd[1772]: Starting security manager 0
bluetoothd[1772]: register_interface: path /org/bluez/1772/hci0
bluetoothd[1772]: Registered interface org.bluez.Service on path 
/org/bluez/1772/hci0
bluetoothd[1772]: gateway_server_probe: path /org/bluez/1772/hci0
bluetoothd[1772]: Adding record with handle 0x10000
bluetoothd[1772]: Record pattern UUID 00000003-0000-1000-8000-00805f9
bluetoothd[1772]: Record pattern UUID 00000100-0000-1000-8000-00805f9
bluetoothd[1772]: Record pattern UUID 00001002-0000-1000-8000-00805f9
bluetoothd[1772]: Record pattern UUID 0000111e-0000-1000-8000-00805f9
bluetoothd[1772]: Record pattern UUID 00001203-0000-1000-8000-00805f9
bluetoothd[1772]: proxy_probe: path /org/bluez/1772/hci0
bluetoothd[1772]: Registered interface org.bluez.SerialProxyManager on 
path /org/bluez/1772/hci0
bluetoothd[1772]: Creating device 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66
bluetoothd[1772]: Probe drivers for 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66
bluetoothd[1772]: adapter_get_device(00:0E:ED:01:DA:66)
bluetoothd[1772]: audio handle_uuid: server not enabled for 
00001112-0000-1000-8000-00805F9B34FB (0x1112)
bluetoothd[1772]: Found Handsfree AG record
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00000002-0000-1000-8000-0002ee000002
bluetoothd[1772]: Registered interface org.bluez.Serial on path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001101-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001103-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001105-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001106-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001112-0000-1000-8000-00805F9B34FB
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
0000111f-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
0000112d-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00005002-0000-1000-8000-0002ee000001
bluetoothd[1772]: Adapter /org/bluez/1772/hci0 has been enabled
bluetoothd[1772]: child 1773 exited

# Passkey agent started
bluetoothd[1772]: Agent registered for hci0 at :1.4:/org/bluez/agent_1782

# Attempt from a mobile (Nokia 6810) to connect the handsfree device
bluetoothd[1772]: adapter_get_device(00:0E:ED:01:DA:66)
bluetoothd[1772]: adapter_create_device(00:0E:ED:01:DA:66)
bluetoothd[1772]: Creating device 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66
bluetoothd[1772]: pin_code_request (sba=00:02:5B:00:A5:A5, 
dba=00:0E:ED:01:DA:66)
bluetoothd[1772]: adapter_get_device(00:0E:ED:01:DA:66)
bluetoothd[1772]: /org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: requesting 
agent authentication
Agent_message was called!
Agent called: RequestPinCode
Device path = /org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66
Agent has been released
bluetoothd[1772]: link_key_notify (sba=00:02:5B:00:A5:A5, 
dba=00:0E:ED:01:DA:66)
bluetoothd[1772]: adapter_get_device(00:0E:ED:01:DA:66)
bluetoothd[1772]: hcid_dbus_bonding_process_complete: status=00
bluetoothd[1772]: setting timer for reverse service discovery
bluetoothd[1772]: adapter_get_device(00:0E:ED:01:DA:66)
bluetoothd[1772]: Probe drivers for 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66
bluetoothd[1772]: audio handle_uuid: server not enabled for 
00001112-0000-1000-8000-00805F9B34FB (0x1112)
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001112-0000-1000-8000-00805F9B34FB
bluetoothd[1772]: Registered interface org.bluez.Serial on path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66
bluetoothd[1772]: Probe drivers for 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66
bluetoothd[1772]: audio handle_uuid: server not enabled for 
00001112-0000-1000-8000-00805f9b34fb (0x1112)
bluetoothd[1772]: Found Handsfree AG record
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001105-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001106-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001103-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001101-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001101-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
0000111f-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00001112-0000-1000-8000-00805f9b34fb
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00000002-0000-1000-8000-0002ee000002
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
00005002-0000-1000-8000-0002ee000001
bluetoothd[1772]: serial_probe: path 
/org/bluez/1772/hci0/dev_00_0E_ED_01_DA_66: 
0000112d-0000-1000-8000-00805f9b34fb

What's wrong ? I need support.

Thanks in advance for your help.
Regards.

Thierry

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2009-02-06 12:08 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <664d43d60812151512y6631ebf2j9665e1473193077d@mail.gmail.com>
2008-12-15 23:14 ` [PATCH] Gateway profile event
2008-12-17  2:48   ` Marcel Holtmann
2008-12-19  7:36     ` event
2009-01-16 13:22     ` event
2009-01-18 16:07       ` Marcel Holtmann
2009-01-19  8:37         ` event
2009-01-19  9:20           ` Marcel Holtmann
2009-01-19 10:02             ` event
2009-01-19 10:11               ` Marcel Holtmann
2009-02-06 12:08 Thierry Pierret

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox