2011-10-20 13:52:14

by Frederic Danis

[permalink] [raw]
Subject: [PATCH 0/4] Add new dbus telephony driver

This patch add a new telephony driver allowing HSP/HFP to be managed
through an external DBus agent.
BlueZ takes care of SDP and RFComm parts while the upper layer is
processed by the agent.

Current AT parser and dbus code which are only needed by current
drivers (built-in drivers) are move to telephony-builtin.[ch].

Frédéric Danis (4):
Audio: move at parsing code to telephony-builtin.[ch] files
Audio: move dbus callbacks to telephony-builtin.c
Audio: add adapter to telephony_init/_exit
Audio: add dbus telephony driver

Makefile.am | 9 +-
acinclude.m4 | 1 +
audio/headset.c | 1485 +++----------------------------------------
audio/headset.h | 4 +
audio/manager.c | 4 +-
audio/telephony-builtin.c | 1570 +++++++++++++++++++++++++++++++++++++++++++++
audio/telephony-builtin.h | 218 +++++++
audio/telephony-dbus.c | 554 ++++++++++++++++
audio/telephony-dummy.c | 5 +-
audio/telephony-maemo5.c | 5 +-
audio/telephony-maemo6.c | 5 +-
audio/telephony-ofono.c | 5 +-
audio/telephony.h | 225 +------
doc/audio-api.txt | 99 +++
14 files changed, 2590 insertions(+), 1599 deletions(-)
create mode 100644 audio/telephony-builtin.c
create mode 100644 audio/telephony-builtin.h
create mode 100644 audio/telephony-dbus.c



2011-10-20 13:52:15

by Frederic Danis

[permalink] [raw]
Subject: [PATCH 1/4] Audio: move at parsing code to telephony-builtin.[ch] files

---
Makefile.am | 7 +-
acinclude.m4 | 1 +
audio/headset.c | 1306 +++------------------------------------------
audio/headset.h | 4 +
audio/telephony-builtin.c | 1287 ++++++++++++++++++++++++++++++++++++++++++++
audio/telephony-builtin.h | 218 ++++++++
audio/telephony-dummy.c | 1 +
audio/telephony-maemo5.c | 1 +
audio/telephony-maemo6.c | 1 +
audio/telephony-ofono.c | 1 +
audio/telephony.h | 207 +-------
11 files changed, 1615 insertions(+), 1419 deletions(-)
create mode 100644 audio/telephony-builtin.c
create mode 100644 audio/telephony-builtin.h

diff --git a/Makefile.am b/Makefile.am
index 138ab32..d528c84 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -157,6 +157,10 @@ builtin_sources += audio/main.c \
audio/telephony.h audio/a2dp-codecs.h
builtin_nodist += audio/telephony.c

+if TELEPHONYBUILTIN
+builtin_sources += audio/telephony-builtin.h audio/telephony-builtin.c
+endif
+
noinst_LIBRARIES += audio/libtelephony.a

audio_libtelephony_a_SOURCES = audio/telephony.h audio/telephony-dummy.c \
@@ -340,7 +344,8 @@ EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
audio/audio.conf audio/telephony-dummy.c \
audio/telephony-maemo5.c audio/telephony-ofono.c \
audio/telephony-maemo6.c sap/sap-dummy.c sap/sap-u8500.c \
- proximity/proximity.conf
+ proximity/proximity.conf audio/telephony-builtin.h \
+ audio/telephony-builtin.c

if ALSA
alsadir = $(libdir)/alsa-lib
diff --git a/acinclude.m4 b/acinclude.m4
index 2097d77..f3f5200 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -433,4 +433,5 @@ AC_DEFUN([AC_ARG_BLUEZ], [
AM_CONDITIONAL(DBUSOOBPLUGIN, test "${dbusoob_enable}" = "yes")
AM_CONDITIONAL(WIIMOTEPLUGIN, test "${wiimote_enable}" = "yes")
AM_CONDITIONAL(THERMOMETERPLUGIN, test "${thermometer_enable}" = "yes")
+ AM_CONDITIONAL(TELEPHONYBUILTIN, test "${telephony_driver}" = "dummy" || test "${telephony_driver}" = "maemo5" || test "${telephony_driver}" = "maemo6" || test "${telephony_driver}" = "ofono")
])
diff --git a/audio/headset.c b/audio/headset.c
index 6aef6a8..8bec0ed 100644
--- a/audio/headset.c
+++ b/audio/headset.c
@@ -59,42 +59,11 @@
#include "dbus-common.h"
#include "../src/adapter.h"
#include "../src/device.h"
+#include "telephony-builtin.h"

#define DC_TIMEOUT 3

-#define RING_INTERVAL 3
-
-#define BUF_SIZE 1024
-
-#define HEADSET_GAIN_SPEAKER 'S'
-#define HEADSET_GAIN_MICROPHONE 'M'
-
-static struct {
- gboolean telephony_ready; /* Telephony plugin initialized */
- uint32_t features; /* HFP AG features */
- const struct indicator *indicators; /* Available HFP indicators */
- int er_mode; /* Event reporting mode */
- int er_ind; /* Event reporting for indicators */
- int rh; /* Response and Hold state */
- char *number; /* Incoming phone number */
- int number_type; /* Incoming number type */
- guint ring_timer; /* For incoming call indication */
- const char *chld; /* Response to AT+CHLD=? */
-} ag = {
- .telephony_ready = FALSE,
- .features = 0,
- .er_mode = 3,
- .er_ind = 0,
- .rh = BTRH_NOT_SUPPORTED,
- .number = NULL,
- .number_type = 0,
- .ring_timer = 0,
-};
-
static gboolean sco_hci = TRUE;
-static gboolean fast_connectable = FALSE;
-
-static GSList *active_devices = NULL;

