diff options
Diffstat (limited to 'audio/headset.c')
-rw-r--r-- | audio/headset.c | 269 |
1 files changed, 148 insertions, 121 deletions
diff --git a/audio/headset.c b/audio/headset.c index 7bb1746d..e7425d5a 100644 --- a/audio/headset.c +++ b/audio/headset.c @@ -108,92 +108,188 @@ struct headset { int sp_gain; int mic_gain; + unsigned int hfp_features; headset_lock_t lock; }; +struct event { + const char *cmd; + int (*callback) (struct device *device, const char *buf); +}; + static int rfcomm_connect(struct device *device, struct pending_connect *c); static int get_handles(struct device *device, struct pending_connect *c); -static void pending_connect_free(struct pending_connect *c) +static int headset_send(struct headset *hs, const char *str) { - if (c->io) { - g_io_channel_close(c->io); - g_io_channel_unref(c->io); + GIOError err; + gsize total_written, written, count; + + if (hs->state < HEADSET_STATE_CONNECTED || !hs->rfcomm) { + error("headset_send: the headset is not connected"); + return -EIO; } - if (c->msg) - dbus_message_unref(c->msg); - if (c->call) { - dbus_pending_call_cancel(c->call); - dbus_pending_call_unref(c->call); + + count = strlen(str); + written = total_written = 0; + + while (total_written < count) { + err = g_io_channel_write(hs->rfcomm, str + total_written, + count - total_written, &written); + if (err != G_IO_ERROR_NONE) + return -errno; + total_written += written; } - g_free(c); + return 0; +} + +static int supported_features(struct device *device, const char *buf) +{ + struct headset *hs = device->headset; + int err; + + hs->hfp_features = strtoul(&buf[8], NULL, 10); + + err = headset_send(hs, "\r\n+BRSF:0\r\n"); + if (err < 0) + return err; + + return headset_send(device->headset, "\r\nOK\r\n"); +} + +static int report_indicators(struct device *device, const char *buf) +{ + struct headset *hs = device->headset; + int err; + + if (buf[7] == '=') + err = headset_send(hs, "\r\n+CIND:(\"service\",(0,1))," + "(\"call\",(0,1)),(\"callsetup\",(0-3))\r\n"); + else + err = headset_send(hs, "\r\n+CIND:1, 0, 0\r\n"); + + if (err < 0) + return err; + + return headset_send(device->headset, "\r\nOK\r\n"); +} + +static int event_reporting(struct device *device, const char *buf) +{ + return headset_send(device->headset, "\r\nOK\r\n"); +} + +static int call_hold(struct device *device, const char *buf) +{ + struct headset *hs = device->headset; + int err; + + err = headset_send(hs, "\r\n+CHLD:(0,1,1x,2,2x,3,4)\r\n"); + if (err < 0) + return err; + + return headset_send(device->headset, "\r\nOK\r\n"); } -static void hs_signal_gain_setting(struct device *device, const char *buf) +static int answer_call(struct device *device, const char *buf) +{ + dbus_connection_emit_signal(device->conn, device->path, + AUDIO_HEADSET_INTERFACE, "AnswerRequested", + DBUS_TYPE_INVALID); + + return headset_send(device->headset, "\r\nOK\r\n"); +} + +static int terminate_call(struct device *device, const char *buf) +{ + return headset_send(device->headset, "\r\nOK\r\n"); +} + +static int signal_gain_setting(struct device *device, const char *buf) { const char *name; dbus_uint16_t gain; - if (strlen(buf) < 6) { + if (strlen(buf) < 8) { error("Too short string for Gain setting"); - return; + return -1; } - gain = (dbus_uint16_t) strtol(&buf[5], NULL, 10); + gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10); if (gain > 15) { error("Invalid gain value received: %u", gain); - return; + return -1; } - switch (buf[3]) { + switch (buf[5]) { case HEADSET_GAIN_SPEAKER: if (device->headset->sp_gain == gain) - return; + goto ok; name = "SpeakerGainChanged"; device->headset->sp_gain = gain; break; case HEADSET_GAIN_MICROPHONE: if (device->headset->mic_gain == gain) - return; + goto ok; name = "MicrophoneGainChanged"; device->headset->mic_gain = gain; break; default: error("Unknown gain setting"); - return; + return G_IO_ERROR_INVAL; } dbus_connection_emit_signal(device->conn, device->path, - AUDIO_HEADSET_INTERFACE, name, - DBUS_TYPE_UINT16, &gain, - DBUS_TYPE_INVALID); + AUDIO_HEADSET_INTERFACE, name, + DBUS_TYPE_UINT16, &gain, + DBUS_TYPE_INVALID); + +ok: + return headset_send(device->headset, "\r\nOK\r\n"); } -static headset_event_t parse_headset_event(const char *buf, char *rsp, - int rsp_len) +static struct event event_callbacks[] = { + {"ATA", answer_call}, + {"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", answer_call}, + {0} +}; + +static GIOError handle_event(struct device *device, const char *buf) { - printf("Received: %s\n", buf); + struct event *pt; - /* Return an error if this is not a proper AT command */ - if (strncmp(buf, "AT", 2)) { - snprintf(rsp, rsp_len, "\r\nERROR\r\n"); - return HEADSET_EVENT_INVALID; + debug("Received %s", buf); + + for (pt = event_callbacks; pt->cmd; pt++) { + if (!strncmp(buf, pt->cmd, strlen(pt->cmd))) + return pt->callback(device, buf); } - buf += 2; + return -EINVAL; +} - if (!strncmp(buf, "+CKPD", 5)) { - snprintf(rsp, rsp_len, "\r\nOK\r\n"); - return HEADSET_EVENT_KEYPRESS; - } else if (!strncmp(buf, "+VG", 3)) { - snprintf(rsp, rsp_len, "\r\nOK\r\n"); - return HEADSET_EVENT_GAIN; - } else { - snprintf(rsp, rsp_len, "\r\nERROR\r\n"); - return HEADSET_EVENT_UNKNOWN; +static void pending_connect_free(struct pending_connect *c) +{ + if (c->io) { + g_io_channel_close(c->io); + g_io_channel_unref(c->io); } + if (c->msg) + dbus_message_unref(c->msg); + if (c->call) { + dbus_pending_call_cancel(c->call); + dbus_pending_call_unref(c->call); + } + + g_free(c); } static void close_sco(struct device *device) @@ -214,10 +310,10 @@ static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, { struct headset *hs; unsigned char buf[BUF_SIZE]; - char *cr, rsp[BUF_SIZE]; + char *cr; gsize bytes_read = 0; - gsize free_space, count, bytes_written, total_bytes_written; - GIOError err; + gsize free_space; + int err; off_t cmd_len; if (cond & G_IO_NVAL) @@ -253,49 +349,13 @@ static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, if (!cr) return TRUE; - cmd_len = 1 + (off_t) cr - (off_t) &hs->buf[hs->data_start]; + cmd_len = 1 + (off_t) cr - (off_t) &hs->buf[hs->data_start]; *cr = '\0'; - memset(rsp, 0, sizeof(rsp)); - - switch (parse_headset_event(&hs->buf[hs->data_start], rsp, - sizeof(rsp))) { - case HEADSET_EVENT_GAIN: - hs_signal_gain_setting(device, &hs->buf[hs->data_start] + 2); - break; - - case HEADSET_EVENT_KEYPRESS: - if (hs->ring_timer) { - g_source_remove(hs->ring_timer); - hs->ring_timer = 0; - } - - dbus_connection_emit_signal(device->conn, device->path, - AUDIO_HEADSET_INTERFACE, - "AnswerRequested", - DBUS_TYPE_INVALID); - break; - - case HEADSET_EVENT_INVALID: - case HEADSET_EVENT_UNKNOWN: - default: - debug("Unknown headset event"); - break; - } - - count = strlen(rsp); - total_bytes_written = bytes_written = 0; - err = G_IO_ERROR_NONE; - - while (err == G_IO_ERROR_NONE && total_bytes_written < count) { - err = g_io_channel_write(hs->rfcomm, - rsp + total_bytes_written, - count - total_bytes_written, - &bytes_written); - if (err != G_IO_ERROR_NONE) - error("Error while writting to the audio output channel"); - total_bytes_written += bytes_written; - }; + err = handle_event(device, &hs->buf[hs->data_start]); + if (err < 0) + error("Error handling command %s: %s (%d)", &hs->buf[hs->data_start], + strerror(-err), -err); hs->data_start += cmd_len; hs->data_length -= cmd_len; @@ -328,30 +388,6 @@ static gboolean sco_cb(GIOChannel *chan, GIOCondition cond, return FALSE; } -static GIOError headset_send(struct headset *hs, const char *str) -{ - GIOError err; - gsize total_written, written, count; - - if (hs->state < HEADSET_STATE_CONNECTED || !hs->rfcomm) { - error("headset_send: the headset is not connected"); - return G_IO_ERROR_UNKNOWN; - } - - count = strlen(str); - written = total_written = 0; - - while (total_written < count) { - err = g_io_channel_write(hs->rfcomm, str + total_written, - count - total_written, &written); - if (err != G_IO_ERROR_NONE) - return err; - total_written += written; - } - - return G_IO_ERROR_NONE; -} - static void pending_connect_ok(struct pending_connect *c, struct device *dev) { struct headset *hs = dev->headset; @@ -593,6 +629,7 @@ static void get_record_reply(DBusPendingCall *call, void *data) struct device *device = data; struct headset *hs = device->headset; struct pending_connect *c; + unsigned int id; c = hs->pending->data; @@ -642,21 +679,11 @@ static void get_record_reply(DBusPendingCall *call, void *data) goto failed_not_supported; } - if (hs->type == SVC_HEADSET && - ((uuid.type == SDP_UUID32 && - uuid.value.uuid32 != HEADSET_SVCLASS_ID) || - (uuid.type == SDP_UUID16 && - uuid.value.uuid16 != HEADSET_SVCLASS_ID))) { - error("Service classes did not contain the expected UUID hsp"); - goto failed_not_supported; - } + id = hs->enable_hfp ? HANDSFREE_SVCLASS_ID : HEADSET_SVCLASS_ID; - if (hs->type == SVC_HANDSFREE && - ((uuid.type == SDP_UUID32 && - uuid.value.uuid32 != HANDSFREE_SVCLASS_ID) || - (uuid.type == SDP_UUID16 && - uuid.value.uuid16 != HANDSFREE_SVCLASS_ID))) { - error("Service classes did not contain the expected UUID hfp"); + if ((uuid.type == SDP_UUID32 && uuid.value.uuid32 != id) + || (uuid.type == SDP_UUID16 && uuid.value.uuid16 != id)) { + error("Service classes did not contain the expected UUID"); goto failed_not_supported; } |