* [PATCH 1/2] clean up audio/gateway.c
@ 2010-02-02 21:41 Gustavo F. Padovan
2010-02-02 21:41 ` [PATCH 2/2] Implement HandsfreeGateway Interface Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-02 21:41 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
remove all code related to the AT engine
---
audio/gateway.c | 760 +------------------------------------------------------
1 files changed, 1 insertions(+), 759 deletions(-)
diff --git a/audio/gateway.c b/audio/gateway.c
index a1c1ea2..3dc09ff 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -53,52 +53,6 @@
#include "dbus-common.h"
#define RFCOMM_BUF_SIZE 256
-/* not-more-then-16 defined by GSM + 1 for NULL + padding */
-#define AG_INDICATOR_DESCR_SIZE 20
-#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 */
-/* Releases all held calls or sets User Determined User Busy (UDUB)
- * for a waiting call */
-#define AG_CHLD_0 0x01
-/* Releases all active calls (if any exist) and accepts the other
- * (held or waiting) call */
-#define AG_CHLD_1 0x02
-/* Releases specified active call only <x> */
-#define AG_CHLD_1x 0x04
-/* Places all active calls (if any exist) on hold and accepts the other
- * (held or waiting) call */
-#define AG_CHLD_2 0x08
-/* Request private consultation mode with specified call <x> (Place all
- * calls on hold EXCEPT the call <x>) */
-#define AG_CHLD_2x 0x10
-/* Adds a held call to the conversation */
-#define AG_CHLD_3 0x20
-/* Connects the two calls and disconnects the subscriber from both calls
- * (Explicit Call Transfer). Support for this value and its associated
- * functionality is optional for the HF. */
-#define AG_CHLD_4 0x40
-
-#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 {
gateway_state_t state;
@@ -108,387 +62,10 @@ struct gateway {
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;
- gboolean call_active;
-
- int sp_gain;
- int mic_gain;
};
-static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond,
- struct audio_device *device);
-
int gateway_close(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 (count > 0) {
- status = g_io_channel_write_chars(io, data, count, &written,
- NULL);
- if (status != G_IO_STATUS_NORMAL)
- 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;
- gsize read = 0;
- gboolean got_ok = FALSE;
- gboolean got_error = FALSE;
- gchar *resp_buf = response;
- gsize toread = RFCOMM_BUF_SIZE - 1;
- GIOStatus status;
-
- if (!io_channel_write_all(rfcomm, data, count))
- return FALSE;
-
- while (!(got_ok || got_error)) {
- status = g_io_channel_read_chars(rfcomm, resp_buf, toread,
- &read, NULL);
- if (status == G_IO_STATUS_NORMAL)
- 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>))
- * ... */
-static GSList *parse_indicator_names(gchar *names, GSList *indies)
-{
- gchar *current = names - 1;
- GSList *result = indies;
- gchar *next;
- struct indicator *ind;
-
- while (current != NULL) {
- current += 2;
- next = strstr(current, ",(");
- ind = g_slice_new(struct indicator);
- strncpy(ind->descr, current, 20);
- ind->descr[(intptr_t) next - (intptr_t) current] = '\0';
- result = g_slist_append(result, (gpointer) ind);
- current = strstr(next + 1, ",(");
- }
- return result;
-}
-
-/* get values from <val0>,<val1>,... */
-static GSList *parse_indicator_values(gchar *values, GSList *indies)
-{
- gint val;
- gchar *current = values - 1;
- GSList *runner = indies;
- struct indicator *ind;
-
- while (current != NULL) {
- current += 1;
- sscanf(current, "%d", &val);
- current = strchr(current, ',');
- ind = 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 (strstr(features, "0"))
- result |= AG_CHLD_0;
-
- if (strstr(features, "1"))
- result |= AG_CHLD_1;
-
- if (strstr(features, "1x"))
- result |= AG_CHLD_1x;
-
- if (strstr(features, "2"))
- result |= AG_CHLD_2;
-
- if (strstr(features, "2x"))
- result |= AG_CHLD_2x;
-
- if (strstr(features, "3"))
- result |= AG_CHLD_3;
-
- if (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;
-
- debug("at the begin of establish_service_level_conn()");
- res = rfcomm_send_and_read(gw, AG_FEATURES, buf,
- sizeof(AG_FEATURES) - 1);
- if (!res || sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features) != 1)
- 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 ((gw->ag_features & AG_FEATURE_3WAY) != 0) {
- 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 ((gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) != 0)
- 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, guint index,
- gint value)
-{
- struct gateway *gw = dev->gateway;
- struct indicator *ind = g_slist_nth_data(gw->indies, index - 1);
- gchar *name = ind->descr;
-
- ind->value = value;
-
- debug("at the begin of process_ind_change, name is %s", name);
- if (!strcmp(name, "\"call\"")) {
- if (value > 0) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallStarted", DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- gw->call_active = TRUE;
- } else {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallEnded", DBUS_TYPE_INVALID);
- gw->call_active = FALSE;
- }
-
- } else if (!strcmp(name, "\"callsetup\"")) {
- if (value == 0 && gw->is_dialing) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallTerminated",
- DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- } else if (!gw->is_dialing && value > 0)
- gw->is_dialing = TRUE;
-
- } else if (!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
- */
- /* '2' means: all calls held, no active calls */
- if (value == 2) {
- if (gw->is_dialing) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallTerminated",
- DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- }
- }
- } else if (!strcmp(name, "\"service\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "RegistrationStatus",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"signal\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "SignalStrength",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"roam\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "RoamingStatus",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"battchg\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "BatteryCharge",
- DBUS_TYPE_UINT16, &value);
-}
-
-static void process_ring(struct audio_device *device, GIOChannel *chan,
- gchar *buf)
-{
- gchar number[AG_CALLER_NUM_SIZE];
- gchar *cli;
- gchar *sep;
- gsize read;
- GIOStatus status;
-
- rfcomm_stop_watch(device);
- status = g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read, NULL);
- if (status != G_IO_STATUS_NORMAL)
- return;
-
- debug("at the begin of process_ring");
- if (strlen(buf) > AG_CALLER_NUM_SIZE + 10)
- error("process_ring(): buf is too long '%s'", buf);
- else if ((cli = strstr(buf, "\r\n+CLIP"))) {
- if (sscanf(cli, "\r\n+CLIP: \"%s", number) == 1) {
- sep = strchr(number, '"');
- sep[0] = '\0';
-
- /* FIXME:signal will be emitted on each RING+CLIP.
- * That's bad */
- cli = number;
- g_dbus_emit_signal(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "Ring",
- DBUS_TYPE_STRING, &cli,
- DBUS_TYPE_INVALID);
- device->gateway->is_dialing = TRUE;
- } else
- error("process_ring(): '%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;
- gsize read;
- /* some space for value */
- gchar indicator[AG_INDICATOR_DESCR_SIZE + 4];
- gint value;
- guint index;
- gchar *sep;
-
- debug("at the begin of rfcomm_ag_data_cb()");
- if (cond & G_IO_NVAL)
- return FALSE;
-
- gw = device->gateway;
-
- if (cond & (G_IO_ERR | G_IO_HUP)) {
- debug("connection with remote BT is closed");
- gateway_close(device);
- return FALSE;
- }
-
- if (g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read, NULL)
- != G_IO_STATUS_NORMAL)
- return TRUE;
- buf[read] = '\0';
-
- if (strlen(buf) > AG_INDICATOR_DESCR_SIZE + 14)
- error("rfcomm_ag_data_cb(): buf is too long '%s'", buf);
- else if (sscanf(buf, "\r\n+CIEV:%s\r\n", indicator) == 1) {
- sep = strchr(indicator, ',');
- sep[0] = '\0';
- sep += 1;
- index = atoi(indicator);
- value = atoi(sep);
- process_ind_change(device, index, value);
- } else if (strstr(buf, "RING"))
- process_ring(device, chan, buf);
- else if (sscanf(buf, "\r\n+BVRA:%d\r\n", &value) == 1) {
- if (value == 0)
- 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 (sscanf(buf, "\r\n+VGS:%d\r\n", &value) == 1) {
- gw->sp_gain = value;
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "SpeakerGain",
- DBUS_TYPE_UINT16, &value);
- } else if (sscanf(buf, "\r\n+VGM:%d\r\n", &value) == 1) {
- gw->mic_gain = value;
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "MicrophoneGain",
- DBUS_TYPE_UINT16, &value);
- } 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)
{
@@ -541,7 +118,6 @@ static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- DBusMessage *conn_mes = gw->connect_message;
gchar gw_addr[18];
GIOFlags flags;
@@ -563,29 +139,6 @@ static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
if (!gw->rfcomm)
gw->rfcomm = g_io_channel_ref(chan);
- if (establish_service_level_conn(dev->gateway)) {
- gboolean value = TRUE;
-
- debug("%s: Connected to %s", dev->path, gw_addr);
- rfcomm_start_watch(dev);
- if (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;
- }
-
- gw->state = GATEWAY_STATE_CONNECTED;
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return;
- } 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);
@@ -732,321 +285,20 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
-static DBusMessage *process_ag_reponse(DBusMessage *msg, gchar *response)
-{
- DBusMessage *reply;
-
-
- debug("in process_ag_reponse, response is %s", response);
- if (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)
-{
- struct audio_device *dev = data;
- struct gateway *gw = dev->gateway;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- if (gw->call_active)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".CallAlreadyAnswered",
- "Call AlreadyAnswered");
-
- return process_simple(msg, dev, AG_ANSWER);
-}
-
-#define AG_HANGUP "AT+CHUP\r"
-
-static DBusMessage *ag_terminate_call(DBusConnection *conn, DBusMessage *msg,
- void *data)
-{
- struct audio_device *dev = data;
- struct gateway *gw = dev->gateway;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- return process_simple(msg, dev, AG_HANGUP);
-}
-
-/* according to GSM spec */
-#define ALLOWED_NUMBER_SYMBOLS "1234567890*#ABCD"
-#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;
- DBusMessage *result;
-
- debug("at the begin of ag_call()");
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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);
-
- result = process_ag_reponse(msg, buf);
- return result;
-}
-
-#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, *sep;
- DBusMessage *reply;
- GIOStatus status;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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 (strstr(buf, "+COPS")) {
- if (!strrchr(buf, ','))
- result = "0";
- else {
- result = strchr(buf, '\"') + 1;
- sep = strchr(result, '\"');
- sep[0] = '\0';
- }
-
- 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_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;
- gboolean got_ok = TRUE;
- gint num_len;
- gint i = 0;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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");
-
- num_len = strlen(number);
- 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_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
-#define CNUM_LEN 5 /* length of "+CNUM" string */
-#define MAX_NUMBER_CNT 16
-static DBusMessage *ag_get_subscriber_num(DBusConnection *conn,
- DBusMessage *msg, void *data)
-{
- struct audio_device *device = data;
- struct gateway *gw = device->gateway;
- gchar buf[RFCOMM_BUF_SIZE];
- gchar *number, *end;
- DBusMessage *reply = dbus_message_new_method_return(msg);
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- rfcomm_stop_watch(device);
- rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf,
- strlen(AG_GET_SUBSCRIBER_NUMS));
- rfcomm_start_watch(device);
-
- if (strlen(buf) > AG_CALLER_NUM_SIZE + 21)
- error("ag_get_subscriber_num(): buf is too long '%s'", buf);
- else if (strstr(buf, "+CNUM")) {
- number = strchr(buf, ',');
- number++;
- end = strchr(number, ',');
- if (end) {
- *end = '\0';
- dbus_message_append_args(reply, DBUS_TYPE_STRING,
- &number, DBUS_TYPE_INVALID);
- }
- } else
- error("ag_get_subscriber_num(): read wrong data '%s'", buf);
-
- return reply;
-}
-
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- struct audio_device *device = data;
- struct gateway *gw = device->gateway;
- DBusMessage *reply;
- DBusMessageIter iter;
- DBusMessageIter dict;
- gboolean value;
- guint index = 0;
- struct indicator *ind;
-
- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;
-
- dbus_message_iter_init_append(reply, &iter);
-
- dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
- DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
- DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
- DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
-
- /* Connected */
- value = gateway_is_connected(device);
- dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
-
- if (!value)
- goto done;
-
- while ((ind = g_slist_nth_data(gw->indies, index))) {
- if(!strcmp(ind->descr, "\"service\""))
- dict_append_entry(&dict, "RegistrationStatus",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"signal\""))
- dict_append_entry(&dict, "SignalStrength",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"roam\""))
- dict_append_entry(&dict, "RoamingStatus",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"battchg\""))
- dict_append_entry(&dict, "BatteryCharge",
- DBUS_TYPE_UINT16, &ind->value);
- index++;
- }
-
- /* SpeakerGain */
- dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16,
- &device->gateway->sp_gain);
-
- /* MicrophoneGain */
- dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16,
- &device->gateway->mic_gain);
-done:
- dbus_message_iter_close_container(&iter, &dict);
- return reply;
+ return NULL;
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
{ "Disconnect", "", "", ag_disconnect },
- { "AnswerCall", "", "", ag_answer },
- { "TerminateCall", "", "", ag_terminate_call },
- { "Call", "s", "", ag_call },
- { "GetOperatorName", "", "s", ag_get_operator },
- { "SendDTMF", "s", "", ag_send_dtmf },
- { "GetSubscriberNumber", "", "s", ag_get_subscriber_num },
{ "GetProperties", "", "a{sv}", ag_get_properties },
{ NULL, NULL, NULL, NULL }
};
static GDBusSignalTable gateway_signals[] = {
- { "Ring", "s" },
- { "CallTerminated", "" },
- { "CallStarted", "" },
- { "CallEnded", "" },
{ "PropertyChanged", "sv" },
{ NULL, NULL }
};
@@ -1063,9 +315,6 @@ struct gateway *gateway_init(struct audio_device *dev)
debug("in gateway_init, dev is %p", dev);
gw = g_new0(struct gateway, 1);
- gw->indies = NULL;
- gw->is_dialing = FALSE;
- gw->call_active = FALSE;
gw->state = GATEWAY_STATE_DISCONNECTED;
return gw;
@@ -1107,11 +356,6 @@ void gateway_start_service(struct audio_device *device)
rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
}
-static void indicator_slice_free(gpointer mem)
-{
- g_slice_free(struct indicator, mem);
-}
-
int gateway_close(struct audio_device *device)
{
struct gateway *gw = device->gateway;
@@ -1119,8 +363,6 @@ int gateway_close(struct audio_device *device)
GIOChannel *sco = gw->sco;
gboolean value = FALSE;
- g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL);
- g_slist_free(gw->indies);
if (rfcomm) {
g_io_channel_shutdown(rfcomm, TRUE, NULL);
g_io_channel_unref(rfcomm);
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH 2/2] Implement HandsfreeGateway Interface
2010-02-02 21:41 [PATCH 1/2] clean up audio/gateway.c Gustavo F. Padovan
@ 2010-02-02 21:41 ` Gustavo F. Padovan
2010-02-02 22:47 ` [PATCH] " Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-02 21:41 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/device.c | 3 +
audio/gateway.c | 434 +++++++++++++++++++++++++++++++++++++++++++------------
audio/gateway.h | 13 ++-
audio/manager.c | 24 +--
audio/unix.c | 8 +
doc/hfp-api.txt | 82 +++++++++++
6 files changed, 453 insertions(+), 111 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/device.c b/audio/device.c
index f7141e5..b8ea927 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -663,6 +663,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->headset)
headset_unregister(device);
+ if (device->gateway)
+ gateway_unregister(device);
+
if (device->sink)
sink_unregister(device);
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..fcac1e5 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,8 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
+ * Copyright (C) 2010 Gustavo F. Padovan <padovan@profusion.mobi>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -32,12 +34,15 @@
#include <string.h>
#include <fcntl.h>
#include <errno.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>
#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sco.h>
@@ -52,20 +57,103 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#define MAX_OPEN_TRIES 5
+#define OPEN_WAIT 300
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
- GIOChannel *rfcomm;
- guint rfcomm_watch_id;
+ int channel;
+ GIOChannel *rfcomm; /* remote AG requested rfcomm connection */
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val = state2str(new_state);
+
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +167,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,63 +181,91 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
if (gw->sco_start_cb)
gw->sco_start_cb(dev, gw->sco_start_cb_data);
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ return;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ return;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
+ char src[18], dst[18];
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent)
+ error("Handfree Agent not registered");
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ ba2str(&dev->src, src);
+ ba2str(&dev->dst, dst);
- gateway_close(dev);
+ sk = g_io_channel_unix_get_fd(chan);
+
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev)) {
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ } else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ return;
+
+fail:
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg, ERROR_INTERFACE
+ ".Failed", ".Connection attempt failed");
+ }
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
+ struct gateway *gw = dev->gateway;
int ch = -1;
sdp_list_t *protos, *classes;
uuid_t uuid;
@@ -182,8 +299,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -197,6 +312,8 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
goto fail;
}
+ gw->channel = ch;
+
io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -204,25 +321,24 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ if (err)
+ g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg, ERROR_INTERFACE
+ ".NotSupported", "Not supported");
+ }
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
sco_cb = dev->gateway->sco_start_cb;
if (sco_cb)
@@ -243,22 +359,61 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
{
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
+ DBusMessage *reply;
debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assined");
- 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");
}
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
debug("at the end of ag_connect()");
- return NULL;
+
+ return reply;
+}
+
+void gateway_exit(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+}
+
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ gboolean value = FALSE;
+
+ if (gw->rfcomm) {
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ gw->state = GATEWAY_STATE_DISCONNECTED;
+
+ emit_property_changed(device->conn, device->path,
+ AUDIO_GATEWAY_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ return 0;
}
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
@@ -269,6 +424,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -279,22 +437,120 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
"Device not Connected");
gateway_close(device);
+
ba2str(&device->dst, gw_addr);
debug("Disconnected from %s, %s", gw_addr, device->path);
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "Connected",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".InvalidArguments", "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ gw->msg = dbus_message_ref(msg);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed", "Permission denied");
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+ dbus_message_unref(gw->msg);
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -303,9 +559,16 @@ static GDBusSignalTable gateway_signals[] = {
{ NULL, NULL }
};
+void gateway_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
@@ -313,10 +576,7 @@ struct gateway *gateway_init(struct audio_device *dev)
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -326,17 +586,6 @@ gboolean gateway_is_connected(struct audio_device *dev)
dev->gateway->state == GATEWAY_STATE_CONNECTED);
}
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
-{
- if (!io)
- return -EINVAL;
-
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
-
- return 0;
-}
-
int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
{
struct gateway *gw = dev->gateway;
@@ -347,42 +596,38 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
+ (GIOFunc) sco_io_cb, dev);
+
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
return 0;
}
-void gateway_start_service(struct audio_device *device)
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch)
{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ if (!chan)
+ return -EINVAL;
+
+ dev->gateway->rfcomm = g_io_channel_ref(chan);
+ dev->gateway->channel = ch;
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL,
+ &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -399,8 +644,12 @@ gboolean gateway_request_stream(struct audio_device *dev,
gw->sco_start_cb_data = user_data;
get_records(dev);
} else if (!gw->sco) {
+ char source[128], destination[128];
+
gw->sco_start_cb = cb;
gw->sco_start_cb_data = user_data;
+ ba2str(&dev->src, source);
+ ba2str(&dev->dst, destination);
io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -464,4 +713,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..b576f9d 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -4,6 +4,7 @@
*
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -22,20 +23,26 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
struct gateway *gateway_init(struct audio_device *device);
+void gateway_exit(struct audio_device *device);
gboolean gateway_is_connected(struct audio_device *dev);
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..69d400c 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -586,7 +586,7 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
goto drop;
}
- if (gateway_connect_rfcomm(device, chan) < 0) {
+ if (gateway_connect_rfcomm(device, chan, ch) < 0) {
error("Allocating new GIOChannel failed!");
goto drop;
}
@@ -791,9 +791,13 @@ static void audio_remove(struct btd_device *device)
if (!dev)
return;
+ if (dev->gateway)
+ gateway_exit(dev);
+
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
+
}
static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
@@ -905,22 +909,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..bd1a415 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -395,6 +395,9 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
struct bt_start_stream_rsp *rsp = (void *) buf;
struct bt_new_stream_ind *ind = (void *) buf;
+ if (!dev)
+ goto failed;
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
@@ -416,6 +419,11 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
}
client->req_id = 0;
+ return;
+
+failed:
+ error("gateway_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
}
static void headset_suspend_complete(struct audio_device *dev, void *user_data)
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH] Implement HandsfreeGateway Interface
2010-02-02 21:41 ` [PATCH 2/2] Implement HandsfreeGateway Interface Gustavo F. Padovan
@ 2010-02-02 22:47 ` Gustavo F. Padovan
2010-02-03 18:26 ` Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-02 22:47 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/device.c | 3 +
audio/gateway.c | 444 ++++++++++++++++++++++++++++++++++++++++++-------------
audio/gateway.h | 13 ++-
audio/manager.c | 24 +--
audio/unix.c | 8 +
doc/hfp-api.txt | 82 ++++++++++
6 files changed, 456 insertions(+), 118 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/device.c b/audio/device.c
index f7141e5..b8ea927 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -663,6 +663,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->headset)
headset_unregister(device);
+ if (device->gateway)
+ gateway_unregister(device);
+
if (device->sink)
sink_unregister(device);
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..e23e9d5 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,8 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
+ * Copyright (C) 2010 Gustavo F. Padovan <padovan@profusion.mobi>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -32,12 +34,15 @@
#include <string.h>
#include <fcntl.h>
#include <errno.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>
#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sco.h>
@@ -52,20 +57,103 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#define MAX_OPEN_TRIES 5
+#define OPEN_WAIT 300
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
- GIOChannel *rfcomm;
- guint rfcomm_watch_id;
+ int channel;
+ GIOChannel *rfcomm; /* remote AG requested rfcomm connection */
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val = state2str(new_state);
+
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +167,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,64 +181,95 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
if (gw->sco_start_cb)
gw->sco_start_cb(dev, gw->sco_start_cb_data);
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ goto done;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+
+done:
+ dbus_message_unref(reply);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
+ char src[18], dst[18];
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent)
+ error("Handfree Agent not registered");
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ ba2str(&dev->src, src);
+ ba2str(&dev->dst, dst);
- gateway_close(dev);
+ sk = g_io_channel_unix_get_fd(chan);
+
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev))
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ return;
+
+fail:
+ if (gw->msg)
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".Failed",
+ "Connection attempt failed");
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
- int ch = -1;
+ struct gateway *gw = dev->gateway;
+ int ch;
sdp_list_t *protos, *classes;
uuid_t uuid;
gateway_stream_cb_t sco_cb;
@@ -182,8 +302,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -197,6 +315,8 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
goto fail;
}
+ gw->channel = ch;
+
io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -204,25 +324,24 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ if (err)
+ g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg)
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".NotSupported",
+ "Not supported");
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
sco_cb = dev->gateway->sco_start_cb;
if (sco_cb)
@@ -243,22 +362,61 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
{
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
+ DBusMessage *reply;
- debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assined");
- 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("at the end of ag_connect()");
- return NULL;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ return reply;
+}
+
+void gateway_exit(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+}
+
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ gboolean value = FALSE;
+ int sock;
+
+ if (gw->rfcomm) {
+ sock = g_io_channel_unix_get_fd(gw->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ gw->state = GATEWAY_STATE_DISCONNECTED;
+
+ emit_property_changed(device->conn, device->path,
+ AUDIO_GATEWAY_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ return 0;
}
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
@@ -269,6 +427,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -279,22 +440,121 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
"Device not Connected");
gateway_close(device);
+
ba2str(&device->dst, gw_addr);
debug("Disconnected from %s, %s", gw_addr, device->path);
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "Connected",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ gw->msg = dbus_message_ref(msg);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Permission denied");
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+ dbus_message_unref(gw->msg);
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -303,9 +563,16 @@ static GDBusSignalTable gateway_signals[] = {
{ NULL, NULL }
};
+void gateway_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
@@ -313,10 +580,7 @@ struct gateway *gateway_init(struct audio_device *dev)
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -326,17 +590,6 @@ gboolean gateway_is_connected(struct audio_device *dev)
dev->gateway->state == GATEWAY_STATE_CONNECTED);
}
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
-{
- if (!io)
- return -EINVAL;
-
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
-
- return 0;
-}
-
int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
{
struct gateway *gw = dev->gateway;
@@ -347,42 +600,36 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
+ (GIOFunc) sco_io_cb, dev);
+
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
return 0;
}
-void gateway_start_service(struct audio_device *device)
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan, int ch)
{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ if (!chan)
+ return -EINVAL;
+
+ dev->gateway->rfcomm = g_io_channel_ref(chan);
+ dev->gateway->channel = ch;
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -410,10 +657,8 @@ gboolean gateway_request_stream(struct audio_device *dev,
g_error_free(err);
return FALSE;
}
- } else {
- if (cb)
- cb(dev, user_data);
- }
+ } else if (cb)
+ cb(dev, user_data);
return TRUE;
}
@@ -464,4 +709,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..b576f9d 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -4,6 +4,7 @@
*
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -22,20 +23,26 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
struct gateway *gateway_init(struct audio_device *device);
+void gateway_exit(struct audio_device *device);
gboolean gateway_is_connected(struct audio_device *dev);
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..69d400c 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -586,7 +586,7 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
goto drop;
}
- if (gateway_connect_rfcomm(device, chan) < 0) {
+ if (gateway_connect_rfcomm(device, chan, ch) < 0) {
error("Allocating new GIOChannel failed!");
goto drop;
}
@@ -791,9 +791,13 @@ static void audio_remove(struct btd_device *device)
if (!dev)
return;
+ if (dev->gateway)
+ gateway_exit(dev);
+
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
+
}
static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
@@ -905,22 +909,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..bd1a415 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -395,6 +395,9 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
struct bt_start_stream_rsp *rsp = (void *) buf;
struct bt_new_stream_ind *ind = (void *) buf;
+ if (!dev)
+ goto failed;
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
@@ -416,6 +419,11 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
}
client->req_id = 0;
+ return;
+
+failed:
+ error("gateway_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
}
static void headset_suspend_complete(struct audio_device *dev, void *user_data)
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH] Implement HandsfreeGateway Interface
2010-02-02 22:47 ` [PATCH] " Gustavo F. Padovan
@ 2010-02-03 18:26 ` Gustavo F. Padovan
2010-02-03 19:30 ` Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-03 18:26 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/device.c | 3 +
audio/gateway.c | 417 ++++++++++++++++++++++++++++++++++++++++++-------------
audio/gateway.h | 13 ++-
audio/manager.c | 22 +--
audio/unix.c | 8 +
doc/hfp-api.txt | 82 +++++++++++
6 files changed, 433 insertions(+), 112 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/device.c b/audio/device.c
index f7141e5..b8ea927 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -663,6 +663,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->headset)
headset_unregister(device);
+ if (device->gateway)
+ gateway_unregister(device);
+
if (device->sink)
sink_unregister(device);
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..cc32450 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,8 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2009-2010 Zhenhua Zhang <zhenhua.zhang@intel.com>
+ * Copyright (C) 2010 Gustavo F. Padovan <padovan@profusion.mobi>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -52,20 +54,99 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
GIOChannel *rfcomm;
- guint rfcomm_watch_id;
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val = state2str(new_state);
+
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +160,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,64 +174,97 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
if (gw->sco_start_cb)
gw->sco_start_cb(dev, gw->sco_start_cb_data);
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ goto done;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+
+done:
+ dbus_message_unref(reply);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent)
+ error("Handfree Agent not registered");
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ sk = g_io_channel_unix_get_fd(chan);
- gateway_close(dev);
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev))
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ g_dbus_send_message(dev->conn, reply);
+ dbus_message_unref(gw->msg);
+
+ return;
+
+fail:
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".Failed",
+ "Connection attempt failed");
+ dbus_message_unref(gw->msg);
+ }
+
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
- int ch = -1;
+ struct gateway *gw = dev->gateway;
+ int ch;
sdp_list_t *protos, *classes;
uuid_t uuid;
gateway_stream_cb_t sco_cb;
@@ -182,8 +297,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -204,25 +317,26 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ if (err)
+ g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".NotSupported",
+ "Not supported");
+ dbus_message_unref(gw->msg);
+ }
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
sco_cb = dev->gateway->sco_start_cb;
if (sco_cb)
@@ -244,23 +358,58 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
- debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assigned");
- gw->connect_message = dbus_message_ref(msg);
- if (get_records(au_dev) < 0) {
- dbus_message_unref(gw->connect_message);
+ if (get_records(au_dev) < 0)
return g_dbus_create_error(msg, ERROR_INTERFACE
".ConnectAttemptFailed",
"Connect Attempt Failed");
- }
- debug("at the end of ag_connect()");
+
+ gw->msg = dbus_message_ref(msg);
+
return NULL;
}
+void gateway_exit(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+}
+
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ gboolean value = FALSE;
+ int sock;
+
+ if (gw->rfcomm) {
+ sock = g_io_channel_unix_get_fd(gw->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ gw->state = GATEWAY_STATE_DISCONNECTED;
+
+ emit_property_changed(device->conn, device->path,
+ AUDIO_GATEWAY_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ return 0;
+}
+
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -269,6 +418,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -285,16 +437,110 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "State",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Permission denied");
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -303,9 +549,16 @@ static GDBusSignalTable gateway_signals[] = {
{ NULL, NULL }
};
+void gateway_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
@@ -313,10 +566,7 @@ struct gateway *gateway_init(struct audio_device *dev)
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -331,8 +581,7 @@ int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
if (!io)
return -EINVAL;
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
+ dev->gateway->rfcomm = g_io_channel_ref(io);
return 0;
}
@@ -347,42 +596,25 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
+ (GIOFunc) sco_io_cb, dev);
+
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
return 0;
}
-void gateway_start_service(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
-}
-
-int gateway_close(struct audio_device *device)
-{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -410,10 +642,8 @@ gboolean gateway_request_stream(struct audio_device *dev,
g_error_free(err);
return FALSE;
}
- } else {
- if (cb)
- cb(dev, user_data);
- }
+ } else if (cb)
+ cb(dev, user_data);
return TRUE;
}
@@ -464,4 +694,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..0cc1da9 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -4,6 +4,7 @@
*
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009-2010 Zhenhua Zhang <zhenhua.zhang@intel.com>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -22,20 +23,24 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
-#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
struct gateway *gateway_init(struct audio_device *device);
+void gateway_exit(struct audio_device *device);
gboolean gateway_is_connected(struct audio_device *dev);
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..0238e72 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -791,9 +791,13 @@ static void audio_remove(struct btd_device *device)
if (!dev)
return;
+ if (dev->gateway)
+ gateway_exit(dev);
+
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
+
}
static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
@@ -905,22 +909,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..bd1a415 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -395,6 +395,9 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
struct bt_start_stream_rsp *rsp = (void *) buf;
struct bt_new_stream_ind *ind = (void *) buf;
+ if (!dev)
+ goto failed;
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
@@ -416,6 +419,11 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
}
client->req_id = 0;
+ return;
+
+failed:
+ error("gateway_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
}
static void headset_suspend_complete(struct audio_device *dev, void *user_data)
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH] Implement HandsfreeGateway Interface
2010-02-03 18:26 ` Gustavo F. Padovan
@ 2010-02-03 19:30 ` Gustavo F. Padovan
2010-02-04 1:36 ` Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-03 19:30 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/device.c | 3 +
audio/gateway.c | 417 ++++++++++++++++++++++++++++++++++++++++++-------------
audio/gateway.h | 12 +-
audio/manager.c | 22 +--
audio/unix.c | 8 +
doc/hfp-api.txt | 82 +++++++++++
6 files changed, 432 insertions(+), 112 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/device.c b/audio/device.c
index f7141e5..b8ea927 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -663,6 +663,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->headset)
headset_unregister(device);
+ if (device->gateway)
+ gateway_unregister(device);
+
if (device->sink)
sink_unregister(device);
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..e89b7b6 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,7 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2010 ProFUSION embedded systems
*
*
* This program is free software; you can redistribute it and/or modify
@@ -52,20 +53,103 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
GIOChannel *rfcomm;
- guint rfcomm_watch_id;
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val;
+
+ if (gw->state == new_state)
+ return;
+
+ val = state2str(new_state);
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +163,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,64 +177,99 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
if (gw->sco_start_cb)
gw->sco_start_cb(dev, gw->sco_start_cb_data);
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ goto done;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+
+done:
+ dbus_message_unref(reply);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent) {
+ error("Handfree Agent not registered");
+ goto fail;
+ }
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ sk = g_io_channel_unix_get_fd(chan);
- gateway_close(dev);
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev))
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ g_dbus_send_message(dev->conn, reply);
+ dbus_message_unref(gw->msg);
+
+ return;
+
+fail:
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".Failed",
+ "Connection attempt failed");
+ dbus_message_unref(gw->msg);
+ }
+
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
- int ch = -1;
+ struct gateway *gw = dev->gateway;
+ int ch;
sdp_list_t *protos, *classes;
uuid_t uuid;
gateway_stream_cb_t sco_cb;
@@ -182,8 +302,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -204,25 +322,26 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ if (err)
+ g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".NotSupported",
+ "Not supported");
+ dbus_message_unref(gw->msg);
+ }
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
sco_cb = dev->gateway->sco_start_cb;
if (sco_cb)
@@ -244,23 +363,53 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
- debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assigned");
- gw->connect_message = dbus_message_ref(msg);
- if (get_records(au_dev) < 0) {
- dbus_message_unref(gw->connect_message);
+ if (get_records(au_dev) < 0)
return g_dbus_create_error(msg, ERROR_INTERFACE
".ConnectAttemptFailed",
"Connect Attempt Failed");
- }
- debug("at the end of ag_connect()");
+
+ gw->msg = dbus_message_ref(msg);
+
return NULL;
}
+void gateway_exit(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+}
+
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ int sock;
+
+ if (gw->rfcomm) {
+ sock = g_io_channel_unix_get_fd(gw->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ change_state(device, GATEWAY_STATE_DISCONNECTED);
+
+ return 0;
+}
+
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -269,6 +418,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -285,16 +437,110 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "State",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Permission denied");
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -303,9 +549,16 @@ static GDBusSignalTable gateway_signals[] = {
{ NULL, NULL }
};
+void gateway_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
@@ -313,10 +566,7 @@ struct gateway *gateway_init(struct audio_device *dev)
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -331,8 +581,7 @@ int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
if (!io)
return -EINVAL;
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
+ dev->gateway->rfcomm = g_io_channel_ref(io);
return 0;
}
@@ -347,42 +596,25 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
- return 0;
-}
+ (GIOFunc) sco_io_cb, dev);
-void gateway_start_service(struct audio_device *device)
-{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -410,10 +642,8 @@ gboolean gateway_request_stream(struct audio_device *dev,
g_error_free(err);
return FALSE;
}
- } else {
- if (cb)
- cb(dev, user_data);
- }
+ } else if (cb)
+ cb(dev, user_data);
return TRUE;
}
@@ -464,4 +694,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..ee7ebf5 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -22,20 +22,24 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
-#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
struct gateway *gateway_init(struct audio_device *device);
+void gateway_exit(struct audio_device *device);
gboolean gateway_is_connected(struct audio_device *dev);
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..0238e72 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -791,9 +791,13 @@ static void audio_remove(struct btd_device *device)
if (!dev)
return;
+ if (dev->gateway)
+ gateway_exit(dev);
+
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
+
}
static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
@@ -905,22 +909,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..bd1a415 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -395,6 +395,9 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
struct bt_start_stream_rsp *rsp = (void *) buf;
struct bt_new_stream_ind *ind = (void *) buf;
+ if (!dev)
+ goto failed;
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
@@ -416,6 +419,11 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
}
client->req_id = 0;
+ return;
+
+failed:
+ error("gateway_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
}
static void headset_suspend_complete(struct audio_device *dev, void *user_data)
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH] Implement HandsfreeGateway Interface
2010-02-03 19:30 ` Gustavo F. Padovan
@ 2010-02-04 1:36 ` Gustavo F. Padovan
2010-02-04 17:11 ` Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-04 1:36 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/device.c | 3 +
audio/gateway.c | 413 ++++++++++++++++++++++++++++++++++++++++++-------------
audio/gateway.h | 11 +-
audio/manager.c | 19 +--
audio/unix.c | 8 +
doc/hfp-api.txt | 82 +++++++++++
6 files changed, 424 insertions(+), 112 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/device.c b/audio/device.c
index f7141e5..b8ea927 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -663,6 +663,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->headset)
headset_unregister(device);
+ if (device->gateway)
+ gateway_unregister(device);
+
if (device->sink)
sink_unregister(device);
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..f8a15ac 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,7 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2010 ProFUSION embedded systems
*
*
* This program is free software; you can redistribute it and/or modify
@@ -52,20 +53,103 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
GIOChannel *rfcomm;
- guint rfcomm_watch_id;
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val;
+
+ if (gw->state == new_state)
+ return;
+
+ val = state2str(new_state);
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +163,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,64 +177,99 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
if (gw->sco_start_cb)
gw->sco_start_cb(dev, gw->sco_start_cb_data);
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ goto done;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+
+done:
+ dbus_message_unref(reply);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent) {
+ error("Handfree Agent not registered");
+ goto fail;
+ }
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ sk = g_io_channel_unix_get_fd(chan);
- gateway_close(dev);
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev))
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ g_dbus_send_message(dev->conn, reply);
+ dbus_message_unref(gw->msg);
+
+ return;
+
+fail:
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".Failed",
+ "Connection attempt failed");
+ dbus_message_unref(gw->msg);
+ }
+
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
- int ch = -1;
+ struct gateway *gw = dev->gateway;
+ int ch;
sdp_list_t *protos, *classes;
uuid_t uuid;
gateway_stream_cb_t sco_cb;
@@ -182,8 +302,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -204,25 +322,26 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ if (err)
+ g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".NotSupported",
+ "Not supported");
+ dbus_message_unref(gw->msg);
+ }
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
sco_cb = dev->gateway->sco_start_cb;
if (sco_cb)
@@ -244,23 +363,47 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
- debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assigned");
- gw->connect_message = dbus_message_ref(msg);
- if (get_records(au_dev) < 0) {
- dbus_message_unref(gw->connect_message);
+ if (get_records(au_dev) < 0)
return g_dbus_create_error(msg, ERROR_INTERFACE
".ConnectAttemptFailed",
"Connect Attempt Failed");
- }
- debug("at the end of ag_connect()");
+
+ gw->msg = dbus_message_ref(msg);
+
return NULL;
}
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ int sock;
+
+ if (gw->rfcomm) {
+ sock = g_io_channel_unix_get_fd(gw->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ change_state(device, GATEWAY_STATE_DISCONNECTED);
+
+ return 0;
+}
+
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -269,6 +412,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -285,16 +431,109 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "State",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Permission denied");
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -303,9 +542,19 @@ static GDBusSignalTable gateway_signals[] = {
{ NULL, NULL }
};
+void gateway_unregister(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
@@ -313,10 +562,7 @@ struct gateway *gateway_init(struct audio_device *dev)
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -331,8 +577,7 @@ int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
if (!io)
return -EINVAL;
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
+ dev->gateway->rfcomm = g_io_channel_ref(io);
return 0;
}
@@ -347,42 +592,25 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
- return 0;
-}
+ (GIOFunc) sco_io_cb, dev);
-void gateway_start_service(struct audio_device *device)
-{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -410,10 +638,8 @@ gboolean gateway_request_stream(struct audio_device *dev,
g_error_free(err);
return FALSE;
}
- } else {
- if (cb)
- cb(dev, user_data);
- }
+ } else if (cb)
+ cb(dev, user_data);
return TRUE;
}
@@ -464,4 +690,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..830e8e0 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -22,20 +22,23 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
-#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
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);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..06c7d78 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -794,6 +794,7 @@ static void audio_remove(struct btd_device *device)
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
+
}
static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
@@ -905,22 +906,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..bd1a415 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -395,6 +395,9 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
struct bt_start_stream_rsp *rsp = (void *) buf;
struct bt_new_stream_ind *ind = (void *) buf;
+ if (!dev)
+ goto failed;
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
@@ -416,6 +419,11 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
}
client->req_id = 0;
+ return;
+
+failed:
+ error("gateway_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
}
static void headset_suspend_complete(struct audio_device *dev, void *user_data)
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH] Implement HandsfreeGateway Interface
2010-02-04 1:36 ` Gustavo F. Padovan
@ 2010-02-04 17:11 ` Gustavo F. Padovan
2010-02-04 18:18 ` Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-04 17:11 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/device.c | 3 +
audio/gateway.c | 422 +++++++++++++++++++++++++++++++++++++++++--------------
audio/gateway.h | 14 ++-
audio/manager.c | 19 +--
audio/unix.c | 13 ++-
doc/hfp-api.txt | 82 +++++++++++
6 files changed, 427 insertions(+), 126 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/device.c b/audio/device.c
index f7141e5..b8ea927 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -663,6 +663,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->headset)
headset_unregister(device);
+ if (device->gateway)
+ gateway_unregister(device);
+
if (device->sink)
sink_unregister(device);
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..2e374d1 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,7 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2010 ProFUSION embedded systems
*
*
* This program is free software; you can redistribute it and/or modify
@@ -52,20 +53,102 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
GIOChannel *rfcomm;
- guint rfcomm_watch_id;
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val;
+
+ if (gw->state == new_state)
+ return;
+
+ val = state2str(new_state);
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +162,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,67 +176,99 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
- if (gw->sco_start_cb)
- gw->sco_start_cb(dev, gw->sco_start_cb_data);
-
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ goto done;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+
+done:
+ dbus_message_unref(reply);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent) {
+ error("Handfree Agent not registered");
+ goto fail;
+ }
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ sk = g_io_channel_unix_get_fd(chan);
- gateway_close(dev);
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev))
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ g_dbus_send_message(dev->conn, reply);
+
+ return;
+
+fail:
+ if (gw->msg)
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".Failed",
+ "Connection attempt failed");
+
+
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
- int ch = -1;
+ struct gateway *gw = dev->gateway;
+ int ch;
sdp_list_t *protos, *classes;
uuid_t uuid;
- gateway_stream_cb_t sco_cb;
GIOChannel *io;
GError *err = NULL;
@@ -182,8 +298,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -204,29 +318,25 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg)
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".NotSupported",
+ "Not supported");
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
- sco_cb = dev->gateway->sco_start_cb;
- if (sco_cb)
- sco_cb(NULL, dev->gateway->sco_start_cb_data);
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
}
static int get_records(struct audio_device *device)
@@ -244,23 +354,47 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
- debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assigned");
- gw->connect_message = dbus_message_ref(msg);
- if (get_records(au_dev) < 0) {
- dbus_message_unref(gw->connect_message);
+ if (get_records(au_dev) < 0)
return g_dbus_create_error(msg, ERROR_INTERFACE
".ConnectAttemptFailed",
"Connect Attempt Failed");
- }
- debug("at the end of ag_connect()");
+
+ gw->msg = dbus_message_ref(msg);
+
return NULL;
}
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ int sock;
+
+ if (gw->rfcomm) {
+ sock = g_io_channel_unix_get_fd(gw->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ change_state(device, GATEWAY_STATE_DISCONNECTED);
+
+ return 0;
+}
+
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -269,6 +403,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -285,16 +422,109 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "State",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Permission denied");
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -303,9 +533,19 @@ static GDBusSignalTable gateway_signals[] = {
{ NULL, NULL }
};
+void gateway_unregister(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
@@ -313,10 +553,7 @@ struct gateway *gateway_init(struct audio_device *dev)
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -331,8 +568,7 @@ int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
if (!io)
return -EINVAL;
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
+ dev->gateway->rfcomm = g_io_channel_ref(io);
return 0;
}
@@ -347,42 +583,25 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
- return 0;
-}
+ (GIOFunc) sco_io_cb, dev);
-void gateway_start_service(struct audio_device *device)
-{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -410,10 +629,8 @@ gboolean gateway_request_stream(struct audio_device *dev,
g_error_free(err);
return FALSE;
}
- } else {
- if (cb)
- cb(dev, user_data);
- }
+ } else if (cb)
+ cb(dev, err, user_data);
return TRUE;
}
@@ -430,7 +647,7 @@ int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t sco_cb,
}
if (sco_cb)
- sco_cb(dev, user_data);
+ sco_cb(dev, NULL, user_data);
return 0;
}
@@ -464,4 +681,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..a45ef82 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -22,20 +22,24 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
-#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
-typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+typedef void (*gateway_stream_cb_t) (struct audio_device *dev, GError *err,
+ void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
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);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..06c7d78 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -794,6 +794,7 @@ static void audio_remove(struct btd_device *device)
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
+
}
static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
@@ -905,22 +906,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..c2d6623 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -318,14 +318,14 @@ failed:
unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
}
-static void gateway_setup_complete(struct audio_device *dev, void *user_data)
+static void gateway_setup_complete(struct audio_device *dev, GError *err, void *user_data)
{
struct unix_client *client = user_data;
char buf[BT_SUGGESTED_BUFFER_SIZE];
struct bt_set_configuration_rsp *rsp = (void *) buf;
- if (!dev) {
- unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+ if (err) {
+ unix_ipc_error(client, BT_SET_CONFIGURATION, err->code);
return;
}
@@ -388,13 +388,18 @@ failed:
unix_ipc_error(client, BT_START_STREAM, EIO);
}
-static void gateway_resume_complete(struct audio_device *dev, void *user_data)
+static void gateway_resume_complete(struct audio_device *dev, GError *err, 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;
+ if (err) {
+ unix_ipc_error(client, BT_START_STREAM, err->code);
+ return;
+ }
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
_______________________________________________
ofono mailing list
ofono@ofono.org
http://lists.ofono.org/listinfo/ofono
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH] Implement HandsfreeGateway Interface
2010-02-04 17:11 ` Gustavo F. Padovan
@ 2010-02-04 18:18 ` Gustavo F. Padovan
2010-02-04 18:48 ` Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-04 18:18 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/device.c | 3 +
audio/gateway.c | 433 ++++++++++++++++++++++++++++++++++++++++++-------------
audio/gateway.h | 14 ++-
audio/manager.c | 19 +--
audio/unix.c | 13 ++-
doc/hfp-api.txt | 82 +++++++++++
6 files changed, 438 insertions(+), 126 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/device.c b/audio/device.c
index f7141e5..b8ea927 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -663,6 +663,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->headset)
headset_unregister(device);
+ if (device->gateway)
+ gateway_unregister(device);
+
if (device->sink)
sink_unregister(device);
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..eaac8ab 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,7 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2010 ProFUSION embedded systems
*
*
* This program is free software; you can redistribute it and/or modify
@@ -52,20 +53,102 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
GIOChannel *rfcomm;
- guint rfcomm_watch_id;
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val;
+
+ if (gw->state == new_state)
+ return;
+
+ val = state2str(new_state);
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +162,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,67 +176,99 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
- if (gw->sco_start_cb)
- gw->sco_start_cb(dev, gw->sco_start_cb_data);
-
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ goto done;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+
+done:
+ dbus_message_unref(reply);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent) {
+ error("Handfree Agent not registered");
+ goto fail;
+ }
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ sk = g_io_channel_unix_get_fd(chan);
- gateway_close(dev);
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev))
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ g_dbus_send_message(dev->conn, reply);
+
+ return;
+
+fail:
+ if (gw->msg)
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".Failed",
+ "Connection attempt failed");
+
+
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
- int ch = -1;
+ struct gateway *gw = dev->gateway;
+ int ch;
sdp_list_t *protos, *classes;
uuid_t uuid;
- gateway_stream_cb_t sco_cb;
GIOChannel *io;
GError *err = NULL;
@@ -164,16 +280,19 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!recs || !recs->data) {
error("No records found");
+ perr = EIO;
goto fail;
}
if (sdp_get_service_classes(recs->data, &classes) < 0) {
error("Unable to get service classes from record");
+ perr = EINVAL;
goto fail;
}
if (sdp_get_access_protos(recs->data, &protos) < 0) {
error("Unable to get access protocols from record");
+ perr = ENODATA;
goto fail;
}
@@ -182,10 +301,9 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
+ perr = EIO;
goto fail;
}
@@ -194,6 +312,7 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
sdp_list_free(protos, NULL);
if (ch <= 0) {
error("Unable to extract RFCOMM channel from service record");
+ perr = EIO;
goto fail;
}
@@ -204,29 +323,31 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg)
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".NotSupported",
+ "Not supported");
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
- sco_cb = dev->gateway->sco_start_cb;
- if (sco_cb)
- sco_cb(NULL, dev->gateway->sco_start_cb_data);
+ if (!err)
+ g_set_error(&err, BT_IO_ERROR, BT_IO_ERROR_FAILED,
+ "connect: %s (%d)", strerror(perr), perr);
+
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+
+ g_error_free(err);
}
static int get_records(struct audio_device *device)
@@ -244,23 +365,47 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
- debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assigned");
- gw->connect_message = dbus_message_ref(msg);
- if (get_records(au_dev) < 0) {
- dbus_message_unref(gw->connect_message);
+ if (get_records(au_dev) < 0)
return g_dbus_create_error(msg, ERROR_INTERFACE
".ConnectAttemptFailed",
"Connect Attempt Failed");
- }
- debug("at the end of ag_connect()");
+
+ gw->msg = dbus_message_ref(msg);
+
return NULL;
}
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ int sock;
+
+ if (gw->rfcomm) {
+ sock = g_io_channel_unix_get_fd(gw->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ change_state(device, GATEWAY_STATE_DISCONNECTED);
+
+ return 0;
+}
+
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -269,6 +414,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -285,16 +433,109 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "State",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Permission denied");
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -303,9 +544,19 @@ static GDBusSignalTable gateway_signals[] = {
{ NULL, NULL }
};
+void gateway_unregister(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
@@ -313,10 +564,7 @@ struct gateway *gateway_init(struct audio_device *dev)
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -331,8 +579,7 @@ int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
if (!io)
return -EINVAL;
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
+ dev->gateway->rfcomm = g_io_channel_ref(io);
return 0;
}
@@ -347,42 +594,25 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
- return 0;
-}
+ (GIOFunc) sco_io_cb, dev);
-void gateway_start_service(struct audio_device *device)
-{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -410,10 +640,8 @@ gboolean gateway_request_stream(struct audio_device *dev,
g_error_free(err);
return FALSE;
}
- } else {
- if (cb)
- cb(dev, user_data);
- }
+ } else if (cb)
+ cb(dev, err, user_data);
return TRUE;
}
@@ -430,7 +658,7 @@ int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t sco_cb,
}
if (sco_cb)
- sco_cb(dev, user_data);
+ sco_cb(dev, NULL, user_data);
return 0;
}
@@ -464,4 +692,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..a45ef82 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -22,20 +22,24 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
-#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
-typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+typedef void (*gateway_stream_cb_t) (struct audio_device *dev, GError *err,
+ void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
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);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..06c7d78 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -794,6 +794,7 @@ static void audio_remove(struct btd_device *device)
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
+
}
static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
@@ -905,22 +906,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..c2d6623 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -318,14 +318,14 @@ failed:
unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
}
-static void gateway_setup_complete(struct audio_device *dev, void *user_data)
+static void gateway_setup_complete(struct audio_device *dev, GError *err, void *user_data)
{
struct unix_client *client = user_data;
char buf[BT_SUGGESTED_BUFFER_SIZE];
struct bt_set_configuration_rsp *rsp = (void *) buf;
- if (!dev) {
- unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+ if (err) {
+ unix_ipc_error(client, BT_SET_CONFIGURATION, err->code);
return;
}
@@ -388,13 +388,18 @@ failed:
unix_ipc_error(client, BT_START_STREAM, EIO);
}
-static void gateway_resume_complete(struct audio_device *dev, void *user_data)
+static void gateway_resume_complete(struct audio_device *dev, GError *err, 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;
+ if (err) {
+ unix_ipc_error(client, BT_START_STREAM, err->code);
+ return;
+ }
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH] Implement HandsfreeGateway Interface
2010-02-04 18:18 ` Gustavo F. Padovan
@ 2010-02-04 18:48 ` Gustavo F. Padovan
2010-02-04 19:02 ` Johan Hedberg
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-02-04 18:48 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/device.c | 3 +
audio/gateway.c | 447 +++++++++++++++++++++++++++++++++++++++++--------------
audio/gateway.h | 14 +-
audio/manager.c | 19 +--
audio/unix.c | 13 +-
doc/hfp-api.txt | 82 ++++++++++
6 files changed, 445 insertions(+), 133 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/device.c b/audio/device.c
index f7141e5..b8ea927 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -663,6 +663,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->headset)
headset_unregister(device);
+ if (device->gateway)
+ gateway_unregister(device);
+
if (device->sink)
sink_unregister(device);
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..8150a5e 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,7 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2010 ProFUSION embedded systems
*
*
* This program is free software; you can redistribute it and/or modify
@@ -52,20 +53,102 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
GIOChannel *rfcomm;
- guint rfcomm_watch_id;
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val;
+
+ if (gw->state == new_state)
+ return;
+
+ val = state2str(new_state);
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +162,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,88 +176,123 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
- if (gw->sco_start_cb)
- gw->sco_start_cb(dev, gw->sco_start_cb_data);
-
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ goto done;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+
+done:
+ dbus_message_unref(reply);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent) {
+ error("Handfree Agent not registered");
+ goto fail;
+ }
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ sk = g_io_channel_unix_get_fd(chan);
- gateway_close(dev);
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev))
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ g_dbus_send_message(dev->conn, reply);
+
+ return;
+
+fail:
+ if (gw->msg)
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".Failed",
+ "Connection attempt failed");
+
+
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
-static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
- int ch = -1;
+ struct gateway *gw = dev->gateway;
+ int ch;
sdp_list_t *protos, *classes;
uuid_t uuid;
- gateway_stream_cb_t sco_cb;
GIOChannel *io;
- GError *err = NULL;
+ GError *gerr = NULL;
- if (perr < 0) {
- error("Unable to get service record: %s (%d)", strerror(-perr),
- -perr);
+ if (err < 0) {
+ error("Unable to get service record: %s (%d)", strerror(-err),
+ -err);
goto fail;
}
if (!recs || !recs->data) {
error("No records found");
+ err = -EIO;
goto fail;
}
if (sdp_get_service_classes(recs->data, &classes) < 0) {
error("Unable to get service classes from record");
+ err = -EINVAL;
goto fail;
}
if (sdp_get_access_protos(recs->data, &protos) < 0) {
error("Unable to get access protocols from record");
+ err = -ENODATA;
goto fail;
}
@@ -182,10 +301,9 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
+ err = -EIO;
goto fail;
}
@@ -194,39 +312,42 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
sdp_list_free(protos, NULL);
if (ch <= 0) {
error("Unable to extract RFCOMM channel from service record");
+ err = -EIO;
goto fail;
}
- io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err,
+ io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &gerr,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
BT_IO_OPT_CHANNEL, ch,
BT_IO_OPT_INVALID);
if (!io) {
- error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ error("Unable to connect: %s", gerr->message);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg)
+ error_common_reply(dev->conn, gw->msg,
+ ERROR_INTERFACE ".NotSupported",
+ "Not supported");
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
- sco_cb = dev->gateway->sco_start_cb;
- if (sco_cb)
- sco_cb(NULL, dev->gateway->sco_start_cb_data);
+ if (!gerr)
+ g_set_error(&gerr, BT_IO_ERROR, BT_IO_ERROR_FAILED,
+ "connect: %s (%d)", strerror(-err), -err);
+
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, gerr, gw->sco_start_cb_data);
+
+ g_error_free(gerr);
}
static int get_records(struct audio_device *device)
@@ -244,23 +365,47 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
- debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assigned");
- gw->connect_message = dbus_message_ref(msg);
- if (get_records(au_dev) < 0) {
- dbus_message_unref(gw->connect_message);
+ if (get_records(au_dev) < 0)
return g_dbus_create_error(msg, ERROR_INTERFACE
".ConnectAttemptFailed",
"Connect Attempt Failed");
- }
- debug("at the end of ag_connect()");
+
+ gw->msg = dbus_message_ref(msg);
+
return NULL;
}
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ int sock;
+
+ if (gw->rfcomm) {
+ sock = g_io_channel_unix_get_fd(gw->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ change_state(device, GATEWAY_STATE_DISCONNECTED);
+
+ return 0;
+}
+
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -269,6 +414,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -285,16 +433,109 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "State",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Permission denied");
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -303,9 +544,19 @@ static GDBusSignalTable gateway_signals[] = {
{ NULL, NULL }
};
+void gateway_unregister(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
@@ -313,10 +564,7 @@ struct gateway *gateway_init(struct audio_device *dev)
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -331,8 +579,7 @@ int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
if (!io)
return -EINVAL;
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
+ dev->gateway->rfcomm = g_io_channel_ref(io);
return 0;
}
@@ -347,42 +594,25 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
- return 0;
-}
+ (GIOFunc) sco_io_cb, dev);
-void gateway_start_service(struct audio_device *device)
-{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -410,10 +640,8 @@ gboolean gateway_request_stream(struct audio_device *dev,
g_error_free(err);
return FALSE;
}
- } else {
- if (cb)
- cb(dev, user_data);
- }
+ } else if (cb)
+ cb(dev, err, user_data);
return TRUE;
}
@@ -430,7 +658,7 @@ int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t sco_cb,
}
if (sco_cb)
- sco_cb(dev, user_data);
+ sco_cb(dev, NULL, user_data);
return 0;
}
@@ -464,4 +692,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..a45ef82 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -22,20 +22,24 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
-#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
-typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+typedef void (*gateway_stream_cb_t) (struct audio_device *dev, GError *err,
+ void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
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);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..06c7d78 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -794,6 +794,7 @@ static void audio_remove(struct btd_device *device)
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
+
}
static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
@@ -905,22 +906,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..c2d6623 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -318,14 +318,14 @@ failed:
unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
}
-static void gateway_setup_complete(struct audio_device *dev, void *user_data)
+static void gateway_setup_complete(struct audio_device *dev, GError *err, void *user_data)
{
struct unix_client *client = user_data;
char buf[BT_SUGGESTED_BUFFER_SIZE];
struct bt_set_configuration_rsp *rsp = (void *) buf;
- if (!dev) {
- unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+ if (err) {
+ unix_ipc_error(client, BT_SET_CONFIGURATION, err->code);
return;
}
@@ -388,13 +388,18 @@ failed:
unix_ipc_error(client, BT_START_STREAM, EIO);
}
-static void gateway_resume_complete(struct audio_device *dev, void *user_data)
+static void gateway_resume_complete(struct audio_device *dev, GError *err, 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;
+ if (err) {
+ unix_ipc_error(client, BT_START_STREAM, err->code);
+ return;
+ }
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH] Implement HandsfreeGateway Interface
2010-02-04 18:48 ` Gustavo F. Padovan
@ 2010-02-04 19:02 ` Johan Hedberg
0 siblings, 0 replies; 14+ messages in thread
From: Johan Hedberg @ 2010-02-04 19:02 UTC (permalink / raw)
To: Gustavo F. Padovan; +Cc: linux-bluetooth, ofono
Hi Gustavo,
On Thu, Feb 04, 2010, Gustavo F. Padovan wrote:
> Create a interface where a Handsfree agent can register and use BlueZ to
> handle the rfcomm and sco links.
>
> Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
> prototype on this code.
> ---
> audio/device.c | 3 +
> audio/gateway.c | 447 +++++++++++++++++++++++++++++++++++++++++--------------
> audio/gateway.h | 14 +-
> audio/manager.c | 19 +--
> audio/unix.c | 13 +-
> doc/hfp-api.txt | 82 ++++++++++
> 6 files changed, 445 insertions(+), 133 deletions(-)
> create mode 100644 doc/hfp-api.txt
I didn't find any more obvious issues with this last version so the two
patches are now finally pushed upstream. Thanks.
Johan
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 1/2] clean up audio/gateway.c
@ 2010-01-21 20:31 Gustavo F. Padovan
2010-01-21 20:31 ` [PATCH 2/2] Implement HandsfreeGateway Interface Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-01-21 20:31 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono, johan.hedberg
remove all code related to the AT engine
---
audio/gateway.c | 760 +------------------------------------------------------
1 files changed, 1 insertions(+), 759 deletions(-)
diff --git a/audio/gateway.c b/audio/gateway.c
index a1c1ea2..3dc09ff 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -53,52 +53,6 @@
#include "dbus-common.h"
#define RFCOMM_BUF_SIZE 256
-/* not-more-then-16 defined by GSM + 1 for NULL + padding */
-#define AG_INDICATOR_DESCR_SIZE 20
-#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 */
-/* Releases all held calls or sets User Determined User Busy (UDUB)
- * for a waiting call */
-#define AG_CHLD_0 0x01
-/* Releases all active calls (if any exist) and accepts the other
- * (held or waiting) call */
-#define AG_CHLD_1 0x02
-/* Releases specified active call only <x> */
-#define AG_CHLD_1x 0x04
-/* Places all active calls (if any exist) on hold and accepts the other
- * (held or waiting) call */
-#define AG_CHLD_2 0x08
-/* Request private consultation mode with specified call <x> (Place all
- * calls on hold EXCEPT the call <x>) */
-#define AG_CHLD_2x 0x10
-/* Adds a held call to the conversation */
-#define AG_CHLD_3 0x20
-/* Connects the two calls and disconnects the subscriber from both calls
- * (Explicit Call Transfer). Support for this value and its associated
- * functionality is optional for the HF. */
-#define AG_CHLD_4 0x40
-
-#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 {
gateway_state_t state;
@@ -108,387 +62,10 @@ struct gateway {
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;
- gboolean call_active;
-
- int sp_gain;
- int mic_gain;
};
-static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond,
- struct audio_device *device);
-
int gateway_close(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 (count > 0) {
- status = g_io_channel_write_chars(io, data, count, &written,
- NULL);
- if (status != G_IO_STATUS_NORMAL)
- 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;
- gsize read = 0;
- gboolean got_ok = FALSE;
- gboolean got_error = FALSE;
- gchar *resp_buf = response;
- gsize toread = RFCOMM_BUF_SIZE - 1;
- GIOStatus status;
-
- if (!io_channel_write_all(rfcomm, data, count))
- return FALSE;
-
- while (!(got_ok || got_error)) {
- status = g_io_channel_read_chars(rfcomm, resp_buf, toread,
- &read, NULL);
- if (status == G_IO_STATUS_NORMAL)
- 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>))
- * ... */
-static GSList *parse_indicator_names(gchar *names, GSList *indies)
-{
- gchar *current = names - 1;
- GSList *result = indies;
- gchar *next;
- struct indicator *ind;
-
- while (current != NULL) {
- current += 2;
- next = strstr(current, ",(");
- ind = g_slice_new(struct indicator);
- strncpy(ind->descr, current, 20);
- ind->descr[(intptr_t) next - (intptr_t) current] = '\0';
- result = g_slist_append(result, (gpointer) ind);
- current = strstr(next + 1, ",(");
- }
- return result;
-}
-
-/* get values from <val0>,<val1>,... */
-static GSList *parse_indicator_values(gchar *values, GSList *indies)
-{
- gint val;
- gchar *current = values - 1;
- GSList *runner = indies;
- struct indicator *ind;
-
- while (current != NULL) {
- current += 1;
- sscanf(current, "%d", &val);
- current = strchr(current, ',');
- ind = 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 (strstr(features, "0"))
- result |= AG_CHLD_0;
-
- if (strstr(features, "1"))
- result |= AG_CHLD_1;
-
- if (strstr(features, "1x"))
- result |= AG_CHLD_1x;
-
- if (strstr(features, "2"))
- result |= AG_CHLD_2;
-
- if (strstr(features, "2x"))
- result |= AG_CHLD_2x;
-
- if (strstr(features, "3"))
- result |= AG_CHLD_3;
-
- if (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;
-
- debug("at the begin of establish_service_level_conn()");
- res = rfcomm_send_and_read(gw, AG_FEATURES, buf,
- sizeof(AG_FEATURES) - 1);
- if (!res || sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features) != 1)
- 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 ((gw->ag_features & AG_FEATURE_3WAY) != 0) {
- 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 ((gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) != 0)
- 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, guint index,
- gint value)
-{
- struct gateway *gw = dev->gateway;
- struct indicator *ind = g_slist_nth_data(gw->indies, index - 1);
- gchar *name = ind->descr;
-
- ind->value = value;
-
- debug("at the begin of process_ind_change, name is %s", name);
- if (!strcmp(name, "\"call\"")) {
- if (value > 0) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallStarted", DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- gw->call_active = TRUE;
- } else {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallEnded", DBUS_TYPE_INVALID);
- gw->call_active = FALSE;
- }
-
- } else if (!strcmp(name, "\"callsetup\"")) {
- if (value == 0 && gw->is_dialing) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallTerminated",
- DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- } else if (!gw->is_dialing && value > 0)
- gw->is_dialing = TRUE;
-
- } else if (!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
- */
- /* '2' means: all calls held, no active calls */
- if (value == 2) {
- if (gw->is_dialing) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallTerminated",
- DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- }
- }
- } else if (!strcmp(name, "\"service\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "RegistrationStatus",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"signal\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "SignalStrength",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"roam\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "RoamingStatus",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"battchg\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "BatteryCharge",
- DBUS_TYPE_UINT16, &value);
-}
-
-static void process_ring(struct audio_device *device, GIOChannel *chan,
- gchar *buf)
-{
- gchar number[AG_CALLER_NUM_SIZE];
- gchar *cli;
- gchar *sep;
- gsize read;
- GIOStatus status;
-
- rfcomm_stop_watch(device);
- status = g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read, NULL);
- if (status != G_IO_STATUS_NORMAL)
- return;
-
- debug("at the begin of process_ring");
- if (strlen(buf) > AG_CALLER_NUM_SIZE + 10)
- error("process_ring(): buf is too long '%s'", buf);
- else if ((cli = strstr(buf, "\r\n+CLIP"))) {
- if (sscanf(cli, "\r\n+CLIP: \"%s", number) == 1) {
- sep = strchr(number, '"');
- sep[0] = '\0';
-
- /* FIXME:signal will be emitted on each RING+CLIP.
- * That's bad */
- cli = number;
- g_dbus_emit_signal(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "Ring",
- DBUS_TYPE_STRING, &cli,
- DBUS_TYPE_INVALID);
- device->gateway->is_dialing = TRUE;
- } else
- error("process_ring(): '%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;
- gsize read;
- /* some space for value */
- gchar indicator[AG_INDICATOR_DESCR_SIZE + 4];
- gint value;
- guint index;
- gchar *sep;
-
- debug("at the begin of rfcomm_ag_data_cb()");
- if (cond & G_IO_NVAL)
- return FALSE;
-
- gw = device->gateway;
-
- if (cond & (G_IO_ERR | G_IO_HUP)) {
- debug("connection with remote BT is closed");
- gateway_close(device);
- return FALSE;
- }
-
- if (g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read, NULL)
- != G_IO_STATUS_NORMAL)
- return TRUE;
- buf[read] = '\0';
-
- if (strlen(buf) > AG_INDICATOR_DESCR_SIZE + 14)
- error("rfcomm_ag_data_cb(): buf is too long '%s'", buf);
- else if (sscanf(buf, "\r\n+CIEV:%s\r\n", indicator) == 1) {
- sep = strchr(indicator, ',');
- sep[0] = '\0';
- sep += 1;
- index = atoi(indicator);
- value = atoi(sep);
- process_ind_change(device, index, value);
- } else if (strstr(buf, "RING"))
- process_ring(device, chan, buf);
- else if (sscanf(buf, "\r\n+BVRA:%d\r\n", &value) == 1) {
- if (value == 0)
- 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 (sscanf(buf, "\r\n+VGS:%d\r\n", &value) == 1) {
- gw->sp_gain = value;
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "SpeakerGain",
- DBUS_TYPE_UINT16, &value);
- } else if (sscanf(buf, "\r\n+VGM:%d\r\n", &value) == 1) {
- gw->mic_gain = value;
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "MicrophoneGain",
- DBUS_TYPE_UINT16, &value);
- } 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)
{
@@ -541,7 +118,6 @@ static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- DBusMessage *conn_mes = gw->connect_message;
gchar gw_addr[18];
GIOFlags flags;
@@ -563,29 +139,6 @@ static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
if (!gw->rfcomm)
gw->rfcomm = g_io_channel_ref(chan);
- if (establish_service_level_conn(dev->gateway)) {
- gboolean value = TRUE;
-
- debug("%s: Connected to %s", dev->path, gw_addr);
- rfcomm_start_watch(dev);
- if (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;
- }
-
- gw->state = GATEWAY_STATE_CONNECTED;
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return;
- } 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);
@@ -732,321 +285,20 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
-static DBusMessage *process_ag_reponse(DBusMessage *msg, gchar *response)
-{
- DBusMessage *reply;
-
-
- debug("in process_ag_reponse, response is %s", response);
- if (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)
-{
- struct audio_device *dev = data;
- struct gateway *gw = dev->gateway;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- if (gw->call_active)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".CallAlreadyAnswered",
- "Call AlreadyAnswered");
-
- return process_simple(msg, dev, AG_ANSWER);
-}
-
-#define AG_HANGUP "AT+CHUP\r"
-
-static DBusMessage *ag_terminate_call(DBusConnection *conn, DBusMessage *msg,
- void *data)
-{
- struct audio_device *dev = data;
- struct gateway *gw = dev->gateway;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- return process_simple(msg, dev, AG_HANGUP);
-}
-
-/* according to GSM spec */
-#define ALLOWED_NUMBER_SYMBOLS "1234567890*#ABCD"
-#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;
- DBusMessage *result;
-
- debug("at the begin of ag_call()");
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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);
-
- result = process_ag_reponse(msg, buf);
- return result;
-}
-
-#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, *sep;
- DBusMessage *reply;
- GIOStatus status;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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 (strstr(buf, "+COPS")) {
- if (!strrchr(buf, ','))
- result = "0";
- else {
- result = strchr(buf, '\"') + 1;
- sep = strchr(result, '\"');
- sep[0] = '\0';
- }
-
- 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_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;
- gboolean got_ok = TRUE;
- gint num_len;
- gint i = 0;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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");
-
- num_len = strlen(number);
- 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_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
-#define CNUM_LEN 5 /* length of "+CNUM" string */
-#define MAX_NUMBER_CNT 16
-static DBusMessage *ag_get_subscriber_num(DBusConnection *conn,
- DBusMessage *msg, void *data)
-{
- struct audio_device *device = data;
- struct gateway *gw = device->gateway;
- gchar buf[RFCOMM_BUF_SIZE];
- gchar *number, *end;
- DBusMessage *reply = dbus_message_new_method_return(msg);
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- rfcomm_stop_watch(device);
- rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf,
- strlen(AG_GET_SUBSCRIBER_NUMS));
- rfcomm_start_watch(device);
-
- if (strlen(buf) > AG_CALLER_NUM_SIZE + 21)
- error("ag_get_subscriber_num(): buf is too long '%s'", buf);
- else if (strstr(buf, "+CNUM")) {
- number = strchr(buf, ',');
- number++;
- end = strchr(number, ',');
- if (end) {
- *end = '\0';
- dbus_message_append_args(reply, DBUS_TYPE_STRING,
- &number, DBUS_TYPE_INVALID);
- }
- } else
- error("ag_get_subscriber_num(): read wrong data '%s'", buf);
-
- return reply;
-}
-
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- struct audio_device *device = data;
- struct gateway *gw = device->gateway;
- DBusMessage *reply;
- DBusMessageIter iter;
- DBusMessageIter dict;
- gboolean value;
- guint index = 0;
- struct indicator *ind;
-
- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;
-
- dbus_message_iter_init_append(reply, &iter);
-
- dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
- DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
- DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
- DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
-
- /* Connected */
- value = gateway_is_connected(device);
- dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
-
- if (!value)
- goto done;
-
- while ((ind = g_slist_nth_data(gw->indies, index))) {
- if(!strcmp(ind->descr, "\"service\""))
- dict_append_entry(&dict, "RegistrationStatus",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"signal\""))
- dict_append_entry(&dict, "SignalStrength",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"roam\""))
- dict_append_entry(&dict, "RoamingStatus",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"battchg\""))
- dict_append_entry(&dict, "BatteryCharge",
- DBUS_TYPE_UINT16, &ind->value);
- index++;
- }
-
- /* SpeakerGain */
- dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16,
- &device->gateway->sp_gain);
-
- /* MicrophoneGain */
- dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16,
- &device->gateway->mic_gain);
-done:
- dbus_message_iter_close_container(&iter, &dict);
- return reply;
+ return NULL;
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
{ "Disconnect", "", "", ag_disconnect },
- { "AnswerCall", "", "", ag_answer },
- { "TerminateCall", "", "", ag_terminate_call },
- { "Call", "s", "", ag_call },
- { "GetOperatorName", "", "s", ag_get_operator },
- { "SendDTMF", "s", "", ag_send_dtmf },
- { "GetSubscriberNumber", "", "s", ag_get_subscriber_num },
{ "GetProperties", "", "a{sv}", ag_get_properties },
{ NULL, NULL, NULL, NULL }
};
static GDBusSignalTable gateway_signals[] = {
- { "Ring", "s" },
- { "CallTerminated", "" },
- { "CallStarted", "" },
- { "CallEnded", "" },
{ "PropertyChanged", "sv" },
{ NULL, NULL }
};
@@ -1063,9 +315,6 @@ struct gateway *gateway_init(struct audio_device *dev)
debug("in gateway_init, dev is %p", dev);
gw = g_new0(struct gateway, 1);
- gw->indies = NULL;
- gw->is_dialing = FALSE;
- gw->call_active = FALSE;
gw->state = GATEWAY_STATE_DISCONNECTED;
return gw;
@@ -1107,11 +356,6 @@ void gateway_start_service(struct audio_device *device)
rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
}
-static void indicator_slice_free(gpointer mem)
-{
- g_slice_free(struct indicator, mem);
-}
-
int gateway_close(struct audio_device *device)
{
struct gateway *gw = device->gateway;
@@ -1119,8 +363,6 @@ int gateway_close(struct audio_device *device)
GIOChannel *sco = gw->sco;
gboolean value = FALSE;
- g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL);
- g_slist_free(gw->indies);
if (rfcomm) {
g_io_channel_shutdown(rfcomm, TRUE, NULL);
g_io_channel_unref(rfcomm);
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH 2/2] Implement HandsfreeGateway Interface
2010-01-21 20:31 [PATCH 1/2] clean up audio/gateway.c Gustavo F. Padovan
@ 2010-01-21 20:31 ` Gustavo F. Padovan
2010-01-21 21:20 ` Vinicius Gomes
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-01-21 20:31 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono, johan.hedberg
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/gateway.c | 428 +++++++++++++++++++++++++++++++++++++++++++------------
audio/gateway.h | 12 ++-
audio/manager.c | 20 +--
audio/unix.c | 8 +
doc/hfp-api.txt | 81 +++++++++++
5 files changed, 439 insertions(+), 110 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..8cfb6e9 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,8 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
+ * Copyright (C) 2010 Gustavo F. Padovan <padovan@profusion.mobi>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -32,12 +34,15 @@
#include <string.h>
#include <fcntl.h>
#include <errno.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>
#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sco.h>
@@ -52,20 +57,105 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#define MAX_OPEN_TRIES 5
+#define OPEN_WAIT 300
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
- GIOChannel *rfcomm;
- guint rfcomm_watch_id;
+ int channel;
+ GIOChannel *rfcomm; /* remote AG requested rfcomm connection */
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t i)
+{
+ switch (i) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val = state2str(new_state);
+
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(dev->conn, msg);
+
+ g_dbus_remove_watch(dev->conn, agent->watch);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +169,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,63 +183,93 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = chan;
+ g_io_channel_ref(chan);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
if (gw->sco_start_cb)
gw->sco_start_cb(dev, gw->sco_start_cb_data);
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ return;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ return;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
+ char src[18], dst[18];
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent)
+ error("Handfree Agent not registered");
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ ba2str(&dev->src, src);
+ ba2str(&dev->dst, dst);
- gateway_close(dev);
+ sk = g_io_channel_unix_get_fd(chan);
+
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev)) {
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ } else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ return;
+
+fail:
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg, ERROR_INTERFACE
+ ".Failed", ".Connection attempt failed");
+ }
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
+ struct gateway *gw = dev->gateway;
int ch = -1;
sdp_list_t *protos, *classes;
uuid_t uuid;
@@ -182,8 +303,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -197,6 +316,8 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
goto fail;
}
+ gw->channel = ch;
+
io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -204,25 +325,24 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ if (err)
+ g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg, ERROR_INTERFACE
+ ".NotSupported", "Not supported");
+ }
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
sco_cb = dev->gateway->sco_start_cb;
if (sco_cb)
@@ -243,22 +363,55 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
{
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
+ DBusMessage *reply;
debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assined");
- 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");
}
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
debug("at the end of ag_connect()");
- return NULL;
+
+ return reply;
+}
+
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ gboolean value = FALSE;
+
+ if (gw->rfcomm) {
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ gw->state = GATEWAY_STATE_DISCONNECTED;
+
+ emit_property_changed(device->conn, device->path,
+ AUDIO_GATEWAY_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ return 0;
}
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
@@ -269,6 +422,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -279,22 +435,124 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
"Device not Connected");
gateway_close(device);
+
ba2str(&device->dst, gw_addr);
debug("Disconnected from %s, %s", gw_addr, device->path);
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "Connected",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".InvalidArguments", "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ gw->msg = dbus_message_ref(msg);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+
+ if (!gw->agent)
+ goto done;
+
+ agent = gw->agent;
+ if (strcmp(agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed", "Permission denied");
+
+ agent_disconnect(device, gw->agent);
+
+ g_dbus_remove_watch(conn, agent->watch);
+
+ dbus_message_unref(gw->msg);
+
+ agent_free(agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -307,17 +565,15 @@ struct gateway *gateway_init(struct audio_device *dev)
{
struct gateway *gw;
+ gw = g_new0(struct gateway, 1);
+
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
gateway_methods, gateway_signals,
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
return gw;
-
}
gboolean gateway_is_connected(struct audio_device *dev)
@@ -326,17 +582,6 @@ gboolean gateway_is_connected(struct audio_device *dev)
dev->gateway->state == GATEWAY_STATE_CONNECTED);
}
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
-{
- if (!io)
- return -EINVAL;
-
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
-
- return 0;
-}
-
int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
{
struct gateway *gw = dev->gateway;
@@ -347,42 +592,38 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
+ (GIOFunc) sco_io_cb, dev);
+
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
return 0;
}
-void gateway_start_service(struct audio_device *device)
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch)
{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ if (!chan)
+ return -EINVAL;
+
+ dev->gateway->rfcomm = g_io_channel_ref(chan);
+ dev->gateway->channel = ch;
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL,
+ &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -399,8 +640,12 @@ gboolean gateway_request_stream(struct audio_device *dev,
gw->sco_start_cb_data = user_data;
get_records(dev);
} else if (!gw->sco) {
+ char source[128], destination[128];
+
gw->sco_start_cb = cb;
gw->sco_start_cb_data = user_data;
+ ba2str(&dev->src, source);
+ ba2str(&dev->dst, destination);
io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -464,4 +709,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..eaf6801 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -4,6 +4,7 @@
*
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -22,20 +23,25 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
struct gateway *gateway_init(struct audio_device *device);
+void gateway_exit();
gboolean gateway_is_connected(struct audio_device *dev);
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..3ac1a60 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -586,7 +586,7 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
goto drop;
}
- if (gateway_connect_rfcomm(device, chan) < 0) {
+ if (gateway_connect_rfcomm(device, chan, ch) < 0) {
error("Allocating new GIOChannel failed!");
goto drop;
}
@@ -905,22 +905,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..bd1a415 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -395,6 +395,9 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
struct bt_start_stream_rsp *rsp = (void *) buf;
struct bt_new_stream_ind *ind = (void *) buf;
+ if (!dev)
+ goto failed;
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
@@ -416,6 +419,11 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
}
client->req_id = 0;
+ return;
+
+failed:
+ error("gateway_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
}
static void headset_suspend_complete(struct audio_device *dev, void *user_data)
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..5055046
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,81 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH 2/2] Implement HandsfreeGateway Interface
2010-01-21 20:31 ` [PATCH 2/2] Implement HandsfreeGateway Interface Gustavo F. Padovan
@ 2010-01-21 21:20 ` Vinicius Gomes
2010-01-21 21:25 ` Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Vinicius Gomes @ 2010-01-21 21:20 UTC (permalink / raw)
To: Gustavo F. Padovan; +Cc: linux-bluetooth, ofono
SGkgR3VzdGF2bywKCk9uIFRodSwgSmFuIDIxLCAyMDEwIGF0IDU6MzEgUE0sIEd1c3Rhdm8gRi4g
UGFkb3Zhbgo8cGFkb3ZhbkBwcm9mdXNpb24ubW9iaT4gd3JvdGU6Cj4gKyDCoCDCoCDCoCBndy0+
c2NvID0gY2hhbjsKPiArIMKgIMKgIMKgIGdfaW9fY2hhbm5lbF9yZWYoY2hhbik7Cj4gKwoKWW91
IG11c3QgaGF2ZSBmb3Jnb3R0ZW4gdG8gY2hhbmdlIHRoaXMuCgo+IEBAIC0zMDcsMTcgKzU2NSwx
NSBAQCBzdHJ1Y3QgZ2F0ZXdheSAqZ2F0ZXdheV9pbml0KHN0cnVjdCBhdWRpb19kZXZpY2UgKmRl
dikKPiDCoHsKPiDCoCDCoCDCoCDCoHN0cnVjdCBnYXRld2F5ICpndzsKPgo+ICsgwqAgwqAgwqAg
Z3cgPSBnX25ldzAoc3RydWN0IGdhdGV3YXksIDEpOwo+ICsKPiDCoCDCoCDCoCDCoGlmICghZ19k
YnVzX3JlZ2lzdGVyX2ludGVyZmFjZShkZXYtPmNvbm4sIGRldi0+cGF0aCwKPiDCoCDCoCDCoCDC
oCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoCDCoEFVRElPX0dB
VEVXQVlfSU5URVJGQUNFLAo+IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKg
IMKgIMKgIMKgIMKgIMKgIMKgIMKgZ2F0ZXdheV9tZXRob2RzLCBnYXRld2F5X3NpZ25hbHMsCj4g
wqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAgwqAg
wqBOVUxMLCBkZXYsIE5VTEwpKQo+IMKgIMKgIMKgIMKgIMKgIMKgIMKgIMKgcmV0dXJuIE5VTEw7
Cj4KClNlZW1zIGxpa2UgZ3cgaXMgbGVha2luZyBpZiByZWdpc3RyYXRpb24gZmFpbHMuIE1vdmUg
aXQgZG93biBhIGxpdHRsZSwgcGVyaGFwcz8KCj4gLSDCoCDCoCDCoCBkZWJ1ZygiaW4gZ2F0ZXdh
eV9pbml0LCBkZXYgaXMgJXAiLCBkZXYpOwo+IC0gwqAgwqAgwqAgZ3cgPSBnX25ldzAoc3RydWN0
IGdhdGV3YXksIDEpOwo+IC0gwqAgwqAgwqAgZ3ctPnN0YXRlID0gR0FURVdBWV9TVEFURV9ESVND
T05ORUNURUQ7Cj4gwqAgwqAgwqAgwqByZXR1cm4gZ3c7Cj4gLQo+IMKgfQo+CgoKQ2hlZXJzLAot
LSAKVmluaWNpdXMgR29tZXMKSU5kVCAtIEluc3RpdHV0byBOb2tpYSBkZSBUZWNub2xvZ2lhCg==
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 2/2] Implement HandsfreeGateway Interface
2010-01-21 21:20 ` Vinicius Gomes
@ 2010-01-21 21:25 ` Gustavo F. Padovan
0 siblings, 0 replies; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-01-21 21:25 UTC (permalink / raw)
To: Vinicius Gomes; +Cc: linux-bluetooth, ofono
On Thu, Jan 21, 2010 at 7:20 PM, Vinicius Gomes
<vinicius.gomes@openbossa.org> wrote:
> Hi Gustavo,
>
> On Thu, Jan 21, 2010 at 5:31 PM, Gustavo F. Padovan
> <padovan@profusion.mobi> wrote:
>> + gw->sco = chan;
>> + g_io_channel_ref(chan);
>> +
>
> You must have forgotten to change this.
>
>> @@ -307,17 +565,15 @@ struct gateway *gateway_init(struct audio_device *dev)
>> {
>> struct gateway *gw;
>>
>> + gw = g_new0(struct gateway, 1);
>> +
>> if (!g_dbus_register_interface(dev->conn, dev->path,
>> AUDIO_GATEWAY_INTERFACE,
>> gateway_methods, gateway_signals,
>> NULL, dev, NULL))
>> return NULL;
>>
>
> Seems like gw is leaking if registration fails. Move it down a little, perhaps?
Sounds good. Thanks. :)
>
>> - debug("in gateway_init, dev is %p", dev);
>> - gw = g_new0(struct gateway, 1);
>> - gw->state = GATEWAY_STATE_DISCONNECTED;
>> return gw;
>> -
>> }
>>
>
>
> Cheers,
> --
> Vinicius Gomes
> INdT - Instituto Nokia de Tecnologia
>
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC] HFP support into oFono and BlueZ
@ 2010-01-20 19:58 Gustavo F. Padovan
2010-01-27 19:12 ` HFP support into BlueZ and oFono Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-01-20 19:58 UTC (permalink / raw)
To: linux-bluetooth, ofono, zhenhua.zhang
[-- Attachment #1: Type: text/plain, Size: 655 bytes --]
> New version of the patches. Known issues:
> "'org.bluez.HandsfreeAgent.Release' part not implemented" and it's not
> working with more than one bluetooth devices in some cases. Comments
> are welcome.
>
Another new version of the patches. Now ofono query all bluetooth
devices and searches for the ones with Gateway interface.
Bluez has some fixes and now supports connection coming from the AG.
The patches are using a per-device agent as described by the last
version of Handsfree API.
I did not implemented yet 'org.bluez.HandsfreeAgent.Release'.
Comments are welcome. :)
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
[-- Attachment #2: 0001-clean-up-audio-gateway.c.patch --]
[-- Type: application/octet-stream, Size: 24620 bytes --]
From 1dee86673257625823137717b06d0757d5715781 Mon Sep 17 00:00:00 2001
From: Gustavo F. Padovan <padovan@profusion.mobi>
Date: Fri, 6 Nov 2009 18:08:58 -0200
Subject: [PATCH 1/2] clean up audio/gateway.c
remove all code related to the AT engine
---
audio/gateway.c | 760 +------------------------------------------------------
1 files changed, 1 insertions(+), 759 deletions(-)
diff --git a/audio/gateway.c b/audio/gateway.c
index a1c1ea2..3dc09ff 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -53,52 +53,6 @@
#include "dbus-common.h"
#define RFCOMM_BUF_SIZE 256
-/* not-more-then-16 defined by GSM + 1 for NULL + padding */
-#define AG_INDICATOR_DESCR_SIZE 20
-#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 */
-/* Releases all held calls or sets User Determined User Busy (UDUB)
- * for a waiting call */
-#define AG_CHLD_0 0x01
-/* Releases all active calls (if any exist) and accepts the other
- * (held or waiting) call */
-#define AG_CHLD_1 0x02
-/* Releases specified active call only <x> */
-#define AG_CHLD_1x 0x04
-/* Places all active calls (if any exist) on hold and accepts the other
- * (held or waiting) call */
-#define AG_CHLD_2 0x08
-/* Request private consultation mode with specified call <x> (Place all
- * calls on hold EXCEPT the call <x>) */
-#define AG_CHLD_2x 0x10
-/* Adds a held call to the conversation */
-#define AG_CHLD_3 0x20
-/* Connects the two calls and disconnects the subscriber from both calls
- * (Explicit Call Transfer). Support for this value and its associated
- * functionality is optional for the HF. */
-#define AG_CHLD_4 0x40
-
-#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 {
gateway_state_t state;
@@ -108,387 +62,10 @@ struct gateway {
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;
- gboolean call_active;
-
- int sp_gain;
- int mic_gain;
};
-static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond,
- struct audio_device *device);
-
int gateway_close(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 (count > 0) {
- status = g_io_channel_write_chars(io, data, count, &written,
- NULL);
- if (status != G_IO_STATUS_NORMAL)
- 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;
- gsize read = 0;
- gboolean got_ok = FALSE;
- gboolean got_error = FALSE;
- gchar *resp_buf = response;
- gsize toread = RFCOMM_BUF_SIZE - 1;
- GIOStatus status;
-
- if (!io_channel_write_all(rfcomm, data, count))
- return FALSE;
-
- while (!(got_ok || got_error)) {
- status = g_io_channel_read_chars(rfcomm, resp_buf, toread,
- &read, NULL);
- if (status == G_IO_STATUS_NORMAL)
- 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>))
- * ... */
-static GSList *parse_indicator_names(gchar *names, GSList *indies)
-{
- gchar *current = names - 1;
- GSList *result = indies;
- gchar *next;
- struct indicator *ind;
-
- while (current != NULL) {
- current += 2;
- next = strstr(current, ",(");
- ind = g_slice_new(struct indicator);
- strncpy(ind->descr, current, 20);
- ind->descr[(intptr_t) next - (intptr_t) current] = '\0';
- result = g_slist_append(result, (gpointer) ind);
- current = strstr(next + 1, ",(");
- }
- return result;
-}
-
-/* get values from <val0>,<val1>,... */
-static GSList *parse_indicator_values(gchar *values, GSList *indies)
-{
- gint val;
- gchar *current = values - 1;
- GSList *runner = indies;
- struct indicator *ind;
-
- while (current != NULL) {
- current += 1;
- sscanf(current, "%d", &val);
- current = strchr(current, ',');
- ind = 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 (strstr(features, "0"))
- result |= AG_CHLD_0;
-
- if (strstr(features, "1"))
- result |= AG_CHLD_1;
-
- if (strstr(features, "1x"))
- result |= AG_CHLD_1x;
-
- if (strstr(features, "2"))
- result |= AG_CHLD_2;
-
- if (strstr(features, "2x"))
- result |= AG_CHLD_2x;
-
- if (strstr(features, "3"))
- result |= AG_CHLD_3;
-
- if (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;
-
- debug("at the begin of establish_service_level_conn()");
- res = rfcomm_send_and_read(gw, AG_FEATURES, buf,
- sizeof(AG_FEATURES) - 1);
- if (!res || sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features) != 1)
- 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 ((gw->ag_features & AG_FEATURE_3WAY) != 0) {
- 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 ((gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) != 0)
- 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, guint index,
- gint value)
-{
- struct gateway *gw = dev->gateway;
- struct indicator *ind = g_slist_nth_data(gw->indies, index - 1);
- gchar *name = ind->descr;
-
- ind->value = value;
-
- debug("at the begin of process_ind_change, name is %s", name);
- if (!strcmp(name, "\"call\"")) {
- if (value > 0) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallStarted", DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- gw->call_active = TRUE;
- } else {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallEnded", DBUS_TYPE_INVALID);
- gw->call_active = FALSE;
- }
-
- } else if (!strcmp(name, "\"callsetup\"")) {
- if (value == 0 && gw->is_dialing) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallTerminated",
- DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- } else if (!gw->is_dialing && value > 0)
- gw->is_dialing = TRUE;
-
- } else if (!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
- */
- /* '2' means: all calls held, no active calls */
- if (value == 2) {
- if (gw->is_dialing) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallTerminated",
- DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- }
- }
- } else if (!strcmp(name, "\"service\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "RegistrationStatus",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"signal\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "SignalStrength",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"roam\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "RoamingStatus",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"battchg\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "BatteryCharge",
- DBUS_TYPE_UINT16, &value);
-}
-
-static void process_ring(struct audio_device *device, GIOChannel *chan,
- gchar *buf)
-{
- gchar number[AG_CALLER_NUM_SIZE];
- gchar *cli;
- gchar *sep;
- gsize read;
- GIOStatus status;
-
- rfcomm_stop_watch(device);
- status = g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read, NULL);
- if (status != G_IO_STATUS_NORMAL)
- return;
-
- debug("at the begin of process_ring");
- if (strlen(buf) > AG_CALLER_NUM_SIZE + 10)
- error("process_ring(): buf is too long '%s'", buf);
- else if ((cli = strstr(buf, "\r\n+CLIP"))) {
- if (sscanf(cli, "\r\n+CLIP: \"%s", number) == 1) {
- sep = strchr(number, '"');
- sep[0] = '\0';
-
- /* FIXME:signal will be emitted on each RING+CLIP.
- * That's bad */
- cli = number;
- g_dbus_emit_signal(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "Ring",
- DBUS_TYPE_STRING, &cli,
- DBUS_TYPE_INVALID);
- device->gateway->is_dialing = TRUE;
- } else
- error("process_ring(): '%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;
- gsize read;
- /* some space for value */
- gchar indicator[AG_INDICATOR_DESCR_SIZE + 4];
- gint value;
- guint index;
- gchar *sep;
-
- debug("at the begin of rfcomm_ag_data_cb()");
- if (cond & G_IO_NVAL)
- return FALSE;
-
- gw = device->gateway;
-
- if (cond & (G_IO_ERR | G_IO_HUP)) {
- debug("connection with remote BT is closed");
- gateway_close(device);
- return FALSE;
- }
-
- if (g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read, NULL)
- != G_IO_STATUS_NORMAL)
- return TRUE;
- buf[read] = '\0';
-
- if (strlen(buf) > AG_INDICATOR_DESCR_SIZE + 14)
- error("rfcomm_ag_data_cb(): buf is too long '%s'", buf);
- else if (sscanf(buf, "\r\n+CIEV:%s\r\n", indicator) == 1) {
- sep = strchr(indicator, ',');
- sep[0] = '\0';
- sep += 1;
- index = atoi(indicator);
- value = atoi(sep);
- process_ind_change(device, index, value);
- } else if (strstr(buf, "RING"))
- process_ring(device, chan, buf);
- else if (sscanf(buf, "\r\n+BVRA:%d\r\n", &value) == 1) {
- if (value == 0)
- 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 (sscanf(buf, "\r\n+VGS:%d\r\n", &value) == 1) {
- gw->sp_gain = value;
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "SpeakerGain",
- DBUS_TYPE_UINT16, &value);
- } else if (sscanf(buf, "\r\n+VGM:%d\r\n", &value) == 1) {
- gw->mic_gain = value;
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "MicrophoneGain",
- DBUS_TYPE_UINT16, &value);
- } 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)
{
@@ -541,7 +118,6 @@ static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- DBusMessage *conn_mes = gw->connect_message;
gchar gw_addr[18];
GIOFlags flags;
@@ -563,29 +139,6 @@ static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
if (!gw->rfcomm)
gw->rfcomm = g_io_channel_ref(chan);
- if (establish_service_level_conn(dev->gateway)) {
- gboolean value = TRUE;
-
- debug("%s: Connected to %s", dev->path, gw_addr);
- rfcomm_start_watch(dev);
- if (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;
- }
-
- gw->state = GATEWAY_STATE_CONNECTED;
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return;
- } 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);
@@ -732,321 +285,20 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
-static DBusMessage *process_ag_reponse(DBusMessage *msg, gchar *response)
-{
- DBusMessage *reply;
-
-
- debug("in process_ag_reponse, response is %s", response);
- if (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)
-{
- struct audio_device *dev = data;
- struct gateway *gw = dev->gateway;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- if (gw->call_active)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".CallAlreadyAnswered",
- "Call AlreadyAnswered");
-
- return process_simple(msg, dev, AG_ANSWER);
-}
-
-#define AG_HANGUP "AT+CHUP\r"
-
-static DBusMessage *ag_terminate_call(DBusConnection *conn, DBusMessage *msg,
- void *data)
-{
- struct audio_device *dev = data;
- struct gateway *gw = dev->gateway;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- return process_simple(msg, dev, AG_HANGUP);
-}
-
-/* according to GSM spec */
-#define ALLOWED_NUMBER_SYMBOLS "1234567890*#ABCD"
-#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;
- DBusMessage *result;
-
- debug("at the begin of ag_call()");
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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);
-
- result = process_ag_reponse(msg, buf);
- return result;
-}
-
-#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, *sep;
- DBusMessage *reply;
- GIOStatus status;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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 (strstr(buf, "+COPS")) {
- if (!strrchr(buf, ','))
- result = "0";
- else {
- result = strchr(buf, '\"') + 1;
- sep = strchr(result, '\"');
- sep[0] = '\0';
- }
-
- 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_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;
- gboolean got_ok = TRUE;
- gint num_len;
- gint i = 0;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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");
-
- num_len = strlen(number);
- 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_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
-#define CNUM_LEN 5 /* length of "+CNUM" string */
-#define MAX_NUMBER_CNT 16
-static DBusMessage *ag_get_subscriber_num(DBusConnection *conn,
- DBusMessage *msg, void *data)
-{
- struct audio_device *device = data;
- struct gateway *gw = device->gateway;
- gchar buf[RFCOMM_BUF_SIZE];
- gchar *number, *end;
- DBusMessage *reply = dbus_message_new_method_return(msg);
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- rfcomm_stop_watch(device);
- rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf,
- strlen(AG_GET_SUBSCRIBER_NUMS));
- rfcomm_start_watch(device);
-
- if (strlen(buf) > AG_CALLER_NUM_SIZE + 21)
- error("ag_get_subscriber_num(): buf is too long '%s'", buf);
- else if (strstr(buf, "+CNUM")) {
- number = strchr(buf, ',');
- number++;
- end = strchr(number, ',');
- if (end) {
- *end = '\0';
- dbus_message_append_args(reply, DBUS_TYPE_STRING,
- &number, DBUS_TYPE_INVALID);
- }
- } else
- error("ag_get_subscriber_num(): read wrong data '%s'", buf);
-
- return reply;
-}
-
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- struct audio_device *device = data;
- struct gateway *gw = device->gateway;
- DBusMessage *reply;
- DBusMessageIter iter;
- DBusMessageIter dict;
- gboolean value;
- guint index = 0;
- struct indicator *ind;
-
- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;
-
- dbus_message_iter_init_append(reply, &iter);
-
- dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
- DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
- DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
- DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
-
- /* Connected */
- value = gateway_is_connected(device);
- dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
-
- if (!value)
- goto done;
-
- while ((ind = g_slist_nth_data(gw->indies, index))) {
- if(!strcmp(ind->descr, "\"service\""))
- dict_append_entry(&dict, "RegistrationStatus",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"signal\""))
- dict_append_entry(&dict, "SignalStrength",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"roam\""))
- dict_append_entry(&dict, "RoamingStatus",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"battchg\""))
- dict_append_entry(&dict, "BatteryCharge",
- DBUS_TYPE_UINT16, &ind->value);
- index++;
- }
-
- /* SpeakerGain */
- dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16,
- &device->gateway->sp_gain);
-
- /* MicrophoneGain */
- dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16,
- &device->gateway->mic_gain);
-done:
- dbus_message_iter_close_container(&iter, &dict);
- return reply;
+ return NULL;
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
{ "Disconnect", "", "", ag_disconnect },
- { "AnswerCall", "", "", ag_answer },
- { "TerminateCall", "", "", ag_terminate_call },
- { "Call", "s", "", ag_call },
- { "GetOperatorName", "", "s", ag_get_operator },
- { "SendDTMF", "s", "", ag_send_dtmf },
- { "GetSubscriberNumber", "", "s", ag_get_subscriber_num },
{ "GetProperties", "", "a{sv}", ag_get_properties },
{ NULL, NULL, NULL, NULL }
};
static GDBusSignalTable gateway_signals[] = {
- { "Ring", "s" },
- { "CallTerminated", "" },
- { "CallStarted", "" },
- { "CallEnded", "" },
{ "PropertyChanged", "sv" },
{ NULL, NULL }
};
@@ -1063,9 +315,6 @@ struct gateway *gateway_init(struct audio_device *dev)
debug("in gateway_init, dev is %p", dev);
gw = g_new0(struct gateway, 1);
- gw->indies = NULL;
- gw->is_dialing = FALSE;
- gw->call_active = FALSE;
gw->state = GATEWAY_STATE_DISCONNECTED;
return gw;
@@ -1107,11 +356,6 @@ void gateway_start_service(struct audio_device *device)
rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
}
-static void indicator_slice_free(gpointer mem)
-{
- g_slice_free(struct indicator, mem);
-}
-
int gateway_close(struct audio_device *device)
{
struct gateway *gw = device->gateway;
@@ -1119,8 +363,6 @@ int gateway_close(struct audio_device *device)
GIOChannel *sco = gw->sco;
gboolean value = FALSE;
- g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL);
- g_slist_free(gw->indies);
if (rfcomm) {
g_io_channel_shutdown(rfcomm, TRUE, NULL);
g_io_channel_unref(rfcomm);
--
1.6.4.4
[-- Attachment #3: 0002-Implement-HandsfreeGateway-Interface.patch --]
[-- Type: application/octet-stream, Size: 24074 bytes --]
From 0755bce0e732a73a7d1d68772aa2090603003784 Mon Sep 17 00:00:00 2001
From: Gustavo F. Padovan <padovan@profusion.mobi>
Date: Fri, 8 Jan 2010 17:38:21 -0200
Subject: [PATCH 2/2] Implement HandsfreeGateway Interface
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/gateway.c | 442 +++++++++++++++++++++++++++++++++++++++++++------------
audio/gateway.h | 12 +-
audio/manager.c | 20 +--
audio/unix.c | 8 +
doc/hfp-api.txt | 81 ++++++++++
5 files changed, 453 insertions(+), 110 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..d927a6c 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,8 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
+ * Copyright (C) 2010 Gustavo F. Padovan <padovan@profusion.mobi>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -32,12 +34,15 @@
#include <string.h>
#include <fcntl.h>
#include <errno.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>
#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sco.h>
@@ -52,19 +57,100 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#define MAX_OPEN_TRIES 5
+#define OPEN_WAIT 300
+
+struct agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
- GIOChannel *rfcomm;
- guint rfcomm_watch_id;
+ int channel;
+ GIOChannel *rfcomm; /* remote AG requested rfcomm connection */
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct agent *agent;
+ DBusMessage *msg;
};
-int gateway_close(struct audio_device *device);
+int gateway_close(gpointer data);
+
+static const char *connected2str(int i)
+{
+ switch (i) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, int new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val = connected2str(new_state);
+
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(dev->conn, msg);
+
+ g_dbus_remove_watch(dev->conn, agent->watch);
+}
+
+static gboolean agent_sendfd(struct agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev->gateway, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
@@ -79,6 +165,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,63 +179,105 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = chan;
+ g_io_channel_ref(chan);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
if (gw->sco_start_cb)
gw->sco_start_cb(dev, gw->sco_start_cb_data);
- /* why is this here? */
fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct gateway *gw = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!gw->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ return;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ info("Agent reply: file descriptor passed successfuly");
+ return;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ g_idle_add(gateway_close, gw);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ struct rfcomm_dev_req req;
+ DBusMessage *reply;
+ int sk;
+ char src[18], dst[18];
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent)
+ error("Handfree Agent not registered");
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ ba2str(&dev->src, src);
+ ba2str(&dev->dst, dst);
- gateway_close(dev);
+ /* make /dev/rfcomm serial port from chan */
+ memset(&req, 0, sizeof(req));
+ req.dev_id = -1;
+ req.flags = (1 << RFCOMM_REUSE_DLC);
+ bacpy(&req.src, &dev->src);
+ bacpy(&req.dst, &dev->dst);
+ req.channel = gw->channel;
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ gw->rfcomm = chan;
+ g_io_channel_ref(chan);
+
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev)) {
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ } else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ return;
+
+fail:
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg, ERROR_INTERFACE
+ ".Failed", ".Connection attempt failed");
+ }
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
+ return;
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
+ struct gateway *gw = dev->gateway;
int ch = -1;
sdp_list_t *protos, *classes;
uuid_t uuid;
@@ -182,8 +311,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -197,6 +324,8 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
goto fail;
}
+ gw->channel = ch;
+
io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -204,25 +333,24 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ if (err)
+ g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg, ERROR_INTERFACE
+ ".NotSupported", "Not supported");
+ }
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
sco_cb = dev->gateway->sco_start_cb;
if (sco_cb)
@@ -243,22 +371,56 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
{
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
+ DBusMessage *reply;
debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assined");
- 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");
}
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
debug("at the end of ag_connect()");
- return NULL;
+
+ return reply;
+}
+
+int gateway_close(gpointer data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ gboolean value = FALSE;
+
+ if (gw->rfcomm) {
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ gw->state = GATEWAY_STATE_DISCONNECTED;
+
+ emit_property_changed(device->conn, device->path,
+ AUDIO_GATEWAY_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ return 0;
}
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
@@ -269,6 +431,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -279,22 +444,128 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
"Device not Connected");
gateway_close(device);
+
ba2str(&device->dst, gw_addr);
debug("Disconnected from %s, %s", gw_addr, device->path);
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = connected2str(gw->state);
+ dict_append_entry(&dict, "Connected",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct agent *agent;
+ const char *path, *name;
+
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".InvalidArguments", "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct agent, 1);
+ if (!agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed",
+ "Failed to create a new agent");
+
+ agent->name = strdup(name);
+ agent->path = strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ gw->msg = dbus_message_ref(msg);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct agent *agent;
+
+ if (!gw->agent)
+ goto done;
+
+ agent = gw->agent;
+ if (strcmp(agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed", "Permission denied");
+
+ agent_disconnect(device, gw->agent);
+
+ g_dbus_remove_watch(conn, agent->watch);
+
+ dbus_message_unref(gw->msg);
+
+ agent_free(agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -307,17 +578,15 @@ struct gateway *gateway_init(struct audio_device *dev)
{
struct gateway *gw;
+ gw = g_new0(struct gateway, 1);
+
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
gateway_methods, gateway_signals,
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
return gw;
-
}
gboolean gateway_is_connected(struct audio_device *dev)
@@ -326,17 +595,6 @@ gboolean gateway_is_connected(struct audio_device *dev)
dev->gateway->state == GATEWAY_STATE_CONNECTED);
}
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
-{
- if (!io)
- return -EINVAL;
-
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
-
- return 0;
-}
-
int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
{
struct gateway *gw = dev->gateway;
@@ -347,42 +605,39 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
+ (GIOFunc) sco_io_cb, dev);
+
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
return 0;
}
-void gateway_start_service(struct audio_device *device)
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch)
{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ if (!chan)
+ return -EINVAL;
+
+ g_io_channel_ref(chan);
+ dev->gateway->rfcomm = chan;
+ dev->gateway->channel = ch;
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL,
+ &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -399,8 +654,12 @@ gboolean gateway_request_stream(struct audio_device *dev,
gw->sco_start_cb_data = user_data;
get_records(dev);
} else if (!gw->sco) {
+ char source[128], destination[128];
+
gw->sco_start_cb = cb;
gw->sco_start_cb_data = user_data;
+ ba2str(&dev->src, source);
+ ba2str(&dev->dst, destination);
io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -464,4 +723,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..eaf6801 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -4,6 +4,7 @@
*
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -22,20 +23,25 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
struct gateway *gateway_init(struct audio_device *device);
+void gateway_exit();
gboolean gateway_is_connected(struct audio_device *dev);
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..3ac1a60 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -586,7 +586,7 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
goto drop;
}
- if (gateway_connect_rfcomm(device, chan) < 0) {
+ if (gateway_connect_rfcomm(device, chan, ch) < 0) {
error("Allocating new GIOChannel failed!");
goto drop;
}
@@ -905,22 +905,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..bd1a415 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -395,6 +395,9 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
struct bt_start_stream_rsp *rsp = (void *) buf;
struct bt_new_stream_ind *ind = (void *) buf;
+ if (!dev)
+ goto failed;
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
@@ -416,6 +419,11 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
}
client->req_id = 0;
+ return;
+
+failed:
+ error("gateway_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
}
static void headset_suspend_complete(struct audio_device *dev, void *user_data)
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..ed52be1
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,81 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent.
--
1.6.4.4
[-- Attachment #4: 0001-Add-HFP-support-through-BlueZ.patch --]
[-- Type: application/octet-stream, Size: 14998 bytes --]
From 61f4d5df1d54e318eeb039bc048c22763ca3e20b Mon Sep 17 00:00:00 2001
From: Gustavo F. Padovan <padovan@profusion.mobi>
Date: Fri, 8 Jan 2010 17:25:43 -0200
Subject: [PATCH] Add HFP support through BlueZ
It uses BlueZ through to get HFP working following the
org.bluez.HandsfreeGateway and org.bluez.HandsfreeAgent from the BlueZ
D-Bus API.
You need the HFP patch into BlueZ and dbus 1.3.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for its prototype
on this code.
---
drivers/hfpmodem/hfpmodem.h | 2 +
plugins/hfp.c | 419 +++++++++++++++++++++++++++++++++++++++++--
plugins/modemconf.c | 1 -
3 files changed, 407 insertions(+), 15 deletions(-)
diff --git a/drivers/hfpmodem/hfpmodem.h b/drivers/hfpmodem/hfpmodem.h
index 509846b..5fee68f 100644
--- a/drivers/hfpmodem/hfpmodem.h
+++ b/drivers/hfpmodem/hfpmodem.h
@@ -63,11 +63,13 @@ enum hfp_indicator {
struct hfp_data {
GAtChat *chat;
+ char *handsfree_path;
unsigned int ag_features;
unsigned int ag_mpty_features;
unsigned int hf_features;
unsigned char cind_pos[HFP_INDICATOR_LAST];
unsigned int cind_val[HFP_INDICATOR_LAST];
+ unsigned int at_timeout;
};
extern void hfp_netreg_init();
diff --git a/plugins/hfp.c b/plugins/hfp.c
index 3bbd922..42269f2 100644
--- a/plugins/hfp.c
+++ b/plugins/hfp.c
@@ -30,6 +30,8 @@
#include <glib.h>
#include <gatchat.h>
#include <gattty.h>
+#include <gdbus.h>
+#include <ofono.h>
#define OFONO_API_SUBJECT_TO_CHANGE
#include <ofono/plugin.h>
@@ -52,11 +54,26 @@
#include <drivers/hfpmodem/hfpmodem.h>
+#include <ofono/dbus.h>
+
+#define BLUEZ_SERVICE "org.bluez"
+#define BLUEZ_PATH "/"
+#define BLUEZ_MANAGER_INTERFACE "org.bluez.Manager"
+#define BLUEZ_ADAPTER_INTERFACE "org.bluez.Adapter"
+#define BLUEZ_DEVICE_INTERFACE "org.bluez.Device"
+#define BLUEZ_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
+
+#define HFP_AGENT_INTERFACE "org.bluez.HandsfreeAgent"
+
+#define HFP_AG_UUID "0000111F-0000-1000-8000-00805F9B34FB"
+
static const char *brsf_prefix[] = { "+BRSF:", NULL };
static const char *cind_prefix[] = { "+CIND:", NULL };
static const char *cmer_prefix[] = { "+CMER:", NULL };
static const char *chld_prefix[] = { "+CHLD:", NULL };
+static DBusConnection *connection;
+
static int hfp_disable(struct ofono_modem *modem);
static void hfp_debug(const char *str, void *user_data)
@@ -135,6 +152,60 @@ static void cmer_cb(gboolean ok, GAtResult *result, gpointer user_data)
sevice_level_conn_established(modem);
}
+static int send_method_call(const char *dest, const char *path,
+ const char *interface, const char *method,
+ DBusPendingCallNotifyFunction cb,
+ void *user_data, int type, ...)
+{
+ DBusMessage *msg;
+ DBusPendingCall *call;
+ va_list args;
+
+ msg = dbus_message_new_method_call(dest, path, interface, method);
+ if (!msg) {
+ ofono_error("Unable to allocate new D-Bus %s message", method);
+ return -ENOMEM;
+ }
+
+ va_start(args, type);
+
+ if (!dbus_message_append_args_valist(msg, type, args)) {
+ dbus_message_unref(msg);
+ va_end(args);
+ return -EIO;
+ }
+
+ va_end(args);
+
+ if (!cb) {
+ g_dbus_send_message(connection, msg);
+ return 0;
+ }
+
+ if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) {
+ ofono_error("Sending %s failed", method);
+ dbus_message_unref(msg);
+ return -EIO;
+ }
+
+ dbus_pending_call_set_notify(call, cb, user_data, NULL);
+ dbus_pending_call_unref(call);
+ dbus_message_unref(msg);
+
+ return 0;
+}
+
+static gboolean hfp_enable_timeout(gpointer user)
+{
+ struct ofono_modem *modem = user;
+
+ if (ofono_modem_get_powered(modem))
+ return FALSE;
+
+ hfp_disable(modem);
+ return FALSE;
+}
+
static void cind_status_cb(gboolean ok, GAtResult *result,
gpointer user_data)
{
@@ -260,8 +331,7 @@ error:
}
/* either oFono or Phone could request SLC connection */
-static int service_level_connection(struct ofono_modem *modem,
- const char *tty)
+static int service_level_connection(struct ofono_modem *modem, int fd)
{
struct hfp_data *data = ofono_modem_get_data(modem);
GIOChannel *io;
@@ -269,7 +339,7 @@ static int service_level_connection(struct ofono_modem *modem,
GAtChat *chat;
char buf[64];
- io = g_at_tty_open(tty, NULL);
+ io = g_io_channel_unix_new(fd);
if (!io) {
ofono_error("Service level connection failed: %s (%d)",
strerror(errno), errno);
@@ -288,7 +358,6 @@ static int service_level_connection(struct ofono_modem *modem,
g_at_chat_set_debug(chat, hfp_debug, NULL);
sprintf(buf, "AT+BRSF=%d", data->hf_features);
-
g_at_chat_send(chat, buf, brsf_prefix,
brsf_cb, modem, NULL);
data->chat = chat;
@@ -296,10 +365,46 @@ static int service_level_connection(struct ofono_modem *modem,
return -EINPROGRESS;
}
-static int hfp_probe(struct ofono_modem *modem)
+static DBusMessage *hfp_agent_new_connection(DBusConnection *conn, DBusMessage *msg, void *data)
{
+ int fd;
+ struct ofono_modem *modem = data;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID))
+ return __ofono_error_invalid_args(msg);
+
+ service_level_connection(modem, fd);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *hfp_agent_release(DBusConnection *conn, DBusMessage *msg, void *data)
+{
+ struct ofono_modem *modem = data;
+ struct hfp_data *hfp_data = ofono_modem_get_data(modem);
+
+ g_at_chat_shutdown(hfp_data->chat);
+
+ return NULL;
+}
+
+static GDBusMethodTable agent_methods[] = {
+ { "NewConnection", "h", "", hfp_agent_new_connection,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Release", "", "", hfp_agent_release },
+ {NULL, NULL, NULL, NULL}
+};
+
+static int hfp_create_modem(const char *device)
+{
+ struct ofono_modem *modem;
struct hfp_data *data;
+ ofono_info("Using device: %s", device);
+
+ modem = ofono_modem_create(NULL, "hfp");
+
data = g_try_new0(struct hfp_data, 1);
if (!data)
return -ENOMEM;
@@ -310,36 +415,315 @@ static int hfp_probe(struct ofono_modem *modem)
data->hf_features |= HF_FEATURE_ENHANCED_CALL_STATUS;
data->hf_features |= HF_FEATURE_ENHANCED_CALL_CONTROL;
+ data->handsfree_path = g_strdup(device);
+
ofono_modem_set_data(modem, data);
+ ofono_modem_register(modem);
+
+ return 0;
+}
+
+static void parse_uuids(DBusMessageIter *i, const char *device)
+{
+ DBusMessageIter variant, ai;
+ const char *value;
+
+ dbus_message_iter_recurse(i, &variant);
+ dbus_message_iter_recurse(&variant, &ai);
+
+ while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
+ dbus_message_iter_get_basic(&ai, &value);
+ if (!strcasecmp(value, HFP_AG_UUID))
+ hfp_create_modem(device);
+
+ if (!dbus_message_iter_next(&ai))
+ return;
+ }
+}
+
+static void parse_get_properties(DBusMessage *reply, const char *device)
+{
+ DBusMessageIter arg, element, variant;
+ const char *key;
+
+ if (!dbus_message_iter_init(reply, &arg)) {
+ ofono_debug("GetProperties reply has no arguments.");
+ return;
+ }
+
+ if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_ARRAY) {
+ ofono_debug("GetProperties argument is not an array.");
+ return;
+ }
+
+ dbus_message_iter_recurse(&arg, &element);
+ while (dbus_message_iter_get_arg_type(&element) != DBUS_TYPE_INVALID) {
+ if (dbus_message_iter_get_arg_type(&element) ==
+ DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict;
+
+ dbus_message_iter_recurse(&element, &dict);
+
+ if (dbus_message_iter_get_arg_type(&dict) !=
+ DBUS_TYPE_STRING) {
+ ofono_debug("Property name not a string.");
+ return;
+ }
+
+ dbus_message_iter_get_basic(&dict, &key);
+
+ if (!dbus_message_iter_next(&dict)) {
+ ofono_debug("Property value missing");
+ return;
+ }
+
+ if (dbus_message_iter_get_arg_type(&dict) !=
+ DBUS_TYPE_VARIANT) {
+ ofono_debug("Property value not a variant.");
+ return;
+ }
+
+ if (!strcmp(key, "UUIDs"))
+ parse_uuids(&dict, device);
+
+ dbus_message_iter_recurse(&dict, &variant);
+ }
+
+ if (!dbus_message_iter_next(&element))
+ return;
+ }
+}
+
+static void get_properties_cb(DBusPendingCall *call, gpointer user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ const char *device = user_data;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ if (dbus_message_is_error(reply, DBUS_ERROR_SERVICE_UNKNOWN)) {
+ ofono_debug("Bluetooth daemon is apparently not available.");
+ goto done;
+ }
+
+ if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+ if (!dbus_message_is_error(reply, DBUS_ERROR_UNKNOWN_METHOD))
+ ofono_info("Error from GetProperties reply: %s",
+ dbus_message_get_error_name(reply));
+
+ goto done;
+ }
+
+ parse_get_properties(reply, device);
+
+done:
+ dbus_message_unref(reply);
+}
+
+static void list_devices_cb(DBusPendingCall *call, gpointer user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ struct ofono_modem *modem = user_data;
+ const char **device = NULL;
+ int num, ret, i;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ if (dbus_message_is_error(reply, DBUS_ERROR_SERVICE_UNKNOWN)) {
+ ofono_debug("Bluetooth daemon is apparently not available.");
+ goto done;
+ }
+
+ dbus_error_init(&err);
+ if (dbus_message_get_args(reply, &err, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_OBJECT_PATH, &device,
+ &num, DBUS_TYPE_INVALID) == FALSE) {
+ if (device == NULL) {
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ if (dbus_error_is_set(&err) == TRUE) {
+ ofono_error("%s", err.message);
+ dbus_error_free(&err);
+ }
+ goto done;
+ }
+
+ for (i = 0 ; i < num ; i++) {
+ ret = send_method_call(BLUEZ_SERVICE, device[i],
+ BLUEZ_DEVICE_INTERFACE, "GetProperties",
+ get_properties_cb, (void *)device[i],
+ DBUS_TYPE_INVALID);
+ if (ret < 0)
+ ofono_error("GetProperties failed(%d)", ret);
+ }
+
+done:
+ dbus_message_unref(reply);
+}
+
+static void list_adapters_cb(DBusPendingCall *call, gpointer user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ const char **adapter = NULL;
+ int num, ret, i;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ if (dbus_message_is_error(reply, DBUS_ERROR_SERVICE_UNKNOWN)) {
+ ofono_debug("Bluetooth daemon is apparently not available.");
+ goto done;
+ }
+
+ dbus_error_init(&err);
+ if (dbus_message_get_args(reply, &err, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_OBJECT_PATH, &adapter,
+ &num, DBUS_TYPE_INVALID) == FALSE) {
+ if (adapter == NULL) {
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ if (dbus_error_is_set(&err) == TRUE) {
+ ofono_error("%s", err.message);
+ dbus_error_free(&err);
+ }
+ goto done;
+ }
+
+ for (i = 0 ; i < num ; i++) {
+ ret = send_method_call(BLUEZ_SERVICE, adapter[i],
+ BLUEZ_ADAPTER_INTERFACE, "ListDevices",
+ list_devices_cb, NULL,
+ DBUS_TYPE_INVALID);
+
+ if (ret < 0)
+ ofono_error("ListDevices failed(%d)", ret);
+ }
+
+done:
+ dbus_message_unref(reply);
+
+}
+
+static int hfp_load_modems()
+{
+ return send_method_call(BLUEZ_SERVICE, BLUEZ_PATH,
+ BLUEZ_MANAGER_INTERFACE, "ListAdapters",
+ list_adapters_cb, NULL,
+ DBUS_TYPE_INVALID);
+}
+
+static int hfp_register_ofono_handsfree(struct ofono_modem *modem)
+{
+ const char *obj_path = ofono_modem_get_path(modem);
+ struct hfp_data *data = ofono_modem_get_data(modem);
+
+ ofono_debug("Registering oFono Agent to bluetooth daemon");
+
+ if (!data->handsfree_path)
+ return -EINVAL;
+
+ return send_method_call(BLUEZ_SERVICE, data->handsfree_path,
+ BLUEZ_GATEWAY_INTERFACE, "RegisterAgent",
+ NULL, NULL, DBUS_TYPE_OBJECT_PATH,
+ &obj_path, DBUS_TYPE_INVALID);
+}
+
+static int hfp_unregister_ofono_handsfree(struct ofono_modem *modem)
+{
+ const char *obj_path = ofono_modem_get_path(modem);
+ struct hfp_data *data = ofono_modem_get_data(modem);
+
+ ofono_debug("Unregistering oFono Agent from bluetooth daemon");
+
+ if (!data->handsfree_path)
+ return -EINVAL;
+
+ return send_method_call(BLUEZ_SERVICE, data->handsfree_path,
+ BLUEZ_GATEWAY_INTERFACE, "UnregisterAgent",
+ NULL, NULL, DBUS_TYPE_OBJECT_PATH,
+ &obj_path, DBUS_TYPE_INVALID);
+}
+
+static int hfp_probe(struct ofono_modem *modem)
+{
+ struct hfp_data *data;
+ const char *obj_path;
+
+ obj_path = ofono_modem_get_path(modem);
+
+ g_dbus_register_interface(connection, obj_path, HFP_AGENT_INTERFACE,
+ agent_methods, NULL, NULL, modem, NULL);
+
+ if (hfp_register_ofono_handsfree(modem) != 0)
+ return -EINVAL;
+
return 0;
}
static void hfp_remove(struct ofono_modem *modem)
{
- gpointer data = ofono_modem_get_data(modem);
+ struct hfp_data *data = ofono_modem_get_data(modem);
+
+ hfp_unregister_ofono_handsfree(modem);
+
+ if (data->handsfree_path)
+ g_free(data->handsfree_path);
if (data)
g_free(data);
+ dbus_connection_unref(connection);
+
ofono_modem_set_data(modem, NULL);
}
+static int hfp_connect_ofono_handsfree(struct ofono_modem *modem)
+{
+ struct hfp_data *data = ofono_modem_get_data(modem);
+
+ ofono_debug("Connect to bluetooth daemon");
+
+ if (!data->handsfree_path || !connection)
+ return -EINVAL;
+
+ return send_method_call(BLUEZ_SERVICE, data->handsfree_path,
+ BLUEZ_GATEWAY_INTERFACE, "Connect",
+ NULL, NULL, DBUS_TYPE_INVALID);
+}
+
/* power up hardware */
static int hfp_enable(struct ofono_modem *modem)
{
- const char *tty;
- int ret;
+ struct hfp_data *data = ofono_modem_get_data(modem);
DBG("%p", modem);
- tty = ofono_modem_get_string(modem, "Device");
- if (tty == NULL)
+ data->at_timeout =
+ g_timeout_add_seconds(10, hfp_enable_timeout, modem);
+
+ if (hfp_connect_ofono_handsfree(modem) < 0)
return -EINVAL;
- ret = service_level_connection(modem, tty);
+ return -EINPROGRESS;
+}
+
+static int hfp_disconnect_ofono_handsfree(struct ofono_modem *modem)
+{
+ struct hfp_data *data = ofono_modem_get_data(modem);
+
+ if (!data->handsfree_path || !connection)
+ return -EINVAL;
- return ret;
+ return send_method_call(BLUEZ_SERVICE, data->handsfree_path,
+ BLUEZ_GATEWAY_INTERFACE, "Disconnect",
+ NULL, NULL, DBUS_TYPE_INVALID);
}
static int hfp_disable(struct ofono_modem *modem)
@@ -359,6 +743,7 @@ static int hfp_disable(struct ofono_modem *modem)
ofono_modem_set_powered(modem, FALSE);
+ hfp_disconnect_ofono_handsfree(modem);
return 0;
}
@@ -390,8 +775,14 @@ static struct ofono_modem_driver hfp_driver = {
static int hfp_init(void)
{
- DBG("");
- return ofono_modem_driver_register(&hfp_driver);
+ DBusConnection *conn = ofono_dbus_get_connection();
+
+ ofono_modem_driver_register(&hfp_driver);
+
+ connection = dbus_connection_ref(conn);
+ hfp_load_modems();
+
+ return 0;
}
static void hfp_exit(void)
diff --git a/plugins/modemconf.c b/plugins/modemconf.c
index c8c261f..932d610 100644
--- a/plugins/modemconf.c
+++ b/plugins/modemconf.c
@@ -129,7 +129,6 @@ static struct ofono_modem *create_modem(GKeyFile *keyfile, const char *group)
if (!g_strcmp0(driver, "atgen") || !g_strcmp0(driver, "g1") ||
!g_strcmp0(driver, "calypso") ||
- !g_strcmp0(driver, "hfp") ||
!g_strcmp0(driver, "palmpre"))
set_device(modem, keyfile, group);
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* HFP support into BlueZ and oFono
2010-01-20 19:58 [RFC] HFP support into oFono and BlueZ Gustavo F. Padovan
@ 2010-01-27 19:12 ` Gustavo F. Padovan
2010-01-27 19:12 ` [PATCH 1/2] clean up audio/gateway.c Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-01-27 19:12 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Hi All,
New patches for the HFP support. The BlueZ part was updated with the Johan's comments and the oFono patch supports load/remove of bluetooth devices dynamically.
We still have bug: the link RFCOMM on the AG side is closed only when bluetoothd shuts down. Any idea on that?
Comments are welcome.
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 1/2] clean up audio/gateway.c
2010-01-27 19:12 ` HFP support into BlueZ and oFono Gustavo F. Padovan
@ 2010-01-27 19:12 ` Gustavo F. Padovan
2010-01-27 19:12 ` [PATCH 2/2] Implement HandsfreeGateway Interface Gustavo F. Padovan
0 siblings, 1 reply; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-01-27 19:12 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
remove all code related to the AT engine
---
audio/gateway.c | 760 +------------------------------------------------------
1 files changed, 1 insertions(+), 759 deletions(-)
diff --git a/audio/gateway.c b/audio/gateway.c
index a1c1ea2..3dc09ff 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -53,52 +53,6 @@
#include "dbus-common.h"
#define RFCOMM_BUF_SIZE 256
-/* not-more-then-16 defined by GSM + 1 for NULL + padding */
-#define AG_INDICATOR_DESCR_SIZE 20
-#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 */
-/* Releases all held calls or sets User Determined User Busy (UDUB)
- * for a waiting call */
-#define AG_CHLD_0 0x01
-/* Releases all active calls (if any exist) and accepts the other
- * (held or waiting) call */
-#define AG_CHLD_1 0x02
-/* Releases specified active call only <x> */
-#define AG_CHLD_1x 0x04
-/* Places all active calls (if any exist) on hold and accepts the other
- * (held or waiting) call */
-#define AG_CHLD_2 0x08
-/* Request private consultation mode with specified call <x> (Place all
- * calls on hold EXCEPT the call <x>) */
-#define AG_CHLD_2x 0x10
-/* Adds a held call to the conversation */
-#define AG_CHLD_3 0x20
-/* Connects the two calls and disconnects the subscriber from both calls
- * (Explicit Call Transfer). Support for this value and its associated
- * functionality is optional for the HF. */
-#define AG_CHLD_4 0x40
-
-#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 {
gateway_state_t state;
@@ -108,387 +62,10 @@ struct gateway {
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;
- gboolean call_active;
-
- int sp_gain;
- int mic_gain;
};
-static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond,
- struct audio_device *device);
-
int gateway_close(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 (count > 0) {
- status = g_io_channel_write_chars(io, data, count, &written,
- NULL);
- if (status != G_IO_STATUS_NORMAL)
- 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;
- gsize read = 0;
- gboolean got_ok = FALSE;
- gboolean got_error = FALSE;
- gchar *resp_buf = response;
- gsize toread = RFCOMM_BUF_SIZE - 1;
- GIOStatus status;
-
- if (!io_channel_write_all(rfcomm, data, count))
- return FALSE;
-
- while (!(got_ok || got_error)) {
- status = g_io_channel_read_chars(rfcomm, resp_buf, toread,
- &read, NULL);
- if (status == G_IO_STATUS_NORMAL)
- 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>))
- * ... */
-static GSList *parse_indicator_names(gchar *names, GSList *indies)
-{
- gchar *current = names - 1;
- GSList *result = indies;
- gchar *next;
- struct indicator *ind;
-
- while (current != NULL) {
- current += 2;
- next = strstr(current, ",(");
- ind = g_slice_new(struct indicator);
- strncpy(ind->descr, current, 20);
- ind->descr[(intptr_t) next - (intptr_t) current] = '\0';
- result = g_slist_append(result, (gpointer) ind);
- current = strstr(next + 1, ",(");
- }
- return result;
-}
-
-/* get values from <val0>,<val1>,... */
-static GSList *parse_indicator_values(gchar *values, GSList *indies)
-{
- gint val;
- gchar *current = values - 1;
- GSList *runner = indies;
- struct indicator *ind;
-
- while (current != NULL) {
- current += 1;
- sscanf(current, "%d", &val);
- current = strchr(current, ',');
- ind = 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 (strstr(features, "0"))
- result |= AG_CHLD_0;
-
- if (strstr(features, "1"))
- result |= AG_CHLD_1;
-
- if (strstr(features, "1x"))
- result |= AG_CHLD_1x;
-
- if (strstr(features, "2"))
- result |= AG_CHLD_2;
-
- if (strstr(features, "2x"))
- result |= AG_CHLD_2x;
-
- if (strstr(features, "3"))
- result |= AG_CHLD_3;
-
- if (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;
-
- debug("at the begin of establish_service_level_conn()");
- res = rfcomm_send_and_read(gw, AG_FEATURES, buf,
- sizeof(AG_FEATURES) - 1);
- if (!res || sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features) != 1)
- 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 ((gw->ag_features & AG_FEATURE_3WAY) != 0) {
- 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 ((gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) != 0)
- 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, guint index,
- gint value)
-{
- struct gateway *gw = dev->gateway;
- struct indicator *ind = g_slist_nth_data(gw->indies, index - 1);
- gchar *name = ind->descr;
-
- ind->value = value;
-
- debug("at the begin of process_ind_change, name is %s", name);
- if (!strcmp(name, "\"call\"")) {
- if (value > 0) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallStarted", DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- gw->call_active = TRUE;
- } else {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallEnded", DBUS_TYPE_INVALID);
- gw->call_active = FALSE;
- }
-
- } else if (!strcmp(name, "\"callsetup\"")) {
- if (value == 0 && gw->is_dialing) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallTerminated",
- DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- } else if (!gw->is_dialing && value > 0)
- gw->is_dialing = TRUE;
-
- } else if (!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
- */
- /* '2' means: all calls held, no active calls */
- if (value == 2) {
- if (gw->is_dialing) {
- g_dbus_emit_signal(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "CallTerminated",
- DBUS_TYPE_INVALID);
- gw->is_dialing = FALSE;
- }
- }
- } else if (!strcmp(name, "\"service\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "RegistrationStatus",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"signal\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "SignalStrength",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"roam\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "RoamingStatus",
- DBUS_TYPE_UINT16, &value);
- else if (!strcmp(name, "\"battchg\""))
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE, "BatteryCharge",
- DBUS_TYPE_UINT16, &value);
-}
-
-static void process_ring(struct audio_device *device, GIOChannel *chan,
- gchar *buf)
-{
- gchar number[AG_CALLER_NUM_SIZE];
- gchar *cli;
- gchar *sep;
- gsize read;
- GIOStatus status;
-
- rfcomm_stop_watch(device);
- status = g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read, NULL);
- if (status != G_IO_STATUS_NORMAL)
- return;
-
- debug("at the begin of process_ring");
- if (strlen(buf) > AG_CALLER_NUM_SIZE + 10)
- error("process_ring(): buf is too long '%s'", buf);
- else if ((cli = strstr(buf, "\r\n+CLIP"))) {
- if (sscanf(cli, "\r\n+CLIP: \"%s", number) == 1) {
- sep = strchr(number, '"');
- sep[0] = '\0';
-
- /* FIXME:signal will be emitted on each RING+CLIP.
- * That's bad */
- cli = number;
- g_dbus_emit_signal(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "Ring",
- DBUS_TYPE_STRING, &cli,
- DBUS_TYPE_INVALID);
- device->gateway->is_dialing = TRUE;
- } else
- error("process_ring(): '%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;
- gsize read;
- /* some space for value */
- gchar indicator[AG_INDICATOR_DESCR_SIZE + 4];
- gint value;
- guint index;
- gchar *sep;
-
- debug("at the begin of rfcomm_ag_data_cb()");
- if (cond & G_IO_NVAL)
- return FALSE;
-
- gw = device->gateway;
-
- if (cond & (G_IO_ERR | G_IO_HUP)) {
- debug("connection with remote BT is closed");
- gateway_close(device);
- return FALSE;
- }
-
- if (g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read, NULL)
- != G_IO_STATUS_NORMAL)
- return TRUE;
- buf[read] = '\0';
-
- if (strlen(buf) > AG_INDICATOR_DESCR_SIZE + 14)
- error("rfcomm_ag_data_cb(): buf is too long '%s'", buf);
- else if (sscanf(buf, "\r\n+CIEV:%s\r\n", indicator) == 1) {
- sep = strchr(indicator, ',');
- sep[0] = '\0';
- sep += 1;
- index = atoi(indicator);
- value = atoi(sep);
- process_ind_change(device, index, value);
- } else if (strstr(buf, "RING"))
- process_ring(device, chan, buf);
- else if (sscanf(buf, "\r\n+BVRA:%d\r\n", &value) == 1) {
- if (value == 0)
- 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 (sscanf(buf, "\r\n+VGS:%d\r\n", &value) == 1) {
- gw->sp_gain = value;
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "SpeakerGain",
- DBUS_TYPE_UINT16, &value);
- } else if (sscanf(buf, "\r\n+VGM:%d\r\n", &value) == 1) {
- gw->mic_gain = value;
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE, "MicrophoneGain",
- DBUS_TYPE_UINT16, &value);
- } 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)
{
@@ -541,7 +118,6 @@ static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- DBusMessage *conn_mes = gw->connect_message;
gchar gw_addr[18];
GIOFlags flags;
@@ -563,29 +139,6 @@ static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
if (!gw->rfcomm)
gw->rfcomm = g_io_channel_ref(chan);
- if (establish_service_level_conn(dev->gateway)) {
- gboolean value = TRUE;
-
- debug("%s: Connected to %s", dev->path, gw_addr);
- rfcomm_start_watch(dev);
- if (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;
- }
-
- gw->state = GATEWAY_STATE_CONNECTED;
- emit_property_changed(dev->conn, dev->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return;
- } 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);
@@ -732,321 +285,20 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
return reply;
}
-static DBusMessage *process_ag_reponse(DBusMessage *msg, gchar *response)
-{
- DBusMessage *reply;
-
-
- debug("in process_ag_reponse, response is %s", response);
- if (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)
-{
- struct audio_device *dev = data;
- struct gateway *gw = dev->gateway;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- if (gw->call_active)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".CallAlreadyAnswered",
- "Call AlreadyAnswered");
-
- return process_simple(msg, dev, AG_ANSWER);
-}
-
-#define AG_HANGUP "AT+CHUP\r"
-
-static DBusMessage *ag_terminate_call(DBusConnection *conn, DBusMessage *msg,
- void *data)
-{
- struct audio_device *dev = data;
- struct gateway *gw = dev->gateway;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- return process_simple(msg, dev, AG_HANGUP);
-}
-
-/* according to GSM spec */
-#define ALLOWED_NUMBER_SYMBOLS "1234567890*#ABCD"
-#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;
- DBusMessage *result;
-
- debug("at the begin of ag_call()");
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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);
-
- result = process_ag_reponse(msg, buf);
- return result;
-}
-
-#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, *sep;
- DBusMessage *reply;
- GIOStatus status;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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 (strstr(buf, "+COPS")) {
- if (!strrchr(buf, ','))
- result = "0";
- else {
- result = strchr(buf, '\"') + 1;
- sep = strchr(result, '\"');
- sep[0] = '\0';
- }
-
- 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_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;
- gboolean got_ok = TRUE;
- gint num_len;
- gint i = 0;
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- 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");
-
- num_len = strlen(number);
- 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_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
-#define CNUM_LEN 5 /* length of "+CNUM" string */
-#define MAX_NUMBER_CNT 16
-static DBusMessage *ag_get_subscriber_num(DBusConnection *conn,
- DBusMessage *msg, void *data)
-{
- struct audio_device *device = data;
- struct gateway *gw = device->gateway;
- gchar buf[RFCOMM_BUF_SIZE];
- gchar *number, *end;
- DBusMessage *reply = dbus_message_new_method_return(msg);
-
- if (!gw->rfcomm)
- return g_dbus_create_error(msg, ERROR_INTERFACE
- ".NotConnected",
- "Not Connected");
-
- rfcomm_stop_watch(device);
- rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf,
- strlen(AG_GET_SUBSCRIBER_NUMS));
- rfcomm_start_watch(device);
-
- if (strlen(buf) > AG_CALLER_NUM_SIZE + 21)
- error("ag_get_subscriber_num(): buf is too long '%s'", buf);
- else if (strstr(buf, "+CNUM")) {
- number = strchr(buf, ',');
- number++;
- end = strchr(number, ',');
- if (end) {
- *end = '\0';
- dbus_message_append_args(reply, DBUS_TYPE_STRING,
- &number, DBUS_TYPE_INVALID);
- }
- } else
- error("ag_get_subscriber_num(): read wrong data '%s'", buf);
-
- return reply;
-}
-
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- struct audio_device *device = data;
- struct gateway *gw = device->gateway;
- DBusMessage *reply;
- DBusMessageIter iter;
- DBusMessageIter dict;
- gboolean value;
- guint index = 0;
- struct indicator *ind;
-
- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;
-
- dbus_message_iter_init_append(reply, &iter);
-
- dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
- DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
- DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
- DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
-
- /* Connected */
- value = gateway_is_connected(device);
- dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
-
- if (!value)
- goto done;
-
- while ((ind = g_slist_nth_data(gw->indies, index))) {
- if(!strcmp(ind->descr, "\"service\""))
- dict_append_entry(&dict, "RegistrationStatus",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"signal\""))
- dict_append_entry(&dict, "SignalStrength",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"roam\""))
- dict_append_entry(&dict, "RoamingStatus",
- DBUS_TYPE_UINT16, &ind->value);
- else if (!strcmp(ind->descr, "\"battchg\""))
- dict_append_entry(&dict, "BatteryCharge",
- DBUS_TYPE_UINT16, &ind->value);
- index++;
- }
-
- /* SpeakerGain */
- dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16,
- &device->gateway->sp_gain);
-
- /* MicrophoneGain */
- dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16,
- &device->gateway->mic_gain);
-done:
- dbus_message_iter_close_container(&iter, &dict);
- return reply;
+ return NULL;
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
{ "Disconnect", "", "", ag_disconnect },
- { "AnswerCall", "", "", ag_answer },
- { "TerminateCall", "", "", ag_terminate_call },
- { "Call", "s", "", ag_call },
- { "GetOperatorName", "", "s", ag_get_operator },
- { "SendDTMF", "s", "", ag_send_dtmf },
- { "GetSubscriberNumber", "", "s", ag_get_subscriber_num },
{ "GetProperties", "", "a{sv}", ag_get_properties },
{ NULL, NULL, NULL, NULL }
};
static GDBusSignalTable gateway_signals[] = {
- { "Ring", "s" },
- { "CallTerminated", "" },
- { "CallStarted", "" },
- { "CallEnded", "" },
{ "PropertyChanged", "sv" },
{ NULL, NULL }
};
@@ -1063,9 +315,6 @@ struct gateway *gateway_init(struct audio_device *dev)
debug("in gateway_init, dev is %p", dev);
gw = g_new0(struct gateway, 1);
- gw->indies = NULL;
- gw->is_dialing = FALSE;
- gw->call_active = FALSE;
gw->state = GATEWAY_STATE_DISCONNECTED;
return gw;
@@ -1107,11 +356,6 @@ void gateway_start_service(struct audio_device *device)
rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
}
-static void indicator_slice_free(gpointer mem)
-{
- g_slice_free(struct indicator, mem);
-}
-
int gateway_close(struct audio_device *device)
{
struct gateway *gw = device->gateway;
@@ -1119,8 +363,6 @@ int gateway_close(struct audio_device *device)
GIOChannel *sco = gw->sco;
gboolean value = FALSE;
- g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL);
- g_slist_free(gw->indies);
if (rfcomm) {
g_io_channel_shutdown(rfcomm, TRUE, NULL);
g_io_channel_unref(rfcomm);
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH 2/2] Implement HandsfreeGateway Interface
2010-01-27 19:12 ` [PATCH 1/2] clean up audio/gateway.c Gustavo F. Padovan
@ 2010-01-27 19:12 ` Gustavo F. Padovan
0 siblings, 0 replies; 14+ messages in thread
From: Gustavo F. Padovan @ 2010-01-27 19:12 UTC (permalink / raw)
To: linux-bluetooth; +Cc: ofono
Create a interface where a Handsfree agent can register and use BlueZ to
handle the rfcomm and sco links.
Many thanks to Zhenhua Zhang <zhenhua.zhang@intel.com> for his
prototype on this code.
---
audio/gateway.c | 430 +++++++++++++++++++++++++++++++++++++++++++------------
audio/gateway.h | 12 ++-
audio/manager.c | 23 +--
audio/unix.c | 8 +
doc/hfp-api.txt | 82 +++++++++++
5 files changed, 443 insertions(+), 112 deletions(-)
create mode 100644 doc/hfp-api.txt
diff --git a/audio/gateway.c b/audio/gateway.c
index 3dc09ff..9017074 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -5,6 +5,8 @@
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
+ * Copyright (C) 2010 Gustavo F. Padovan <padovan@profusion.mobi>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -32,12 +34,15 @@
#include <string.h>
#include <fcntl.h>
#include <errno.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>
#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sco.h>
@@ -52,20 +57,108 @@
#include "btio.h"
#include "dbus-common.h"
-#define RFCOMM_BUF_SIZE 256
+#define MAX_OPEN_TRIES 5
+#define OPEN_WAIT 300
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
struct gateway {
gateway_state_t state;
- GIOChannel *rfcomm;
- guint rfcomm_watch_id;
+ int channel;
+ GIOChannel *rfcomm; /* remote AG requested rfcomm connection */
GIOChannel *sco;
gateway_stream_cb_t sco_start_cb;
void *sco_start_cb_data;
- DBusMessage *connect_message;
+ struct hf_agent *agent;
+ DBusMessage *msg;
};
int gateway_close(struct audio_device *device);
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val = state2str(new_state);
+
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(dev->conn, msg);
+
+ g_dbus_remove_watch(dev->conn, agent->watch);
+
+ agent_free(agent);
+ dev->gateway->agent = NULL;
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *dev)
{
@@ -79,6 +172,7 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_shutdown(gw->sco, TRUE, NULL);
g_io_channel_unref(gw->sco);
gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
return FALSE;
}
@@ -92,63 +186,91 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
debug("at the begin of sco_connect_cb() in gateway.c");
+ gw->sco = g_io_channel_ref(chan);
+
if (err) {
error("sco_connect_cb(): %s", err->message);
- /* not sure, but from other point of view,
- * what is the reason to have headset which
- * cannot play audio? */
- if (gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
gateway_close(dev);
return;
}
- gw->sco = g_io_channel_ref(chan);
if (gw->sco_start_cb)
gw->sco_start_cb(dev, gw->sco_start_cb_data);
- /* why is this here? */
- fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) sco_io_cb, dev);
}
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ debug("RFCOMM disconnected from server before agent reply");
+ return;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ debug("Agent reply: file descriptor passed successfuly");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ return;
+ }
+
+ debug("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+}
+
static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
gpointer user_data)
{
struct audio_device *dev = user_data;
struct gateway *gw = dev->gateway;
- gchar gw_addr[18];
- GIOFlags flags;
+ DBusMessage *reply;
+ int sk;
+ char src[18], dst[18];
if (err) {
error("connect(): %s", err->message);
if (gw->sco_start_cb)
gw->sco_start_cb(NULL, gw->sco_start_cb_data);
- return;
+ goto fail;
}
- ba2str(&dev->dst, gw_addr);
- /* Blocking mode should be default, but just in case: */
- 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);
- if (!gw->rfcomm)
- gw->rfcomm = g_io_channel_ref(chan);
+ if (!gw->agent)
+ error("Handfree Agent not registered");
- if (NULL != gw->sco_start_cb)
- gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+ ba2str(&dev->src, src);
+ ba2str(&dev->dst, dst);
- gateway_close(dev);
+ sk = g_io_channel_unix_get_fd(chan);
+
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ if (!agent_sendfd(gw->agent, sk, newconnection_reply, dev)) {
+ reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
+ "Can not pass file descriptor");
+ } else
+ reply = dbus_message_new_method_return(gw->msg);
+
+ return;
+
+fail:
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg, ERROR_INTERFACE
+ ".Failed", ".Connection attempt failed");
+ }
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
}
static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
{
struct audio_device *dev = user_data;
- DBusMessage *msg = dev->gateway->connect_message;
+ struct gateway *gw = dev->gateway;
int ch = -1;
sdp_list_t *protos, *classes;
uuid_t uuid;
@@ -182,8 +304,6 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
- sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
- NULL);
sdp_list_free(protos, NULL);
error("Invalid service record or not HFP");
goto fail;
@@ -197,6 +317,8 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
goto fail;
}
+ gw->channel = ch;
+
io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -204,25 +326,24 @@ static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
BT_IO_OPT_INVALID);
if (!io) {
error("Unable to connect: %s", err->message);
- if (msg) {
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".ConnectionAttemptFailed",
- err->message);
- msg = NULL;
- }
- g_error_free(err);
+ if (err)
+ g_error_free(err);
gateway_close(dev);
+ goto fail;
}
g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
return;
fail:
- if (msg)
- error_common_reply(dev->conn, msg, ERROR_INTERFACE
- ".NotSupported", "Not supported");
+ if (gw->msg) {
+ error_common_reply(dev->conn, gw->msg, ERROR_INTERFACE
+ ".NotSupported", "Not supported");
+ }
- dev->gateway->connect_message = NULL;
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
sco_cb = dev->gateway->sco_start_cb;
if (sco_cb)
@@ -243,22 +364,60 @@ static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
{
struct audio_device *au_dev = (struct audio_device *) data;
struct gateway *gw = au_dev->gateway;
+ DBusMessage *reply;
debug("at the begin of ag_connect()");
- if (gw->rfcomm)
+
+ if (!gw->agent)
return g_dbus_create_error(msg, ERROR_INTERFACE
- ".AlreadyConnected",
- "Already Connected");
+ ".Failed", "Agent not assined");
- 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");
}
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
debug("at the end of ag_connect()");
- return NULL;
+
+ return reply;
+}
+
+void gateway_exit(struct audio_device *dev)
+{
+ agent_disconnect(dev, dev->gateway->agent);
+}
+
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ gboolean value = FALSE;
+
+ if (gw->rfcomm) {
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ gw->state = GATEWAY_STATE_DISCONNECTED;
+
+ emit_property_changed(device->conn, device->path,
+ AUDIO_GATEWAY_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ return 0;
}
static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
@@ -269,6 +428,9 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
DBusMessage *reply = NULL;
char gw_addr[18];
+ if (!device->conn)
+ return NULL;
+
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
@@ -279,22 +441,119 @@ static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
"Device not Connected");
gateway_close(device);
+
ba2str(&device->dst, gw_addr);
debug("Disconnected from %s, %s", gw_addr, device->path);
return reply;
}
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ debug("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- return NULL;
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "Connected",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+
+ if (gw->agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".InvalidArguments", "Invalid argument");
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ gw->msg = dbus_message_ref(msg);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+
+ if (!gw->agent)
+ goto done;
+
+ agent = gw->agent;
+ if (strcmp(agent->name, dbus_message_get_sender(msg)) != 0)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed", "Permission denied");
+
+ agent_disconnect(device, gw->agent);
+
+ dbus_message_unref(gw->msg);
+
+done:
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable gateway_methods[] = {
{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
- { "Disconnect", "", "", ag_disconnect },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
{ "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
{ NULL, NULL, NULL, NULL }
};
@@ -305,18 +564,13 @@ static GDBusSignalTable gateway_signals[] = {
struct gateway *gateway_init(struct audio_device *dev)
{
- struct gateway *gw;
-
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_GATEWAY_INTERFACE,
gateway_methods, gateway_signals,
NULL, dev, NULL))
return NULL;
- debug("in gateway_init, dev is %p", dev);
- gw = g_new0(struct gateway, 1);
- gw->state = GATEWAY_STATE_DISCONNECTED;
- return gw;
+ return g_new0(struct gateway, 1);
}
@@ -326,17 +580,6 @@ gboolean gateway_is_connected(struct audio_device *dev)
dev->gateway->state == GATEWAY_STATE_CONNECTED);
}
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
-{
- if (!io)
- return -EINVAL;
-
- g_io_channel_ref(io);
- dev->gateway->rfcomm = io;
-
- return 0;
-}
-
int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
{
struct gateway *gw = dev->gateway;
@@ -347,42 +590,38 @@ int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
gw->sco = g_io_channel_ref(io);
g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) sco_io_cb, dev);
+ (GIOFunc) sco_io_cb, dev);
+
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
return 0;
}
-void gateway_start_service(struct audio_device *device)
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch)
{
- rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+ if (!chan)
+ return -EINVAL;
+
+ dev->gateway->rfcomm = g_io_channel_ref(chan);
+ dev->gateway->channel = ch;
+
+ return 0;
}
-int gateway_close(struct audio_device *device)
+void gateway_start_service(struct audio_device *dev)
{
- struct gateway *gw = device->gateway;
- GIOChannel *rfcomm = gw->rfcomm;
- GIOChannel *sco = gw->sco;
- gboolean value = FALSE;
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
- if (rfcomm) {
- g_io_channel_shutdown(rfcomm, TRUE, NULL);
- g_io_channel_unref(rfcomm);
- gw->rfcomm = NULL;
- }
+ if (gw->rfcomm == NULL)
+ return;
- if (sco) {
- g_io_channel_shutdown(sco, TRUE, NULL);
- g_io_channel_unref(sco);
- gw->sco = NULL;
- gw->sco_start_cb = NULL;
- gw->sco_start_cb_data = NULL;
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL,
+ &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
}
-
- gw->state = GATEWAY_STATE_DISCONNECTED;
-
- emit_property_changed(device->conn, device->path,
- AUDIO_GATEWAY_INTERFACE,
- "Connected", DBUS_TYPE_BOOLEAN, &value);
- return 0;
}
/* These are functions to be called from unix.c for audio system
@@ -399,8 +638,12 @@ gboolean gateway_request_stream(struct audio_device *dev,
gw->sco_start_cb_data = user_data;
get_records(dev);
} else if (!gw->sco) {
+ char source[128], destination[128];
+
gw->sco_start_cb = cb;
gw->sco_start_cb_data = user_data;
+ ba2str(&dev->src, source);
+ ba2str(&dev->dst, destination);
io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &dev->src,
BT_IO_OPT_DEST_BDADDR, &dev->dst,
@@ -464,4 +707,3 @@ void gateway_suspend_stream(struct audio_device *dev)
gw->sco_start_cb = NULL;
gw->sco_start_cb_data = NULL;
}
-
diff --git a/audio/gateway.h b/audio/gateway.h
index 3b0457f..bf17a4f 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -4,6 +4,7 @@
*
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com>
*
*
* This program is free software; you can redistribute it and/or modify
@@ -22,20 +23,25 @@
*
*/
-#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
#define DEFAULT_HSP_HS_CHANNEL 6
#define DEFAULT_HFP_HS_CHANNEL 7
typedef enum {
GATEWAY_STATE_DISCONNECTED,
- GATEWAY_STATE_CONNECTED
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_PLAYING,
} gateway_state_t;
typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+
struct gateway *gateway_init(struct audio_device *device);
+void gateway_exit(struct audio_device *device);
gboolean gateway_is_connected(struct audio_device *dev);
-int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_rfcomm(struct audio_device *dev,
+ GIOChannel *chan, int ch);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
gboolean gateway_request_stream(struct audio_device *dev,
diff --git a/audio/manager.c b/audio/manager.c
index 413c1f3..8c854fc 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -198,7 +198,7 @@ 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)
+ if (enabled.gateway && (device->gateway == NULL))
device->gateway = gateway_init(device);
break;
case AUDIO_SINK_SVCLASS_ID:
@@ -567,8 +567,8 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
return;
}
- server_uuid = HFP_HS_UUID;
- remote_uuid = HFP_AG_UUID;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
svclass = HANDSFREE_AGW_SVCLASS_ID;
device = manager_get_device(&src, &dst, TRUE);
@@ -586,7 +586,7 @@ static void hf_io_cb(GIOChannel *chan, gpointer data)
goto drop;
}
- if (gateway_connect_rfcomm(device, chan) < 0) {
+ if (gateway_connect_rfcomm(device, chan, ch) < 0) {
error("Allocating new GIOChannel failed!");
goto drop;
}
@@ -791,6 +791,9 @@ static void audio_remove(struct btd_device *device)
if (!dev)
return;
+ if (dev->gateway)
+ gateway_exit(dev);
+
devices = g_slist_remove(devices, dev);
audio_device_unregister(dev);
@@ -905,22 +908,12 @@ static void headset_server_remove(struct btd_adapter *adapter)
static int gateway_server_probe(struct btd_adapter *adapter)
{
struct audio_adapter *adp;
- const gchar *path = adapter_get_path(adapter);
- int ret;
-
- DBG("path %s", path);
adp = audio_adapter_get(adapter);
if (!adp)
return -EINVAL;
- ret = gateway_server_init(adp);
- if (ret < 0) {
- audio_adapter_ref(adp);
- return ret;
- }
-
- return 0;
+ return gateway_server_init(adp);
}
static void gateway_server_remove(struct btd_adapter *adapter)
diff --git a/audio/unix.c b/audio/unix.c
index 5cf4f94..bd1a415 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -395,6 +395,9 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
struct bt_start_stream_rsp *rsp = (void *) buf;
struct bt_new_stream_ind *ind = (void *) buf;
+ if (!dev)
+ goto failed;
+
memset(buf, 0, sizeof(buf));
rsp->h.type = BT_RESPONSE;
rsp->h.name = BT_START_STREAM;
@@ -416,6 +419,11 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data)
}
client->req_id = 0;
+ return;
+
+failed:
+ error("gateway_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
}
static void headset_suspend_complete(struct audio_device *dev, void *user_data)
diff --git a/doc/hfp-api.txt b/doc/hfp-api.txt
new file mode 100644
index 0000000..8180de0
--- /dev/null
+++ b/doc/hfp-api.txt
@@ -0,0 +1,82 @@
+Gateway hierarchy
+========================
+
+Service org.bluez
+Interface org.bluez.HandsfreeGateway
+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. It is intended to be used with external
+telephony stacks / handlers of the HFP protocol.
+
+Methods void Connect()
+
+ Connect to the AG service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the AG service on the remote device
+
+ dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void RegisterAgent(object path)
+
+ The object path defines the path the of the agent
+ that will be called when a new Handsfree connection
+ is established.
+
+ If an application disconnects from the bus all of its
+ registered agents will be removed.
+
+ void UnregisterAgent(object path)
+
+ This unregisters the agent that has been previously
+ registered. The object path parameter must match the
+ same value that has been used on registration.
+
+Signals PropertyChanged(string name, variant value)
+
+ This signal indicates a changed value of the given
+ property.
+
+Properties string State [readonly]
+
+ Indicates the state of the connection. Possible
+ values are:
+ "disconnected"
+ "connecting"
+ "connected"
+ "playing"
+
+HandsfreeAgent hierarchy
+===============
+
+Service unique name
+Interface org.bluez.HandsfreeAgent
+Object path freely definable
+
+Methods void NewConnection(filedescriptor fd)
+
+ This method gets called whenever a new handsfree
+ connection has been established. The objectpath
+ contains the object path of the remote device. This
+ method assumes that DBus daemon with file descriptor
+ passing capability is being used.
+
+ The agent should only return successfully once the
+ establishment of the service level connection (SLC)
+ has been completed. In the case of Handsfree this
+ means that BRSF exchange has been performed and
+ necessary initialization has been done.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void Release()
+
+ This method gets called whenever the service daemon
+ unregisters the agent or whenever the Adapter where
+ the HandsfreeAgent registers itself is removed.
--
1.6.4.4
^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2010-02-04 19:02 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-02-02 21:41 [PATCH 1/2] clean up audio/gateway.c Gustavo F. Padovan
2010-02-02 21:41 ` [PATCH 2/2] Implement HandsfreeGateway Interface Gustavo F. Padovan
2010-02-02 22:47 ` [PATCH] " Gustavo F. Padovan
2010-02-03 18:26 ` Gustavo F. Padovan
2010-02-03 19:30 ` Gustavo F. Padovan
2010-02-04 1:36 ` Gustavo F. Padovan
2010-02-04 17:11 ` Gustavo F. Padovan
2010-02-04 18:18 ` Gustavo F. Padovan
2010-02-04 18:48 ` Gustavo F. Padovan
2010-02-04 19:02 ` Johan Hedberg
-- strict thread matches above, loose matches on Subject: below --
2010-01-21 20:31 [PATCH 1/2] clean up audio/gateway.c Gustavo F. Padovan
2010-01-21 20:31 ` [PATCH 2/2] Implement HandsfreeGateway Interface Gustavo F. Padovan
2010-01-21 21:20 ` Vinicius Gomes
2010-01-21 21:25 ` Gustavo F. Padovan
2010-01-20 19:58 [RFC] HFP support into oFono and BlueZ Gustavo F. Padovan
2010-01-27 19:12 ` HFP support into BlueZ and oFono Gustavo F. Padovan
2010-01-27 19:12 ` [PATCH 1/2] clean up audio/gateway.c Gustavo F. Padovan
2010-01-27 19:12 ` [PATCH 2/2] Implement HandsfreeGateway Interface Gustavo F. Padovan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).