static char *str_state[] = {
"HEADSET_STATE_DISCONNECTED",
@@ -132,25 +101,6 @@ struct pending_connect {
uint16_t svclass;
};

-struct headset_slc {
- char buf[BUF_SIZE];
- int data_start;
- int data_length;
-
- gboolean cli_active;
- gboolean cme_enabled;
- gboolean cwa_enabled;
- gboolean pending_ring;
- gboolean inband_ring;
- gboolean nrec;
- gboolean nrec_req;
-
- int sp_gain;
- int mic_gain;
-
- unsigned int hf_features;
-};
-
struct headset {
uint32_t hsp_handle;
uint32_t hfp_handle;
@@ -174,15 +124,10 @@ struct headset {
struct pending_connect *pending;

headset_lock_t lock;
- struct headset_slc *slc;
+ void *slc;
GSList *nrec_cbs;
};

-struct event {
- const char *cmd;
- int (*callback) (struct audio_device *device, const char *buf);
-};
-
static GSList *headset_callbacks = NULL;

static void error_connect_failed(DBusConnection *conn, DBusMessage *msg,
@@ -198,78 +143,6 @@ static int rfcomm_connect(struct audio_device *device, headset_stream_cb_t cb,
static int get_records(struct audio_device *device, headset_stream_cb_t cb,
void *user_data, unsigned int *cb_id);

-static void print_ag_features(uint32_t features)
-{
- GString *gstr;
- char *str;
-
- if (features == 0) {
- DBG("HFP AG features: (none)");
- return;
- }
-
- gstr = g_string_new("HFP AG features: ");
-
- if (features & AG_FEATURE_THREE_WAY_CALLING)
- g_string_append(gstr, "\"Three-way calling\" ");
- if (features & AG_FEATURE_EC_ANDOR_NR)
- g_string_append(gstr, "\"EC and/or NR function\" ");
- if (features & AG_FEATURE_VOICE_RECOGNITION)
- g_string_append(gstr, "\"Voice recognition function\" ");
- if (features & AG_FEATURE_INBAND_RINGTONE)
- g_string_append(gstr, "\"In-band ring tone capability\" ");
- if (features & AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG)
- g_string_append(gstr, "\"Attach a number to a voice tag\" ");
- if (features & AG_FEATURE_REJECT_A_CALL)
- g_string_append(gstr, "\"Ability to reject a call\" ");
- if (features & AG_FEATURE_ENHANCED_CALL_STATUS)
- g_string_append(gstr, "\"Enhanced call status\" ");
- if (features & AG_FEATURE_ENHANCED_CALL_CONTROL)
- g_string_append(gstr, "\"Enhanced call control\" ");
- if (features & AG_FEATURE_EXTENDED_ERROR_RESULT_CODES)
- g_string_append(gstr, "\"Extended Error Result Codes\" ");
-
- str = g_string_free(gstr, FALSE);
-
- DBG("%s", str);
-
- g_free(str);
-}
-
-static void print_hf_features(uint32_t features)
-{
- GString *gstr;
- char *str;
-
- if (features == 0) {
- DBG("HFP HF features: (none)");
- return;
- }
-
- gstr = g_string_new("HFP HF features: ");
-
- if (features & HF_FEATURE_EC_ANDOR_NR)
- g_string_append(gstr, "\"EC and/or NR function\" ");
- if (features & HF_FEATURE_CALL_WAITING_AND_3WAY)
- g_string_append(gstr, "\"Call waiting and 3-way calling\" ");
- if (features & HF_FEATURE_CLI_PRESENTATION)
- g_string_append(gstr, "\"CLI presentation capability\" ");
- if (features & HF_FEATURE_VOICE_RECOGNITION)
- g_string_append(gstr, "\"Voice recognition activation\" ");
- if (features & HF_FEATURE_REMOTE_VOLUME_CONTROL)
- g_string_append(gstr, "\"Remote volume control\" ");
- if (features & HF_FEATURE_ENHANCED_CALL_STATUS)
- g_string_append(gstr, "\"Enhanced call status\" ");
- if (features & HF_FEATURE_ENHANCED_CALL_CONTROL)
- g_string_append(gstr, "\"Enhanced call control\" ");
-
- str = g_string_free(gstr, FALSE);
-
- DBG("%s", str);
-
- g_free(str);
-}
-
static const char *state2str(headset_state_t state)
{
switch (state) {
@@ -287,143 +160,6 @@ static const char *state2str(headset_state_t state)
return NULL;
}

-static int headset_send_valist(struct headset *hs, char *format, va_list ap)
-{
- char rsp[BUF_SIZE];
- ssize_t total_written, count;
- int fd;
-
- count = vsnprintf(rsp, sizeof(rsp), format, ap);
-
- if (count < 0)
- return -EINVAL;
-
- if (!hs->rfcomm) {
- error("headset_send: the headset is not connected");
- return -EIO;
- }
-
- total_written = 0;
- fd = g_io_channel_unix_get_fd(hs->rfcomm);
-
- while (total_written < count) {
- ssize_t written;
-
- written = write(fd, rsp + total_written,
- count - total_written);
- if (written < 0)
- return -errno;
-
- total_written += written;
- }
-
- return 0;
-}
-
-static int __attribute__((format(printf, 2, 3)))
- headset_send(struct headset *hs, char *format, ...)
-{
- va_list ap;
- int ret;
-
- va_start(ap, format);
- ret = headset_send_valist(hs, format, ap);
- va_end(ap);
-
- return ret;
-}
-
-static int supported_features(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
- int err;
-
- if (strlen(buf) < 9)
- return -EINVAL;
-
- slc->hf_features = strtoul(&buf[8], NULL, 10);
-
- print_hf_features(slc->hf_features);
-
- err = headset_send(hs, "\r\n+BRSF: %u\r\n", ag.features);
- if (err < 0)
- return err;
-
- return headset_send(hs, "\r\nOK\r\n");
-}
-
-static char *indicator_ranges(const struct indicator *indicators)
-{
- int i;
- GString *gstr;
-
- gstr = g_string_new("\r\n+CIND: ");
-
- for (i = 0; indicators[i].desc != NULL; i++) {
- if (i == 0)
- g_string_append_printf(gstr, "(\"%s\",(%s))",
- indicators[i].desc,
- indicators[i].range);
- else
- g_string_append_printf(gstr, ",(\"%s\",(%s))",
- indicators[i].desc,
- indicators[i].range);
- }
-
- g_string_append(gstr, "\r\n");
-
- return g_string_free(gstr, FALSE);
-}
-
-static char *indicator_values(const struct indicator *indicators)
-{
- int i;
- GString *gstr;
-
- gstr = g_string_new("\r\n+CIND: ");
-
- for (i = 0; indicators[i].desc != NULL; i++) {
- if (i == 0)
- g_string_append_printf(gstr, "%d", indicators[i].val);
- else
- g_string_append_printf(gstr, ",%d", indicators[i].val);
- }
-
- g_string_append(gstr, "\r\n");
-
- return g_string_free(gstr, FALSE);
-}
-
-static int report_indicators(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
- int err;
- char *str;
-
- if (strlen(buf) < 8)
- return -EINVAL;
-
- if (ag.indicators == NULL) {
- error("HFP AG indicators not initialized");
- return headset_send(hs, "\r\nERROR\r\n");
- }
-
- if (buf[7] == '=')
- str = indicator_ranges(ag.indicators);
- else
- str = indicator_values(ag.indicators);
-
- err = headset_send(hs, "%s", str);
-
- g_free(str);
-
- if (err < 0)
- return err;
-
- return headset_send(hs, "\r\nOK\r\n");
-}
-
static void pending_connect_complete(struct connect_cb *cb, struct audio_device *dev)
{
struct headset *hs = dev->headset;
@@ -504,64 +240,11 @@ static unsigned int connect_cb_new(struct headset *hs,
return cb->id;
}

-static void __attribute__((format(printf, 3, 4)))
- send_foreach_headset(GSList *devices,
- int (*cmp) (struct headset *hs),
- char *format, ...)
-{
- GSList *l;
- va_list ap;
-
- for (l = devices; l != NULL; l = l->next) {
- struct audio_device *device = l->data;
- struct headset *hs = device->headset;
- int ret;
-
- assert(hs != NULL);
-
- if (cmp && cmp(hs) != 0)
- continue;
-
- va_start(ap, format);
- ret = headset_send_valist(hs, format, ap);
- if (ret < 0)
- error("Failed to send to headset: %s (%d)",
- strerror(-ret), -ret);
- va_end(ap);
- }
-}
-
-static int cli_cmp(struct headset *hs)
-{
- struct headset_slc *slc = hs->slc;
-
- if (!hs->hfp_active)
- return -1;
-
- if (slc->cli_active)
- return 0;
- else
- return -1;
-}
-
-static gboolean ring_timer_cb(gpointer data)
-{
- send_foreach_headset(active_devices, NULL, "\r\nRING\r\n");
-
- if (ag.number)
- send_foreach_headset(active_devices, cli_cmp,
- "\r\n+CLIP: \"%s\",%d\r\n",
- ag.number, ag.number_type);
-
- return TRUE;
-}
-
static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
{
int sk;
struct audio_device *dev = user_data;
struct headset *hs = dev->headset;
- struct headset_slc *slc = hs->slc;
struct pending_connect *p = hs->pending;

if (err) {
@@ -603,13 +286,8 @@ static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)

headset_set_state(dev, HEADSET_STATE_PLAYING);

- if (slc->pending_ring) {
- ring_timer_cb(NULL);
- ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL,
- ring_timer_cb,
- NULL);
- slc->pending_ring = FALSE;
- }
+ if (telephony_pending_ring(hs->slc))
+ telephony_start_ring(hs->slc);
}

static int sco_connect(struct audio_device *dev, headset_stream_cb_t cb,
@@ -648,15 +326,7 @@ static int sco_connect(struct audio_device *dev, headset_stream_cb_t cb,
return 0;
}

-static int hfp_cmp(struct headset *hs)
-{
- if (hs->hfp_active)
- return 0;
- else
- return -1;
-}
-
-static void hfp_slc_complete(struct audio_device *dev)
+void headset_slc_complete(struct audio_device *dev)
{
struct headset *hs = dev->headset;
struct pending_connect *p = hs->pending;
@@ -685,569 +355,6 @@ static void hfp_slc_complete(struct audio_device *dev)
}
}

-static int telephony_generic_rsp(struct audio_device *device, cme_error_t err)
-{
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
-
- if ((err != CME_ERROR_NONE) && slc->cme_enabled)
- return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
-
- switch (err) {
- case CME_ERROR_NONE:
- return headset_send(hs, "\r\nOK\r\n");
- case CME_ERROR_NO_NETWORK_SERVICE:
- return headset_send(hs, "\r\nNO CARRIER\r\n");
- default:
- return headset_send(hs, "\r\nERROR\r\n");
- }
-}
-
-int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err)
-{
- struct audio_device *device = telephony_device;
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
- int ret;
-
- if (err != CME_ERROR_NONE)
- return telephony_generic_rsp(telephony_device, err);
-
- ret = headset_send(hs, "\r\nOK\r\n");
- if (ret < 0)
- return ret;
-
- if (hs->state != HEADSET_STATE_CONNECTING)
- return 0;
-
- if (slc->hf_features & HF_FEATURE_CALL_WAITING_AND_3WAY &&
- ag.features & AG_FEATURE_THREE_WAY_CALLING)
- return 0;
-
- hfp_slc_complete(device);
-
- return 0;
-}
-
-static int event_reporting(struct audio_device *dev, const char *buf)
-{
- char **tokens; /* <mode>, <keyp>, <disp>, <ind>, <bfr> */
-
- if (strlen(buf) < 13)
- return -EINVAL;
-
- tokens = g_strsplit(&buf[8], ",", 5);
- if (g_strv_length(tokens) < 4) {
- g_strfreev(tokens);
- return -EINVAL;
- }
-
- ag.er_mode = atoi(tokens[0]);
- ag.er_ind = atoi(tokens[3]);
-
- g_strfreev(tokens);
- tokens = NULL;
-
- DBG("Event reporting (CMER): mode=%d, ind=%d",
- ag.er_mode, ag.er_ind);
-
- switch (ag.er_ind) {
- case 0:
- case 1:
- telephony_event_reporting_req(dev, ag.er_ind);
- break;
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int call_hold(struct audio_device *dev, const char *buf)
-{
- struct headset *hs = dev->headset;
- int err;
-
- if (strlen(buf) < 9)
- return -EINVAL;
-
- if (buf[8] != '?') {
- telephony_call_hold_req(dev, &buf[8]);
- return 0;
- }
-
- err = headset_send(hs, "\r\n+CHLD: (%s)\r\n", ag.chld);
- if (err < 0)
- return err;
-
- err = headset_send(hs, "\r\nOK\r\n");
- if (err < 0)
- return err;
-
- if (hs->state != HEADSET_STATE_CONNECTING)
- return 0;
-
- hfp_slc_complete(dev);
-
- return 0;
-}
-
-int telephony_key_press_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-static int key_press(struct audio_device *device, const char *buf)
-{
- if (strlen(buf) < 9)
- return -EINVAL;
-
- g_dbus_emit_signal(device->conn, device->path,
- AUDIO_HEADSET_INTERFACE, "AnswerRequested",
- DBUS_TYPE_INVALID);
-
- if (ag.ring_timer) {
- g_source_remove(ag.ring_timer);
- ag.ring_timer = 0;
- }
-
- telephony_key_press_req(device, &buf[8]);
-
- return 0;
-}
-
-int telephony_answer_call_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-static int answer_call(struct audio_device *device, const char *buf)
-{
- if (ag.ring_timer) {
- g_source_remove(ag.ring_timer);
- ag.ring_timer = 0;
- }
-
- if (ag.number) {
- g_free(ag.number);
- ag.number = NULL;
- }
-
- telephony_answer_call_req(device);
-
- return 0;
-}
-
-int telephony_terminate_call_rsp(void *telephony_device,
- cme_error_t err)
-{
- struct audio_device *device = telephony_device;
- struct headset *hs = device->headset;
-
- if (err != CME_ERROR_NONE)
- return telephony_generic_rsp(telephony_device, err);
-
- g_dbus_emit_signal(device->conn, device->path,
- AUDIO_HEADSET_INTERFACE, "CallTerminated",
- DBUS_TYPE_INVALID);
-
- return headset_send(hs, "\r\nOK\r\n");
-}
-
-static int terminate_call(struct audio_device *device, const char *buf)
-{
- if (ag.number) {
- g_free(ag.number);
- ag.number = NULL;
- }
-
- if (ag.ring_timer) {
- g_source_remove(ag.ring_timer);
- ag.ring_timer = 0;
- }
-
- telephony_terminate_call_req(device);
-
- return 0;
-}
-
-static int cli_notification(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
-
- if (strlen(buf) < 9)
- return -EINVAL;
-
- slc->cli_active = buf[8] == '1' ? TRUE : FALSE;
-
- return headset_send(hs, "\r\nOK\r\n");
-}
-
-int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-static int response_and_hold(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
-
- if (strlen(buf) < 8)
- return -EINVAL;
-
- if (ag.rh == BTRH_NOT_SUPPORTED)
- return telephony_generic_rsp(device, CME_ERROR_NOT_SUPPORTED);
-
- if (buf[7] == '=') {
- telephony_response_and_hold_req(device, atoi(&buf[8]) < 0);
- return 0;
- }
-
- if (ag.rh >= 0)
- headset_send(hs, "\r\n+BTRH: %d\r\n", ag.rh);
-
- return headset_send(hs, "\r\nOK\r\n");
-}
-
-int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-static int last_dialed_number(struct audio_device *device, const char *buf)
-{
- telephony_last_dialed_number_req(device);
-
- return 0;
-}
-
-int telephony_dial_number_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-static int dial_number(struct audio_device *device, const char *buf)
-{
- char number[BUF_SIZE];
- size_t buf_len;
-
- buf_len = strlen(buf);
-
- if (buf[buf_len - 1] != ';') {
- DBG("Rejecting non-voice call dial request");
- return -EINVAL;
- }
-
- memset(number, 0, sizeof(number));
- strncpy(number, &buf[3], buf_len - 4);
-
- telephony_dial_number_req(device, number);
-
- return 0;
-}
-
-static int headset_set_gain(struct audio_device *device, uint16_t gain, char type)
-{
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
- const char *name, *property;
-
- if (gain > 15) {
- error("Invalid gain value: %u", gain);
- return -EINVAL;
- }
-
- switch (type) {
- case HEADSET_GAIN_SPEAKER:
- if (slc->sp_gain == gain) {
- DBG("Ignoring no-change in speaker gain");
- return -EALREADY;
- }
- name = "SpeakerGainChanged";
- property = "SpeakerGain";
- slc->sp_gain = gain;
- break;
- case HEADSET_GAIN_MICROPHONE:
- if (slc->mic_gain == gain) {
- DBG("Ignoring no-change in microphone gain");
- return -EALREADY;
- }
- name = "MicrophoneGainChanged";
- property = "MicrophoneGain";
- slc->mic_gain = gain;
- break;
- default:
- error("Unknown gain setting");
- return -EINVAL;
- }
-
- g_dbus_emit_signal(device->conn, device->path,
- AUDIO_HEADSET_INTERFACE, name,
- DBUS_TYPE_UINT16, &gain,
- DBUS_TYPE_INVALID);
-
- emit_property_changed(device->conn, device->path,
- AUDIO_HEADSET_INTERFACE, property,
- DBUS_TYPE_UINT16, &gain);
-
- return 0;
-}
-
-static int signal_gain_setting(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
- dbus_uint16_t gain;
- int err;
-
- if (strlen(buf) < 8) {
- error("Too short string for Gain setting");
- return -EINVAL;
- }
-
- gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10);
-
- err = headset_set_gain(device, gain, buf[5]);
- if (err < 0 && err != -EALREADY)
- return err;
-
- return headset_send(hs, "\r\nOK\r\n");
-}
-
-int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-static int dtmf_tone(struct audio_device *device, const char *buf)
-{
- char tone;
-
- if (strlen(buf) < 8) {
- error("Too short string for DTMF tone");
- return -EINVAL;
- }
-
- tone = buf[7];
- if (tone >= '#' && tone <= 'D')
- telephony_transmit_dtmf_req(device, tone);
- else
- return -EINVAL;
-
- return 0;
-}
-
-int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-static int subscriber_number(struct audio_device *device, const char *buf)
-{
- telephony_subscriber_number_req(device);
-
- return 0;
-}
-
-int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-static int list_current_calls(struct audio_device *device, const char *buf)
-{
- telephony_list_current_calls_req(device);
-
- return 0;
-}
-
-static int extended_errors(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
-
- if (strlen(buf) < 9)
- return -EINVAL;
-
- if (buf[8] == '1') {
- slc->cme_enabled = TRUE;
- DBG("CME errors enabled for headset %p", hs);
- } else {
- slc->cme_enabled = FALSE;
- DBG("CME errors disabled for headset %p", hs);
- }
-
- return headset_send(hs, "\r\nOK\r\n");
-}
-
-static int call_waiting_notify(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
-
- if (strlen(buf) < 9)
- return -EINVAL;
-
- if (buf[8] == '1') {
- slc->cwa_enabled = TRUE;
- DBG("Call waiting notification enabled for headset %p", hs);
- } else {
- slc->cwa_enabled = FALSE;
- DBG("Call waiting notification disabled for headset %p", hs);
- }
-
- return headset_send(hs, "\r\nOK\r\n");
-}
-
-int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-int telephony_call_hold_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err)
-{
- struct audio_device *device = telephony_device;
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
-
- if (err == CME_ERROR_NONE) {
- GSList *l;
-
- for (l = hs->nrec_cbs; l; l = l->next) {
- struct headset_nrec_callback *nrec_cb = l->data;
-
- nrec_cb->cb(device, slc->nrec_req, nrec_cb->user_data);
- }
-
- slc->nrec = hs->slc->nrec_req;
- }
-
- return telephony_generic_rsp(telephony_device, err);
-}
-
-int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err)
-{
- return telephony_generic_rsp(telephony_device, err);
-}
-
-int telephony_operator_selection_ind(int mode, const char *oper)
-{
- if (!active_devices)
- return -ENODEV;
-
- send_foreach_headset(active_devices, hfp_cmp,
- "\r\n+COPS: %d,0,\"%s\"\r\n",
- mode, oper);
- return 0;
-}
-
-static int operator_selection(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
-
- if (strlen(buf) < 8)
- return -EINVAL;
-
- switch (buf[7]) {
- case '?':
- telephony_operator_selection_req(device);
- break;
- case '=':
- return headset_send(hs, "\r\nOK\r\n");
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int nr_and_ec(struct audio_device *device, const char *buf)
-{
- struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
-
- if (strlen(buf) < 9)
- return -EINVAL;
-
- if (buf[8] == '0')
- slc->nrec_req = FALSE;
- else
- slc->nrec_req = TRUE;
-
- telephony_nr_and_ec_req(device, slc->nrec_req);
-
- return 0;
-}
-
-static int voice_dial(struct audio_device *device, const char *buf)
-{
- gboolean enable;
-
- if (strlen(buf) < 9)
- return -EINVAL;
-
- if (buf[8] == '0')
- enable = FALSE;
- else
- enable = TRUE;
-
- telephony_voice_dial_req(device, enable);
-
- return 0;
-}
-
-static int apple_command(struct audio_device *device, const char *buf)
-{
- DBG("Got Apple command: %s", buf);
-
- return telephony_generic_rsp(device, CME_ERROR_NONE);
-}
-
-static struct event event_callbacks[] = {
- { "ATA", answer_call },
- { "ATD", dial_number },
- { "AT+VG", signal_gain_setting },
- { "AT+BRSF", supported_features },
- { "AT+CIND", report_indicators },
- { "AT+CMER", event_reporting },
- { "AT+CHLD", call_hold },
- { "AT+CHUP", terminate_call },
- { "AT+CKPD", key_press },
- { "AT+CLIP", cli_notification },
- { "AT+BTRH", response_and_hold },
- { "AT+BLDN", last_dialed_number },
- { "AT+VTS", dtmf_tone },
- { "AT+CNUM", subscriber_number },
- { "AT+CLCC", list_current_calls },
- { "AT+CMEE", extended_errors },
- { "AT+CCWA", call_waiting_notify },
- { "AT+COPS", operator_selection },
- { "AT+NREC", nr_and_ec },
- { "AT+BVRA", voice_dial },
- { "AT+XAPL", apple_command },
- { "AT+IPHONEACCEV", apple_command },
- { 0 }
-};
-
-static int handle_event(struct audio_device *device, const char *buf)
-{
- struct event *ev;
-
- DBG("Received %s", buf);
-
- for (ev = event_callbacks; ev->cmd; ev++) {
- if (!strncmp(buf, ev->cmd, strlen(ev->cmd)))
- return ev->callback(device, buf);
- }
-
- return -EINVAL;
-}
-
static void close_sco(struct audio_device *device)
{
struct headset *hs = device->headset;
@@ -1266,94 +373,6 @@ static void close_sco(struct audio_device *device)
}
}

-static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond,
- struct audio_device *device)
-{
- struct headset *hs;
- struct headset_slc *slc;
- unsigned char buf[BUF_SIZE];
- ssize_t bytes_read;
- size_t free_space;
- int fd;
-
- if (cond & G_IO_NVAL)
- return FALSE;
-
- hs = device->headset;
- slc = hs->slc;
-
- if (cond & (G_IO_ERR | G_IO_HUP)) {
- DBG("ERR or HUP on RFCOMM socket");
- goto failed;
- }
-
- fd = g_io_channel_unix_get_fd(chan);
-
- bytes_read = read(fd, buf, sizeof(buf) - 1);
- if (bytes_read < 0)
- return TRUE;
-
- free_space = sizeof(slc->buf) - slc->data_start -
- slc->data_length - 1;
-
- if (free_space < (size_t) bytes_read) {
- /* Very likely that the HS is sending us garbage so
- * just ignore the data and disconnect */
- error("Too much data to fit incomming buffer");
- goto failed;
- }
-
- memcpy(&slc->buf[slc->data_start], buf, bytes_read);
- slc->data_length += bytes_read;
-
- /* Make sure the data is null terminated so we can use string
- * functions */
- slc->buf[slc->data_start + slc->data_length] = '\0';
-
- while (slc->data_length > 0) {
- char *cr;
- int err;
- off_t cmd_len;
-
- cr = strchr(&slc->buf[slc->data_start], '\r');
- if (!cr)
- break;
-
- cmd_len = 1 + (off_t) cr - (off_t) &slc->buf[slc->data_start];
- *cr = '\0';
-
- if (cmd_len > 1)
- err = handle_event(device, &slc->buf[slc->data_start]);
- else
- /* Silently skip empty commands */
- err = 0;
-
- if (err == -EINVAL) {
- error("Badly formated or unrecognized command: %s",
- &slc->buf[slc->data_start]);
- err = headset_send(hs, "\r\nERROR\r\n");
- if (err < 0)
- goto failed;
- } else if (err < 0)
- error("Error handling command %s: %s (%d)",
- &slc->buf[slc->data_start],
- strerror(-err), -err);
-
- slc->data_start += cmd_len;
- slc->data_length -= cmd_len;
-
- if (!slc->data_length)
- slc->data_start = 0;
- }
-
- return TRUE;
-
-failed:
- headset_set_state(device, HEADSET_STATE_DISCONNECTED);
-
- return FALSE;
-}
-
static gboolean sco_cb(GIOChannel *chan, GIOCondition cond,
struct audio_device *device)
{
@@ -1381,7 +400,7 @@ void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
}

/* For HFP telephony isn't ready just disconnect */
- if (hs->hfp_active && !ag.telephony_ready) {
+ if (hs->hfp_active && !telephony_get_ready_state()) {
error("Unable to accept HFP connection since the telephony "
"subsystem isn't initialized");
goto failed;
@@ -1397,16 +416,10 @@ void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
else
hs->auto_dc = FALSE;

- g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL,
- (GIOFunc) rfcomm_io_cb, dev);
+ hs->slc = telephony_device_connecting(chan, dev);

DBG("%s: Connected to %s", dev->path, hs_address);

- hs->slc = g_new0(struct headset_slc, 1);
- hs->slc->sp_gain = 15;
- hs->slc->mic_gain = 15;
- hs->slc->nrec = TRUE;
-
/* In HFP mode wait for Service Level Connection */
if (hs->hfp_active)
return;
@@ -1740,7 +753,7 @@ static DBusMessage *hs_connect(DBusConnection *conn, DBusMessage *msg,
else if (hs->state > HEADSET_STATE_CONNECTING)
return btd_error_already_connected(msg);

- if (hs->hfp_handle && !ag.telephony_ready)
+ if (hs->hfp_handle && !telephony_get_ready_state())
return btd_error_not_ready(msg);

device->auto_connect = FALSE;
@@ -1762,7 +775,9 @@ static DBusMessage *hs_ring(DBusConnection *conn, DBusMessage *msg,
struct audio_device *device = data;
struct headset *hs = device->headset;
DBusMessage *reply = NULL;
+#if 0
int err;
+#endif

if (hs->state < HEADSET_STATE_CONNECTED)
return btd_error_not_connected(msg);
@@ -1771,20 +786,20 @@ static DBusMessage *hs_ring(DBusConnection *conn, DBusMessage *msg,
if (!reply)
return NULL;

- if (ag.ring_timer) {
+ if (telephony_is_ringing()) {
DBG("IndicateCall received when already indicating");
return reply;
}

+#if 0
err = headset_send(hs, "\r\nRING\r\n");
if (err < 0) {
dbus_message_unref(reply);
return btd_error_failed(msg, strerror(-err));
}
+#endif

- ring_timer_cb(NULL);
- ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
- NULL);
+ telephony_start_ring(hs->slc);

return reply;
}
@@ -1804,10 +819,9 @@ static DBusMessage *hs_cancel_call(DBusConnection *conn,
if (!reply)
return NULL;

- if (ag.ring_timer) {
- g_source_remove(ag.ring_timer);
- ag.ring_timer = 0;
- } else
+ if (telephony_is_ringing())
+ telephony_stop_ring(hs->slc);
+ else
DBG("Got CancelCall method call but no call is active");

return reply;
@@ -1858,7 +872,6 @@ static DBusMessage *hs_get_speaker_gain(DBusConnection *conn,
{
struct audio_device *device = data;
struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
DBusMessage *reply;
dbus_uint16_t gain;

@@ -1869,7 +882,7 @@ static DBusMessage *hs_get_speaker_gain(DBusConnection *conn,
if (!reply)
return NULL;

- gain = (dbus_uint16_t) slc->sp_gain;
+ gain = (dbus_uint16_t) telephony_get_speaker_gain(hs->slc);

dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
DBUS_TYPE_INVALID);
@@ -1883,18 +896,17 @@ static DBusMessage *hs_get_mic_gain(DBusConnection *conn,
{
struct audio_device *device = data;
struct headset *hs = device->headset;
- struct headset_slc *slc = hs->slc;
DBusMessage *reply;
dbus_uint16_t gain;

- if (hs->state < HEADSET_STATE_CONNECTED || slc == NULL)
+ if (hs->state < HEADSET_STATE_CONNECTED || hs->slc == NULL)
return btd_error_not_available(msg);

reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;

- gain = (dbus_uint16_t) slc->mic_gain;
+ gain = (dbus_uint16_t) telephony_get_mic_gain(hs->slc);

dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
DBUS_TYPE_INVALID);
@@ -1909,13 +921,14 @@ static DBusMessage *hs_set_gain(DBusConnection *conn,
{
struct audio_device *device = data;
struct headset *hs = device->headset;
+ struct at_slc *slc = hs->slc;
DBusMessage *reply;
int err;

if (hs->state < HEADSET_STATE_CONNECTED)
return btd_error_not_connected(msg);

- err = headset_set_gain(device, gain, type);
+ err = telephony_set_gain(slc, gain, type);
if (err < 0)
return btd_error_invalid_args(msg);

@@ -1924,7 +937,7 @@ static DBusMessage *hs_set_gain(DBusConnection *conn,
return NULL;

if (hs->state == HEADSET_STATE_PLAYING) {
- err = headset_send(hs, "\r\n+VG%c=%u\r\n", type, gain);
+ err = telephony_send_gain(slc, type, gain);
if (err < 0) {
dbus_message_unref(reply);
return btd_error_failed(msg, strerror(-err));
@@ -1969,6 +982,7 @@ static DBusMessage *hs_get_properties(DBusConnection *conn,
DBusMessageIter dict;
gboolean value;
const char *state;
+ int gain;

reply = dbus_message_new_method_return(msg);
if (!reply)
@@ -1999,14 +1013,12 @@ static DBusMessage *hs_get_properties(DBusConnection *conn,
goto done;

/* SpeakerGain */
- dict_append_entry(&dict, "SpeakerGain",
- DBUS_TYPE_UINT16,
- &device->headset->slc->sp_gain);
+ gain = telephony_get_speaker_gain(device->headset->slc);
+ dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16, &gain);

/* MicrophoneGain */
- dict_append_entry(&dict, "MicrophoneGain",
- DBUS_TYPE_UINT16,
- &device->headset->slc->mic_gain);
+ gain = telephony_get_mic_gain(device->headset->slc);
+ dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16, &gain);

done:
dbus_message_iter_close_container(&iter, &dict);
@@ -2245,7 +1257,7 @@ uint32_t headset_config_init(GKeyFile *config)

/* Use the default values if there is no config file */
if (config == NULL)
- return ag.features;
+ return telephony_get_ag_features();

str = g_key_file_get_string(config, "General", "SCORouting",
&err);
@@ -2269,13 +1281,14 @@ uint32_t headset_config_init(GKeyFile *config)
DBG("audio.conf: %s", err->message);
g_clear_error(&err);
} else {
- fast_connectable = strcmp(str, "true") == 0;
- if (fast_connectable)
+ if (strcmp(str, "true") == 0) {
+ telephony_set_fast_connectable(TRUE);
manager_set_fast_connectable(FALSE);
+ }
g_free(str);
}

- return ag.features;
+ return telephony_get_ag_features();
}

static gboolean hs_dc_timeout(struct audio_device *dev)
@@ -2482,20 +1495,14 @@ int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
int headset_connect_sco(struct audio_device *dev, GIOChannel *io)
{
struct headset *hs = dev->headset;
- struct headset_slc *slc = hs->slc;

if (hs->sco)
return -EISCONN;

hs->sco = g_io_channel_ref(io);

- if (slc->pending_ring) {
- ring_timer_cb(NULL);
- ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL,
- ring_timer_cb,
- NULL);
- slc->pending_ring = FALSE;
- }
+ if (telephony_pending_ring(hs->slc))
+ telephony_start_ring(hs->slc);

return 0;
}
@@ -2503,11 +1510,11 @@ int headset_connect_sco(struct audio_device *dev, GIOChannel *io)
void headset_set_state(struct audio_device *dev, headset_state_t state)
{
struct headset *hs = dev->headset;
- struct headset_slc *slc = hs->slc;
gboolean value;
const char *state_str;
headset_state_t old_state = hs->state;
GSList *l;
+ int gain;

if (old_state == state)
return;
@@ -2518,6 +1525,10 @@ void headset_set_state(struct audio_device *dev, headset_state_t state)
case HEADSET_STATE_DISCONNECTED:
value = FALSE;
close_sco(dev);
+
+ if (dev->headset->slc)
+ telephony_device_disconnect(dev->headset->slc);
+
headset_close_rfcomm(dev);
emit_property_changed(dev->conn, dev->path,
AUDIO_HEADSET_INTERFACE, "State",
@@ -2532,7 +1543,7 @@ void headset_set_state(struct audio_device *dev, headset_state_t state)
DBUS_TYPE_BOOLEAN, &value);
telephony_device_disconnected(dev);
}
- active_devices = g_slist_remove(active_devices, dev);
+ telephony_device_set_active(dev, FALSE);
break;
case HEADSET_STATE_CONNECTING:
emit_property_changed(dev->conn, dev->path,
@@ -2546,10 +1557,11 @@ void headset_set_state(struct audio_device *dev, headset_state_t state)
AUDIO_HEADSET_INTERFACE, "State",
DBUS_TYPE_STRING, &state_str);
if (hs->state < state) {
- if (ag.features & AG_FEATURE_INBAND_RINGTONE)
- slc->inband_ring = TRUE;
+ if (telephony_get_ag_features()
+ & AG_FEATURE_INBAND_RINGTONE)
+ telephony_set_inband_ringtone(hs->slc, TRUE);
else
- slc->inband_ring = FALSE;
+ telephony_set_inband_ringtone(hs->slc, FALSE);
g_dbus_emit_signal(dev->conn, dev->path,
AUDIO_HEADSET_INTERFACE,
"Connected",
@@ -2559,7 +1571,7 @@ void headset_set_state(struct audio_device *dev, headset_state_t state)
AUDIO_HEADSET_INTERFACE,
"Connected",
DBUS_TYPE_BOOLEAN, &value);
- active_devices = g_slist_append(active_devices, dev);
+ telephony_device_set_active(dev, TRUE);
telephony_device_connected(dev);
} else if (hs->state == HEADSET_STATE_PLAYING) {
value = FALSE;
@@ -2594,10 +1606,15 @@ void headset_set_state(struct audio_device *dev, headset_state_t state)
AUDIO_HEADSET_INTERFACE, "Playing",
DBUS_TYPE_BOOLEAN, &value);

- if (slc->sp_gain >= 0)
- headset_send(hs, "\r\n+VGS=%u\r\n", slc->sp_gain);
- if (slc->mic_gain >= 0)
- headset_send(hs, "\r\n+VGM=%u\r\n", slc->mic_gain);
+ gain = telephony_get_speaker_gain(hs->slc);
+ if (gain >= 0)
+ telephony_send_gain(hs->slc, HEADSET_GAIN_SPEAKER,
+ gain);
+
+ gain = telephony_get_mic_gain(hs->slc);
+ if (gain >= 0)
+ telephony_send_gain(hs->slc, HEADSET_GAIN_MICROPHONE,
+ gain);
break;
}

@@ -2709,7 +1726,7 @@ gboolean headset_get_nrec(struct audio_device *dev)
if (!hs->slc)
return TRUE;

- return hs->slc->nrec;
+ return telephony_get_nrec(hs->slc);
}

unsigned int headset_add_nrec_cb(struct audio_device *dev,
@@ -2746,6 +1763,18 @@ gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id)
return FALSE;
}

+void headset_set_nrec(struct audio_device *dev, gboolean state)
+{
+ struct headset *hs = dev->headset;
+ GSList *l;
+
+ for (l = hs->nrec_cbs; l; l = l->next) {
+ struct headset_nrec_callback *nrec_cb = l->data;
+
+ nrec_cb->cb(dev, state, nrec_cb->user_data);
+ }
+}
+
gboolean headset_get_inband(struct audio_device *dev)
{
struct headset *hs = dev->headset;
@@ -2753,7 +1782,7 @@ gboolean headset_get_inband(struct audio_device *dev)
if (!hs->slc)
return TRUE;

- return hs->slc->inband_ring;
+ return telephony_get_inband_ringtone(hs->slc);
}

gboolean headset_get_sco_hci(struct audio_device *dev)
@@ -2772,195 +1801,6 @@ void headset_shutdown(struct audio_device *dev)
headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
}

-int telephony_event_ind(int index)
-{
- if (!active_devices)
- return -ENODEV;
-
- if (!ag.er_ind) {
- DBG("telephony_report_event called but events are disabled");
- return -EINVAL;
- }
-
- send_foreach_headset(active_devices, hfp_cmp,
- "\r\n+CIEV: %d,%d\r\n", index + 1,
- ag.indicators[index].val);
-
- return 0;
-}
-
-int telephony_response_and_hold_ind(int rh)
-{
- if (!active_devices)
- return -ENODEV;
-
- ag.rh = rh;
-
- /* If we aren't in any response and hold state don't send anything */
- if (ag.rh < 0)
- return 0;
-
- send_foreach_headset(active_devices, hfp_cmp, "\r\n+BTRH: %d\r\n",
- ag.rh);
-
- return 0;
-}
-
-int telephony_incoming_call_ind(const char *number, int type)
-{
- struct audio_device *dev;
- struct headset *hs;
- struct headset_slc *slc;
-
- if (fast_connectable)
- manager_set_fast_connectable(TRUE);
-
- if (!active_devices)
- return -ENODEV;
-
- /* Get the latest connected device */
- dev = active_devices->data;
- hs = dev->headset;
- slc = hs->slc;
-
- if (ag.ring_timer) {
- DBG("telephony_incoming_call_ind: already calling");
- return -EBUSY;
- }
-
- /* With HSP 1.2 the RING messages should *not* be sent if inband
- * ringtone is being used */
- if (!hs->hfp_active && slc->inband_ring)
- return 0;
-
- g_free(ag.number);
- ag.number = g_strdup(number);
- ag.number_type = type;
-
- if (slc->inband_ring && hs->hfp_active &&
- hs->state != HEADSET_STATE_PLAYING) {
- slc->pending_ring = TRUE;
- return 0;
- }
-
- ring_timer_cb(NULL);
- ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
- NULL);
-
- return 0;
-}
-
-int telephony_calling_stopped_ind(void)
-{
- struct audio_device *dev;
-
- if (fast_connectable)
- manager_set_fast_connectable(FALSE);
-
- if (ag.ring_timer) {
- g_source_remove(ag.ring_timer);
- ag.ring_timer = 0;
- }
-
- if (!active_devices)
- return 0;
-
- /* In case SCO isn't fully up yet */
- dev = active_devices->data;
-
- if (!dev->headset->slc->pending_ring && !ag.ring_timer)
- return -EINVAL;
-
- dev->headset->slc->pending_ring = FALSE;
-
- return 0;
-}
-
-int telephony_ready_ind(uint32_t features,
- const struct indicator *indicators, int rh,
- const char *chld)
-{
- ag.telephony_ready = TRUE;
- ag.features = features;
- ag.indicators = indicators;
- ag.rh = rh;
- ag.chld = chld;
-
- DBG("Telephony plugin initialized");
-
- print_ag_features(ag.features);
-
- return 0;
-}
-
-int telephony_deinit(void)
-{
- g_free(ag.number);
-
- memset(&ag, 0, sizeof(ag));
-
- ag.er_mode = 3;
- ag.rh = BTRH_NOT_SUPPORTED;
-
- DBG("Telephony deinitialized");
-
- return 0;
-}
-
-int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
- int mprty, const char *number,
- int type)
-{
- if (!active_devices)
- return -ENODEV;
-
- if (number && strlen(number) > 0)
- send_foreach_headset(active_devices, hfp_cmp,
- "\r\n+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n",
- idx, dir, status, mode, mprty, number, type);
- else
- send_foreach_headset(active_devices, hfp_cmp,
- "\r\n+CLCC: %d,%d,%d,%d,%d\r\n",
- idx, dir, status, mode, mprty);
-
- return 0;
-}
-
-int telephony_subscriber_number_ind(const char *number, int type, int service)
-{
- if (!active_devices)
- return -ENODEV;
-
- send_foreach_headset(active_devices, hfp_cmp,
- "\r\n+CNUM: ,%s,%d,,%d\r\n",
- number, type, service);
-
- return 0;
-}
-
-static int cwa_cmp(struct headset *hs)
-{
- if (!hs->hfp_active)
- return -1;
-
- if (hs->slc->cwa_enabled)
- return 0;
- else
- return -1;
-}
-
-int telephony_call_waiting_ind(const char *number, int type)
-{
- if (!active_devices)
- return -ENODEV;
-
- send_foreach_headset(active_devices, cwa_cmp,
- "\r\n+CCWA: \"%s\",%d\r\n",
- number, type);
-
- return 0;
-}
-
unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data)
{
struct headset_state_callback *state_cb;
@@ -2991,3 +1831,11 @@ gboolean headset_remove_state_cb(unsigned int id)

return FALSE;
}
+
+struct at_slc *headset_get_slc(struct audio_device *dev)
+{
+ if (!dev->headset)
+ return NULL;
+
+ return dev->headset->slc;
+}
diff --git a/audio/headset.h b/audio/headset.h
index 99eeca8..a26e6b7 100644
--- a/audio/headset.h
+++ b/audio/headset.h
@@ -100,6 +100,7 @@ gboolean headset_get_nrec(struct audio_device *dev);
unsigned int headset_add_nrec_cb(struct audio_device *dev,
headset_nrec_cb cb, void *user_data);
gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id);
+void headset_set_nrec(struct audio_device *dev, gboolean state);
gboolean headset_get_inband(struct audio_device *dev);
gboolean headset_get_sco_hci(struct audio_device *dev);

@@ -111,3 +112,6 @@ gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock);
gboolean headset_suspend(struct audio_device *dev, void *data);
gboolean headset_play(struct audio_device *dev, void *data);
void headset_shutdown(struct audio_device *dev);
+
+struct at_slc *headset_get_slc(struct audio_device *dev);
+void headset_slc_complete(struct audio_device *dev);
diff --git a/audio/telephony-builtin.c b/audio/telephony-builtin.c
new file mode 100644
index 0000000..84a34f2
--- /dev/null
+++ b/audio/telephony-builtin.c
@@ -0,0 +1,1287 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Intel Corporation
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <[email protected]>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "device.h"
+#include "manager.h"
+#include "telephony.h"
+#include "headset.h"
+#include "dbus-common.h"
+#include "telephony-builtin.h"
+
+#define RING_INTERVAL 3
+
+#define BUF_SIZE 1024
+
+static struct {
+ gboolean telephony_ready; /* Telephony plugin initialized */
+ uint32_t features; /* HFP AG features */
+ const struct indicator *indicators; /* Available HFP indicators */
+ int er_mode; /* Event reporting mode */
+ int er_ind; /* Event reporting for indicators */
+ int rh; /* Response and Hold state */
+ char *number; /* Incoming phone number */
+ int number_type; /* Incoming number type */
+ guint ring_timer; /* For incoming call indication */
+ const char *chld; /* Response to AT+CHLD=? */
+} ag = {
+ .telephony_ready = FALSE,
+ .features = 0,
+ .er_mode = 3,
+ .er_ind = 0,
+ .rh = BTRH_NOT_SUPPORTED,
+ .number = NULL,
+ .number_type = 0,
+ .ring_timer = 0,
+};
+
+static gboolean fast_connectable = FALSE;
+
+static GSList *active_devices = NULL;
+
+struct at_slc {
+ struct audio_device *device;
+ GIOChannel *rfcomm;
+
+ char buf[BUF_SIZE];
+ int data_start;
+ int data_length;
+
+ gboolean cli_active;
+ gboolean cme_enabled;
+ gboolean cwa_enabled;
+ gboolean pending_ring;
+ gboolean inband_ring;
+ gboolean nrec;
+ gboolean nrec_req;
+
+ int sp_gain;
+ int mic_gain;
+
+ unsigned int hf_features;
+};
+
+struct event {
+ const char *cmd;
+ int (*callback) (struct at_slc *slc, const char *buf);
+};
+
+static void print_ag_features(uint32_t features)
+{
+ GString *gstr;
+ char *str;
+
+ if (features == 0) {
+ DBG("HFP AG features: (none)");
+ return;
+ }
+
+ gstr = g_string_new("HFP AG features: ");
+
+ if (features & AG_FEATURE_THREE_WAY_CALLING)
+ g_string_append(gstr, "\"Three-way calling\" ");
+ if (features & AG_FEATURE_EC_ANDOR_NR)
+ g_string_append(gstr, "\"EC and/or NR function\" ");
+ if (features & AG_FEATURE_VOICE_RECOGNITION)
+ g_string_append(gstr, "\"Voice recognition function\" ");
+ if (features & AG_FEATURE_INBAND_RINGTONE)
+ g_string_append(gstr, "\"In-band ring tone capability\" ");
+ if (features & AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG)
+ g_string_append(gstr, "\"Attach a number to a voice tag\" ");
+ if (features & AG_FEATURE_REJECT_A_CALL)
+ g_string_append(gstr, "\"Ability to reject a call\" ");
+ if (features & AG_FEATURE_ENHANCED_CALL_STATUS)
+ g_string_append(gstr, "\"Enhanced call status\" ");
+ if (features & AG_FEATURE_ENHANCED_CALL_CONTROL)
+ g_string_append(gstr, "\"Enhanced call control\" ");
+ if (features & AG_FEATURE_EXTENDED_ERROR_RESULT_CODES)
+ g_string_append(gstr, "\"Extended Error Result Codes\" ");
+
+ str = g_string_free(gstr, FALSE);
+
+ DBG("%s", str);
+
+ g_free(str);
+}
+
+static void print_hf_features(uint32_t features)
+{
+ GString *gstr;
+ char *str;
+
+ if (features == 0) {
+ DBG("HFP HF features: (none)");
+ return;
+ }
+
+ gstr = g_string_new("HFP HF features: ");
+
+ if (features & HF_FEATURE_EC_ANDOR_NR)
+ g_string_append(gstr, "\"EC and/or NR function\" ");
+ if (features & HF_FEATURE_CALL_WAITING_AND_3WAY)
+ g_string_append(gstr, "\"Call waiting and 3-way calling\" ");
+ if (features & HF_FEATURE_CLI_PRESENTATION)
+ g_string_append(gstr, "\"CLI presentation capability\" ");
+ if (features & HF_FEATURE_VOICE_RECOGNITION)
+ g_string_append(gstr, "\"Voice recognition activation\" ");
+ if (features & HF_FEATURE_REMOTE_VOLUME_CONTROL)
+ g_string_append(gstr, "\"Remote volume control\" ");
+ if (features & HF_FEATURE_ENHANCED_CALL_STATUS)
+ g_string_append(gstr, "\"Enhanced call status\" ");
+ if (features & HF_FEATURE_ENHANCED_CALL_CONTROL)
+ g_string_append(gstr, "\"Enhanced call control\" ");
+
+ str = g_string_free(gstr, FALSE);
+
+ DBG("%s", str);
+
+ g_free(str);
+}
+
+static int at_send_valist(struct at_slc *slc, char *format, va_list ap)
+{
+ char rsp[BUF_SIZE];
+ ssize_t total_written, count;
+ int fd;
+
+ count = vsnprintf(rsp, sizeof(rsp), format, ap);
+
+ if (count < 0)
+ return -EINVAL;
+
+ if (!slc->rfcomm) {
+ error("headset_send: the headset is not connected");
+ return -EIO;
+ }
+
+ total_written = 0;
+ fd = g_io_channel_unix_get_fd(slc->rfcomm);
+
+ while (total_written < count) {
+ ssize_t written;
+
+ written = write(fd, rsp + total_written,
+ count - total_written);
+ if (written < 0)
+ return -errno;
+
+ total_written += written;
+ }
+
+ return 0;
+}
+
+static int __attribute__((format(printf, 2, 3)))
+ at_send(struct at_slc *slc, char *format, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, format);
+ ret = at_send_valist(slc, format, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+static int supported_features(struct at_slc *slc, const char *buf)
+{
+ int err;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ slc->hf_features = strtoul(&buf[8], NULL, 10);
+
+ print_hf_features(slc->hf_features);
+
+ err = at_send(slc, "\r\n+BRSF: %u\r\n", ag.features);
+ if (err < 0)
+ return err;
+
+ return at_send(slc, "\r\nOK\r\n");
+}
+
+static char *indicator_ranges(const struct indicator *indicators)
+{
+ int i;
+ GString *gstr;
+
+ gstr = g_string_new("\r\n+CIND: ");
+
+ for (i = 0; indicators[i].desc != NULL; i++) {
+ if (i == 0)
+ g_string_append_printf(gstr, "(\"%s\",(%s))",
+ indicators[i].desc,
+ indicators[i].range);
+ else
+ g_string_append_printf(gstr, ",(\"%s\",(%s))",
+ indicators[i].desc,
+ indicators[i].range);
+ }
+
+ g_string_append(gstr, "\r\n");
+
+ return g_string_free(gstr, FALSE);
+}
+
+static char *indicator_values(const struct indicator *indicators)
+{
+ int i;
+ GString *gstr;
+
+ gstr = g_string_new("\r\n+CIND: ");
+
+ for (i = 0; indicators[i].desc != NULL; i++) {
+ if (i == 0)
+ g_string_append_printf(gstr, "%d", indicators[i].val);
+ else
+ g_string_append_printf(gstr, ",%d", indicators[i].val);
+ }
+
+ g_string_append(gstr, "\r\n");
+
+ return g_string_free(gstr, FALSE);
+}
+
+static int report_indicators(struct at_slc *slc, const char *buf)
+{
+ int err;
+ char *str;
+
+ if (strlen(buf) < 8)
+ return -EINVAL;
+
+ if (ag.indicators == NULL) {
+ error("HFP AG indicators not initialized");
+ return at_send(slc, "\r\nERROR\r\n");
+ }
+
+ if (buf[7] == '=')
+ str = indicator_ranges(ag.indicators);
+ else
+ str = indicator_values(ag.indicators);
+
+ err = at_send(slc, "%s", str);
+
+ g_free(str);
+
+ if (err < 0)
+ return err;
+
+ return at_send(slc, "\r\nOK\r\n");
+}
+
+static void __attribute__((format(printf, 3, 4)))
+ send_foreach_headset(GSList *devices,
+ int (*cmp) (struct at_slc *slc),
+ char *format, ...)
+{
+ GSList *l;
+ va_list ap;
+
+ for (l = devices; l != NULL; l = l->next) {
+ struct at_slc *slc = l->data;
+ int ret;
+
+ assert(slc != NULL);
+
+ if (cmp && cmp(slc) != 0)
+ continue;
+
+ va_start(ap, format);
+ ret = at_send_valist(slc, format, ap);
+ if (ret < 0)
+ error("Failed to send to headset: %s (%d)",
+ strerror(-ret), -ret);
+ va_end(ap);
+ }
+}
+
+static int cli_cmp(struct at_slc *slc)
+{
+ if (!headset_get_hfp_active(slc->device))
+ return -1;
+
+ if (slc->cli_active)
+ return 0;
+ else
+ return -1;
+}
+
+static gboolean ring_timer_cb(gpointer data)
+{
+ send_foreach_headset(active_devices, NULL, "\r\nRING\r\n");
+
+ if (ag.number)
+ send_foreach_headset(active_devices, cli_cmp,
+ "\r\n+CLIP: \"%s\",%d\r\n",
+ ag.number, ag.number_type);
+
+ return TRUE;
+}
+
+static int hfp_cmp(struct at_slc *slc)
+{
+ if (headset_get_hfp_active(slc->device))
+ return 0;
+ else
+ return -1;
+}
+
+static int telephony_generic_rsp(struct at_slc *slc, cme_error_t err)
+{
+ if ((err != CME_ERROR_NONE) && slc->cme_enabled)
+ return at_send(slc, "\r\n+CME ERROR: %d\r\n", err);
+
+ switch (err) {
+ case CME_ERROR_NONE:
+ return at_send(slc, "\r\nOK\r\n");
+ case CME_ERROR_NO_NETWORK_SERVICE:
+ return at_send(slc, "\r\nNO CARRIER\r\n");
+ default:
+ return at_send(slc, "\r\nERROR\r\n");
+ }
+}
+
+int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err)
+{
+ struct at_slc *slc = telephony_device;
+ int ret;
+
+ if (err != CME_ERROR_NONE)
+ return telephony_generic_rsp(telephony_device, err);
+
+ ret = at_send(slc, "\r\nOK\r\n");
+ if (ret < 0)
+ return ret;
+
+ if (headset_get_state(slc->device) != HEADSET_STATE_CONNECTING)
+ return 0;
+
+ if (slc->hf_features & HF_FEATURE_CALL_WAITING_AND_3WAY &&
+ ag.features & AG_FEATURE_THREE_WAY_CALLING)
+ return 0;
+
+ headset_slc_complete(slc->device);
+
+ return 0;
+}
+
+static int event_reporting(struct at_slc *slc, const char *buf)
+{
+ char **tokens; /* <mode>, <keyp>, <disp>, <ind>, <bfr> */
+
+ if (strlen(buf) < 13)
+ return -EINVAL;
+
+ tokens = g_strsplit(&buf[8], ",", 5);
+ if (g_strv_length(tokens) < 4) {
+ g_strfreev(tokens);
+ return -EINVAL;
+ }
+
+ ag.er_mode = atoi(tokens[0]);
+ ag.er_ind = atoi(tokens[3]);
+
+ g_strfreev(tokens);
+ tokens = NULL;
+
+ DBG("Event reporting (CMER): mode=%d, ind=%d",
+ ag.er_mode, ag.er_ind);
+
+ switch (ag.er_ind) {
+ case 0:
+ case 1:
+ telephony_event_reporting_req(slc, ag.er_ind);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int call_hold(struct at_slc *slc, const char *buf)
+{
+ int err;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] != '?') {
+ telephony_call_hold_req(slc->device, &buf[8]);
+ return 0;
+ }
+
+ err = at_send(slc, "\r\n+CHLD: (%s)\r\n", ag.chld);
+ if (err < 0)
+ return err;
+
+ err = at_send(slc, "\r\nOK\r\n");
+ if (err < 0)
+ return err;
+
+ if (headset_get_state(slc->device) != HEADSET_STATE_CONNECTING)
+ return 0;
+
+ headset_slc_complete(slc->device);
+
+ return 0;
+}
+
+int telephony_key_press_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int key_press(struct at_slc *slc, const char *buf)
+{
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ g_dbus_emit_signal(slc->device->conn, slc->device->path,
+ AUDIO_HEADSET_INTERFACE, "AnswerRequested",
+ DBUS_TYPE_INVALID);
+
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ }
+
+ telephony_key_press_req(slc, &buf[8]);
+
+ return 0;
+}
+
+int telephony_answer_call_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int answer_call(struct at_slc *slc, const char *buf)
+{
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ }
+
+ if (ag.number) {
+ g_free(ag.number);
+ ag.number = NULL;
+ }
+
+ telephony_answer_call_req(slc);
+
+ return 0;
+}
+
+int telephony_terminate_call_rsp(void *telephony_device, cme_error_t err)
+{
+ struct at_slc *slc = telephony_device;
+
+ if (err != CME_ERROR_NONE)
+ return telephony_generic_rsp(telephony_device, err);
+
+ g_dbus_emit_signal(slc->device->conn, slc->device->path,
+ AUDIO_HEADSET_INTERFACE, "CallTerminated",
+ DBUS_TYPE_INVALID);
+
+ return at_send(slc, "\r\nOK\r\n");
+}
+
+static int terminate_call(struct at_slc *slc, const char *buf)
+{
+ if (ag.number) {
+ g_free(ag.number);
+ ag.number = NULL;
+ }
+
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ }
+
+ telephony_terminate_call_req(slc);
+
+ return 0;
+}
+
+static int cli_notification(struct at_slc *slc, const char *buf)
+{
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ slc->cli_active = buf[8] == '1' ? TRUE : FALSE;
+
+ return at_send(slc, "\r\nOK\r\n");
+}
+
+int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int response_and_hold(struct at_slc *slc, const char *buf)
+{
+ if (strlen(buf) < 8)
+ return -EINVAL;
+
+ if (ag.rh == BTRH_NOT_SUPPORTED)
+ return telephony_generic_rsp(slc, CME_ERROR_NOT_SUPPORTED);
+
+ if (buf[7] == '=') {
+ telephony_response_and_hold_req(slc, atoi(&buf[8]) < 0);
+ return 0;
+ }
+
+ if (ag.rh >= 0)
+ at_send(slc, "\r\n+BTRH: %d\r\n", ag.rh);
+
+ return at_send(slc, "\r\nOK\r\n");
+}
+
+int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int last_dialed_number(struct at_slc *slc, const char *buf)
+{
+ telephony_last_dialed_number_req(slc);
+
+ return 0;
+}
+
+int telephony_dial_number_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int dial_number(struct at_slc *slc, const char *buf)
+{
+ char number[BUF_SIZE];
+ size_t buf_len;
+
+ buf_len = strlen(buf);
+
+ if (buf[buf_len - 1] != ';') {
+ DBG("Rejecting non-voice call dial request");
+ return -EINVAL;
+ }
+
+ memset(number, 0, sizeof(number));
+ strncpy(number, &buf[3], buf_len - 4);
+
+ telephony_dial_number_req(slc, number);
+
+ return 0;
+}
+
+int telephony_set_gain(void *slc, uint16_t gain, char type)
+{
+ struct at_slc *s = slc;
+ const char *name, *property;
+
+ if (gain > 15) {
+ error("Invalid gain value: %u", gain);
+ return -EINVAL;
+ }
+
+ switch (type) {
+ case HEADSET_GAIN_SPEAKER:
+ if (s->sp_gain == gain) {
+ DBG("Ignoring no-change in speaker gain");
+ return -EALREADY;
+ }
+ name = "SpeakerGainChanged";
+ property = "SpeakerGain";
+ s->sp_gain = gain;
+ break;
+ case HEADSET_GAIN_MICROPHONE:
+ if (s->mic_gain == gain) {
+ DBG("Ignoring no-change in microphone gain");
+ return -EALREADY;
+ }
+ name = "MicrophoneGainChanged";
+ property = "MicrophoneGain";
+ s->mic_gain = gain;
+ break;
+ default:
+ error("Unknown gain setting");
+ return -EINVAL;
+ }
+
+ g_dbus_emit_signal(s->device->conn, s->device->path,
+ AUDIO_HEADSET_INTERFACE, name,
+ DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID);
+
+ emit_property_changed(s->device->conn, s->device->path,
+ AUDIO_HEADSET_INTERFACE, property,
+ DBUS_TYPE_UINT16, &gain);
+
+ return 0;
+}
+
+static int signal_gain_setting(struct at_slc *slc, const char *buf)
+{
+ dbus_uint16_t gain;
+ int err;
+
+ if (strlen(buf) < 8) {
+ error("Too short string for Gain setting");
+ return -EINVAL;
+ }
+
+ gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10);
+
+ err = telephony_set_gain(slc, gain, buf[5]);
+ if (err < 0 && err != -EALREADY)
+ return err;
+
+ return at_send(slc, "\r\nOK\r\n");
+}
+
+int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int dtmf_tone(struct at_slc *slc, const char *buf)
+{
+ char tone;
+
+ if (strlen(buf) < 8) {
+ error("Too short string for DTMF tone");
+ return -EINVAL;
+ }
+
+ tone = buf[7];
+ if (tone >= '#' && tone <= 'D')
+ telephony_transmit_dtmf_req(slc, tone);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int subscriber_number(struct at_slc *slc, const char *buf)
+{
+ telephony_subscriber_number_req(slc);
+
+ return 0;
+}
+
+int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int list_current_calls(struct at_slc *slc, const char *buf)
+{
+ telephony_list_current_calls_req(slc);
+
+ return 0;
+}
+
+static int extended_errors(struct at_slc *slc, const char *buf)
+{
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] == '1') {
+ slc->cme_enabled = TRUE;
+ DBG("CME errors enabled for headset %p", slc->device->headset);
+ } else {
+ slc->cme_enabled = FALSE;
+ DBG("CME errors disabled for headset %p", slc->device->headset);
+ }
+
+ return at_send(slc, "\r\nOK\r\n");
+}
+
+static int call_waiting_notify(struct at_slc *slc, const char *buf)
+{
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] == '1') {
+ slc->cwa_enabled = TRUE;
+ DBG("Call waiting notification enabled for headset %p",
+ slc->device->headset);
+ } else {
+ slc->cwa_enabled = FALSE;
+ DBG("Call waiting notification disabled for headset %p",
+ slc->device->headset);
+ }
+
+ return at_send(slc, "\r\nOK\r\n");
+}
+
+int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_call_hold_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err)
+{
+ struct at_slc *slc= telephony_device;
+
+ if (err == CME_ERROR_NONE) {
+ headset_set_nrec(slc->device, slc->nrec_req);
+ slc->nrec = slc->nrec_req;
+ }
+
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_operator_selection_ind(int mode, const char *oper)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+COPS: %d,0,\"%s\"\r\n",
+ mode, oper);
+ return 0;
+}
+
+static int operator_selection(struct at_slc *slc, const char *buf)
+{
+ if (strlen(buf) < 8)
+ return -EINVAL;
+
+ switch (buf[7]) {
+ case '?':
+ telephony_operator_selection_req(slc);
+ break;
+ case '=':
+ return at_send(slc, "\r\nOK\r\n");
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nr_and_ec(struct at_slc *slc, const char *buf)
+{
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] == '0')
+ slc->nrec_req = FALSE;
+ else
+ slc->nrec_req = TRUE;
+
+ telephony_nr_and_ec_req(slc, slc->nrec_req);
+
+ return 0;
+}
+
+static int voice_dial(struct at_slc *slc, const char *buf)
+{
+ gboolean enable;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] == '0')
+ enable = FALSE;
+ else
+ enable = TRUE;
+
+ telephony_voice_dial_req(slc, enable);
+
+ return 0;
+}
+
+static int apple_command(struct at_slc *slc, const char *buf)
+{
+ DBG("Got Apple command: %s", buf);
+
+ return telephony_generic_rsp(slc, CME_ERROR_NONE);
+}
+
+static struct event event_callbacks[] = {
+ { "ATA", answer_call },
+ { "ATD", dial_number },
+ { "AT+VG", signal_gain_setting },
+ { "AT+BRSF", supported_features },
+ { "AT+CIND", report_indicators },
+ { "AT+CMER", event_reporting },
+ { "AT+CHLD", call_hold },
+ { "AT+CHUP", terminate_call },
+ { "AT+CKPD", key_press },
+ { "AT+CLIP", cli_notification },
+ { "AT+BTRH", response_and_hold },
+ { "AT+BLDN", last_dialed_number },
+ { "AT+VTS", dtmf_tone },
+ { "AT+CNUM", subscriber_number },
+ { "AT+CLCC", list_current_calls },
+ { "AT+CMEE", extended_errors },
+ { "AT+CCWA", call_waiting_notify },
+ { "AT+COPS", operator_selection },
+ { "AT+NREC", nr_and_ec },
+ { "AT+BVRA", voice_dial },
+ { "AT+XAPL", apple_command },
+ { "AT+IPHONEACCEV", apple_command },
+ { 0 }
+};
+
+static int handle_event(struct at_slc *slc, const char *buf)
+{
+ struct event *ev;
+
+ DBG("Received %s", buf);
+
+ for (ev = event_callbacks; ev->cmd; ev++) {
+ if (!strncmp(buf, ev->cmd, strlen(ev->cmd)))
+ return ev->callback(slc, buf);
+ }
+
+ return -EINVAL;
+}
+
+static gboolean io_cb(GIOChannel *chan, GIOCondition cond, struct at_slc *slc)
+{
+ unsigned char buf[BUF_SIZE];
+ ssize_t bytes_read;
+ size_t free_space;
+ int fd;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_ERR | G_IO_HUP)) {
+ DBG("ERR or HUP on RFCOMM socket");
+ goto failed;
+ }
+
+ fd = g_io_channel_unix_get_fd(chan);
+
+ bytes_read = read(fd, buf, sizeof(buf) - 1);
+ if (bytes_read < 0)
+ return TRUE;
+
+ free_space = sizeof(slc->buf) - slc->data_start -
+ slc->data_length - 1;
+
+ if (free_space < (size_t) bytes_read) {
+ /* Very likely that the HS is sending us garbage so
+ * just ignore the data and disconnect */
+ error("Too much data to fit incomming buffer");
+ goto failed;
+ }
+
+ memcpy(&slc->buf[slc->data_start], buf, bytes_read);
+ slc->data_length += bytes_read;
+
+ /* Make sure the data is null terminated so we can use string
+ * functions */
+ slc->buf[slc->data_start + slc->data_length] = '\0';
+
+ while (slc->data_length > 0) {
+ char *cr;
+ int err;
+ off_t cmd_len;
+
+ cr = strchr(&slc->buf[slc->data_start], '\r');
+ if (!cr)
+ break;
+
+ cmd_len = 1 + (off_t) cr - (off_t) &slc->buf[slc->data_start];
+ *cr = '\0';
+
+ if (cmd_len > 1)
+ err = handle_event(slc, &slc->buf[slc->data_start]);
+ else
+ /* Silently skip empty commands */
+ err = 0;
+
+ if (err == -EINVAL) {
+ error("Badly formated or unrecognized command: %s",
+ &slc->buf[slc->data_start]);
+ err = at_send(slc, "\r\nERROR\r\n");
+ if (err < 0)
+ goto failed;
+ } else if (err < 0)
+ error("Error handling command %s: %s (%d)",
+ &slc->buf[slc->data_start],
+ strerror(-err), -err);
+
+ slc->data_start += cmd_len;
+ slc->data_length -= cmd_len;
+
+ if (!slc->data_length)
+ slc->data_start = 0;
+ }
+
+ return TRUE;
+
+failed:
+ headset_set_state(slc->device, HEADSET_STATE_DISCONNECTED);
+
+ return FALSE;
+}
+
+int telephony_event_ind(int index)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ if (!ag.er_ind) {
+ DBG("telephony_report_event called but events are disabled");
+ return -EINVAL;
+ }
+
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+CIEV: %d,%d\r\n", index + 1,
+ ag.indicators[index].val);
+
+ return 0;
+}
+
+int telephony_response_and_hold_ind(int rh)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ ag.rh = rh;
+
+ /* If we aren't in any response and hold state don't send anything */
+ if (ag.rh < 0)
+ return 0;
+
+ send_foreach_headset(active_devices, hfp_cmp, "\r\n+BTRH: %d\r\n",
+ ag.rh);
+
+ return 0;
+}
+
+int telephony_incoming_call_ind(const char *number, int type)
+{
+ struct audio_device *dev;
+ struct headset *hs;
+ struct at_slc *slc;
+
+ if (fast_connectable)
+ manager_set_fast_connectable(TRUE);
+
+ if (!active_devices)
+ return -ENODEV;
+
+ /* Get the latest connected device */
+ dev = active_devices->data;
+ hs = dev->headset;
+ slc = headset_get_slc(dev);
+
+ if (ag.ring_timer) {
+ DBG("telephony_incoming_call_ind: already calling");
+ return -EBUSY;
+ }
+
+ /* With HSP 1.2 the RING messages should *not* be sent if inband
+ * ringtone is being used */
+ if (!headset_get_hfp_active(slc->device) && slc->inband_ring)
+ return 0;
+
+ g_free(ag.number);
+ ag.number = g_strdup(number);
+ ag.number_type = type;
+
+ if (slc->inband_ring && headset_get_hfp_active(dev) &&
+ headset_get_state(dev) != HEADSET_STATE_PLAYING) {
+ slc->pending_ring = TRUE;
+ return 0;
+ }
+
+ ring_timer_cb(NULL);
+ ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
+ NULL);
+
+ return 0;
+}
+
+int telephony_calling_stopped_ind(void)
+{
+ struct audio_device *dev;
+ struct at_slc *slc;
+
+ if (fast_connectable)
+ manager_set_fast_connectable(FALSE);
+
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ }
+
+ if (!active_devices)
+ return 0;
+
+ /* In case SCO isn't fully up yet */
+ dev = active_devices->data;
+ slc = headset_get_slc(dev);
+
+ if (!slc->pending_ring && !ag.ring_timer)
+ return -EINVAL;
+
+ slc->pending_ring = FALSE;
+
+ return 0;
+}
+
+int telephony_ready_ind(uint32_t features,
+ const struct indicator *indicators, int rh,
+ const char *chld)
+{
+ ag.telephony_ready = TRUE;
+ ag.features = features;
+ ag.indicators = indicators;
+ ag.rh = rh;
+ ag.chld = chld;
+
+ DBG("Telephony plugin initialized");
+
+ print_ag_features(ag.features);
+
+ return 0;
+}
+
+int telephony_deinit(void)
+{
+ g_free(ag.number);
+
+ memset(&ag, 0, sizeof(ag));
+
+ ag.er_mode = 3;
+ ag.rh = BTRH_NOT_SUPPORTED;
+
+ DBG("Telephony deinitialized");
+
+ return 0;
+}
+
+int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
+ int mprty, const char *number,
+ int type)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ if (number && strlen(number) > 0)
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n",
+ idx, dir, status, mode, mprty, number, type);
+ else
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+CLCC: %d,%d,%d,%d,%d\r\n",
+ idx, dir, status, mode, mprty);
+
+ return 0;
+}
+
+int telephony_subscriber_number_ind(const char *number, int type, int service)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+CNUM: ,%s,%d,,%d\r\n",
+ number, type, service);
+
+ return 0;
+}
+
+static int cwa_cmp(struct at_slc *slc)
+{
+ if (!headset_get_hfp_active(slc->device))
+ return -1;
+
+ if (slc->cwa_enabled)
+ return 0;
+ else
+ return -1;
+}
+
+int telephony_call_waiting_ind(const char *number, int type)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ send_foreach_headset(active_devices, cwa_cmp,
+ "\r\n+CCWA: \"%s\",%d\r\n",
+ number, type);
+
+ return 0;
+}
+
+void *telephony_device_connecting(GIOChannel *io, void *telephony_device)
+{
+ struct at_slc *slc;
+
+ slc = g_new0(struct at_slc, 1);
+ slc->sp_gain = 15;
+ slc->mic_gain = 15;
+ slc->nrec = TRUE;
+ slc->device = telephony_device;
+ slc->rfcomm = io;
+
+ g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) io_cb, slc);
+
+ return slc;
+}
+
+void telephony_device_disconnect(void *slc)
+{
+ return;
+}
+
+void telephony_device_set_active(void *telephony_device, gboolean active)
+{
+ if (active)
+ active_devices = g_slist_append(active_devices,
+ telephony_device);
+ else
+ active_devices = g_slist_remove(active_devices,
+ telephony_device);
+}
+
+gboolean telephony_get_inband_ringtone(void *slc)
+{
+ struct at_slc *s = slc;
+
+ return s->inband_ring;
+}
+
+void telephony_set_inband_ringtone(void *slc, gboolean state)
+{
+ struct at_slc *s = slc;
+
+ s->inband_ring = state;
+}
+
+void telephony_start_ring(void *slc)
+{
+ struct at_slc *s = slc;
+
+ ring_timer_cb(NULL);
+ ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
+ NULL);
+ s->pending_ring = FALSE;
+}
+
+gboolean telephony_pending_ring(void *slc)
+{
+ struct at_slc *s = slc;
+
+ return s->pending_ring;
+}
+
+void telephony_stop_ring(void *slc)
+{
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+}
+
+int telephony_get_speaker_gain(void *slc)
+{
+ struct at_slc *s = slc;
+
+ return s->sp_gain;
+}
+
+int telephony_get_mic_gain(void *slc)
+{
+ struct at_slc *s = slc;
+
+ return s->mic_gain;
+}
+
+int telephony_send_gain(void *slc, char type, uint16_t gain)
+{
+ return at_send(slc, "\r\n+VG%c=%u\r\n", type, gain);
+}
+
+int telephony_get_nrec(void *slc)
+{
+ struct at_slc *s = slc;
+
+ return s->nrec;
+}
+
+gboolean telephony_get_ready_state(void)
+{
+ return ag.telephony_ready;
+}
+
+gboolean telephony_is_ringing(void)
+{
+ return ag.ring_timer ? TRUE : FALSE;
+}
+
+uint32_t telephony_get_ag_features(void)
+{
+ return ag.features;
+}
+
+void telephony_set_fast_connectable(gboolean state)
+{
+ fast_connectable = state;
+}
diff --git a/audio/telephony-builtin.h b/audio/telephony-builtin.h
new file mode 100644
index 0000000..f8b2019
--- /dev/null
+++ b/audio/telephony-builtin.h
@@ -0,0 +1,218 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Intel Corporation
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <[email protected]>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <errno.h>
+
+#define HEADSET_GAIN_SPEAKER 'S'
+#define HEADSET_GAIN_MICROPHONE 'M'
+
+/* Response and hold values */
+#define BTRH_NOT_SUPPORTED -2
+#define BTRH_NONE -1
+#define BTRH_HOLD 0
+#define BTRH_ACCEPT 1
+#define BTRH_REJECT 2
+
+/* Indicator event values */
+#define EV_SERVICE_NONE 0
+#define EV_SERVICE_PRESENT 1
+
+#define EV_CALL_INACTIVE 0
+#define EV_CALL_ACTIVE 1
+
+#define EV_CALLSETUP_INACTIVE 0
+#define EV_CALLSETUP_INCOMING 1
+#define EV_CALLSETUP_OUTGOING 2
+#define EV_CALLSETUP_ALERTING 3
+
+#define EV_CALLHELD_NONE 0
+#define EV_CALLHELD_MULTIPLE 1
+#define EV_CALLHELD_ON_HOLD 2
+
+#define EV_ROAM_INACTIVE 0
+#define EV_ROAM_ACTIVE 1
+
+/* Call parameters */
+#define CALL_DIR_OUTGOING 0
+#define CALL_DIR_INCOMING 1
+
+#define CALL_STATUS_ACTIVE 0
+#define CALL_STATUS_HELD 1
+#define CALL_STATUS_DIALING 2
+#define CALL_STATUS_ALERTING 3
+#define CALL_STATUS_INCOMING 4
+#define CALL_STATUS_WAITING 5
+
+#define CALL_MODE_VOICE 0
+#define CALL_MODE_DATA 1
+#define CALL_MODE_FAX 2
+
+#define CALL_MULTIPARTY_NO 0
+#define CALL_MULTIPARTY_YES 1
+
+/* Subscriber number parameters */
+#define SUBSCRIBER_SERVICE_VOICE 4
+#define SUBSCRIBER_SERVICE_FAX 5
+
+/* Operator selection mode values */
+#define OPERATOR_MODE_AUTO 0
+#define OPERATOR_MODE_MANUAL 1
+#define OPERATOR_MODE_DEREGISTER 2
+#define OPERATOR_MODE_MANUAL_AUTO 4
+
+/* Some common number types */
+#define NUMBER_TYPE_UNKNOWN 128
+#define NUMBER_TYPE_TELEPHONY 129
+#define NUMBER_TYPE_INTERNATIONAL 145
+#define NUMBER_TYPE_NATIONAL 161
+#define NUMBER_TYPE_VOIP 255
+
+/* Extended Audio Gateway Error Result Codes */
+typedef enum {
+ CME_ERROR_NONE = -1,
+ CME_ERROR_AG_FAILURE = 0,
+ CME_ERROR_NO_PHONE_CONNECTION = 1,
+ CME_ERROR_NOT_ALLOWED = 3,
+ CME_ERROR_NOT_SUPPORTED = 4,
+ CME_ERROR_PH_SIM_PIN_REQUIRED = 5,
+ CME_ERROR_SIM_NOT_INSERTED = 10,
+ CME_ERROR_SIM_PIN_REQUIRED = 11,
+ CME_ERROR_SIM_PUK_REQUIRED = 12,
+ CME_ERROR_SIM_FAILURE = 13,
+ CME_ERROR_SIM_BUSY = 14,
+ CME_ERROR_INCORRECT_PASSWORD = 16,
+ CME_ERROR_SIM_PIN2_REQUIRED = 17,
+ CME_ERROR_SIM_PUK2_REQUIRED = 18,
+ CME_ERROR_MEMORY_FULL = 20,
+ CME_ERROR_INVALID_INDEX = 21,
+ CME_ERROR_MEMORY_FAILURE = 23,
+ CME_ERROR_TEXT_STRING_TOO_LONG = 24,
+ CME_ERROR_INVALID_TEXT_STRING = 25,
+ CME_ERROR_DIAL_STRING_TOO_LONG = 26,
+ CME_ERROR_INVALID_DIAL_STRING = 27,
+ CME_ERROR_NO_NETWORK_SERVICE = 30,
+ CME_ERROR_NETWORK_TIMEOUT = 31,
+ CME_ERROR_NETWORK_NOT_ALLOWED = 32,
+} cme_error_t;
+
+struct indicator {
+ const char *desc;
+ const char *range;
+ int val;
+ gboolean ignore_redundant;
+};
+
+/* HF requests (sent by the handsfree device). These are implemented by
+ * telephony-*.c
+ */
+void telephony_event_reporting_req(void *telephony_device, int ind);
+void telephony_response_and_hold_req(void *telephony_device, int rh);
+void telephony_last_dialed_number_req(void *telephony_device);
+void telephony_terminate_call_req(void *telephony_device);
+void telephony_answer_call_req(void *telephony_device);
+void telephony_dial_number_req(void *telephony_device, const char *number);
+void telephony_transmit_dtmf_req(void *telephony_device, char tone);
+void telephony_subscriber_number_req(void *telephony_device);
+void telephony_list_current_calls_req(void *telephony_device);
+void telephony_operator_selection_req(void *telephony_device);
+void telephony_call_hold_req(void *telephony_device, const char *cmd);
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable);
+void telephony_voice_dial_req(void *telephony_device, gboolean enable);
+void telephony_key_press_req(void *telephony_device, const char *keys);
+
+/* AG responses to HF requests. These are implemented by telephony-builtin.c */
+int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err);
+int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err);
+int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_terminate_call_rsp(void *telephony_device, cme_error_t err);
+int telephony_answer_call_rsp(void *telephony_device, cme_error_t err);
+int telephony_dial_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err);
+int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err);
+int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err);
+int telephony_call_hold_rsp(void *telephony_device, cme_error_t err);
+int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err);
+int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err);
+int telephony_key_press_rsp(void *telephony_device, cme_error_t err);
+
+/* Event indications by AG. These are implemented by telephony-builtin */
+int telephony_event_ind(int index);
+int telephony_response_and_hold_ind(int rh);
+int telephony_incoming_call_ind(const char *number, int type);
+int telephony_calling_stopped_ind(void);
+int telephony_ready_ind(uint32_t features, const struct indicator *indicators,
+ int rh, const char *chld);
+int telephony_deinit(void);
+int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
+ int mprty, const char *number,
+ int type);
+int telephony_subscriber_number_ind(const char *number, int type,
+ int service);
+int telephony_call_waiting_ind(const char *number, int type);
+int telephony_operator_selection_ind(int mode, const char *oper);
+
+/* Helper function for quick indicator updates */
+static inline int telephony_update_indicator(struct indicator *indicators,
+ const char *desc,
+ int new_val)
+{
+ int i;
+ struct indicator *ind = NULL;
+
+ for (i = 0; indicators[i].desc != NULL; i++) {
+ if (g_str_equal(indicators[i].desc, desc)) {
+ ind = &indicators[i];
+ break;
+ }
+ }
+
+ if (!ind)
+ return -ENOENT;
+
+ DBG("Telephony indicator \"%s\" %d->%d", desc, ind->val, new_val);
+
+ if (ind->ignore_redundant && ind->val == new_val) {
+ DBG("Ignoring no-change indication");
+ return 0;
+ }
+
+ ind->val = new_val;
+
+ return telephony_event_ind(i);
+}
+
+static inline int telephony_get_indicator(const struct indicator *indicators,
+ const char *desc)
+{
+ int i;
+
+ for (i = 0; indicators[i].desc != NULL; i++) {
+ if (g_str_equal(indicators[i].desc, desc))
+ return indicators[i].val;
+ }
+
+ return -ENOENT;
+}
diff --git a/audio/telephony-dummy.c b/audio/telephony-dummy.c
index 1f89079..657e2d3 100644
--- a/audio/telephony-dummy.c
+++ b/audio/telephony-dummy.c
@@ -35,6 +35,7 @@

