summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/modules/bluetooth/backend-native.c139
-rw-r--r--src/modules/bluetooth/bluez5-util.c5
-rw-r--r--src/modules/bluetooth/bluez5-util.h2
-rw-r--r--src/pulsecore/core-util.h7
4 files changed, 141 insertions, 12 deletions
diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index f7453a4a..c7a4aa1d 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -50,6 +50,45 @@ struct transport_rfcomm {
pa_mainloop_api *mainloop;
};
+struct hfp_config {
+ uint32_t capabilities;
+ int state;
+};
+
+/*
+ * the separate hansfree headset (HF) and Audio Gateway (AG) features
+ */
+enum hfp_hf_features {
+ HFP_HF_EC_NR = 0,
+ HFP_HF_CALL_WAITING = 1,
+ HFP_HF_CLI = 2,
+ HFP_HF_VR = 3,
+ HFP_HF_RVOL = 4,
+ HFP_HF_ESTATUS = 5,
+ HFP_HF_ECALL = 6,
+ HFP_HF_CODECS = 7,
+};
+
+enum hfp_ag_features {
+ HFP_AG_THREE_WAY = 0,
+ HFP_AG_EC_NR = 1,
+ HFP_AG_VR = 2,
+ HFP_AG_RING = 3,
+ HFP_AG_NUM_TAG = 4,
+ HFP_AG_REJECT = 5,
+ HFP_AG_ESTATUS = 6,
+ HFP_AG_ECALL = 7,
+ HFP_AG_EERR = 8,
+ HFP_AG_CODECS = 9,
+};
+
+/* gateway features we support */
+static uint32_t hfp_features =
+ /* LG HBS900 reboots if it doesn't see this */
+ (1 << HFP_AG_THREE_WAY) |
+ /* HFP 1.6 requires this */
+ (1 << HFP_AG_ESTATUS );
+
#define BLUEZ_SERVICE "org.bluez"
#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
@@ -101,6 +140,27 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, D
return p;
}
+static void rfcomm_write(int fd, const char *str)
+{
+ size_t len;
+ char buf[512];
+
+ pa_log_debug("RFCOMM >> %s", str);
+ sprintf(buf, "\r\n%s\r\n", str);
+ len = write(fd, buf, strlen(buf));
+
+ if (len != strlen(buf))
+ pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
+}
+
+static void hfp_send_features(int fd)
+{
+ char buf[512];
+
+ sprintf(buf, "+BRSF: %d", hfp_features);
+ rfcomm_write(fd, buf);
+}
+
static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
pa_bluetooth_device *d = t->device;
struct sockaddr_sco addr;
@@ -216,6 +276,58 @@ static void register_profile(pa_bluetooth_backend *b, const char *profile, const
send_and_add_to_pending(b, m, register_profile_reply, pa_xstrdup(profile));
}
+static void transport_put(pa_bluetooth_transport *t)
+{
+ pa_bluetooth_transport_put(t);
+
+ pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
+}
+
+static int hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf)
+{
+ struct hfp_config *c = t->config;
+ int val;
+
+ /* stateful negotiation */
+ if (c->state == 0 && sscanf(buf, "AT+BRSF=%d", &val) == 1) {
+ c->capabilities = val;
+ pa_log_info("HFP capabilities returns 0x%x", val);
+ hfp_send_features(fd);
+ c->state = 1;
+ return 0;
+ } else if (c->state == 1 && pa_strcont("AT+CIND=?", buf)) {
+ /* we declare minimal indicators */
+ rfcomm_write(fd, "+CIND: (\"call\",(0,1))");
+ c->state = 2;
+ return 0;
+ } else if (c->state == 2 && pa_strcont("AT+CIND?", buf)) {
+ rfcomm_write(fd, "+CIND: 0");
+ c->state = 3;
+ return 0;
+ } else if (c->state == 3 && pa_strcont("AT+CMER=", buf)) {
+ rfcomm_write(fd, "OK");
+ c->state = 4;
+ transport_put(t);
+ return 1;
+ }
+
+
+ if (c->state != 4)
+ goto error;
+
+ /* misc things to handle in fully connected state */
+
+ if (pa_strcont("AT+BTRH?", buf)) {
+ return 0;
+ } else if (pa_strcont("AT+CHLD=?", buf)) {
+ return 0;
+ }
+
+ error:
+ rfcomm_write(fd, "ERROR");
+ return 1;
+}
+
static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
pa_bluetooth_transport *t = userdata;
@@ -246,16 +358,18 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1) {
t->microphone_gain = gain;
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t);
- }
-
- pa_log_debug("RFCOMM >> OK");
-
- len = write(fd, "\r\nOK\r\n", 6);
+ } else if (t->config) {
+ switch (hfp_rfcomm_handle(fd, t, buf)) {
+ case -1:
+ goto fail;
+ case 1:
+ return;
+ }
+ }
+ rfcomm_write(fd, "OK");
/* we ignore any errors, it's not critical and real errors should
* be caught with the HANGUP and ERROR events handled above */
- if (len < 0)
- pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
return;
@@ -365,7 +479,7 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
p = PA_BLUETOOTH_PROFILE_HSP_HS;
pathfd = pa_sprintf_malloc ("%s/fd%d", path, fd);
- t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, 0);
+ t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, is_hfp ? sizeof(struct hfp_config) : 0);
pa_xfree(pathfd);
t->acquire = bluez5_sco_acquire_cb;
@@ -381,9 +495,14 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
rfcomm_io_callback, t);
t->userdata = trfc;
- pa_bluetooth_transport_put(t);
+ if (!is_hfp)
+ transport_put(t);
+ else {
+ pa_log_debug("beginning sleep");
+ sleep(1);
+ pa_log_debug("ending sleep");
+ }
- pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
pa_assert_se(r = dbus_message_new_method_return(m));
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index e4a50a7d..9e2cc53a 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -151,7 +151,10 @@ pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const
if (size > 0) {
t->config = pa_xnew(uint8_t, size);
- memcpy(t->config, config, size);
+ if (config)
+ memcpy(t->config, config, size);
+ else
+ memset(t->config, 0, size);
}
return t;
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 73fe5982..7780a8d5 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -73,7 +73,7 @@ struct pa_bluetooth_transport {
pa_bluetooth_profile_t profile;
uint8_t codec;
- uint8_t *config;
+ void *config;
size_t config_size;
uint16_t microphone_gain;
diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
index 5725ca78..5b7ccda6 100644
--- a/src/pulsecore/core-util.h
+++ b/src/pulsecore/core-util.h
@@ -227,6 +227,13 @@ static inline bool pa_safe_streq(const char *a, const char *b) {
return pa_streq(a, b);
}
+/* see if the string b contains a as a prefix */
+static inline bool pa_strcont(const char *a, const char *b) {
+ if (a == NULL || b == NULL)
+ return a == b;
+ return !strncmp(a, b, strlen(a));
+}
+
bool pa_str_in_list_spaces(const char *needle, const char *haystack);
char *pa_get_host_name_malloc(void);