Hi Frédéric, 2011/12/1 Frédéric Danis <frederic.danis@xxxxxxxxxxxxxxx>: > --- > Makefile.am | 13 +- > audio/headset.c | 614 ++-------------------------------------------- > audio/headset.h | 2 + > audio/manager.c | 4 +- > audio/telephony.c | 529 +++++++++++++++++++++++++++++++++++++++ > audio/telephony.h | 25 +-- > doc/assigned-numbers.txt | 1 + > doc/audio-api.txt | 91 +++++++ > 8 files changed, 648 insertions(+), 631 deletions(-) > create mode 100644 audio/telephony.c > > diff --git a/Makefile.am b/Makefile.am > index 07b8626..31c1083 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -154,14 +154,8 @@ builtin_sources += audio/main.c \ > audio/unix.h audio/unix.c \ > audio/media.h audio/media.c \ > audio/transport.h audio/transport.c \ > - audio/telephony.h audio/a2dp-codecs.h > -builtin_nodist += audio/telephony.c > - > -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.h audio/telephony.c \ > + audio/a2dp-codecs.h > endif > > if SAPPLUGIN > @@ -476,9 +470,6 @@ MAINTAINERCLEANFILES = Makefile.in \ > src/builtin.h: src/genbuiltin $(builtin_sources) > $(AM_V_GEN)$(srcdir)/src/genbuiltin $(builtin_modules) > $@ > > -audio/telephony.c: audio/@TELEPHONY_DRIVER@ > - $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@ > - > sap/sap.c: sap/@SAP_DRIVER@ > $(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@ > > diff --git a/audio/headset.c b/audio/headset.c > index 6aef6a8..74eb7a4 100644 > --- a/audio/headset.c > +++ b/audio/headset.c > @@ -70,8 +70,6 @@ > #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 */ > @@ -81,8 +79,6 @@ static struct { > 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, > @@ -236,40 +232,6 @@ static void print_ag_features(uint32_t features) > 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) { > @@ -333,97 +295,6 @@ static int __attribute__((format(printf, 2, 3))) > 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; > @@ -656,7 +527,7 @@ static int hfp_cmp(struct headset *hs) > 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; > @@ -721,73 +592,10 @@ int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err) > 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]); > + telephony_get_ag_features() & AG_FEATURE_THREE_WAY_CALLING) > 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); > + headset_slc_complete(device); > > return 0; > } > @@ -797,47 +605,11 @@ 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) > { > @@ -854,99 +626,21 @@ int telephony_terminate_call_rsp(void *telephony_device, > 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; > @@ -994,111 +688,21 @@ static int headset_set_gain(struct audio_device *device, uint16_t gain, char typ > 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); > @@ -1146,108 +750,6 @@ int telephony_operator_selection_ind(int mode, const char *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 +768,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 +795,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,8 +811,7 @@ 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); > > @@ -1740,7 +1153,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; > @@ -2245,7 +1658,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); > @@ -2275,7 +1688,7 @@ uint32_t headset_config_init(GKeyFile *config) > g_free(str); > } > > - return ag.features; > + return telephony_get_ag_features(); > } > > static gboolean hs_dc_timeout(struct audio_device *dev) > @@ -2518,6 +1931,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", > @@ -2546,7 +1963,8 @@ 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) > + if (telephony_get_ag_features() & > + AG_FEATURE_INBAND_RINGTONE) > slc->inband_ring = TRUE; > else > slc->inband_ring = FALSE; > @@ -2880,15 +2298,13 @@ 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); > + print_ag_features(telephony_get_ag_features()); > > return 0; > } > diff --git a/audio/headset.h b/audio/headset.h > index 99eeca8..d43952f 100644 > --- a/audio/headset.h > +++ b/audio/headset.h > @@ -111,3 +111,5 @@ 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); > + > +void headset_slc_complete(struct audio_device *dev); > diff --git a/audio/manager.c b/audio/manager.c > index 8de5515..4624552 100644 > --- a/audio/manager.c > +++ b/audio/manager.c > @@ -880,7 +880,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; > } > @@ -896,7 +896,7 @@ static void state_changed(struct btd_adapter *adapter, gboolean powered) > return; > } > > - telephony_exit(); > + telephony_exit(adapter); > telephony = FALSE; > } > > diff --git a/audio/telephony.c b/audio/telephony.c > new file mode 100644 > index 0000000..4aa3892 > --- /dev/null > +++ b/audio/telephony.c > @@ -0,0 +1,529 @@ > +/* > + * > + * BlueZ - Bluetooth protocol stack for Linux > + * > + * Copyright (C) 2011 Intel Corporation > + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@xxxxxxxxxxxx> > + * Copyright (C) 2011 Frederic Danis <frederic.danis@xxxxxxxxx> > + * > + * > + * 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" > + > +#define DEFAULT_HS_HS_CHANNEL 6 > +#define DEFAULT_HF_HS_CHANNEL 7 > + > +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 default_agent { > + char *uuid; /* agent property UUID */ > + uint8_t channel; > + const char *r_uuid; > + uint16_t r_class; > + uint16_t r_profile; > +}; > + > +struct tel_agent { > + char *name; /* agent DBus bus id */ > + char *path; /* agent object path */ > + uint16_t version; > + uint16_t features; > + struct default_agent *properties; > +}; > + > +static DBusConnection *connection = NULL; > + > +struct telsrv telsrv; > + > +static void free_agent(struct tel_agent *agent) > +{ > + if (agent->name) > + g_free(agent->name); > + > + if (agent->path) > + g_free(agent->path); You can call g_free directly. > + g_free(agent); > +} > + > +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->properties->uuid, uuid) != 0) > + continue; > + > + return agent; > + } > + > + return NULL; > +} > + > +static int parse_properties(DBusMessageIter *props, const char **uuid, > + uint16_t *version, uint16_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_UINT16) > + 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->properties->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_UINT16, > + &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->properties->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->properties->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_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; > +} > + > +uint32_t telephony_get_ag_features(void) > +{ > + return 0; > +} > + > +static struct default_agent default_properties[] = { > + {HSP_HS_UUID, > + DEFAULT_HS_HS_CHANNEL, > + HSP_AG_UUID, > + HEADSET_AGW_SVCLASS_ID, > + HEADSET_PROFILE_ID}, > + {HSP_AG_UUID, > + DEFAULT_HS_AG_CHANNEL, > + HSP_HS_UUID, > + HEADSET_SVCLASS_ID, > + HEADSET_PROFILE_ID}, > + {HFP_HS_UUID, > + DEFAULT_HF_HS_CHANNEL, > + HFP_AG_UUID, > + HANDSFREE_AGW_SVCLASS_ID, > + HANDSFREE_PROFILE_ID}, > + {HFP_AG_UUID, > + DEFAULT_HF_AG_CHANNEL, > + HFP_HS_UUID, > + HANDSFREE_SVCLASS_ID, > + HANDSFREE_PROFILE_ID} > +}; > + > +static DBusMessage *register_agent(DBusConnection *conn, > + DBusMessage *msg, void *data) > +{ > + DBusMessageIter args, props; > + const char *sender, *path, *uuid; > + uint16_t version = 0; > + uint16_t features = 0xFFFF; > + struct tel_agent *agent; > + int i; > + > + 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 (find_agent(NULL, NULL, uuid) != NULL) > + return btd_error_already_exists(msg); > + > + /* initialize agent properties */ > + for (i=0 ; i<4; i++) { > + if (strcasecmp(uuid, default_properties[i].uuid) == 0) { > + agent = g_new0(struct tel_agent, 1); > + agent->properties = &default_properties[i]; > + agent->name = g_strdup(sender); > + agent->path = g_strdup(path); > + agent->version = version; > + agent->features = features; > + break; > + } > + } > + > + if (i == 4) > + return btd_error_invalid_args(msg); I guess you can use sizeof(default_properties)/sizeof(struct default_agent) to calculate the size of the array, anyway I would probably split this part in a separate function e.g. agent_new. > + DBG("Register agent : %s%s for %s version 0x%04X with features 0x%02X", > + sender, path, uuid, version, features); > + > + 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); > + > + free_agent(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, adapter, 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; > +} > diff --git a/audio/telephony.h b/audio/telephony.h > index 73b390c..7d1d337 100644 > --- a/audio/telephony.h > +++ b/audio/telephony.h > @@ -144,26 +144,13 @@ struct indicator { > /* 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); > +gboolean telephony_get_ready_state(void); > +uint32_t telephony_get_ag_features(void); > > /* AG responses to HF requests. These are implemented by headset.c */ > int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err); > @@ -240,5 +227,5 @@ static inline int telephony_get_indicator(const struct indicator *indicators, > return -ENOENT; > } > > -int telephony_init(void); > -void telephony_exit(void); > +int telephony_init(void *adapter); > +void telephony_exit(void *adapter); > diff --git a/doc/assigned-numbers.txt b/doc/assigned-numbers.txt > index cda934c..120d7ea 100644 > --- a/doc/assigned-numbers.txt > +++ b/doc/assigned-numbers.txt > @@ -8,6 +8,7 @@ avoid conflicts. > Profile Channel > ----------------------- > DUN 1 > +HSP HS 6 Why not HFP HS? > HFP HF 7 > OPP 9 > FTP 10 > diff --git a/doc/audio-api.txt b/doc/audio-api.txt > index b85400b..73d87cc 100644 > --- a/doc/audio-api.txt > +++ b/doc/audio-api.txt > @@ -456,3 +456,94 @@ 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. > + > + uint16 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 > + > +Methods 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. > + > + possible properties: > + > + strict Device: > + > + BlueZ remote device object. > + > + string UUID: > + > + Profile UUID of the connection. > + > + uint16 Version: > + > + Remote profile version. > + > + uint16 Features: > + > + Remote profile features. > + > + string MediaTransportPath: > + > + Optional. MediaTransport 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 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Luiz Augusto von Dentz -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html