#include "log.h"
#include "telephony.h"
+#include "telephony-builtin.h"
#include "error.h"

#define TELEPHONY_DUMMY_IFACE "org.bluez.TelephonyTest"
diff --git a/audio/telephony-maemo5.c b/audio/telephony-maemo5.c
index 23801c0..51c0985 100644
--- a/audio/telephony-maemo5.c
+++ b/audio/telephony-maemo5.c
@@ -38,6 +38,7 @@

#include "log.h"
#include "telephony.h"
+#include "telephony-builtin.h"
#include "error.h"

/* SSC D-Bus definitions */
diff --git a/audio/telephony-maemo6.c b/audio/telephony-maemo6.c
index 5df3235..cd6856d 100644
--- a/audio/telephony-maemo6.c
+++ b/audio/telephony-maemo6.c
@@ -41,6 +41,7 @@
#include "glib-compat.h"
#include "log.h"
#include "telephony.h"
+#include "telephony-builtin.h"
#include "error.h"

/* SSC D-Bus definitions */
diff --git a/audio/telephony-ofono.c b/audio/telephony-ofono.c
index 3607d7f..4cb4aa5 100644
--- a/audio/telephony-ofono.c
+++ b/audio/telephony-ofono.c
@@ -40,6 +40,7 @@
#include "glib-compat.h"
#include "log.h"
#include "telephony.h"
+#include "telephony-builtin.h"

enum net_registration_status {
NETWORK_REG_STATUS_HOME = 0x00,
diff --git a/audio/telephony.h b/audio/telephony.h
index 73b390c..8043267 100644
--- a/audio/telephony.h
+++ b/audio/telephony.h
@@ -26,13 +26,6 @@
#include <errno.h>
#include <glib.h>

-/* Response and hold values */
-#define BTRH_NOT_SUPPORTED -2
-#define BTRH_NONE -1
-#define BTRH_HOLD 0
-#define BTRH_ACCEPT 1
-#define BTRH_REJECT 2
-
/* HFP feature bits */
#define AG_FEATURE_THREE_WAY_CALLING 0x0001
#define AG_FEATURE_EC_ANDOR_NR 0x0002
@@ -52,193 +45,29 @@
#define HF_FEATURE_ENHANCED_CALL_STATUS 0x0020
#define HF_FEATURE_ENHANCED_CALL_CONTROL 0x0040

-/* Indicator event values */
-#define EV_SERVICE_NONE 0
-#define EV_SERVICE_PRESENT 1
-
-#define EV_CALL_INACTIVE 0
-#define EV_CALL_ACTIVE 1
-
-#define EV_CALLSETUP_INACTIVE 0
-#define EV_CALLSETUP_INCOMING 1
-#define EV_CALLSETUP_OUTGOING 2
-#define EV_CALLSETUP_ALERTING 3
-
-#define EV_CALLHELD_NONE 0
-#define EV_CALLHELD_MULTIPLE 1
-#define EV_CALLHELD_ON_HOLD 2
-
-#define EV_ROAM_INACTIVE 0
-#define EV_ROAM_ACTIVE 1
-
-/* Call parameters */
-#define CALL_DIR_OUTGOING 0
-#define CALL_DIR_INCOMING 1
-
-#define CALL_STATUS_ACTIVE 0
-#define CALL_STATUS_HELD 1
-#define CALL_STATUS_DIALING 2
-#define CALL_STATUS_ALERTING 3
-#define CALL_STATUS_INCOMING 4
-#define CALL_STATUS_WAITING 5
-
-#define CALL_MODE_VOICE 0
-#define CALL_MODE_DATA 1
-#define CALL_MODE_FAX 2
-
-#define CALL_MULTIPARTY_NO 0
-#define CALL_MULTIPARTY_YES 1
-
-/* Subscriber number parameters */
-#define SUBSCRIBER_SERVICE_VOICE 4
-#define SUBSCRIBER_SERVICE_FAX 5
-
-/* Operator selection mode values */
-#define OPERATOR_MODE_AUTO 0
-#define OPERATOR_MODE_MANUAL 1
-#define OPERATOR_MODE_DEREGISTER 2
-#define OPERATOR_MODE_MANUAL_AUTO 4
-
-/* Some common number types */
-#define NUMBER_TYPE_UNKNOWN 128
-#define NUMBER_TYPE_TELEPHONY 129
-#define NUMBER_TYPE_INTERNATIONAL 145
-#define NUMBER_TYPE_NATIONAL 161
-#define NUMBER_TYPE_VOIP 255
-
-/* Extended Audio Gateway Error Result Codes */
-typedef enum {
- CME_ERROR_NONE = -1,
- CME_ERROR_AG_FAILURE = 0,
- CME_ERROR_NO_PHONE_CONNECTION = 1,
- CME_ERROR_NOT_ALLOWED = 3,
- CME_ERROR_NOT_SUPPORTED = 4,
- CME_ERROR_PH_SIM_PIN_REQUIRED = 5,
- CME_ERROR_SIM_NOT_INSERTED = 10,
- CME_ERROR_SIM_PIN_REQUIRED = 11,
- CME_ERROR_SIM_PUK_REQUIRED = 12,
- CME_ERROR_SIM_FAILURE = 13,
- CME_ERROR_SIM_BUSY = 14,
- CME_ERROR_INCORRECT_PASSWORD = 16,
- CME_ERROR_SIM_PIN2_REQUIRED = 17,
- CME_ERROR_SIM_PUK2_REQUIRED = 18,
- CME_ERROR_MEMORY_FULL = 20,
- CME_ERROR_INVALID_INDEX = 21,
- CME_ERROR_MEMORY_FAILURE = 23,
- CME_ERROR_TEXT_STRING_TOO_LONG = 24,
- CME_ERROR_INVALID_TEXT_STRING = 25,
- CME_ERROR_DIAL_STRING_TOO_LONG = 26,
- CME_ERROR_INVALID_DIAL_STRING = 27,
- CME_ERROR_NO_NETWORK_SERVICE = 30,
- CME_ERROR_NETWORK_TIMEOUT = 31,
- CME_ERROR_NETWORK_NOT_ALLOWED = 32,
-} cme_error_t;
-
-struct indicator {
- const char *desc;
- const char *range;
- int val;
- gboolean ignore_redundant;
-};
-
/* Notify telephony-*.c of connected/disconnected devices. Implemented by
* telephony-*.c
*/
+void *telephony_device_connecting(GIOChannel *io, void *telephony_device);
void telephony_device_connected(void *telephony_device);
+void telephony_device_disconnect(void *slc);
void telephony_device_disconnected(void *telephony_device);
-
-/* HF requests (sent by the handsfree device). These are implemented by
- * telephony-*.c
- */
-void telephony_event_reporting_req(void *telephony_device, int ind);
-void telephony_response_and_hold_req(void *telephony_device, int rh);
-void telephony_last_dialed_number_req(void *telephony_device);
-void telephony_terminate_call_req(void *telephony_device);
-void telephony_answer_call_req(void *telephony_device);
-void telephony_dial_number_req(void *telephony_device, const char *number);
-void telephony_transmit_dtmf_req(void *telephony_device, char tone);
-void telephony_subscriber_number_req(void *telephony_device);
-void telephony_list_current_calls_req(void *telephony_device);
-void telephony_operator_selection_req(void *telephony_device);
-void telephony_call_hold_req(void *telephony_device, const char *cmd);
-void telephony_nr_and_ec_req(void *telephony_device, gboolean enable);
-void telephony_voice_dial_req(void *telephony_device, gboolean enable);
-void telephony_key_press_req(void *telephony_device, const char *keys);
-
-/* AG responses to HF requests. These are implemented by headset.c */
-int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err);
-int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err);
-int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err);
-int telephony_terminate_call_rsp(void *telephony_device, cme_error_t err);
-int telephony_answer_call_rsp(void *telephony_device, cme_error_t err);
-int telephony_dial_number_rsp(void *telephony_device, cme_error_t err);
-int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err);
-int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err);
-int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err);
-int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err);
-int telephony_call_hold_rsp(void *telephony_device, cme_error_t err);
-int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err);
-int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err);
-int telephony_key_press_rsp(void *telephony_device, cme_error_t err);
-
-/* Event indications by AG. These are implemented by headset.c */
-int telephony_event_ind(int index);
-int telephony_response_and_hold_ind(int rh);
-int telephony_incoming_call_ind(const char *number, int type);
-int telephony_calling_stopped_ind(void);
-int telephony_ready_ind(uint32_t features, const struct indicator *indicators,
- int rh, const char *chld);
-int telephony_deinit(void);
-int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
- int mprty, const char *number,
- int type);
-int telephony_subscriber_number_ind(const char *number, int type,
- int service);
-int telephony_call_waiting_ind(const char *number, int type);
-int telephony_operator_selection_ind(int mode, const char *oper);
-
-/* Helper function for quick indicator updates */
-static inline int telephony_update_indicator(struct indicator *indicators,
- const char *desc,
- int new_val)
-{
- int i;
- struct indicator *ind = NULL;
-
- for (i = 0; indicators[i].desc != NULL; i++) {
- if (g_str_equal(indicators[i].desc, desc)) {
- ind = &indicators[i];
- break;
- }
- }
-
- if (!ind)
- return -ENOENT;
-
- DBG("Telephony indicator \"%s\" %d->%d", desc, ind->val, new_val);
-
- if (ind->ignore_redundant && ind->val == new_val) {
- DBG("Ignoring no-change indication");
- return 0;
- }
-
- ind->val = new_val;
-
- return telephony_event_ind(i);
-}
-
-static inline int telephony_get_indicator(const struct indicator *indicators,
- const char *desc)
-{
- int i;
-
- for (i = 0; indicators[i].desc != NULL; i++) {
- if (g_str_equal(indicators[i].desc, desc))
- return indicators[i].val;
- }
-
- return -ENOENT;
-}
+void telephony_device_set_active(void *telephony_device, gboolean active);
+
+gboolean telephony_get_ready_state(void);
+gboolean telephony_pending_ring(void *slc);
+void telephony_start_ring(void *slc);
+void telephony_stop_ring(void *slc);
+gboolean telephony_is_ringing(void);
+uint32_t telephony_get_ag_features(void);
+void telephony_set_fast_connectable(gboolean state);
+gboolean telephony_get_nrec(void *slc);
+gboolean telephony_get_inband_ringtone(void *slc);
+void telephony_set_inband_ringtone(void *slc, gboolean state);
+int telephony_get_speaker_gain(void *slc);
+int telephony_get_mic_gain(void *slc);
+int telephony_send_gain(void *slc, char type, uint16_t gain);
+int telephony_set_gain(void *slc, uint16_t gain, char type);

int telephony_init(void);
void telephony_exit(void);
--
1.7.1


2011-10-20 13:52:18

by Frederic Danis

[permalink] [raw]
Subject: [PATCH 4/4] Audio: add dbus telephony driver

---
Makefile.am | 4 +-
audio/telephony-dbus.c | 554 ++++++++++++++++++++++++++++++++++++++++++++++++
doc/audio-api.txt | 99 +++++++++
3 files changed, 655 insertions(+), 2 deletions(-)
create mode 100644 audio/telephony-dbus.c

diff --git a/Makefile.am b/Makefile.am
index d528c84..f4aeac1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -165,7 +165,7 @@ noinst_LIBRARIES += audio/libtelephony.a

audio_libtelephony_a_SOURCES = audio/telephony.h audio/telephony-dummy.c \
audio/telephony-maemo5.c audio/telephony-ofono.c \
- audio/telephony-maemo6.c
+ audio/telephony-maemo6.c audio/telephony-dbus.c
endif

if SAPPLUGIN
@@ -345,7 +345,7 @@ EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
audio/telephony-maemo5.c audio/telephony-ofono.c \
audio/telephony-maemo6.c sap/sap-dummy.c sap/sap-u8500.c \
proximity/proximity.conf audio/telephony-builtin.h \
- audio/telephony-builtin.c
+ audio/telephony-builtin.c audio/telephony-dbus.c

if ALSA
alsadir = $(libdir)/alsa-lib
diff --git a/audio/telephony-dbus.c b/audio/telephony-dbus.c
new file mode 100644
index 0000000..59e26bf
--- /dev/null
+++ b/audio/telephony-dbus.c
@@ -0,0 +1,554 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Intel Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <[email protected]>
+ * Copyright (C) 2011 Frederic Danis <[email protected]>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "btio.h"
+#include "log.h"
+#include "device.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "sdp-client.h"
+#include "headset.h"
+#include "telephony.h"
+#include "dbus-common.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#define AUDIO_TELEPHONY_INTERFACE "org.bluez.Telephony"
+
+struct telsrv {
+ GSList *servers; /* server list */
+};
+
+struct tel_device {
+ struct tel_agent *agent;
+ struct audio_device *au_dev;
+ GIOChannel *rfcomm;
+ uint16_t version;
+ uint16_t features;
+};
+
+struct tel_agent {
+ char *name; /* agent DBus bus id */
+ char *path; /* agent object path */
+ const char *uuid; /* agent property UUID */
+ uint16_t version;
+ uint8_t features;
+ uint16_t r_class;
+ uint16_t r_profile;
+};
+
+static DBusConnection *connection = NULL;
+
+struct telsrv telsrv;
+
+static struct tel_agent *find_agent(const char *sender, const char *path,
+ const char *uuid)
+{
+ GSList *l;
+
+ for (l = telsrv.servers; l; l = l->next) {
+ struct tel_agent *agent = l->data;
+
+ if (sender && g_strcmp0(agent->name, sender) != 0)
+ continue;
+
+ if (path && g_strcmp0(agent->path, path) != 0)
+ continue;
+
+ if (uuid && g_strcmp0(agent->uuid, uuid) != 0)
+ continue;
+
+ return agent;
+ }
+
+ return NULL;
+}
+
+static int parse_properties(DBusMessageIter *props, const char **uuid,
+ uint16_t *version, uint8_t *features)
+{
+ gboolean has_uuid = FALSE;
+
+ while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
+ const char *key;
+ DBusMessageIter value, entry;
+ int var;
+
+ dbus_message_iter_recurse(props, &entry);
+ dbus_message_iter_get_basic(&entry, &key);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_recurse(&entry, &value);
+
+ var = dbus_message_iter_get_arg_type(&value);
+ if (strcasecmp(key, "UUID") == 0) {
+ if (var != DBUS_TYPE_STRING)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, uuid);
+ has_uuid = TRUE;
+ } else if (strcasecmp(key, "Version") == 0) {
+ if (var != DBUS_TYPE_UINT16)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, version);
+ } else if (strcasecmp(key, "Features") == 0) {
+ if (var != DBUS_TYPE_BYTE)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, features);
+ }
+
+ dbus_message_iter_next(props);
+ }
+
+ return (has_uuid) ? 0 : -EINVAL;
+}
+
+static int dev_close(struct tel_device *dev)
+{
+ int sock;
+
+ if (dev->rfcomm) {
+ sock = g_io_channel_unix_get_fd(dev->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+ }
+
+ return 0;
+}
+
+static gboolean agent_sendfd(struct tel_device *dev, int fd,
+ DBusPendingCallNotifyFunction notify)
+{
+ struct tel_agent *agent = dev->agent;
+ DBusMessage *msg;
+ DBusMessageIter iter, dict;
+ char *str;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.TelephonyAgent", "NewConnection");
+
+ dbus_message_iter_init_append(msg, &iter);
+
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_UNIX_FD, &fd);
+
+ 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);
+
+ str = g_strdup(agent->uuid);
+ dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &str);
+ g_free(str);
+
+ dict_append_entry(&dict, "Version", DBUS_TYPE_UINT16, &dev->version);
+
+ if (dev->features != 0xFFFF)
+ dict_append_entry(&dict, "Features", DBUS_TYPE_BYTE,
+ &dev->features);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ if (dbus_connection_send_with_reply(connection, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+ dbus_message_unref(msg);
+
+ return TRUE;
+}
+
+static gboolean agent_disconnect_cb(GIOChannel *chan, GIOCondition cond,
+ struct tel_device *dev)
+{
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ headset_set_state(dev->au_dev, HEADSET_STATE_DISCONNECTED);
+
+ return FALSE;
+}
+
+static void newconnection_reply(DBusPendingCall *call, void *user_data)
+{
+ struct tel_device *dev = user_data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->rfcomm) {
+ DBG("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ DBG("Agent reply: file descriptor passed successfully");
+ g_io_add_watch(dev->rfcomm, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) agent_disconnect_cb, dev);
+ headset_slc_complete(dev->au_dev);
+ goto done;
+ }
+
+ DBG("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ dev_close(dev);
+ headset_set_state(dev->au_dev, HEADSET_STATE_DISCONNECTED);
+
+done:
+ dbus_message_unref(reply);
+}
+
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct tel_device *dev = user_data;
+ sdp_data_t *sdpdata;
+ uuid_t uuid;
+ sdp_list_t *profiles;
+ sdp_profile_desc_t *desc;
+ int sk, ret;
+
+ if (err < 0) {
+ error("Unable to get service record: %s (%d)", strerror(-err),
+ -err);
+ goto failed;
+ }
+
+ if (!recs || !recs->data) {
+ error("No records found");
+ goto failed;
+ }
+
+ sdpdata = sdp_data_get(recs->data, SDP_ATTR_SUPPORTED_FEATURES);
+ if (sdpdata && sdpdata->dtd == SDP_UINT16)
+ dev->features = sdpdata->val.uint16;
+
+ sdp_uuid16_create(&uuid, dev->agent->r_profile);
+
+ sdp_get_profile_descs(recs->data, &profiles);
+ if (profiles == NULL)
+ goto failed;
+
+ desc = profiles->data;
+
+ if (sdp_uuid16_cmp(&desc->uuid, &uuid) == 0)
+ dev->version = desc->version;
+
+ sdp_list_free(profiles, free);
+
+ sk = g_io_channel_unix_get_fd(dev->rfcomm);
+
+ ret = agent_sendfd(dev, sk, newconnection_reply);
+
+ return;
+
+failed:
+ headset_set_state(dev->au_dev, HEADSET_STATE_DISCONNECTED);
+}
+
+void *telephony_device_connecting(GIOChannel *io, void *telephony_device)
+{
+ struct audio_device *device = telephony_device;
+ struct tel_device *dev;
+ const char *agent_uuid;
+ struct tel_agent *agent;
+ uuid_t uuid;
+ int err;
+
+ /*TODO: check for HS roles */
+ if (headset_get_hfp_active(device))
+ agent_uuid = HFP_AG_UUID;
+ else
+ agent_uuid = HSP_AG_UUID;
+
+ agent = find_agent(NULL, NULL, agent_uuid);
+ if (agent == NULL) {
+ error("No agent registered for %s", agent_uuid);
+ return NULL;
+ }
+
+ dev = g_new0(struct tel_device, 1);
+ dev->agent = agent;
+ dev->au_dev = telephony_device;
+ dev->rfcomm = io;
+ dev->features = 0xFFFF;
+
+ sdp_uuid16_create(&uuid, agent->r_class);
+
+ err = bt_search_service(&device->src, &device->dst, &uuid,
+ get_record_cb, dev, NULL);
+ if (err < 0) {
+ g_free(dev);
+ return NULL;
+ }
+
+ return dev;
+}
+
+void telephony_device_set_active(void *telephony_device, gboolean active)
+{
+ return;
+}
+
+void telephony_device_connected(void *telephony_device)
+{
+ DBG("telephony-dbus: device %p connected", telephony_device);
+}
+
+void telephony_device_disconnect(void *slc)
+{
+ struct tel_device *dev = slc;
+
+ dev_close(dev);
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+ DBG("telephony-dbus: device %p disconnected", telephony_device);
+}
+
+gboolean telephony_get_ready_state(void)
+{
+ return find_agent(NULL, NULL, HFP_AG_UUID) ? TRUE : FALSE;
+}
+
+gboolean telephony_pending_ring(void *slc)
+{
+ return FALSE;
+}
+
+void telephony_start_ring(void *slc)
+{
+ return;
+}
+
+uint32_t telephony_get_ag_features(void)
+{
+ return 0;
+}
+
+void telephony_set_fast_connectable(gboolean state)
+{
+ return;
+}
+
+gboolean telephony_get_nrec(void *slc)
+{
+ return FALSE;
+}
+
+gboolean telephony_get_inband_ringtone(void *slc)
+{
+ return FALSE;
+}
+
+void telephony_set_inband_ringtone(void *slc, gboolean state)
+{
+ return;
+}
+
+int telephony_get_speaker_gain(void *slc)
+{
+ return -1;
+}
+
+int telephony_get_mic_gain(void *slc)
+{
+ return -1;
+}
+
+int telephony_send_speaker_gain(void *slc, uint16_t gain)
+{
+ return -1;
+}
+
+int telephony_send_mic_gain(void *slc, uint16_t gain)
+{
+ return -1;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter args, props;
+ const char *sender, *path, *uuid;
+ uint16_t version = 0;
+ uint8_t features = 0;
+ uint16_t r_class, r_profile;
+ struct tel_agent *agent;
+
+ sender = dbus_message_get_sender(msg);
+
+ dbus_message_iter_init(msg, &args);
+
+ dbus_message_iter_get_basic(&args, &path);
+ dbus_message_iter_next(&args);
+
+ if (find_agent(sender, path, NULL) != NULL)
+ return btd_error_already_exists(msg);
+
+ dbus_message_iter_recurse(&args, &props);
+ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+ return btd_error_invalid_args(msg);
+
+ if (parse_properties(&props, &uuid, &version, &features) < 0)
+ return btd_error_invalid_args(msg);
+
+ if (strcasecmp(uuid, HSP_HS_UUID) == 0) {
+ r_class = HEADSET_AGW_SVCLASS_ID;
+ r_profile = HEADSET_PROFILE_ID;
+ } else if (strcasecmp(uuid, HSP_AG_UUID) == 0) {
+ r_class = HEADSET_SVCLASS_ID;
+ r_profile = HEADSET_PROFILE_ID;
+ } else if (strcasecmp(uuid, HFP_HS_UUID) == 0) {
+ r_class = HANDSFREE_AGW_SVCLASS_ID;
+ r_profile = HANDSFREE_PROFILE_ID;
+ } else if (strcasecmp(uuid, HFP_AG_UUID) == 0) {
+ r_class = HANDSFREE_SVCLASS_ID;
+ r_profile = HANDSFREE_PROFILE_ID;
+ } else
+ return btd_error_invalid_args(msg);
+
+ if (find_agent(NULL, NULL, uuid) != NULL)
+ return btd_error_already_exists(msg);
+
+ DBG("Register agent : %s%s for %s version 0x%04X with features 0x%02X",
+ sender, path, uuid, version, features);
+
+ agent = g_new0(struct tel_agent, 1);
+ agent->name = g_strdup(sender);
+ agent->path = g_strdup(path);
+ agent->uuid = g_strdup(uuid);
+ agent->version = version;
+ agent->features = features;
+ agent->r_class = r_class;
+ agent->r_profile = r_profile;
+
+ telsrv.servers = g_slist_append(telsrv.servers, agent);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *sender, *path;
+ struct tel_agent *agent;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ sender = dbus_message_get_sender(msg);
+
+ agent = find_agent(sender, path, NULL);
+ if (agent == NULL)
+ return btd_error_does_not_exist(msg);
+
+ telsrv.servers = g_slist_remove(telsrv.servers, agent);
+
+ DBG("Unregister agent : %s%s", sender, path);
+
+ g_free(agent);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static GDBusMethodTable telsrv_methods[] = {
+ { "RegisterAgent", "oa{sv}", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
+ { NULL, NULL, NULL, NULL }
+};
+
+static void path_unregister(void *data)
+{
+ DBG("Unregistered interface %s", AUDIO_TELEPHONY_INTERFACE);
+}
+
+static int register_interface(void *adapter)
+{
+ const char *path;
+
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return -1;
+
+ path = adapter_get_path(adapter);
+
+ if (!g_dbus_register_interface(connection, path,
+ AUDIO_TELEPHONY_INTERFACE,
+ telsrv_methods, NULL,
+ NULL, NULL, path_unregister)) {
+ error("D-Bus failed to register %s interface",
+ AUDIO_TELEPHONY_INTERFACE);
+ return -1;
+ }
+
+ DBG("Registered interface %s", AUDIO_TELEPHONY_INTERFACE);
+
+ return 0;
+}
+
+static void unregister_interface(void *adapter)
+{
+ g_dbus_unregister_interface(connection, adapter_get_path(adapter),
+ AUDIO_TELEPHONY_INTERFACE);
+}
+
+int telephony_init(void *adapter)
+{
+ DBG("");
+
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+ return register_interface(adapter);
+}
+
+void telephony_exit(void *adapter)
+{
+ DBG("");
+
+ unregister_interface(adapter);
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+}
+
+struct TelephonyDBusTable *telephony_dbus_table(void)
+{
+ return NULL;
+}
diff --git a/doc/audio-api.txt b/doc/audio-api.txt
index b85400b..d32b51d 100644
--- a/doc/audio-api.txt
+++ b/doc/audio-api.txt
@@ -456,3 +456,102 @@ properties boolean Connected [readonly]
uint16 MicrophoneGain [readonly]

The speaker gain when available.
+
+
+Telephony hierarchy [experiemental]
+===================
+
+Service org.bluez
+Interface org.bluez.Telephony
+Object path [variable prefix]/{hci0,hci1,...}
+
+Methods void RegisterAgent(object path, dict properties)
+
+ Register a TelephonyAgent to sender, the sender can
+ register as many agents as it likes.
+
+ Note: If the sender disconnects its agents are
+ automatically unregistered.
+
+ possible properties:
+
+ string UUID:
+
+ UUID of the profile which the agent is
+ for.
+
+ uint16 Version:
+
+ Version of the profile which the agent
+ implements.
+
+ byte Features:
+
+ Agent supported features as defined in
+ profile spec e.g. HFP.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+
+
+ void UnregisterAgent(object path)
+
+ Unregister sender agent.
+
+TelephonyAgent hierarchy
+========================
+
+Service unique name
+Interface org.bluez.TelephonyAgent
+Object path freely definable
+
+ethods void NewConnection(filedescriptor fd, dict properties)
+
+ This method gets called whenever a new connection
+ has been established. 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.
+
+ If Endpoint is set the agent is responsible to
+ create an object implementing org.bluez.MediaTransport
+ and notify the Endpoint using org.bluez.MediaEndpoint.
+
+ possible properties:
+
+ strict Device:
+
+ BlueZ remote device object.
+
+ string UUID:
+
+ Profile UUID of the connection.
+
+ uint16 Version:
+
+ Remote profile version.
+
+ byte Features:
+
+ Remote profile features.
+
+ string Endpoint:
+
+ Optional. Endpoint bus id.
+
+ string EndpointPath:
+
+ Optional. Endpoint object path.
+
+ 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 TelephonyAgent registers itself is removed.
--
1.7.1


2011-10-20 13:52:17

by Frederic Danis

[permalink] [raw]
Subject: [PATCH 3/4] Audio: add adapter to telephony_init/_exit

---
audio/manager.c | 4 ++--
audio/telephony-dummy.c | 4 ++--
audio/telephony-maemo5.c | 4 ++--
audio/telephony-maemo6.c | 4 ++--
audio/telephony-ofono.c | 4 ++--
audio/telephony.h | 4 ++--
6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/audio/manager.c b/audio/manager.c
index 7ec0311..993c064 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -881,7 +881,7 @@ static void state_changed(struct btd_adapter *adapter, gboolean powered)
/* telephony driver already initialized*/
if (telephony == TRUE)
return;
- telephony_init();
+ telephony_init(adapter);
telephony = TRUE;
return;
}
@@ -897,7 +897,7 @@ static void state_changed(struct btd_adapter *adapter, gboolean powered)
return;
}

- telephony_exit();
+ telephony_exit(adapter);
telephony = FALSE;
}

diff --git a/audio/telephony-dummy.c b/audio/telephony-dummy.c
index 657e2d3..ae1d5f0 100644
--- a/audio/telephony-dummy.c
+++ b/audio/telephony-dummy.c
@@ -396,7 +396,7 @@ static GDBusSignalTable dummy_signals[] = {
{ }
};

-int telephony_init(void)
+int telephony_init(void *adapter)
{
uint32_t features = AG_FEATURE_REJECT_A_CALL |
AG_FEATURE_ENHANCED_CALL_STATUS |
@@ -421,7 +421,7 @@ int telephony_init(void)
return 0;
}

-void telephony_exit(void)
+void telephony_exit(void *adapter)
{
DBG("");

diff --git a/audio/telephony-maemo5.c b/audio/telephony-maemo5.c
index 51c0985..bb5b1e5 100644
--- a/audio/telephony-maemo5.c
+++ b/audio/telephony-maemo5.c
@@ -2029,7 +2029,7 @@ static DBusHandlerResult signal_filter(DBusConnection *conn,
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

-int telephony_init(void)
+int telephony_init(void *adapter)
{
const char *battery_cap = "battery";
uint32_t features = AG_FEATURE_EC_ANDOR_NR |
@@ -2090,7 +2090,7 @@ int telephony_init(void)
return 0;
}

-void telephony_exit(void)
+void telephony_exit(void *adapter)
{
g_slist_foreach(calls, (GFunc) csd_call_free, NULL);
g_slist_free(calls);
diff --git a/audio/telephony-maemo6.c b/audio/telephony-maemo6.c
index cd6856d..d496dbe 100644
--- a/audio/telephony-maemo6.c
+++ b/audio/telephony-maemo6.c
@@ -2110,7 +2110,7 @@ done:
remove_pending(call);
}

-int telephony_init(void)
+int telephony_init(void *adapter)
{
const char *battery_cap = "battery";
uint32_t features = AG_FEATURE_EC_ANDOR_NR |
@@ -2166,7 +2166,7 @@ static void remove_watch(gpointer data)
g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data));
}

-void telephony_exit(void)
+void telephony_exit(void *adapter)
{
DBG("");

diff --git a/audio/telephony-ofono.c b/audio/telephony-ofono.c
index 4cb4aa5..9ea9874 100644
--- a/audio/telephony-ofono.c
+++ b/audio/telephony-ofono.c
@@ -1546,7 +1546,7 @@ static void handle_service_disconnect(DBusConnection *conn, void *user_data)
modem_removed(modem_obj_path);
}

-int telephony_init(void)
+int telephony_init(void *adapter)
{
uint32_t features = AG_FEATURE_EC_ANDOR_NR |
AG_FEATURE_INBAND_RINGTONE |
@@ -1616,7 +1616,7 @@ static void pending_free(void *data)
dbus_pending_call_unref(call);
}

-void telephony_exit(void)
+void telephony_exit(void *adapter)
{
DBG("");

diff --git a/audio/telephony.h b/audio/telephony.h
index ecd6d09..64a8bd8 100644
--- a/audio/telephony.h
+++ b/audio/telephony.h
@@ -85,5 +85,5 @@ struct TelephonyDBusTable {

struct TelephonyDBusTable *telephony_dbus_table(void);

-int telephony_init(void);
-void telephony_exit(void);
+int telephony_init(void *adapter);
+void telephony_exit(void *adapter);
--
1.7.1


2011-10-20 13:52:16

by Frederic Danis

[permalink] [raw]
Subject: [PATCH 2/4] Audio: move dbus callbacks to telephony-builtin.c

---
audio/headset.c | 231 ++++++------------------------------
audio/telephony-builtin.c | 291 ++++++++++++++++++++++++++++++++++++++++++++-
audio/telephony.h | 24 +++-
3 files changed, 344 insertions(+), 202 deletions(-)

diff --git a/audio/headset.c b/audio/headset.c
index 8bec0ed..25e7bf8 100644
--- a/audio/headset.c
+++ b/audio/headset.c
@@ -59,7 +59,6 @@
#include "dbus-common.h"
#include "../src/adapter.h"
#include "../src/device.h"
-#include "telephony-builtin.h"

#define DC_TIMEOUT 3

@@ -126,6 +125,7 @@ struct headset {
headset_lock_t lock;
void *slc;
GSList *nrec_cbs;
+ struct TelephonyDBusTable *dbus_cb;
};

static GSList *headset_callbacks = NULL;
@@ -774,34 +774,11 @@ static DBusMessage *hs_ring(DBusConnection *conn, DBusMessage *msg,
{
struct audio_device *device = data;
struct headset *hs = device->headset;
- DBusMessage *reply = NULL;
-#if 0
- int err;
-#endif
-
- if (hs->state < HEADSET_STATE_CONNECTED)
- return btd_error_not_connected(msg);
-
- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;
-
- if (telephony_is_ringing()) {
- DBG("IndicateCall received when already indicating");
- return reply;
- }
-
-#if 0
- err = headset_send(hs, "\r\nRING\r\n");
- if (err < 0) {
- dbus_message_unref(reply);
- return btd_error_failed(msg, strerror(-err));
- }
-#endif

- telephony_start_ring(hs->slc);
+ if (hs->dbus_cb == NULL || hs->dbus_cb->indicate_call == NULL)
+ return btd_error_not_supported(msg);

- return reply;
+ return hs->dbus_cb->indicate_call(conn, msg, hs->slc, hs->state);
}

static DBusMessage *hs_cancel_call(DBusConnection *conn,
@@ -810,21 +787,11 @@ static DBusMessage *hs_cancel_call(DBusConnection *conn,
{
struct audio_device *device = data;
struct headset *hs = device->headset;
- DBusMessage *reply = NULL;

- if (hs->state < HEADSET_STATE_CONNECTED)
- return btd_error_not_connected(msg);
+ if (hs->dbus_cb == NULL || hs->dbus_cb->cancel_call == NULL)
+ return btd_error_not_supported(msg);

- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;
-
- if (telephony_is_ringing())
- telephony_stop_ring(hs->slc);
- else
- DBG("Got CancelCall method call but no call is active");
-
- return reply;
+ return hs->dbus_cb->cancel_call(conn, msg, hs->slc, hs->state);
}

static DBusMessage *hs_play(DBusConnection *conn, DBusMessage *msg,
@@ -872,22 +839,11 @@ static DBusMessage *hs_get_speaker_gain(DBusConnection *conn,
{
struct audio_device *device = data;
struct headset *hs = device->headset;
- DBusMessage *reply;
- dbus_uint16_t gain;
-
- if (hs->state < HEADSET_STATE_CONNECTED)
- return btd_error_not_available(msg);
-
- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;

- gain = (dbus_uint16_t) telephony_get_speaker_gain(hs->slc);
+ if (hs->dbus_cb == NULL || hs->dbus_cb->get_speaker_gain == NULL)
+ return btd_error_not_supported(msg);

- dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
- DBUS_TYPE_INVALID);
-
- return reply;
+ return hs->dbus_cb->get_speaker_gain(conn, msg, hs->slc, hs->state);
}

static DBusMessage *hs_get_mic_gain(DBusConnection *conn,
@@ -896,174 +852,61 @@ static DBusMessage *hs_get_mic_gain(DBusConnection *conn,
{
struct audio_device *device = data;
struct headset *hs = device->headset;
- DBusMessage *reply;
- dbus_uint16_t gain;
-
- if (hs->state < HEADSET_STATE_CONNECTED || hs->slc == NULL)
- return btd_error_not_available(msg);

- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;
-
- gain = (dbus_uint16_t) telephony_get_mic_gain(hs->slc);
+ if (hs->dbus_cb == NULL || hs->dbus_cb->get_mic_gain == NULL)
+ return btd_error_not_supported(msg);

- dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
- DBUS_TYPE_INVALID);
-
- return reply;
-}
-
-static DBusMessage *hs_set_gain(DBusConnection *conn,
- DBusMessage *msg,
- void *data, uint16_t gain,
- char type)
-{
- struct audio_device *device = data;
- struct headset *hs = device->headset;
- struct at_slc *slc = hs->slc;
- DBusMessage *reply;
- int err;
-
- if (hs->state < HEADSET_STATE_CONNECTED)
- return btd_error_not_connected(msg);
-
- err = telephony_set_gain(slc, gain, type);
- if (err < 0)
- return btd_error_invalid_args(msg);
-
- reply = dbus_message_new_method_return(msg);
- if (!reply)
- return NULL;
-
- if (hs->state == HEADSET_STATE_PLAYING) {
- err = telephony_send_gain(slc, type, gain);
- if (err < 0) {
- dbus_message_unref(reply);
- return btd_error_failed(msg, strerror(-err));
- }
- }
-
- return reply;
+ return hs->dbus_cb->get_mic_gain(conn, msg, hs->slc, hs->state);
}

static DBusMessage *hs_set_speaker_gain(DBusConnection *conn,
DBusMessage *msg,
void *data)
{
- uint16_t gain;
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;

- if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
- DBUS_TYPE_INVALID))
- return NULL;
+ if (hs->dbus_cb == NULL || hs->dbus_cb->set_speaker_gain == NULL)
+ return btd_error_not_supported(msg);

- return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_SPEAKER);
+ return hs->dbus_cb->set_speaker_gain(conn, msg, hs->slc, hs->state);
}

static DBusMessage *hs_set_mic_gain(DBusConnection *conn,
DBusMessage *msg,
void *data)
{
- uint16_t gain;
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;

- if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
- DBUS_TYPE_INVALID))
- return NULL;
+ if (hs->dbus_cb == NULL || hs->dbus_cb->set_mic_gain == NULL)
+ return btd_error_not_supported(msg);

- return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_MICROPHONE);
+ return hs->dbus_cb->set_mic_gain(conn, msg, hs->slc, hs->state);
}

static DBusMessage *hs_get_properties(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct audio_device *device = data;
- DBusMessage *reply;
- DBusMessageIter iter;
- DBusMessageIter dict;
- gboolean value;
- const char *state;
- int gain;
-
- 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);
-
-
- /* Playing */
- value = (device->headset->state == HEADSET_STATE_PLAYING);
- dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value);
-
- /* State */
- state = state2str(device->headset->state);
- if (state)
- dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
-
- /* Connected */
- value = (device->headset->state >= HEADSET_STATE_CONNECTED);
- dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
-
- if (!value)
- goto done;
-
- /* SpeakerGain */
- gain = telephony_get_speaker_gain(device->headset->slc);
- dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16, &gain);
+ struct headset *hs = device->headset;

- /* MicrophoneGain */
- gain = telephony_get_mic_gain(device->headset->slc);
- dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
+ if (hs->dbus_cb == NULL || hs->dbus_cb->get_properties == NULL)
+ return btd_error_not_supported(msg);

-done:
- dbus_message_iter_close_container(&iter, &dict);
-
- return reply;
+ return hs->dbus_cb->get_properties(conn, msg, hs->slc, hs->state);
}

static DBusMessage *hs_set_property(DBusConnection *conn,
DBusMessage *msg, void *data)
{
- const char *property;
- DBusMessageIter iter;
- DBusMessageIter sub;
- uint16_t gain;
-
- if (!dbus_message_iter_init(msg, &iter))
- return btd_error_invalid_args(msg);
-
- if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
- return btd_error_invalid_args(msg);
-
- dbus_message_iter_get_basic(&iter, &property);
- dbus_message_iter_next(&iter);
-
- if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
- return btd_error_invalid_args(msg);
- dbus_message_iter_recurse(&iter, &sub);
-
- if (g_str_equal("SpeakerGain", property)) {
- if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
- return btd_error_invalid_args(msg);
-
- dbus_message_iter_get_basic(&sub, &gain);
- return hs_set_gain(conn, msg, data, gain,
- HEADSET_GAIN_SPEAKER);
- } else if (g_str_equal("MicrophoneGain", property)) {
- if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
- return btd_error_invalid_args(msg);
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;

- dbus_message_iter_get_basic(&sub, &gain);
- return hs_set_gain(conn, msg, data, gain,
- HEADSET_GAIN_MICROPHONE);
- }
+ if (hs->dbus_cb == NULL || hs->dbus_cb->set_property == NULL)
+ return btd_error_not_supported(msg);

- return btd_error_invalid_args(msg);
+ return hs->dbus_cb->set_property(conn, msg, hs->slc, hs->state);
}

static GDBusMethodTable headset_methods[] = {
@@ -1236,6 +1079,8 @@ struct headset *headset_init(struct audio_device *dev, uint16_t svc,
}

register_iface:
+ hs->dbus_cb = telephony_dbus_table();
+
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_HEADSET_INTERFACE,
headset_methods, headset_signals, NULL,
@@ -1608,13 +1453,11 @@ void headset_set_state(struct audio_device *dev, headset_state_t state)

gain = telephony_get_speaker_gain(hs->slc);
if (gain >= 0)
- telephony_send_gain(hs->slc, HEADSET_GAIN_SPEAKER,
- gain);
+ telephony_send_speaker_gain(hs->slc, gain);

gain = telephony_get_mic_gain(hs->slc);
if (gain >= 0)
- telephony_send_gain(hs->slc, HEADSET_GAIN_MICROPHONE,
- gain);
+ telephony_send_mic_gain(hs->slc, gain);
break;
}

diff --git a/audio/telephony-builtin.c b/audio/telephony-builtin.c
index 84a34f2..1ec0328 100644
--- a/audio/telephony-builtin.c
+++ b/audio/telephony-builtin.c
@@ -42,6 +42,7 @@

#include "log.h"
#include "device.h"
+#include "error.h"
#include "manager.h"
#include "telephony.h"
#include "headset.h"
@@ -617,7 +618,7 @@ static int dial_number(struct at_slc *slc, const char *buf)
return 0;
}

-int telephony_set_gain(void *slc, uint16_t gain, char type)
+static int telephony_set_gain(void *slc, uint16_t gain, char type)
{
struct at_slc *s = slc;
const char *name, *property;
@@ -1234,7 +1235,7 @@ gboolean telephony_pending_ring(void *slc)
return s->pending_ring;
}

-void telephony_stop_ring(void *slc)
+static void telephony_stop_ring(void *slc)
{
g_source_remove(ag.ring_timer);
ag.ring_timer = 0;
@@ -1254,11 +1255,21 @@ int telephony_get_mic_gain(void *slc)
return s->mic_gain;
}

-int telephony_send_gain(void *slc, char type, uint16_t gain)
+static int telephony_send_gain(void *slc, char type, uint16_t gain)
{
return at_send(slc, "\r\n+VG%c=%u\r\n", type, gain);
}

+int telephony_send_speaker_gain(void *slc, uint16_t gain)
+{
+ return telephony_send_gain(slc, HEADSET_GAIN_SPEAKER, gain);
+}
+
+int telephony_send_mic_gain(void *slc, uint16_t gain)
+{
+ return telephony_send_gain(slc, HEADSET_GAIN_MICROPHONE, gain);
+}
+
int telephony_get_nrec(void *slc)
{
struct at_slc *s = slc;
@@ -1271,7 +1282,7 @@ gboolean telephony_get_ready_state(void)
return ag.telephony_ready;
}

-gboolean telephony_is_ringing(void)
+static gboolean telephony_is_ringing(void)
{
return ag.ring_timer ? TRUE : FALSE;
}
@@ -1285,3 +1296,275 @@ void telephony_set_fast_connectable(gboolean state)
{
fast_connectable = state;
}
+
+static const char *state2str(headset_state_t state)
+{
+ switch (state) {
+ case HEADSET_STATE_DISCONNECTED:
+ return "disconnected";
+ case HEADSET_STATE_CONNECTING:
+ return "connecting";
+ case HEADSET_STATE_CONNECTED:
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ return "connected";
+ case HEADSET_STATE_PLAYING:
+ return "playing";
+ }
+
+ return NULL;
+}
+
+static DBusMessage *builtin_ring(DBusConnection *conn, DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state)
+{
+ DBusMessage *reply = NULL;
+ int err;
+
+ if (state < HEADSET_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (telephony_is_ringing()) {
+ DBG("IndicateCall received when already indicating");
+ return reply;
+ }
+
+ err = at_send(slc, "\r\nRING\r\n");
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return btd_error_failed(msg, strerror(-err));
+ }
+
+ telephony_start_ring(slc);
+
+ return reply;
+}
+
+static DBusMessage *builtin_cancel_call(DBusConnection *conn, DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state)
+{
+ DBusMessage *reply = NULL;
+
+ if (state < HEADSET_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (telephony_is_ringing())
+ telephony_stop_ring(slc);
+ else
+ DBG("Got CancelCall method call but no call is active");
+
+ return reply;
+}
+
+static DBusMessage *builtin_get_speaker_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state)
+{
+ DBusMessage *reply;
+ dbus_uint16_t gain;
+
+ if (state < HEADSET_STATE_CONNECTED)
+ return btd_error_not_available(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ gain = (dbus_uint16_t) telephony_get_speaker_gain(slc);
+
+ dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *builtin_get_mic_gain(DBusConnection *conn, DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state)
+{
+ DBusMessage *reply;
+ dbus_uint16_t gain;
+
+ if (state < HEADSET_STATE_CONNECTED || slc == NULL)
+ return btd_error_not_available(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ gain = (dbus_uint16_t) telephony_get_mic_gain(slc);
+
+ dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *builtin_set_gain(DBusConnection *conn, DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state,
+ uint16_t gain, char type)
+{
+ DBusMessage *reply;
+ int err;
+
+ if (state < HEADSET_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ err = telephony_set_gain(slc, gain, type);
+ if (err < 0)
+ return btd_error_invalid_args(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (state == HEADSET_STATE_PLAYING) {
+ err = telephony_send_gain(slc, type, gain);
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return btd_error_failed(msg, strerror(-err));
+ }
+ }
+
+ return reply;
+}
+
+static DBusMessage *builtin_set_speaker_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state)
+{
+ uint16_t gain;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ return builtin_set_gain(conn, msg, slc, state, gain,
+ HEADSET_GAIN_SPEAKER);
+}
+
+static DBusMessage *builtin_set_mic_gain(DBusConnection *conn, DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state)
+{
+ uint16_t gain;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ return builtin_set_gain(conn, msg, slc, state, gain,
+ HEADSET_GAIN_MICROPHONE);
+}
+
+static DBusMessage *builtin_get_properties(DBusConnection *conn, DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state)
+{
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ gboolean value;
+ const char *str;
+ int gain;
+
+ 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);
+
+ /* Playing */
+ value = (state == HEADSET_STATE_PLAYING);
+ dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value);
+
+ /* State */
+ str = state2str(state);
+ if (str)
+ dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &str);
+
+ /* Connected */
+ value = (state >= HEADSET_STATE_CONNECTED);
+ dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ if (!value)
+ goto done;
+
+ /* SpeakerGain */
+ gain = telephony_get_speaker_gain(slc);
+ dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16, &gain);
+
+ /* MicrophoneGain */
+ gain = telephony_get_mic_gain(slc);
+ dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
+
+done:
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *builtin_set_property(DBusConnection *conn, DBusMessage *msg,
+ void *slc, /*headset_state_t*/ int state)
+{
+ const char *property;
+ DBusMessageIter iter;
+ DBusMessageIter sub;
+ uint16_t gain;
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&iter, &property);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_recurse(&iter, &sub);
+
+ if (g_str_equal("SpeakerGain", property)) {
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&sub, &gain);
+ return builtin_set_gain(conn, msg, slc, state, gain,
+ HEADSET_GAIN_SPEAKER);
+ } else if (g_str_equal("MicrophoneGain", property)) {
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&sub, &gain);
+ return builtin_set_gain(conn, msg, slc, state, gain,
+ HEADSET_GAIN_MICROPHONE);
+ }
+
+ return btd_error_invalid_args(msg);
+}
+
+static struct TelephonyDBusTable dbus_callbacks = {
+ .indicate_call = builtin_ring,
+ .cancel_call = builtin_cancel_call,
+ .get_speaker_gain = builtin_get_speaker_gain,
+ .set_speaker_gain = builtin_set_speaker_gain,
+ .get_mic_gain = builtin_get_mic_gain,
+ .set_mic_gain = builtin_set_mic_gain,
+ .get_properties = builtin_get_properties,
+ .set_property = builtin_set_property
+};
+
+struct TelephonyDBusTable *telephony_dbus_table(void)
+{
+ return &dbus_callbacks;
+}
diff --git a/audio/telephony.h b/audio/telephony.h
index 8043267..ecd6d09 100644
--- a/audio/telephony.h
+++ b/audio/telephony.h
@@ -57,8 +57,6 @@ void telephony_device_set_active(void *telephony_device, gboolean active);
gboolean telephony_get_ready_state(void);
gboolean telephony_pending_ring(void *slc);
void telephony_start_ring(void *slc);
-void telephony_stop_ring(void *slc);
-gboolean telephony_is_ringing(void);
uint32_t telephony_get_ag_features(void);
void telephony_set_fast_connectable(gboolean state);
gboolean telephony_get_nrec(void *slc);
@@ -66,8 +64,26 @@ gboolean telephony_get_inband_ringtone(void *slc);
void telephony_set_inband_ringtone(void *slc, gboolean state);
int telephony_get_speaker_gain(void *slc);
int telephony_get_mic_gain(void *slc);
-int telephony_send_gain(void *slc, char type, uint16_t gain);
-int telephony_set_gain(void *slc, uint16_t gain, char type);
+int telephony_send_speaker_gain(void *slc, uint16_t gain);
+int telephony_send_mic_gain(void *slc, uint16_t gain);
+
+/* DBus interface functions. Implemented by telephony-*.c */
+typedef DBusMessage * (* telephony_dbus_cb)(DBusConnection *conn,
+ DBusMessage *msg,
+ void *slc, int state);
+
+struct TelephonyDBusTable {
+ telephony_dbus_cb indicate_call;
+ telephony_dbus_cb cancel_call;
+ telephony_dbus_cb get_speaker_gain;
+ telephony_dbus_cb set_speaker_gain;
+ telephony_dbus_cb get_mic_gain;
+ telephony_dbus_cb set_mic_gain;
+ telephony_dbus_cb get_properties;
+ telephony_dbus_cb set_property;
+};
+
+struct TelephonyDBusTable *telephony_dbus_table(void);

int telephony_init(void);
void telephony_exit(void);
--
1.7.1