[pulseaudio-commits] 66 commits - configure.ac LICENSE po/POTFILES.in src/.gitignore src/Makefile.am src/modules src/pulsecore

Tanu Kaskinen tanuk at kemper.freedesktop.org
Sun Sep 29 07:22:19 PDT 2013


 LICENSE                                            |   20 
 configure.ac                                       |   41 
 po/POTFILES.in                                     |    1 
 src/.gitignore                                     |    1 
 src/Makefile.am                                    |  104 
 src/modules/bluetooth/bluetooth-util.c             | 2279 ------------------
 src/modules/bluetooth/bluetooth-util.h             |  178 -
 src/modules/bluetooth/bluez4-util.c                | 1905 +++++++++++++++
 src/modules/bluetooth/bluez4-util.h                |  178 +
 src/modules/bluetooth/bluez5-util.c                | 1640 +++++++++++++
 src/modules/bluetooth/bluez5-util.h                |  116 
 src/modules/bluetooth/module-bluetooth-device.c    | 2617 --------------------
 src/modules/bluetooth/module-bluetooth-discover.c  |  154 -
 src/modules/bluetooth/module-bluetooth-proximity.c |  480 ---
 src/modules/bluetooth/module-bluez4-device.c       | 2623 +++++++++++++++++++++
 src/modules/bluetooth/module-bluez4-discover.c     |  194 +
 src/modules/bluetooth/module-bluez5-device.c       | 1898 +++++++++++++++
 src/modules/bluetooth/module-bluez5-discover.c     |  128 +
 src/modules/bluetooth/proximity-helper.c           |  202 -
 src/pulsecore/cli-command.c                        |   41 
 src/pulsecore/module.c                             |   55 
 src/pulsecore/module.h                             |    2 
 22 files changed, 8858 insertions(+), 5999 deletions(-)

New commits:
commit 8753b8c1472ee3889458364406d00ec50c81a7c3
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:59 2013 -0300

    bluetooth: Revive module-bluetooth-discover
    
    Create a wrapper module called module-bluetooth-discover to avoid
    breaking backward-compatibility of default.pa. This wrapper may
    eventually be dropped altoghether with BlueZ 4 support.

diff --git a/src/Makefile.am b/src/Makefile.am
index d67e487..0296b3c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1322,6 +1322,7 @@ endif
 
 if HAVE_BLUEZ
 modlibexec_LTLIBRARIES += \
+		module-bluetooth-discover.la \
 		module-bluetooth-policy.la
 endif
 
@@ -1426,6 +1427,7 @@ SYMDEF_FILES = \
 		module-udev-detect-symdef.h \
 		module-systemd-login-symdef.h \
 		module-bluetooth-policy-symdef.h \
+		module-bluetooth-discover-symdef.h \
 		module-bluez4-discover-symdef.h \
 		module-bluez4-device-symdef.h \
 		module-bluez5-discover-symdef.h \
@@ -2031,6 +2033,12 @@ module_bluetooth_policy_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_bluetooth_policy_la_LIBADD = $(MODULE_LIBADD)
 module_bluetooth_policy_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
 
+# Bluetooth discover
+module_bluetooth_discover_la_SOURCES = modules/bluetooth/module-bluetooth-discover.c
+module_bluetooth_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_bluetooth_discover_la_LIBADD = $(MODULE_LIBADD)
+module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS)
+
 # Bluetooth BlueZ 4 sink / source
 module_bluez4_discover_la_SOURCES = modules/bluetooth/module-bluez4-discover.c
 module_bluez4_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c
new file mode 100644
index 0000000..c4fb26a
--- /dev/null
+++ b/src/modules/bluetooth/module-bluetooth-discover.c
@@ -0,0 +1,78 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/module.h>
+
+#include "module-bluetooth-discover-symdef.h"
+
+PA_MODULE_AUTHOR("João Paulo Rechi Vita");
+PA_MODULE_DESCRIPTION("Detect available Bluetooth daemon and load the corresponding discovery module");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+
+struct userdata {
+    pa_module *bluez5_module;
+    pa_module *bluez4_module;
+};
+
+int pa__init(pa_module* m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+
+    if (pa_module_exists("module-bluez5-discover"))
+        u->bluez5_module = pa_module_load(m->core, "module-bluez5-discover",  NULL);
+
+    if (pa_module_exists("module-bluez4-discover"))
+        u->bluez4_module = pa_module_load(m->core, "module-bluez4-discover",  NULL);
+
+    if (!u->bluez5_module && !u->bluez4_module) {
+        pa_xfree(u);
+        return -1;
+    }
+
+    return 0;
+}
+
+void pa__done(pa_module* m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->bluez5_module)
+        pa_module_unload(m->core, u->bluez5_module, true);
+
+    if (u->bluez4_module)
+        pa_module_unload(m->core, u->bluez4_module, true);
+
+    pa_xfree(u);
+}

commit 1ea8a2bdaed21d6b44ce03d1b158632c88c20edc
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:58 2013 -0300

    cli-command: Use pa_module_exists() in .ifexists

diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
index f937d46..d2cc38a 100644
--- a/src/pulsecore/cli-command.c
+++ b/src/pulsecore/cli-command.c
@@ -2105,46 +2105,7 @@ int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *b
                     return -1;
                 } else {
                     const char *filename = cs+l+strspn(cs+l, whitespace);
-
-                    /* Search DL_SEARCH_PATH unless the filename is absolute */
-                    if (filename[0] == PA_PATH_SEP_CHAR) {
-
-                        *ifstate = access(filename, F_OK) == 0 ? IFSTATE_TRUE : IFSTATE_FALSE;
-                        pa_log_debug("Checking for existence of '%s': %s", filename, *ifstate == IFSTATE_TRUE ? "success" : "failure");
-
-                    } else {
-                        const char *paths, *state = NULL;
-                        char *p;
-
-                        if (!(paths = lt_dlgetsearchpath()))
-                            return -1;
-
-                        while ((p = pa_split(paths, ":", &state))) {
-                            char *pathname;
-
-                            pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", p, filename);
-
-                            *ifstate = access(pathname, F_OK) == 0 ? IFSTATE_TRUE : IFSTATE_FALSE;
-                            pa_log_debug("Checking for existence of '%s': %s", pathname, *ifstate == IFSTATE_TRUE ? "success" : "failure");
-
-                            if (PA_UNLIKELY(pa_run_from_build_tree())) {
-                                /* If run from the build tree, search in <path>/.libs as well */
-                                char *ltpathname = pa_sprintf_malloc("%s" PA_PATH_SEP ".libs" PA_PATH_SEP "%s", p, filename);
-
-                                *ifstate = access(ltpathname, F_OK) == 0 ? IFSTATE_TRUE : IFSTATE_FALSE;
-                                pa_log_debug("Checking for existence of '%s': %s", ltpathname, *ifstate == IFSTATE_TRUE ? "success" : "failure");
-
-                                pa_xfree(ltpathname);
-                            }
-
-                            pa_xfree(p);
-                            pa_xfree(pathname);
-
-                            if (*ifstate == IFSTATE_TRUE)
-                                break;
-                        }
-                    }
-
+                    *ifstate = pa_module_exists(filename) ? IFSTATE_TRUE : IFSTATE_FALSE;
                 }
             } else {
                 pa_strbuf_printf(buf, "Invalid meta command: %s\n", cs);

commit 48d23d48630fc2b68871cd5ea778b3fcf43ff283
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:57 2013 -0300

    module: Create pa_module_exists()
    
    This new function checks if a certain module name is available in the
    system.

diff --git a/configure.ac b/configure.ac
index dd07a78..42cb8b8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1217,6 +1217,7 @@ PACTL_BINARY=${bindir}/pactl${EXEEXT}
 AX_DEFINE_DIR(PACTL_BINARY, PACTL_BINARY, [Location of pactl binary])
 
 AC_SUBST(PA_SOEXT, [.so])
+AC_DEFINE(PA_SOEXT, [".so"], [Shared object extension])
 
 AC_SUBST(pulseconfdir, ["${sysconfdir}/pulse"])
 AX_DEFINE_DIR(PA_DEFAULT_CONFIG_DIR, pulseconfdir, [Location of configuration files])
diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c
index 16582b3..c57acac 100644
--- a/src/pulsecore/module.c
+++ b/src/pulsecore/module.c
@@ -28,6 +28,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <ltdl.h>
 
 #include <pulse/xmalloc.h>
 #include <pulse/proplist.h>
@@ -47,6 +48,60 @@
 #define PA_SYMBOL_GET_N_USED "pa__get_n_used"
 #define PA_SYMBOL_GET_DEPRECATE "pa__get_deprecated"
 
+bool pa_module_exists(const char *name) {
+    const char *paths, *state = NULL;
+    char *n, *p, *pathname;
+    bool result;
+
+    pa_assert(name);
+
+    if (name[0] == PA_PATH_SEP_CHAR) {
+        result = access(name, F_OK) == 0 ? true : false;
+        pa_log_debug("Checking for existence of '%s': %s", name, result ? "success" : "failure");
+        if (result)
+            return true;
+    }
+
+    if (!(paths = lt_dlgetsearchpath()))
+        return false;
+
+    /* strip .so from the end of name, if present */
+    n = pa_xstrdup(name);
+    p = rindex(n, '.');
+    if (p && pa_streq(p, PA_SOEXT))
+        p[0] = 0;
+
+    while ((p = pa_split(paths, ":", &state))) {
+        pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "%s" PA_SOEXT, p, n);
+        result = access(pathname, F_OK) == 0 ? true : false;
+        pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure");
+        pa_xfree(pathname);
+        pa_xfree(p);
+        if (result) {
+            pa_xfree(n);
+            return true;
+        }
+    }
+
+    state = NULL;
+    if (PA_UNLIKELY(pa_run_from_build_tree())) {
+        while ((p = pa_split(paths, ":", &state))) {
+            pathname = pa_sprintf_malloc("%s" PA_PATH_SEP ".libs" PA_PATH_SEP "%s" PA_SOEXT, p, n);
+            result = access(pathname, F_OK) == 0 ? true : false;
+            pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure");
+            pa_xfree(pathname);
+            pa_xfree(p);
+            if (result) {
+                pa_xfree(n);
+                return true;
+            }
+        }
+    }
+
+    pa_xfree(n);
+    return false;
+}
+
 pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
     pa_module *m = NULL;
     bool (*load_once)(void);
diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h
index 5a330c6..af4e9d2 100644
--- a/src/pulsecore/module.h
+++ b/src/pulsecore/module.h
@@ -50,6 +50,8 @@ struct pa_module {
     pa_proplist *proplist;
 };
 
+bool pa_module_exists(const char *name);
+
 pa_module* pa_module_load(pa_core *c, const char *name, const char *argument);
 
 void pa_module_unload(pa_core *c, pa_module *m, bool force);

commit dc4be17e072c34bd4762e232a0ccc307c3268c98
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:56 2013 -0300

    bluetooth: Fail to load driver if discovery module is not loaded
    
    For quite some time now the device driver module doesn't work well
    without the discovery module, so for the BlueZ 5 support we'll prevent
    the device driver module to be loaded if the discovery module is not
    loaded.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 9df9021..225e5c8 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -1514,9 +1514,6 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     DBusConnection *conn;
     unsigned i;
 
-    if ((y = pa_shared_get(c, "bluetooth-discovery")))
-        return pa_bluetooth_discovery_ref(y);
-
     y = pa_xnew0(pa_bluetooth_discovery, 1);
     PA_REFCNT_INIT(y);
     y->core = c;
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 36d7174..013cce5 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -39,6 +39,7 @@
 #include <pulsecore/modargs.h>
 #include <pulsecore/poll.h>
 #include <pulsecore/rtpoll.h>
+#include <pulsecore/shared.h>
 #include <pulsecore/socket-util.h>
 #include <pulsecore/thread.h>
 #include <pulsecore/thread-mq.h>
@@ -1792,8 +1793,12 @@ int pa__init(pa_module* m) {
         goto fail;
     }
 
-    if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))
+    if ((u->discovery = pa_shared_get(u->core, "bluetooth-discovery")))
+        pa_bluetooth_discovery_ref(u->discovery);
+    else {
+        pa_log_error("module-bluez5-discover doesn't seem to be loaded, refusing to load module-bluez5-device");
         goto fail;
+    }
 
     if (!(u->device = pa_bluetooth_discovery_get_device_by_path(u->discovery, path))) {
         pa_log_error("%s is unknown", path);
diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c
index 9f392a2..0b7bf49 100644
--- a/src/modules/bluetooth/module-bluez5-discover.c
+++ b/src/modules/bluetooth/module-bluez5-discover.c
@@ -27,6 +27,7 @@
 #include <pulsecore/core-util.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/module.h>
+#include <pulsecore/shared.h>
 
 #include "bluez5-util.h"
 

commit 774c73309d67eb2406a304fcbc05a369f1bc2aa6
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:55 2013 -0300

    bluetooth: Implement get_n_used() for module-bluez5-device

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 3214cae..36d7174 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -1882,3 +1882,12 @@ void pa__done(pa_module *m) {
 
     pa_xfree(u);
 }
+
+int pa__get_n_used(pa_module *m) {
+    struct userdata *u;
+
+    pa_assert(m);
+    pa_assert_se(u = m->userdata);
+
+    return (u->sink ? pa_sink_linked_by(u->sink) : 0) + (u->source ? pa_source_linked_by(u->source) : 0);
+}

commit ef24f7f3aa40a3dff5869c1e86aa842d81991a48
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:54 2013 -0300

    bluetooth: Handle changes to BlueZ 5 transports state

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index cd9728c..3214cae 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -97,6 +97,7 @@ struct userdata {
     pa_core *core;
 
     pa_hook_slot *device_connection_changed_slot;
+    pa_hook_slot *transport_state_changed_slot;
 
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -1670,6 +1671,62 @@ static int add_card(struct userdata *u) {
 }
 
 /* Run from main thread */
+static void handle_transport_state_change(struct userdata *u, struct pa_bluetooth_transport *t) {
+    bool acquire = false;
+    bool release = false;
+    pa_card_profile *cp;
+    pa_device_port *port;
+
+    pa_assert(u);
+    pa_assert(t);
+
+    /* Update profile availability */
+    if (!(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(t->profile))))
+        return;
+    pa_card_profile_set_available(cp, transport_state_to_availability(t->state));
+
+    /* Update port availability */
+    pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
+    pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_OUTPUT));
+    pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
+    pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_INPUT));
+
+    /* Acquire or release transport as needed */
+    acquire = (t->state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == t->profile);
+    release = (t->state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == t->profile);
+
+    if (acquire && transport_acquire(u, true) >= 0) {
+        if (u->source) {
+            pa_log_debug("Resuming source %s because its transport state changed to playing", u->source->name);
+            pa_source_suspend(u->source, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+        }
+
+        if (u->sink) {
+            pa_log_debug("Resuming sink %s because its transport state changed to playing", u->sink->name);
+            pa_sink_suspend(u->sink, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+        }
+    }
+
+    if (release && u->transport_acquired) {
+        /* FIXME: this release is racy, since the audio stream might have
+         * been set up again in the meantime (but not processed yet by PA).
+         * BlueZ should probably release the transport automatically, and in
+         * that case we would just mark the transport as released */
+
+        /* Remote side closed the stream so we consider it PA_SUSPEND_USER */
+        if (u->source) {
+            pa_log_debug("Suspending source %s because the remote end closed the stream", u->source->name);
+            pa_source_suspend(u->source, true, PA_SUSPEND_USER);
+        }
+
+        if (u->sink) {
+            pa_log_debug("Suspending sink %s because the remote end closed the stream", u->sink->name);
+            pa_sink_suspend(u->sink, true, PA_SUSPEND_USER);
+        }
+    }
+}
+
+/* Run from main thread */
 static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
     pa_assert(d);
     pa_assert(u);
@@ -1683,6 +1740,20 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
     return PA_HOOK_OK;
 }
 
+/* Run from main thread */
+static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t == u->transport && t->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+        pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+
+    if (t->device == u->device)
+        handle_transport_state_change(u, t);
+
+    return PA_HOOK_OK;
+}
+
 /* Run from main thread context */
 static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
     struct bluetooth_msg *u = BLUETOOTH_MSG(obj);
@@ -1735,6 +1806,10 @@ int pa__init(pa_module* m) {
         pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) device_connection_changed_cb, u);
 
+    u->transport_state_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u);
+
     if (add_card(u) < 0)
         goto fail;
 
@@ -1784,6 +1859,9 @@ void pa__done(pa_module *m) {
     if (u->device_connection_changed_slot)
         pa_hook_slot_free(u->device_connection_changed_slot);
 
+    if (u->transport_state_changed_slot)
+        pa_hook_slot_free(u->transport_state_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
 

commit 8d303f0bd118b530259b76703918b95ee4a0e97a
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:53 2013 -0300

    bluetooth: Process source messages for BlueZ 5 cards

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 27ff721..cd9728c 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -660,6 +660,82 @@ static void setup_stream(struct userdata *u) {
         u->read_smoother = pa_smoother_new(PA_USEC_PER_SEC, 2*PA_USEC_PER_SEC, true, true, 10, pa_rtclock_now(), true);
 }
 
+/* Run from IO thread */
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+    struct userdata *u = PA_SOURCE(o)->userdata;
+    bool failed = false;
+    int r;
+
+    pa_assert(u->source == PA_SOURCE(o));
+    pa_assert(u->transport);
+
+    switch (code) {
+
+        case PA_SOURCE_MESSAGE_SET_STATE:
+
+            switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
+
+                case PA_SOURCE_SUSPENDED:
+                    /* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */
+                    if (!PA_SOURCE_IS_OPENED(u->source->thread_info.state))
+                        break;
+
+                    /* Stop the device if the sink is suspended as well */
+                    if (!u->sink || u->sink->state == PA_SINK_SUSPENDED)
+                        transport_release(u);
+
+                    if (u->read_smoother)
+                        pa_smoother_pause(u->read_smoother, pa_rtclock_now());
+
+                    break;
+
+                case PA_SOURCE_IDLE:
+                case PA_SOURCE_RUNNING:
+                    if (u->source->thread_info.state != PA_SOURCE_SUSPENDED)
+                        break;
+
+                    /* Resume the device if the sink was suspended as well */
+                    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+                        if (transport_acquire(u, false) < 0)
+                            failed = true;
+                        else
+                            setup_stream(u);
+                    }
+
+                    /* We don't resume the smoother here. Instead we
+                     * wait until the first packet arrives */
+
+                    break;
+
+                case PA_SOURCE_UNLINKED:
+                case PA_SOURCE_INIT:
+                case PA_SOURCE_INVALID_STATE:
+                    break;
+            }
+
+            break;
+
+        case PA_SOURCE_MESSAGE_GET_LATENCY: {
+            pa_usec_t wi, ri;
+
+            if (u->read_smoother) {
+                wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
+                ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
+
+                *((pa_usec_t*) data) = FIXED_LATENCY_RECORD_A2DP + wi > ri ? FIXED_LATENCY_RECORD_A2DP + wi - ri : 0;
+            } else
+                *((pa_usec_t*) data) = 0;
+
+            return 0;
+        }
+
+    }
+
+    r = pa_source_process_msg(o, code, data, offset, chunk);
+
+    return (r < 0 || !failed) ? r : -1;
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -696,6 +772,7 @@ static int add_source(struct userdata *u) {
     }
 
     u->source->userdata = u;
+    u->source->parent.process_msg = source_process_msg;
 
     return 0;
 }

commit 05366fdc22aa9d7fb563a3d8d4d09307c4659f0c
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:52 2013 -0300

    bluetooth: Process sink messages for BlueZ 5 cards

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 4f2fbd8..27ff721 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -700,6 +700,80 @@ static int add_source(struct userdata *u) {
     return 0;
 }
 
+/* Run from IO thread */
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+    struct userdata *u = PA_SINK(o)->userdata;
+    bool failed = false;
+    int r;
+
+    pa_assert(u->sink == PA_SINK(o));
+    pa_assert(u->transport);
+
+    switch (code) {
+
+        case PA_SINK_MESSAGE_SET_STATE:
+
+            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+
+                case PA_SINK_SUSPENDED:
+                    /* Ignore if transition is PA_SINK_INIT->PA_SINK_SUSPENDED */
+                    if (!PA_SINK_IS_OPENED(u->sink->thread_info.state))
+                        break;
+
+                    /* Stop the device if the source is suspended as well */
+                    if (!u->source || u->source->state == PA_SOURCE_SUSPENDED)
+                        /* We deliberately ignore whether stopping
+                         * actually worked. Since the stream_fd is
+                         * closed it doesn't really matter */
+                        transport_release(u);
+
+                    break;
+
+                case PA_SINK_IDLE:
+                case PA_SINK_RUNNING:
+                    if (u->sink->thread_info.state != PA_SINK_SUSPENDED)
+                        break;
+
+                    /* Resume the device if the source was suspended as well */
+                    if (!u->source || !PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
+                        if (transport_acquire(u, false) < 0)
+                            failed = true;
+                        else
+                            setup_stream(u);
+                    }
+
+                    break;
+
+                case PA_SINK_UNLINKED:
+                case PA_SINK_INIT:
+                case PA_SINK_INVALID_STATE:
+                    break;
+            }
+
+            break;
+
+        case PA_SINK_MESSAGE_GET_LATENCY: {
+            pa_usec_t wi, ri;
+
+            if (u->read_smoother) {
+                ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
+                wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->sample_spec);
+            } else {
+                ri = pa_rtclock_now() - u->started_at;
+                wi = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+            }
+
+            *((pa_usec_t*) data) = FIXED_LATENCY_PLAYBACK_A2DP + wi > ri ? FIXED_LATENCY_PLAYBACK_A2DP + wi - ri : 0;
+
+            return 0;
+        }
+    }
+
+    r = pa_sink_process_msg(o, code, data, offset, chunk);
+
+    return (r < 0 || !failed) ? r : -1;
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     pa_sink_new_data data;
@@ -735,6 +809,7 @@ static int add_sink(struct userdata *u) {
     }
 
     u->sink->userdata = u;
+    u->sink->parent.process_msg = sink_process_msg;
 
     return 0;
 }

commit eb9813c833ee5b5dad352482a5c5e36193561587
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:51 2013 -0300

    bluetooth: Create I/O thread function for BlueZ 5 cards
    
    Create the thread function, the render and push functions for A2DP, the
    process message function for communication between the I/O thread and
    the main thread, and other helper functions related to them.

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 1da077f..4f2fbd8 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -24,19 +24,29 @@
 #include <config.h>
 #endif
 
+#include <errno.h>
+
+#include <arpa/inet.h>
 #include <sbc/sbc.h>
 
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-error.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/i18n.h>
 #include <pulsecore/module.h>
 #include <pulsecore/modargs.h>
 #include <pulsecore/poll.h>
 #include <pulsecore/rtpoll.h>
+#include <pulsecore/socket-util.h>
 #include <pulsecore/thread.h>
 #include <pulsecore/thread-mq.h>
+#include <pulsecore/time-smoother.h>
 
 #include "a2dp-codecs.h"
 #include "bluez5-util.h"
+#include "rtp.h"
 
 #include "module-bluez5-device-symdef.h"
 
@@ -46,11 +56,30 @@ PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(false);
 PA_MODULE_USAGE("path=<device object path>");
 
+#define MAX_PLAYBACK_CATCH_UP_USEC (100 * PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_PLAYBACK_A2DP (25 * PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_RECORD_A2DP   (25 * PA_USEC_PER_MSEC)
+
+#define BITPOOL_DEC_LIMIT 32
+#define BITPOOL_DEC_STEP 5
+
 static const char* const valid_modargs[] = {
     "path",
     NULL
 };
 
+enum {
+    BLUETOOTH_MESSAGE_IO_THREAD_FAILED,
+    BLUETOOTH_MESSAGE_MAX
+};
+
+typedef struct bluetooth_msg {
+    pa_msgobject parent;
+    pa_card *card;
+} bluetooth_msg;
+PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject);
+#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))
+
 typedef struct sbc_info {
     sbc_t sbc;                           /* Codec data */
     bool sbc_initialized;                /* Keep track if the encoder is initialized */
@@ -85,12 +114,19 @@ struct userdata {
     pa_thread_mq thread_mq;
     pa_rtpoll *rtpoll;
     pa_rtpoll_item *rtpoll_item;
+    bluetooth_msg *msg;
 
     int stream_fd;
+    int stream_write_type;
     size_t read_link_mtu;
     size_t write_link_mtu;
     size_t read_block_size;
     size_t write_block_size;
+    uint64_t read_index;
+    uint64_t write_index;
+    pa_usec_t started_at;
+    pa_smoother *read_smoother;
+    pa_memchunk write_memchunk;
     pa_sample_spec sample_spec;
     struct sbc_info sbc_info;
 };
@@ -196,6 +232,320 @@ static void connect_ports(struct userdata *u, void *new_data, pa_direction_t dir
     }
 }
 
+/* Run from IO thread */
+static void a2dp_prepare_buffer(struct userdata *u) {
+    size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
+
+    pa_assert(u);
+
+    if (u->sbc_info.buffer_size >= min_buffer_size)
+        return;
+
+    u->sbc_info.buffer_size = 2 * min_buffer_size;
+    pa_xfree(u->sbc_info.buffer);
+    u->sbc_info.buffer = pa_xmalloc(u->sbc_info.buffer_size);
+}
+
+/* Run from IO thread */
+static int a2dp_process_render(struct userdata *u) {
+    struct sbc_info *sbc_info;
+    struct rtp_header *header;
+    struct rtp_payload *payload;
+    size_t nbytes;
+    void *d;
+    const void *p;
+    size_t to_write, to_encode;
+    unsigned frame_count;
+    int ret = 0;
+
+    pa_assert(u);
+    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK);
+    pa_assert(u->sink);
+
+    /* First, render some data */
+    if (!u->write_memchunk.memblock)
+        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
+
+    pa_assert(u->write_memchunk.length == u->write_block_size);
+
+    a2dp_prepare_buffer(u);
+
+    sbc_info = &u->sbc_info;
+    header = sbc_info->buffer;
+    payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
+
+    frame_count = 0;
+
+    /* Try to create a packet of the full MTU */
+
+    p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
+    to_encode = u->write_memchunk.length;
+
+    d = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
+    to_write = sbc_info->buffer_size - sizeof(*header) - sizeof(*payload);
+
+    while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
+        ssize_t written;
+        ssize_t encoded;
+
+        encoded = sbc_encode(&sbc_info->sbc,
+                             p, to_encode,
+                             d, to_write,
+                             &written);
+
+        if (PA_UNLIKELY(encoded <= 0)) {
+            pa_log_error("SBC encoding error (%li)", (long) encoded);
+            pa_memblock_release(u->write_memchunk.memblock);
+            return -1;
+        }
+
+        pa_assert_fp((size_t) encoded <= to_encode);
+        pa_assert_fp((size_t) encoded == sbc_info->codesize);
+
+        pa_assert_fp((size_t) written <= to_write);
+        pa_assert_fp((size_t) written == sbc_info->frame_length);
+
+        p = (const uint8_t*) p + encoded;
+        to_encode -= encoded;
+
+        d = (uint8_t*) d + written;
+        to_write -= written;
+
+        frame_count++;
+    }
+
+    pa_memblock_release(u->write_memchunk.memblock);
+
+    pa_assert(to_encode == 0);
+
+    PA_ONCE_BEGIN {
+        pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&sbc_info->sbc)));
+    } PA_ONCE_END;
+
+    /* write it to the fifo */
+    memset(sbc_info->buffer, 0, sizeof(*header) + sizeof(*payload));
+    header->v = 2;
+    header->pt = 1;
+    header->sequence_number = htons(sbc_info->seq_num++);
+    header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));
+    header->ssrc = htonl(1);
+    payload->frame_count = frame_count;
+
+    nbytes = (uint8_t*) d - (uint8_t*) sbc_info->buffer;
+
+    for (;;) {
+        ssize_t l;
+
+        l = pa_write(u->stream_fd, sbc_info->buffer, nbytes, &u->stream_write_type);
+
+        pa_assert(l != 0);
+
+        if (l < 0) {
+
+            if (errno == EINTR)
+                /* Retry right away if we got interrupted */
+                continue;
+
+            else if (errno == EAGAIN)
+                /* Hmm, apparently the socket was not writable, give up for now */
+                break;
+
+            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
+            ret = -1;
+            break;
+        }
+
+        pa_assert((size_t) l <= nbytes);
+
+        if ((size_t) l != nbytes) {
+            pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
+                        (unsigned long long) l,
+                        (unsigned long long) nbytes);
+            ret = -1;
+            break;
+        }
+
+        u->write_index += (uint64_t) u->write_memchunk.length;
+        pa_memblock_unref(u->write_memchunk.memblock);
+        pa_memchunk_reset(&u->write_memchunk);
+
+        ret = 1;
+
+        break;
+    }
+
+    return ret;
+}
+
+/* Run from IO thread */
+static int a2dp_process_push(struct userdata *u) {
+    int ret = 0;
+    pa_memchunk memchunk;
+
+    pa_assert(u);
+    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
+    pa_assert(u->source);
+    pa_assert(u->read_smoother);
+
+    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
+    memchunk.index = memchunk.length = 0;
+
+    for (;;) {
+        bool found_tstamp = false;
+        pa_usec_t tstamp;
+        struct sbc_info *sbc_info;
+        struct rtp_header *header;
+        struct rtp_payload *payload;
+        const void *p;
+        void *d;
+        ssize_t l;
+        size_t to_write, to_decode;
+
+        a2dp_prepare_buffer(u);
+
+        sbc_info = &u->sbc_info;
+        header = sbc_info->buffer;
+        payload = (struct rtp_payload*) ((uint8_t*) sbc_info->buffer + sizeof(*header));
+
+        l = pa_read(u->stream_fd, sbc_info->buffer, sbc_info->buffer_size, &u->stream_write_type);
+
+        if (l <= 0) {
+
+            if (l < 0 && errno == EINTR)
+                /* Retry right away if we got interrupted */
+                continue;
+
+            else if (l < 0 && errno == EAGAIN)
+                /* Hmm, apparently the socket was not readable, give up for now. */
+                break;
+
+            pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
+            ret = -1;
+            break;
+        }
+
+        pa_assert((size_t) l <= sbc_info->buffer_size);
+
+        u->read_index += (uint64_t) l;
+
+        /* TODO: get timestamp from rtp */
+        if (!found_tstamp) {
+            /* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */
+            tstamp = pa_rtclock_now();
+        }
+
+        pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+        pa_smoother_resume(u->read_smoother, tstamp, true);
+
+        p = (uint8_t*) sbc_info->buffer + sizeof(*header) + sizeof(*payload);
+        to_decode = l - sizeof(*header) - sizeof(*payload);
+
+        d = pa_memblock_acquire(memchunk.memblock);
+        to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
+
+        while (PA_LIKELY(to_decode > 0)) {
+            size_t written;
+            ssize_t decoded;
+
+            decoded = sbc_decode(&sbc_info->sbc,
+                                 p, to_decode,
+                                 d, to_write,
+                                 &written);
+
+            if (PA_UNLIKELY(decoded <= 0)) {
+                pa_log_error("SBC decoding error (%li)", (long) decoded);
+                pa_memblock_release(memchunk.memblock);
+                pa_memblock_unref(memchunk.memblock);
+                return -1;
+            }
+
+            /* Reset frame length, it can be changed due to bitpool change */
+            sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+            pa_assert_fp((size_t) decoded <= to_decode);
+            pa_assert_fp((size_t) decoded == sbc_info->frame_length);
+
+            pa_assert_fp((size_t) written == sbc_info->codesize);
+
+            p = (const uint8_t*) p + decoded;
+            to_decode -= decoded;
+
+            d = (uint8_t*) d + written;
+            to_write -= written;
+        }
+
+        memchunk.length -= to_write;
+
+        pa_memblock_release(memchunk.memblock);
+
+        pa_source_post(u->source, &memchunk);
+
+        ret = l;
+        break;
+    }
+
+    pa_memblock_unref(memchunk.memblock);
+
+    return ret;
+}
+
+/* Run from I/O thread */
+static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
+    struct sbc_info *sbc_info;
+
+    pa_assert(u);
+
+    sbc_info = &u->sbc_info;
+
+    if (sbc_info->sbc.bitpool == bitpool)
+        return;
+
+    if (bitpool > sbc_info->max_bitpool)
+        bitpool = sbc_info->max_bitpool;
+    else if (bitpool < sbc_info->min_bitpool)
+        bitpool = sbc_info->min_bitpool;
+
+    sbc_info->sbc.bitpool = bitpool;
+
+    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+    pa_log_debug("Bitpool has changed to %u", sbc_info->sbc.bitpool);
+
+    u->read_block_size =
+        (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+        / sbc_info->frame_length * sbc_info->codesize;
+
+    u->write_block_size =
+        (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+        / sbc_info->frame_length * sbc_info->codesize;
+
+    pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
+    pa_sink_set_fixed_latency_within_thread(u->sink,
+            FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+}
+
+/* Run from I/O thread */
+static void a2dp_reduce_bitpool(struct userdata *u) {
+    struct sbc_info *sbc_info;
+    uint8_t bitpool;
+
+    pa_assert(u);
+
+    sbc_info = &u->sbc_info;
+
+    /* Check if bitpool is already at its limit */
+    if (sbc_info->sbc.bitpool <= BITPOOL_DEC_LIMIT)
+        return;
+
+    bitpool = sbc_info->sbc.bitpool - BITPOOL_DEC_STEP;
+
+    if (bitpool < BITPOOL_DEC_LIMIT)
+        bitpool = BITPOOL_DEC_LIMIT;
+
+    a2dp_set_bitpool(u, bitpool);
+}
+
 static void teardown_stream(struct userdata *u) {
     if (u->rtpoll_item) {
         pa_rtpoll_item_free(u->rtpoll_item);
@@ -207,6 +557,16 @@ static void teardown_stream(struct userdata *u) {
         u->stream_fd = -1;
     }
 
+    if (u->read_smoother) {
+        pa_smoother_free(u->read_smoother);
+        u->read_smoother = NULL;
+    }
+
+    if (u->write_memchunk.memblock) {
+        pa_memblock_unref(u->write_memchunk.memblock);
+        pa_memchunk_reset(&u->write_memchunk);
+    }
+
     pa_log_debug("Audio stream torn down");
 }
 
@@ -244,6 +604,62 @@ static void transport_release(struct userdata *u) {
     teardown_stream(u);
 }
 
+/* Run from I/O thread */
+static void transport_config_mtu(struct userdata *u) {
+    u->read_block_size =
+        (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+        / u->sbc_info.frame_length * u->sbc_info.codesize;
+
+    u->write_block_size =
+        (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+        / u->sbc_info.frame_length * u->sbc_info.codesize;
+
+    if (u->sink) {
+        pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
+        pa_sink_set_fixed_latency_within_thread(u->sink,
+                                                FIXED_LATENCY_PLAYBACK_A2DP +
+                                                pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+    }
+
+    if (u->source)
+        pa_source_set_fixed_latency_within_thread(u->source,
+                                                  FIXED_LATENCY_RECORD_A2DP +
+                                                  pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
+}
+
+/* Run from I/O thread */
+static void setup_stream(struct userdata *u) {
+    struct pollfd *pollfd;
+    int one;
+
+    pa_log_info("Transport %s resuming", u->transport->path);
+
+    transport_config_mtu(u);
+
+    pa_make_fd_nonblock(u->stream_fd);
+    pa_make_socket_low_delay(u->stream_fd);
+
+    one = 1;
+    if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0)
+        pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno));
+
+    pa_log_debug("Stream properly set up, we're ready to roll!");
+
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
+        a2dp_set_bitpool(u, u->sbc_info.max_bitpool);
+
+    u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
+    pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+    pollfd->fd = u->stream_fd;
+    pollfd->events = pollfd->revents = 0;
+
+    u->read_index = u->write_index = 0;
+    u->started_at = 0;
+
+    if (u->source)
+        u->read_smoother = pa_smoother_new(PA_USEC_PER_SEC, 2*PA_USEC_PER_SEC, true, true, 10, pa_rtclock_now(), true);
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -483,6 +899,194 @@ static int init_profile(struct userdata *u) {
 
 /* I/O thread function */
 static void thread_func(void *userdata) {
+    struct userdata *u = userdata;
+    unsigned do_write = 0;
+    unsigned pending_read_bytes = 0;
+    bool writable = false;
+
+    pa_assert(u);
+    pa_assert(u->transport);
+
+    pa_log_debug("IO Thread starting up");
+
+    if (u->core->realtime_scheduling)
+        pa_make_realtime(u->core->realtime_priority);
+
+    pa_thread_mq_install(&u->thread_mq);
+
+    /* Setup the stream only if the transport was already acquired */
+    if (u->transport_acquired)
+        setup_stream(u);
+
+    for (;;) {
+        struct pollfd *pollfd;
+        int ret;
+        bool disable_timer = true;
+
+        pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
+
+        if (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state)) {
+
+            /* We should send two blocks to the device before we expect
+             * a response. */
+
+            if (u->write_index == 0 && u->read_index <= 0)
+                do_write = 2;
+
+            if (pollfd && (pollfd->revents & POLLIN)) {
+                int n_read;
+
+                n_read = a2dp_process_push(u);
+
+                if (n_read < 0)
+                    goto io_fail;
+
+                /* We just read something, so we are supposed to write something, too */
+                pending_read_bytes += n_read;
+                do_write += pending_read_bytes / u->write_block_size;
+                pending_read_bytes = pending_read_bytes % u->write_block_size;
+            }
+        }
+
+        if (u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state)) {
+
+            if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
+                pa_sink_process_rewind(u->sink, 0);
+
+            if (pollfd) {
+                if (pollfd->revents & POLLOUT)
+                    writable = true;
+
+                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {
+                    pa_usec_t time_passed;
+                    pa_usec_t audio_sent;
+
+                    /* Hmm, there is no input stream we could synchronize
+                     * to. So let's do things by time */
+
+                    time_passed = pa_rtclock_now() - u->started_at;
+                    audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+
+                    if (audio_sent <= time_passed) {
+                        pa_usec_t audio_to_send = time_passed - audio_sent;
+
+                        /* Never try to catch up for more than 100ms */
+                        if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) {
+                            pa_usec_t skip_usec;
+                            uint64_t skip_bytes;
+
+                            skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC;
+                            skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec);
+
+                            if (skip_bytes > 0) {
+                                pa_memchunk tmp;
+
+                                pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
+                                            (unsigned long long) skip_usec,
+                                            (unsigned long long) skip_bytes);
+
+                                pa_sink_render_full(u->sink, skip_bytes, &tmp);
+                                pa_memblock_unref(tmp.memblock);
+                                u->write_index += skip_bytes;
+
+                                if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
+                                    a2dp_reduce_bitpool(u);
+                            }
+                        }
+
+                        do_write = 1;
+                        pending_read_bytes = 0;
+                    }
+                }
+
+                if (writable && do_write > 0) {
+                    int n_written;
+
+                    if (u->write_index <= 0)
+                        u->started_at = pa_rtclock_now();
+
+                    if ((n_written = a2dp_process_render(u)) < 0)
+                        goto io_fail;
+
+                    if (n_written == 0)
+                        pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!");
+
+                    do_write -= n_written;
+                    writable = false;
+                }
+
+                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0) {
+                    pa_usec_t sleep_for;
+                    pa_usec_t time_passed, next_write_at;
+
+                    if (writable) {
+                        /* Hmm, there is no input stream we could synchronize
+                         * to. So let's estimate when we need to wake up the latest */
+                        time_passed = pa_rtclock_now() - u->started_at;
+                        next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+                        sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
+                        /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
+                    } else
+                        /* drop stream every 500 ms */
+                        sleep_for = PA_USEC_PER_MSEC * 500;
+
+                    pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for);
+                    disable_timer = false;
+                }
+            }
+        }
+
+        if (disable_timer)
+            pa_rtpoll_set_timer_disabled(u->rtpoll);
+
+        /* Hmm, nothing to do. Let's sleep */
+        if (pollfd)
+            pollfd->events = (short) (((u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state) && !writable) ? POLLOUT : 0) |
+                                      (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state) ? POLLIN : 0));
+
+        if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0) {
+            pa_log_debug("pa_rtpoll_run failed with: %d", ret);
+            goto fail;
+        }
+        if (ret == 0) {
+            pa_log_debug("IO thread shutdown requested, stopping cleanly");
+            transport_release(u);
+            goto finish;
+        }
+
+        pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
+
+        if (pollfd && (pollfd->revents & ~(POLLOUT|POLLIN))) {
+            pa_log_info("FD error: %s%s%s%s",
+                        pollfd->revents & POLLERR ? "POLLERR " :"",
+                        pollfd->revents & POLLHUP ? "POLLHUP " :"",
+                        pollfd->revents & POLLPRI ? "POLLPRI " :"",
+                        pollfd->revents & POLLNVAL ? "POLLNVAL " :"");
+            goto io_fail;
+        }
+
+        continue;
+
+io_fail:
+        /* In case of HUP, just tear down the streams */
+        if (!pollfd || (pollfd->revents & POLLHUP) == 0)
+            goto fail;
+
+        do_write = 0;
+        pending_read_bytes = 0;
+        writable = false;
+
+        teardown_stream(u);
+    }
+
+fail:
+    /* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */
+    pa_log_debug("IO thread failed");
+    pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_IO_THREAD_FAILED, NULL, 0, NULL, NULL);
+    pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+    pa_log_debug("IO thread shutting down");
 }
 
 /* Run from main thread */
@@ -562,6 +1166,11 @@ static void stop_thread(struct userdata *u) {
         pa_source_unref(u->source);
         u->source = NULL;
     }
+
+    if (u->read_smoother) {
+        pa_smoother_free(u->read_smoother);
+        u->read_smoother = NULL;
+    }
 }
 
 /* Run from main thread */
@@ -922,6 +1531,23 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
     return PA_HOOK_OK;
 }
 
+/* Run from main thread context */
+static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+    struct bluetooth_msg *u = BLUETOOTH_MSG(obj);
+
+    switch (code) {
+        case BLUETOOTH_MESSAGE_IO_THREAD_FAILED:
+            if (u->card->module->unload_requested)
+                break;
+
+            pa_log_debug("Switching the profile to off due to IO thread failure.");
+            pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+            break;
+    }
+
+    return 0;
+}
+
 int pa__init(pa_module* m) {
     struct userdata *u;
     const char *path;
@@ -960,6 +1586,12 @@ int pa__init(pa_module* m) {
     if (add_card(u) < 0)
         goto fail;
 
+    if (!(u->msg = pa_msgobject_new(bluetooth_msg)))
+        goto fail;
+
+    u->msg->parent.process_msg = device_process_msg;
+    u->msg->card = u->card;
+
     if (u->profile != PA_BLUETOOTH_PROFILE_OFF)
         if (init_profile(u) < 0)
             goto off;
@@ -1000,9 +1632,15 @@ void pa__done(pa_module *m) {
     if (u->device_connection_changed_slot)
         pa_hook_slot_free(u->device_connection_changed_slot);
 
+    if (u->sbc_info.buffer)
+        pa_xfree(u->sbc_info.buffer);
+
     if (u->sbc_info.sbc_initialized)
         sbc_finish(&u->sbc_info.sbc);
 
+    if (u->msg)
+        pa_xfree(u->msg);
+
     if (u->card)
         pa_card_free(u->card);
 

commit 44462904df38386b0b2c21a81494662f5fb8d959
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:50 2013 -0300

    bluetooth: Set card profile for BlueZ 5 cards

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 3bf9a35..1da077f 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -789,6 +789,48 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
 }
 
 /* Run from main thread */
+static int set_profile_cb(pa_card *c, pa_card_profile *new_profile) {
+    struct userdata *u;
+    pa_bluetooth_profile_t *p;
+
+    pa_assert(c);
+    pa_assert(new_profile);
+    pa_assert_se(u = c->userdata);
+
+    p = PA_CARD_PROFILE_DATA(new_profile);
+
+    if (*p != PA_BLUETOOTH_PROFILE_OFF) {
+        const pa_bluetooth_device *d = u->device;
+
+        if (!d->transports[*p] || d->transports[*p]->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
+            pa_log_warn("Refused to switch profile to %s: Not connected", new_profile->name);
+            return -PA_ERR_IO;
+        }
+    }
+
+    stop_thread(u);
+
+    u->profile = *p;
+
+    if (u->profile != PA_BLUETOOTH_PROFILE_OFF)
+        if (init_profile(u) < 0)
+            goto off;
+
+    if (u->sink || u->source)
+        if (start_thread(u) < 0)
+            goto off;
+
+    return 0;
+
+off:
+    stop_thread(u);
+
+    pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+
+    return -PA_ERR_IO;
+}
+
+/* Run from main thread */
 static int add_card(struct userdata *u) {
     const pa_bluetooth_device *d;
     pa_card_new_data data;
@@ -858,6 +900,7 @@ static int add_card(struct userdata *u) {
     }
 
     u->card->userdata = u;
+    u->card->set_profile = set_profile_cb;
 
     p = PA_CARD_PROFILE_DATA(u->card->active_profile);
     u->profile = *p;

commit d243fdd07b4bb59771398ad3efddec96a9337f58
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:49 2013 -0300

    bluetooth: Start / stop I/O thread for BlueZ 5 cards

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 237f897..3bf9a35 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -30,6 +30,10 @@
 #include <pulsecore/i18n.h>
 #include <pulsecore/module.h>
 #include <pulsecore/modargs.h>
+#include <pulsecore/poll.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
 
 #include "a2dp-codecs.h"
 #include "bluez5-util.h"
@@ -77,6 +81,11 @@ struct userdata {
     char *output_port_name;
     char *input_port_name;
 
+    pa_thread *thread;
+    pa_thread_mq thread_mq;
+    pa_rtpoll *rtpoll;
+    pa_rtpoll_item *rtpoll_item;
+
     int stream_fd;
     size_t read_link_mtu;
     size_t write_link_mtu;
@@ -187,6 +196,20 @@ static void connect_ports(struct userdata *u, void *new_data, pa_direction_t dir
     }
 }
 
+static void teardown_stream(struct userdata *u) {
+    if (u->rtpoll_item) {
+        pa_rtpoll_item_free(u->rtpoll_item);
+        u->rtpoll_item = NULL;
+    }
+
+    if (u->stream_fd >= 0) {
+        pa_close(u->stream_fd);
+        u->stream_fd = -1;
+    }
+
+    pa_log_debug("Audio stream torn down");
+}
+
 static int transport_acquire(struct userdata *u, bool optional) {
     pa_assert(u->transport);
 
@@ -205,6 +228,22 @@ static int transport_acquire(struct userdata *u, bool optional) {
     return 0;
 }
 
+static void transport_release(struct userdata *u) {
+    pa_assert(u->transport);
+
+    /* Ignore if already released */
+    if (!u->transport_acquired)
+        return;
+
+    pa_log_debug("Releasing transport %s", u->transport->path);
+
+    u->transport->release(u->transport);
+
+    u->transport_acquired = false;
+
+    teardown_stream(u);
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -442,6 +481,89 @@ static int init_profile(struct userdata *u) {
     return r;
 }
 
+/* I/O thread function */
+static void thread_func(void *userdata) {
+}
+
+/* Run from main thread */
+static int start_thread(struct userdata *u) {
+    pa_assert(u);
+    pa_assert(!u->thread);
+    pa_assert(!u->rtpoll);
+    pa_assert(!u->rtpoll_item);
+
+    u->rtpoll = pa_rtpoll_new();
+    pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll);
+
+    if (!(u->thread = pa_thread_new("bluetooth", thread_func, u))) {
+        pa_log_error("Failed to create IO thread");
+        return -1;
+    }
+
+    if (u->sink) {
+        pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+        pa_sink_set_rtpoll(u->sink, u->rtpoll);
+        pa_sink_put(u->sink);
+
+        if (u->sink->set_volume)
+            u->sink->set_volume(u->sink);
+    }
+
+    if (u->source) {
+        pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+        pa_source_set_rtpoll(u->source, u->rtpoll);
+        pa_source_put(u->source);
+
+        if (u->source->set_volume)
+            u->source->set_volume(u->source);
+    }
+
+    return 0;
+}
+
+/* Run from main thread */
+static void stop_thread(struct userdata *u) {
+    pa_assert(u);
+
+    if (u->sink)
+        pa_sink_unlink(u->sink);
+
+    if (u->source)
+        pa_source_unlink(u->source);
+
+    if (u->thread) {
+        pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+        pa_thread_free(u->thread);
+        u->thread = NULL;
+    }
+
+    if (u->rtpoll_item) {
+        pa_rtpoll_item_free(u->rtpoll_item);
+        u->rtpoll_item = NULL;
+    }
+
+    if (u->rtpoll) {
+        pa_thread_mq_done(&u->thread_mq);
+        pa_rtpoll_free(u->rtpoll);
+        u->rtpoll = NULL;
+    }
+
+    if (u->transport) {
+        transport_release(u);
+        u->transport = NULL;
+    }
+
+    if (u->sink) {
+        pa_sink_unref(u->sink);
+        u->sink = NULL;
+    }
+
+    if (u->source) {
+        pa_source_unref(u->source);
+        u->source = NULL;
+    }
+}
+
 /* Run from main thread */
 static char *cleanup_name(const char *name) {
     char *t, *s, *d;
@@ -799,9 +921,14 @@ int pa__init(pa_module* m) {
         if (init_profile(u) < 0)
             goto off;
 
+    if (u->sink || u->source)
+        if (start_thread(u) < 0)
+            goto off;
+
     return 0;
 
 off:
+    stop_thread(u);
 
     pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
 
@@ -825,6 +952,8 @@ void pa__done(pa_module *m) {
     if (!(u = m->userdata))
         return;
 
+    stop_thread(u);
+
     if (u->device_connection_changed_slot)
         pa_hook_slot_free(u->device_connection_changed_slot);
 

commit 8d1decffd7e4adeb2b6930e81ba9a17b761af263
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:48 2013 -0300

    bluetooth: Create source for BlueZ 5 cards

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index e15c954..237f897 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -72,6 +72,7 @@ struct userdata {
 
     pa_card *card;
     pa_sink *sink;
+    pa_source *source;
     pa_bluetooth_profile_t profile;
     char *output_port_name;
     char *input_port_name;
@@ -205,6 +206,46 @@ static int transport_acquire(struct userdata *u, bool optional) {
 }
 
 /* Run from main thread */
+static int add_source(struct userdata *u) {
+    pa_source_new_data data;
+
+    pa_assert(u->transport);
+
+    pa_source_new_data_init(&data);
+    data.module = u->module;
+    data.card = u->card;
+    data.driver = __FILE__;
+    data.name = pa_sprintf_malloc("bluez_source.%s", u->device->address);
+    data.namereg_fail = false;
+    pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
+    pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
+
+    connect_ports(u, &data, PA_DIRECTION_INPUT);
+
+    if (!u->transport_acquired)
+        switch (u->profile) {
+            case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+                data.suspend_cause = PA_SUSPEND_USER;
+                break;
+            case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+            case PA_BLUETOOTH_PROFILE_OFF:
+                pa_assert_not_reached();
+                break;
+        }
+
+    u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
+    pa_source_new_data_done(&data);
+    if (!u->source) {
+        pa_log_error("Failed to create source");
+        return -1;
+    }
+
+    u->source->userdata = u;
+
+    return 0;
+}
+
+/* Run from main thread */
 static int add_sink(struct userdata *u) {
     pa_sink_new_data data;
 
@@ -394,7 +435,9 @@ static int init_profile(struct userdata *u) {
         if (add_sink(u) < 0)
             r = -1;
 
-    /* TODO: add source */
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+        if (add_source(u) < 0)
+            r = -1;
 
     return r;
 }

commit 0274032141a0574f24c4eef9ea2793bf83832751
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:47 2013 -0300

    bluetooth: Create sink for BlueZ 5 cards

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 8e1e326..e15c954 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -71,6 +71,7 @@ struct userdata {
     bool transport_acquired;
 
     pa_card *card;
+    pa_sink *sink;
     pa_bluetooth_profile_t profile;
     char *output_port_name;
     char *input_port_name;
@@ -166,6 +167,25 @@ static const char *form_factor_to_string(pa_bluetooth_form_factor_t ff) {
     pa_assert_not_reached();
 }
 
+/* Run from main thread */
+static void connect_ports(struct userdata *u, void *new_data, pa_direction_t direction) {
+    pa_device_port *port;
+
+    if (direction == PA_DIRECTION_OUTPUT) {
+        pa_sink_new_data *sink_new_data = new_data;
+
+        pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
+        pa_assert_se(pa_hashmap_put(sink_new_data->ports, port->name, port) >= 0);
+        pa_device_port_ref(port);
+    } else {
+        pa_source_new_data *source_new_data = new_data;
+
+        pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
+        pa_assert_se(pa_hashmap_put(source_new_data->ports, port->name, port) >= 0);
+        pa_device_port_ref(port);
+    }
+}
+
 static int transport_acquire(struct userdata *u, bool optional) {
     pa_assert(u->transport);
 
@@ -185,6 +205,45 @@ static int transport_acquire(struct userdata *u, bool optional) {
 }
 
 /* Run from main thread */
+static int add_sink(struct userdata *u) {
+    pa_sink_new_data data;
+
+    pa_assert(u->transport);
+
+    pa_sink_new_data_init(&data);
+    data.module = u->module;
+    data.card = u->card;
+    data.driver = __FILE__;
+    data.name = pa_sprintf_malloc("bluez_sink.%s", u->device->address);
+    data.namereg_fail = false;
+    pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
+    pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
+
+    connect_ports(u, &data, PA_DIRECTION_OUTPUT);
+
+    if (!u->transport_acquired)
+        switch (u->profile) {
+            case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+                /* Profile switch should have failed */
+            case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+            case PA_BLUETOOTH_PROFILE_OFF:
+                pa_assert_not_reached();
+                break;
+        }
+
+    u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
+    pa_sink_new_data_done(&data);
+    if (!u->sink) {
+        pa_log_error("Failed to create sink");
+        return -1;
+    }
+
+    u->sink->userdata = u;
+
+    return 0;
+}
+
+/* Run from main thread */
 static void transport_config(struct userdata *u) {
     sbc_info_t *sbc_info = &u->sbc_info;
     a2dp_sbc_t *config;
@@ -331,7 +390,11 @@ static int init_profile(struct userdata *u) {
 
     pa_assert(u->transport);
 
-    /* TODO: add sink/source */
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
+        if (add_sink(u) < 0)
+            r = -1;
+
+    /* TODO: add source */
 
     return r;
 }

commit 7e80678cc53e8b5404dffe18ea0745666189c1ba
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:46 2013 -0300

    bluetooth: Initialize profiles for BlueZ 5 cards
    
    Initialized the currently active profile, configure and acquire the
    transport.

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 261c6cc..8e1e326 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -24,11 +24,14 @@
 #include <config.h>
 #endif
 
+#include <sbc/sbc.h>
+
 #include <pulsecore/core-util.h>
 #include <pulsecore/i18n.h>
 #include <pulsecore/module.h>
 #include <pulsecore/modargs.h>
 
+#include "a2dp-codecs.h"
 #include "bluez5-util.h"
 
 #include "module-bluez5-device-symdef.h"
@@ -44,6 +47,18 @@ static const char* const valid_modargs[] = {
     NULL
 };
 
+typedef struct sbc_info {
+    sbc_t sbc;                           /* Codec data */
+    bool sbc_initialized;                /* Keep track if the encoder is initialized */
+    size_t codesize, frame_length;       /* SBC Codesize, frame_length. We simply cache those values here */
+    uint16_t seq_num;                    /* Cumulative packet sequence */
+    uint8_t min_bitpool;
+    uint8_t max_bitpool;
+
+    void* buffer;                        /* Codec transfer buffer */
+    size_t buffer_size;                  /* Size of the buffer */
+} sbc_info_t;
+
 struct userdata {
     pa_module *module;
     pa_core *core;
@@ -52,11 +67,21 @@ struct userdata {
 
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
+    pa_bluetooth_transport *transport;
+    bool transport_acquired;
 
     pa_card *card;
     pa_bluetooth_profile_t profile;
     char *output_port_name;
     char *input_port_name;
+
+    int stream_fd;
+    size_t read_link_mtu;
+    size_t write_link_mtu;
+    size_t read_block_size;
+    size_t write_block_size;
+    pa_sample_spec sample_spec;
+    struct sbc_info sbc_info;
 };
 
 typedef enum pa_bluetooth_form_factor {
@@ -141,6 +166,176 @@ static const char *form_factor_to_string(pa_bluetooth_form_factor_t ff) {
     pa_assert_not_reached();
 }
 
+static int transport_acquire(struct userdata *u, bool optional) {
+    pa_assert(u->transport);
+
+    if (u->transport_acquired)
+        return 0;
+
+    pa_log_debug("Acquiring transport %s", u->transport->path);
+
+    u->stream_fd = u->transport->acquire(u->transport, optional, &u->read_link_mtu, &u->write_link_mtu);
+    if (u->stream_fd < 0)
+        return -1;
+
+    u->transport_acquired = true;
+    pa_log_info("Transport %s acquired: fd %d", u->transport->path, u->stream_fd);
+
+    return 0;
+}
+
+/* Run from main thread */
+static void transport_config(struct userdata *u) {
+    sbc_info_t *sbc_info = &u->sbc_info;
+    a2dp_sbc_t *config;
+
+    pa_assert(u->transport);
+
+    u->sample_spec.format = PA_SAMPLE_S16LE;
+    config = (a2dp_sbc_t *) u->transport->config;
+
+    if (sbc_info->sbc_initialized)
+        sbc_reinit(&sbc_info->sbc, 0);
+    else
+        sbc_init(&sbc_info->sbc, 0);
+    sbc_info->sbc_initialized = true;
+
+    switch (config->frequency) {
+        case SBC_SAMPLING_FREQ_16000:
+            sbc_info->sbc.frequency = SBC_FREQ_16000;
+            u->sample_spec.rate = 16000U;
+            break;
+        case SBC_SAMPLING_FREQ_32000:
+            sbc_info->sbc.frequency = SBC_FREQ_32000;
+            u->sample_spec.rate = 32000U;
+            break;
+        case SBC_SAMPLING_FREQ_44100:
+            sbc_info->sbc.frequency = SBC_FREQ_44100;
+            u->sample_spec.rate = 44100U;
+            break;
+        case SBC_SAMPLING_FREQ_48000:
+            sbc_info->sbc.frequency = SBC_FREQ_48000;
+            u->sample_spec.rate = 48000U;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->channel_mode) {
+        case SBC_CHANNEL_MODE_MONO:
+            sbc_info->sbc.mode = SBC_MODE_MONO;
+            u->sample_spec.channels = 1;
+            break;
+        case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+            sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+            u->sample_spec.channels = 2;
+            break;
+        case SBC_CHANNEL_MODE_STEREO:
+            sbc_info->sbc.mode = SBC_MODE_STEREO;
+            u->sample_spec.channels = 2;
+            break;
+        case SBC_CHANNEL_MODE_JOINT_STEREO:
+            sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
+            u->sample_spec.channels = 2;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->allocation_method) {
+        case SBC_ALLOCATION_SNR:
+            sbc_info->sbc.allocation = SBC_AM_SNR;
+            break;
+        case SBC_ALLOCATION_LOUDNESS:
+            sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->subbands) {
+        case SBC_SUBBANDS_4:
+            sbc_info->sbc.subbands = SBC_SB_4;
+            break;
+        case SBC_SUBBANDS_8:
+            sbc_info->sbc.subbands = SBC_SB_8;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->block_length) {
+        case SBC_BLOCK_LENGTH_4:
+            sbc_info->sbc.blocks = SBC_BLK_4;
+            break;
+        case SBC_BLOCK_LENGTH_8:
+            sbc_info->sbc.blocks = SBC_BLK_8;
+            break;
+        case SBC_BLOCK_LENGTH_12:
+            sbc_info->sbc.blocks = SBC_BLK_12;
+            break;
+        case SBC_BLOCK_LENGTH_16:
+            sbc_info->sbc.blocks = SBC_BLK_16;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    sbc_info->min_bitpool = config->min_bitpool;
+    sbc_info->max_bitpool = config->max_bitpool;
+
+    /* Set minimum bitpool for source to get the maximum possible block_size */
+    sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool;
+    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+
+    pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u",
+                sbc_info->sbc.allocation, sbc_info->sbc.subbands, sbc_info->sbc.blocks, sbc_info->sbc.bitpool);
+}
+
+/* Run from main thread */
+static int setup_transport(struct userdata *u) {
+    pa_bluetooth_transport *t;
+
+    pa_assert(u);
+    pa_assert(!u->transport);
+    pa_assert(u->profile != PA_BLUETOOTH_PROFILE_OFF);
+
+    /* check if profile has a transport */
+    t = u->device->transports[u->profile];
+    if (!t || t->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
+        pa_log_warn("Profile has no transport");
+        return -1;
+    }
+
+    u->transport = t;
+
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+        transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
+    else if (transport_acquire(u, false) < 0)
+        return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
+
+    transport_config(u);
+
+    return 0;
+}
+
+/* Run from main thread */
+static int init_profile(struct userdata *u) {
+    int r = 0;
+    pa_assert(u);
+    pa_assert(u->profile != PA_BLUETOOTH_PROFILE_OFF);
+
+    if (setup_transport(u) < 0)
+        return -1;
+
+    pa_assert(u->transport);
+
+    /* TODO: add sink/source */
+
+    return r;
+}
+
 /* Run from main thread */
 static char *cleanup_name(const char *name) {
     char *t, *s, *d;
@@ -494,6 +689,16 @@ int pa__init(pa_module* m) {
     if (add_card(u) < 0)
         goto fail;
 
+    if (u->profile != PA_BLUETOOTH_PROFILE_OFF)
+        if (init_profile(u) < 0)
+            goto off;
+
+    return 0;
+
+off:
+
+    pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+
     return 0;
 
 fail:
@@ -517,6 +722,9 @@ void pa__done(pa_module *m) {
     if (u->device_connection_changed_slot)
         pa_hook_slot_free(u->device_connection_changed_slot);
 
+    if (u->sbc_info.sbc_initialized)
+        sbc_finish(&u->sbc_info.sbc);
+
     if (u->card)
         pa_card_free(u->card);
 

commit a0ed6d7970d55ca7ae083c40262ae1cefc600590
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:45 2013 -0300

    bluetooth: Create BlueZ 5 card profile for each audio UUID

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index c4f1636..261c6cc 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -220,6 +220,18 @@ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t d
 }
 
 /* Run from main thread */
+static pa_available_t transport_state_to_availability(pa_bluetooth_transport_state_t state) {
+    switch (state) {
+        case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
+            return PA_AVAILABLE_NO;
+        case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
+            return PA_AVAILABLE_YES;
+        default:
+            return PA_AVAILABLE_UNKNOWN;
+    }
+}
+
+/* Run from main thread */
 static void create_card_ports(struct userdata *u, pa_hashmap *ports) {
     pa_device_port *port;
     pa_device_port_new_data port_data;
@@ -311,6 +323,49 @@ static void create_card_ports(struct userdata *u, pa_hashmap *ports) {
 }
 
 /* Run from main thread */
+static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid, pa_hashmap *ports) {
+    pa_device_port *input_port, *output_port;
+    pa_card_profile *cp = NULL;
+    pa_bluetooth_profile_t *p;
+
+    pa_assert(u->input_port_name);
+    pa_assert(u->output_port_name);
+    pa_assert_se(input_port = pa_hashmap_get(ports, u->input_port_name));
+    pa_assert_se(output_port = pa_hashmap_get(ports, u->output_port_name));
+
+    if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) {
+	/* TODO: Change this profile's name to a2dp_sink, to reflect the remote
+         * device's role and be consistent with the a2dp source profile */
+        cp = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
+        cp->priority = 10;
+        cp->n_sinks = 1;
+        cp->n_sources = 0;
+        cp->max_sink_channels = 2;
+        cp->max_source_channels = 0;
+        pa_hashmap_put(output_port->profiles, cp->name, cp);
+
+        p = PA_CARD_PROFILE_DATA(cp);
+        *p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
+    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE)) {
+        cp = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
+        cp->priority = 10;
+        cp->n_sinks = 0;
+        cp->n_sources = 1;
+        cp->max_sink_channels = 0;
+        cp->max_source_channels = 2;
+        pa_hashmap_put(input_port->profiles, cp->name, cp);
+
+        p = PA_CARD_PROFILE_DATA(cp);
+        *p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
+    }
+
+    if (cp && u->device->transports[*p])
+        cp->available = transport_state_to_availability(u->device->transports[*p]->state);
+
+    return cp;
+}
+
+/* Run from main thread */
 static int add_card(struct userdata *u) {
     const pa_bluetooth_device *d;
     pa_card_new_data data;
@@ -318,6 +373,8 @@ static int add_card(struct userdata *u) {
     pa_bluetooth_form_factor_t ff;
     pa_card_profile *cp;
     pa_bluetooth_profile_t *p;
+    const char *uuid;
+    void *state;
 
     pa_assert(u);
     pa_assert(u->device);
@@ -348,6 +405,22 @@ static int add_card(struct userdata *u) {
 
     create_card_ports(u, data.ports);
 
+    PA_HASHMAP_FOREACH(uuid, d->uuids, state) {
+        cp = create_card_profile(u, uuid, data.ports);
+
+        if (!cp)
+            continue;
+
+        if (pa_hashmap_get(data.profiles, cp->name)) {
+            pa_card_profile_free(cp);
+            continue;
+        }
+
+        pa_hashmap_put(data.profiles, cp->name, cp);
+    }
+
+    pa_assert(!pa_hashmap_isempty(data.profiles));
+
     cp = pa_card_profile_new("off", _("Off"), sizeof(pa_bluetooth_profile_t));
     cp->available = PA_AVAILABLE_YES;
     p = PA_CARD_PROFILE_DATA(cp);

commit 5a9007338bb64625564207d5a8870ad32de910c9
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:44 2013 -0300

    bluetooth: Create BlueZ 5 card ports

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 1867de9..c4f1636 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -55,6 +55,8 @@ struct userdata {
 
     pa_card *card;
     pa_bluetooth_profile_t profile;
+    char *output_port_name;
+    char *input_port_name;
 };
 
 typedef enum pa_bluetooth_form_factor {
@@ -172,6 +174,143 @@ static char *cleanup_name(const char *name) {
 }
 
 /* Run from main thread */
+static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
+    static const pa_direction_t profile_direction[] = {
+        [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
+        [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
+        [PA_BLUETOOTH_PROFILE_OFF] = 0
+    };
+
+    return profile_direction[p];
+}
+
+/* Run from main thread */
+static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) {
+    pa_available_t result = PA_AVAILABLE_NO;
+    unsigned i;
+
+    pa_assert(u);
+    pa_assert(u->device);
+
+    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
+        pa_bluetooth_transport *transport;
+
+        if (!(get_profile_direction(i) & direction))
+            continue;
+
+        if (!(transport = u->device->transports[i]))
+            continue;
+
+        switch(transport->state) {
+            case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
+                continue;
+
+            case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
+                if (result == PA_AVAILABLE_NO)
+                    result = PA_AVAILABLE_UNKNOWN;
+
+                break;
+
+            case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
+                return PA_AVAILABLE_YES;
+        }
+    }
+
+    return result;
+}
+
+/* Run from main thread */
+static void create_card_ports(struct userdata *u, pa_hashmap *ports) {
+    pa_device_port *port;
+    pa_device_port_new_data port_data;
+    const char *name_prefix, *input_description, *output_description;
+
+    pa_assert(u);
+    pa_assert(ports);
+    pa_assert(u->device);
+
+    name_prefix = "unknown";
+    input_description = _("Bluetooth Input");
+    output_description = _("Bluetooth Output");
+
+    switch (form_factor_from_class(u->device->class_of_device)) {
+        case PA_BLUETOOTH_FORM_FACTOR_HEADSET:
+            name_prefix = "headset";
+            input_description = output_description = _("Headset");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_HANDSFREE:
+            name_prefix = "handsfree";
+            input_description = output_description = _("Handsfree");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_MICROPHONE:
+            name_prefix = "microphone";
+            input_description = _("Microphone");
+            output_description = _("Bluetooth Output");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_SPEAKER:
+            name_prefix = "speaker";
+            input_description = _("Bluetooth Input");
+            output_description = _("Speaker");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_HEADPHONE:
+            name_prefix = "headphone";
+            input_description = _("Bluetooth Input");
+            output_description = _("Headphone");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_PORTABLE:
+            name_prefix = "portable";
+            input_description = output_description = _("Portable");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_CAR:
+            name_prefix = "car";
+            input_description = output_description = _("Car");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_HIFI:
+            name_prefix = "hifi";
+            input_description = output_description = _("HiFi");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_PHONE:
+            name_prefix = "phone";
+            input_description = output_description = _("Phone");
+            break;
+
+        case PA_BLUETOOTH_FORM_FACTOR_UNKNOWN:
+            name_prefix = "unknown";
+            input_description = _("Bluetooth Input");
+            output_description = _("Bluetooth Output");
+            break;
+    }
+
+    u->output_port_name = pa_sprintf_malloc("%s-output", name_prefix);
+    pa_device_port_new_data_init(&port_data);
+    pa_device_port_new_data_set_name(&port_data, u->output_port_name);
+    pa_device_port_new_data_set_description(&port_data, output_description);
+    pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_OUTPUT);
+    pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_OUTPUT));
+    pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0));
+    pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0);
+    pa_device_port_new_data_done(&port_data);
+
+    u->input_port_name = pa_sprintf_malloc("%s-input", name_prefix);
+    pa_device_port_new_data_init(&port_data);
+    pa_device_port_new_data_set_name(&port_data, u->input_port_name);
+    pa_device_port_new_data_set_description(&port_data, input_description);
+    pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_INPUT);
+    pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_INPUT));
+    pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0));
+    pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0);
+    pa_device_port_new_data_done(&port_data);
+}
+
+/* Run from main thread */
 static int add_card(struct userdata *u) {
     const pa_bluetooth_device *d;
     pa_card_new_data data;
@@ -207,6 +346,8 @@ static int add_card(struct userdata *u) {
     data.name = pa_sprintf_malloc("bluez_card.%s", d->address);
     data.namereg_fail = false;
 
+    create_card_ports(u, data.ports);
+
     cp = pa_card_profile_new("off", _("Off"), sizeof(pa_bluetooth_profile_t));
     cp->available = PA_AVAILABLE_YES;
     p = PA_CARD_PROFILE_DATA(cp);
@@ -309,5 +450,8 @@ void pa__done(pa_module *m) {
     if (u->discovery)
         pa_bluetooth_discovery_unref(u->discovery);
 
+    pa_xfree(u->output_port_name);
+    pa_xfree(u->input_port_name);
+
     pa_xfree(u);
 }

commit 501f5e289893e91d461f6ca74d974385838d190f
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:43 2013 -0300

    bluetooth: Create BlueZ 5 card

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 4bd7f34..1867de9 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -24,6 +24,8 @@
 #include <config.h>
 #endif
 
+#include <pulsecore/core-util.h>
+#include <pulsecore/i18n.h>
 #include <pulsecore/module.h>
 #include <pulsecore/modargs.h>
 
@@ -50,8 +52,182 @@ struct userdata {
 
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
+
+    pa_card *card;
+    pa_bluetooth_profile_t profile;
 };
 
+typedef enum pa_bluetooth_form_factor {
+    PA_BLUETOOTH_FORM_FACTOR_UNKNOWN,
+    PA_BLUETOOTH_FORM_FACTOR_HEADSET,
+    PA_BLUETOOTH_FORM_FACTOR_HANDSFREE,
+    PA_BLUETOOTH_FORM_FACTOR_MICROPHONE,
+    PA_BLUETOOTH_FORM_FACTOR_SPEAKER,
+    PA_BLUETOOTH_FORM_FACTOR_HEADPHONE,
+    PA_BLUETOOTH_FORM_FACTOR_PORTABLE,
+    PA_BLUETOOTH_FORM_FACTOR_CAR,
+    PA_BLUETOOTH_FORM_FACTOR_HIFI,
+    PA_BLUETOOTH_FORM_FACTOR_PHONE,
+} pa_bluetooth_form_factor_t;
+
+/* Run from main thread */
+static pa_bluetooth_form_factor_t form_factor_from_class(uint32_t class_of_device) {
+    unsigned major, minor;
+    pa_bluetooth_form_factor_t r;
+
+    static const pa_bluetooth_form_factor_t table[] = {
+        [1] = PA_BLUETOOTH_FORM_FACTOR_HEADSET,
+        [2] = PA_BLUETOOTH_FORM_FACTOR_HANDSFREE,
+        [4] = PA_BLUETOOTH_FORM_FACTOR_MICROPHONE,
+        [5] = PA_BLUETOOTH_FORM_FACTOR_SPEAKER,
+        [6] = PA_BLUETOOTH_FORM_FACTOR_HEADPHONE,
+        [7] = PA_BLUETOOTH_FORM_FACTOR_PORTABLE,
+        [8] = PA_BLUETOOTH_FORM_FACTOR_CAR,
+        [10] = PA_BLUETOOTH_FORM_FACTOR_HIFI
+    };
+
+    /*
+     * See Bluetooth Assigned Numbers:
+     * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
+     */
+    major = (class_of_device >> 8) & 0x1F;
+    minor = (class_of_device >> 2) & 0x3F;
+
+    switch (major) {
+        case 2:
+            return PA_BLUETOOTH_FORM_FACTOR_PHONE;
+        case 4:
+            break;
+        default:
+            pa_log_debug("Unknown Bluetooth major device class %u", major);
+            return PA_BLUETOOTH_FORM_FACTOR_UNKNOWN;
+    }
+
+    r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BLUETOOTH_FORM_FACTOR_UNKNOWN;
+
+    if (!r)
+        pa_log_debug("Unknown Bluetooth minor device class %u", minor);
+
+    return r;
+}
+
+/* Run from main thread */
+static const char *form_factor_to_string(pa_bluetooth_form_factor_t ff) {
+    switch (ff) {
+        case PA_BLUETOOTH_FORM_FACTOR_UNKNOWN:
+            return "unknown";
+        case PA_BLUETOOTH_FORM_FACTOR_HEADSET:
+            return "headset";
+        case PA_BLUETOOTH_FORM_FACTOR_HANDSFREE:
+            return "hands-free";
+        case PA_BLUETOOTH_FORM_FACTOR_MICROPHONE:
+            return "microphone";
+        case PA_BLUETOOTH_FORM_FACTOR_SPEAKER:
+            return "speaker";
+        case PA_BLUETOOTH_FORM_FACTOR_HEADPHONE:
+            return "headphone";
+        case PA_BLUETOOTH_FORM_FACTOR_PORTABLE:
+            return "portable";
+        case PA_BLUETOOTH_FORM_FACTOR_CAR:
+            return "car";
+        case PA_BLUETOOTH_FORM_FACTOR_HIFI:
+            return "hifi";
+        case PA_BLUETOOTH_FORM_FACTOR_PHONE:
+            return "phone";
+    }
+
+    pa_assert_not_reached();
+}
+
+/* Run from main thread */
+static char *cleanup_name(const char *name) {
+    char *t, *s, *d;
+    bool space = false;
+
+    pa_assert(name);
+
+    while ((*name >= 1 && *name <= 32) || *name >= 127)
+        name++;
+
+    t = pa_xstrdup(name);
+
+    for (s = d = t; *s; s++) {
+
+        if (*s <= 32 || *s >= 127 || *s == '_') {
+            space = true;
+            continue;
+        }
+
+        if (space) {
+            *(d++) = ' ';
+            space = false;
+        }
+
+        *(d++) = *s;
+    }
+
+    *d = 0;
+
+    return t;
+}
+
+/* Run from main thread */
+static int add_card(struct userdata *u) {
+    const pa_bluetooth_device *d;
+    pa_card_new_data data;
+    char *alias;
+    pa_bluetooth_form_factor_t ff;
+    pa_card_profile *cp;
+    pa_bluetooth_profile_t *p;
+
+    pa_assert(u);
+    pa_assert(u->device);
+
+    d = u->device;
+
+    pa_card_new_data_init(&data);
+    data.driver = __FILE__;
+    data.module = u->module;
+
+    alias = cleanup_name(d->alias);
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, alias);
+    pa_xfree(alias);
+
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, d->address);
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "bluez");
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "sound");
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_BUS, "bluetooth");
+
+    if ((ff = form_factor_from_class(d->class_of_device)) != PA_BLUETOOTH_FORM_FACTOR_UNKNOWN)
+        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, form_factor_to_string(ff));
+
+    pa_proplist_sets(data.proplist, "bluez.path", d->path);
+    pa_proplist_setf(data.proplist, "bluez.class", "0x%06x", d->class_of_device);
+    pa_proplist_sets(data.proplist, "bluez.alias", d->alias);
+    data.name = pa_sprintf_malloc("bluez_card.%s", d->address);
+    data.namereg_fail = false;
+
+    cp = pa_card_profile_new("off", _("Off"), sizeof(pa_bluetooth_profile_t));
+    cp->available = PA_AVAILABLE_YES;
+    p = PA_CARD_PROFILE_DATA(cp);
+    *p = PA_BLUETOOTH_PROFILE_OFF;
+    pa_hashmap_put(data.profiles, cp->name, cp);
+
+    u->card = pa_card_new(u->core, &data);
+    pa_card_new_data_done(&data);
+    if (!u->card) {
+        pa_log("Failed to allocate card.");
+        return -1;
+    }
+
+    u->card->userdata = u;
+
+    p = PA_CARD_PROFILE_DATA(u->card->active_profile);
+    u->profile = *p;
+
+    return 0;
+}
+
 /* Run from main thread */
 static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
     pa_assert(d);
@@ -101,6 +277,9 @@ int pa__init(pa_module* m) {
         pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) device_connection_changed_cb, u);
 
+    if (add_card(u) < 0)
+        goto fail;
+
     return 0;
 
 fail:
@@ -124,6 +303,9 @@ void pa__done(pa_module *m) {
     if (u->device_connection_changed_slot)
         pa_hook_slot_free(u->device_connection_changed_slot);
 
+    if (u->card)
+        pa_card_free(u->card);
+
     if (u->discovery)
         pa_bluetooth_discovery_unref(u->discovery);
 

commit 6ba1610b6f87b93793979f1def9ed63382c2a56f
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:42 2013 -0300

    bluetooth: Unload module-bluez5-device on device's disconnection

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 595e306..4bd7f34 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -46,10 +46,26 @@ struct userdata {
     pa_module *module;
     pa_core *core;
 
+    pa_hook_slot *device_connection_changed_slot;
+
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
 };
 
+/* Run from main thread */
+static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
+    pa_assert(d);
+    pa_assert(u);
+
+    if (d != u->device || pa_bluetooth_device_any_transport_connected(d))
+        return PA_HOOK_OK;
+
+    pa_log_debug("Unloading module for device %s", d->path);
+    pa_module_unload(u->core, u->module, true);
+
+    return PA_HOOK_OK;
+}
+
 int pa__init(pa_module* m) {
     struct userdata *u;
     const char *path;
@@ -81,6 +97,10 @@ int pa__init(pa_module* m) {
 
     pa_modargs_free(ma);
 
+    u->device_connection_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) device_connection_changed_cb, u);
+
     return 0;
 
 fail:
@@ -101,6 +121,9 @@ void pa__done(pa_module *m) {
     if (!(u = m->userdata))
         return;
 
+    if (u->device_connection_changed_slot)
+        pa_hook_slot_free(u->device_connection_changed_slot);
+
     if (u->discovery)
         pa_bluetooth_discovery_unref(u->discovery);
 

commit da6195760245914c366430e6f6a3d8e4807db790
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:41 2013 -0300

    bluetooth: Get BlueZ 5 device object
    
    Get the remote device information stored in pa_bluetooth_discovery. This
    also creates the mandatory parameter 'path' for module-bluez5-device,
    which is used to inform the object path of the remote device in BlueZ on
    the module load.

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 890f7e4..595e306 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -25,6 +25,7 @@
 #endif
 
 #include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
 
 #include "bluez5-util.h"
 
@@ -34,10 +35,74 @@ PA_MODULE_AUTHOR("João Paulo Rechi Vita");
 PA_MODULE_DESCRIPTION("BlueZ 5 Bluetooth audio sink and source");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(false);
+PA_MODULE_USAGE("path=<device object path>");
+
+static const char* const valid_modargs[] = {
+    "path",
+    NULL
+};
+
+struct userdata {
+    pa_module *module;
+    pa_core *core;
+
+    pa_bluetooth_discovery *discovery;
+    pa_bluetooth_device *device;
+};
 
 int pa__init(pa_module* m) {
+    struct userdata *u;
+    const char *path;
+    pa_modargs *ma;
+
+    pa_assert(m);
+
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+    u->module = m;
+    u->core = m->core;
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log_error("Failed to parse module arguments");
+        goto fail;
+    }
+
+    if (!(path = pa_modargs_get_value(ma, "path", NULL))) {
+        pa_log_error("Failed to get device path from module arguments");
+        goto fail;
+    }
+
+    if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))
+        goto fail;
+
+    if (!(u->device = pa_bluetooth_discovery_get_device_by_path(u->discovery, path))) {
+        pa_log_error("%s is unknown", path);
+        goto fail;
+    }
+
+    pa_modargs_free(ma);
+
     return 0;
+
+fail:
+
+    if (ma)
+        pa_modargs_free(ma);
+
+    pa__done(m);
+
+    return -1;
 }
 
 void pa__done(pa_module *m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->discovery)
+        pa_bluetooth_discovery_unref(u->discovery);
+
+    pa_xfree(u);
 }

commit 384f4751ebc290ac2afebfe002b1a12c132de167
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:40 2013 -0300

    bluetooth: Create module-bluez5-device

diff --git a/src/Makefile.am b/src/Makefile.am
index b5814bf..d67e487 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1335,7 +1335,8 @@ endif
 if HAVE_BLUEZ_5
 modlibexec_LTLIBRARIES += \
 		libbluez5-util.la \
-		module-bluez5-discover.la
+		module-bluez5-discover.la \
+		module-bluez5-device.la
 endif
 
 if HAVE_OPENSSL
@@ -1428,6 +1429,7 @@ SYMDEF_FILES = \
 		module-bluez4-discover-symdef.h \
 		module-bluez4-device-symdef.h \
 		module-bluez5-discover-symdef.h \
+		module-bluez5-device-symdef.h \
 		module-raop-sink-symdef.h \
 		module-raop-discover-symdef.h \
 		module-gconf-symdef.h \
@@ -2062,6 +2064,11 @@ module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_bluez5_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluez5-util.la
 module_bluez5_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
 
+module_bluez5_device_la_SOURCES = modules/bluetooth/module-bluez5-device.c
+module_bluez5_device_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) $(SBC_LIBS) libbluez5-util.la
+module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS)
+
 # Apple Airtunes/RAOP
 module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
 module_raop_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
new file mode 100644
index 0000000..890f7e4
--- /dev/null
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -0,0 +1,43 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008-2013 João Paulo Rechi Vita
+  Copyright 2011-2013 BMW Car IT GmbH.
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/module.h>
+
+#include "bluez5-util.h"
+
+#include "module-bluez5-device-symdef.h"
+
+PA_MODULE_AUTHOR("João Paulo Rechi Vita");
+PA_MODULE_DESCRIPTION("BlueZ 5 Bluetooth audio sink and source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(false);
+
+int pa__init(pa_module* m) {
+    return 0;
+}
+
+void pa__done(pa_module *m) {
+}

commit a5fdf965ed66fbff03e4092b537cd1a73e59a88c
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:39 2013 -0300

    bluetooth: Track devices in module-bluez5-discover

diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c
index 8cc0631..9f392a2 100644
--- a/src/modules/bluetooth/module-bluez5-discover.c
+++ b/src/modules/bluetooth/module-bluez5-discover.c
@@ -24,6 +24,7 @@
 #endif
 
 #include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/module.h>
 
@@ -39,9 +40,48 @@ PA_MODULE_LOAD_ONCE(true);
 struct userdata {
     pa_module *module;
     pa_core *core;
+    pa_hashmap *loaded_device_paths;
+    pa_hook_slot *device_connection_changed_slot;
     pa_bluetooth_discovery *discovery;
 };
 
+static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
+    bool module_loaded;
+
+    pa_assert(d);
+    pa_assert(u);
+
+    module_loaded = pa_hashmap_get(u->loaded_device_paths, d->path) ? true : false;
+
+    if (module_loaded && !pa_bluetooth_device_any_transport_connected(d)) {
+        /* disconnection, the module unloads itself */
+        pa_log_debug("Unregistering module for %s", d->path);
+        pa_hashmap_remove(u->loaded_device_paths, d->path);
+        return PA_HOOK_OK;
+    }
+
+    if (!module_loaded && pa_bluetooth_device_any_transport_connected(d)) {
+        /* a new device has been connected */
+        pa_module *m;
+        char *args = pa_sprintf_malloc("path=%s", d->path);
+
+        pa_log_debug("Loading module-bluez5-device %s", args);
+        m = pa_module_load(u->module->core, "module-bluez5-device", args);
+        pa_xfree(args);
+
+        if (m)
+            /* No need to duplicate the path here since the device object will
+             * exist for the whole hashmap entry lifespan */
+            pa_hashmap_put(u->loaded_device_paths, d->path, d->path);
+        else
+            pa_log_warn("Failed to load module for device %s", d->path);
+
+        return PA_HOOK_OK;
+    }
+
+    return PA_HOOK_OK;
+}
+
 int pa__init(pa_module *m) {
     struct userdata *u;
 
@@ -50,10 +90,15 @@ int pa__init(pa_module *m) {
     m->userdata = u = pa_xnew0(struct userdata, 1);
     u->module = m;
     u->core = m->core;
+    u->loaded_device_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
 
     if (!(u->discovery = pa_bluetooth_discovery_get(u->core)))
         goto fail;
 
+    u->device_connection_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) device_connection_changed_cb, u);
+
     return 0;
 
 fail:
@@ -69,8 +114,14 @@ void pa__done(pa_module *m) {
     if (!(u = m->userdata))
         return;
 
+    if (u->device_connection_changed_slot)
+        pa_hook_slot_free(u->device_connection_changed_slot);
+
     if (u->discovery)
         pa_bluetooth_discovery_unref(u->discovery);
 
+    if (u->loaded_device_paths)
+        pa_hashmap_free(u->loaded_device_paths);
+
     pa_xfree(u);
 }

commit e352da60a744a34d7867ad3d737821325e95a447
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:38 2013 -0300

    bluetooth: Handle PropertiesChanged for org.bluez.MediaTransport1

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 0d19091..9df9021 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -288,6 +288,73 @@ bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     return false;
 }
 
+static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
+    pa_assert(value);
+    pa_assert(state);
+
+    if (pa_streq(value, "idle"))
+        *state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
+    else if (pa_streq(value, "pending") || pa_streq(value, "active"))
+        *state = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
+    else
+        return -1;
+
+    return 0;
+}
+
+static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
+    const char *key;
+    DBusMessageIter variant_i;
+
+    key = check_variant_property(i);
+    if (key == NULL)
+        return;
+
+    dbus_message_iter_recurse(i, &variant_i);
+
+    switch (dbus_message_iter_get_arg_type(&variant_i)) {
+
+        case DBUS_TYPE_STRING: {
+
+            const char *value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "State")) {
+                pa_bluetooth_transport_state_t state;
+
+                if (transport_state_from_string(value, &state) < 0) {
+                    pa_log_error("Invalid state received: %s", value);
+                    return;
+                }
+
+                transport_state_changed(t, state);
+            }
+
+            break;
+        }
+    }
+
+    return;
+}
+
+static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter *i) {
+    DBusMessageIter element_i;
+
+    dbus_message_iter_recurse(i, &element_i);
+
+    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+        DBusMessageIter dict_i;
+
+        dbus_message_iter_recurse(&element_i, &dict_i);
+
+        parse_transport_property(t, &dict_i);
+
+        dbus_message_iter_next(&element_i);
+    }
+
+    return 0;
+}
+
 static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) {
     pa_bluetooth_device *d;
 
@@ -953,6 +1020,15 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
             }
 
             parse_device_properties(d, &arg_i, true);
+        } else if (pa_streq(iface, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) {
+            pa_bluetooth_transport *t;
+
+            pa_log_debug("Properties changed in transport %s", dbus_message_get_path(m));
+
+            if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+            parse_transport_properties(t, &arg_i);
         }
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@@ -1480,6 +1556,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
             ",arg0='" BLUEZ_ADAPTER_INTERFACE "'",
             "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
             ",arg0='" BLUEZ_DEVICE_INTERFACE "'",
+            "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+            ",arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
             NULL) < 0) {
         pa_log_error("Failed to add D-Bus matches: %s", err.message);
         goto fail;
@@ -1547,6 +1625,8 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
                 "member='PropertiesChanged',arg0='" BLUEZ_ADAPTER_INTERFACE "'",
                 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
                 "member='PropertiesChanged',arg0='" BLUEZ_DEVICE_INTERFACE "'",
+                "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
+                "member='PropertiesChanged',arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
                 NULL);
 
         if (y->filter_added)

commit f65cafe64dbc9c3c2856d3e28827e931bf51f9ed
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:37 2013 -0300

    bluetooth: Handle PropertiesChanged for org.bluez.Device1

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 17889b6..0d19091 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -936,6 +936,23 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
             }
 
             parse_adapter_properties(a, &arg_i, true);
+
+        } else if (pa_streq(iface, BLUEZ_DEVICE_INTERFACE)) {
+            pa_bluetooth_device *d;
+
+            pa_log_debug("Properties changed in device %s", dbus_message_get_path(m));
+
+            if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
+                pa_log_warn("Properties changed in unknown device");
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            }
+
+            if (d->device_info_valid != 1) {
+                pa_log_warn("Properties changed in a device which information is unknown or invalid");
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            }
+
+            parse_device_properties(d, &arg_i, true);
         }
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@@ -1461,6 +1478,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
             "member='InterfacesRemoved'",
             "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
             ",arg0='" BLUEZ_ADAPTER_INTERFACE "'",
+            "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+            ",arg0='" BLUEZ_DEVICE_INTERFACE "'",
             NULL) < 0) {
         pa_log_error("Failed to add D-Bus matches: %s", err.message);
         goto fail;
@@ -1526,6 +1545,8 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
                 "member='InterfacesRemoved'",
                 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
                 "member='PropertiesChanged',arg0='" BLUEZ_ADAPTER_INTERFACE "'",
+                "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
+                "member='PropertiesChanged',arg0='" BLUEZ_DEVICE_INTERFACE "'",
                 NULL);
 
         if (y->filter_added)

commit 82fd8cc845f9366405d0093a1219cd11f521c96d
Author: João Paulo Rechi Vita <jprvita at gmail.com>
Date:   Tue Sep 24 19:45:36 2013 -0300

    bluetooth: Protect from a misbehaving bluetoothd
    
    bluetoothd always send the GetManagedObjects() reply messages with the
    objects array argument following an in-depth order starting from the
    root. That means parents will always be known at the time their children
    objects are parsed, if clients parse the objects in the same order they
    appear in the array, as we do in PulseAudio.
    
    This commit tries to protect PulseAudio in the case bluetoothd changes
    that behavior for some reason. It hasn't been tested, since this
    situation never occurs.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 541948f..17889b6 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -489,11 +489,13 @@ static void parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bo
                     return;
                 }
 
-                if (d->adapter) {
+                if (d->adapter_path) {
                     pa_log_warn("Device %s: Received a duplicate 'Adapter' property, ignoring", d->path);
                     return;
                 }
 
+                d->adapter_path = pa_xstrdup(value);
+
                 d->adapter = pa_hashmap_get(d->discovery->adapters, value);
                 if (!d->adapter)
                     pa_log_info("Device %s: 'Adapter' property references an unknown adapter %s.", d->path, value);
@@ -561,7 +563,7 @@ static int parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i, b
         dbus_message_iter_next(&element_i);
     }
 
-    if (!d->address || !d->adapter || !d->alias) {
+    if (!d->address || !d->adapter_path || !d->alias) {
         pa_log_error("Non-optional information missing for device %s", d->path);
         d->device_info_valid = -1;
         return -1;
@@ -685,6 +687,7 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
 static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
     DBusMessageIter element_i;
     const char *path;
+    void *state;
     pa_bluetooth_device *d;
 
     pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
@@ -745,6 +748,15 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
         dbus_message_iter_next(&element_i);
     }
 
+    PA_HASHMAP_FOREACH(d, y->devices, state)
+        if (!d->adapter && d->adapter_path) {
+            d->adapter = pa_hashmap_get(d->discovery->adapters, d->adapter_path);
+            if (!d->adapter) {
+                pa_log_error("Device %s is child of nonexistent adapter %s", d->path, d->adapter_path);
+                d->device_info_valid = -1;
+            }
+        }
+
     return;
 }
 
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 41673e3..bbc5b71 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -80,6 +80,7 @@ struct pa_bluetooth_device {
 
     /* Device information */
     char *path;
+    char *adapter_path;
     char *alias;
     char *address;
     uint32_t class_of_device;

commit 89e97215c7166f1d6993d1381232257a68f51c84
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:35 2013 -0300

    bluetooth: Parse BlueZ 5 device properties
    
    This code is based on previous work by Mikel Astiz.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 7b348e6..541948f 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -297,6 +297,7 @@ static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char
     d = pa_xnew0(pa_bluetooth_device, 1);
     d->discovery = y;
     d->path = pa_xstrdup(path);
+    d->uuids = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
 
     pa_hashmap_put(y->devices, d->path, d);
 
@@ -348,6 +349,9 @@ static void device_free(pa_bluetooth_device *d) {
         pa_bluetooth_transport_free(t);
     }
 
+    if (d->uuids)
+        pa_hashmap_free(d->uuids);
+
     d->discovery = NULL;
     d->adapter = NULL;
     pa_xfree(d->path);
@@ -432,6 +436,141 @@ static void adapter_remove_all(pa_bluetooth_discovery *y) {
         adapter_free(a);
 }
 
+static void parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
+    const char *key;
+    DBusMessageIter variant_i;
+
+    pa_assert(d);
+
+    key = check_variant_property(i);
+    if (key == NULL) {
+        pa_log_error("Received invalid property for device %s", d->path);
+        return;
+    }
+
+    dbus_message_iter_recurse(i, &variant_i);
+
+    switch (dbus_message_iter_get_arg_type(&variant_i)) {
+
+        case DBUS_TYPE_STRING: {
+            const char *value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Alias")) {
+                pa_xfree(d->alias);
+                d->alias = pa_xstrdup(value);
+                pa_log_debug("%s: %s", key, value);
+            } else if (pa_streq(key, "Address")) {
+                if (is_property_change) {
+                    pa_log_warn("Device property 'Address' expected to be constant but changed for %s, ignoring", d->path);
+                    return;
+                }
+
+                if (d->address) {
+                    pa_log_warn("Device %s: Received a duplicate 'Address' property, ignoring", d->path);
+                    return;
+                }
+
+                d->address = pa_xstrdup(value);
+                pa_log_debug("%s: %s", key, value);
+            }
+
+            break;
+        }
+
+        case DBUS_TYPE_OBJECT_PATH: {
+            const char *value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Adapter")) {
+
+                if (is_property_change) {
+                    pa_log_warn("Device property 'Adapter' expected to be constant but changed for %s, ignoring", d->path);
+                    return;
+                }
+
+                if (d->adapter) {
+                    pa_log_warn("Device %s: Received a duplicate 'Adapter' property, ignoring", d->path);
+                    return;
+                }
+
+                d->adapter = pa_hashmap_get(d->discovery->adapters, value);
+                if (!d->adapter)
+                    pa_log_info("Device %s: 'Adapter' property references an unknown adapter %s.", d->path, value);
+
+                pa_log_debug("%s: %s", key, value);
+            }
+
+            break;
+        }
+
+        case DBUS_TYPE_UINT32: {
+            uint32_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Class")) {
+                d->class_of_device = value;
+                pa_log_debug("%s: %d", key, value);
+            }
+
+            break;
+        }
+
+        case DBUS_TYPE_ARRAY: {
+            DBusMessageIter ai;
+            dbus_message_iter_recurse(&variant_i, &ai);
+
+            if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && pa_streq(key, "UUIDs")) {
+                /* bluetoothd never removes UUIDs from a device object so there
+                 * is no need to handle it here. */
+                while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
+                    const char *value;
+                    char *uuid;
+
+                    dbus_message_iter_get_basic(&ai, &value);
+
+                    if (pa_hashmap_get(d->uuids, value)) {
+                        dbus_message_iter_next(&ai);
+                        continue;
+                    }
+
+                    uuid = pa_xstrdup(value);
+                    pa_hashmap_put(d->uuids, uuid, uuid);
+
+                    pa_log_debug("%s: %s", key, value);
+                    dbus_message_iter_next(&ai);
+                }
+            }
+
+            break;
+        }
+    }
+}
+
+static int parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
+    DBusMessageIter element_i;
+    int ret = 0;
+
+    dbus_message_iter_recurse(i, &element_i);
+
+    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+        DBusMessageIter dict_i;
+
+        dbus_message_iter_recurse(&element_i, &dict_i);
+        parse_device_property(d, &dict_i, is_property_change);
+        dbus_message_iter_next(&element_i);
+    }
+
+    if (!d->address || !d->adapter || !d->alias) {
+        pa_log_error("Non-optional information missing for device %s", d->path);
+        d->device_info_valid = -1;
+        return -1;
+    }
+
+    d->device_info_valid = 1;
+    return ret;
+}
+
 static void parse_adapter_properties(pa_bluetooth_adapter *a, DBusMessageIter *i, bool is_property_change) {
     DBusMessageIter element_i;
 
@@ -546,6 +685,7 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
 static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
     DBusMessageIter element_i;
     const char *path;
+    pa_bluetooth_device *d;
 
     pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
     dbus_message_iter_get_basic(dict_i, &path);
@@ -586,7 +726,6 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
             register_endpoint(y, path, A2DP_SINK_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK);
 
         } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
-            pa_bluetooth_device *d;
 
             if ((d = pa_hashmap_get(y->devices, path))) {
                 if (d->device_info_valid != 0) {
@@ -598,7 +737,7 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
 
             pa_log_debug("Device %s found", d->path);
 
-            /* TODO: parse device properties */
+            parse_device_properties(d, &iface_i, false);
 
         } else
             pa_log_debug("Unknown interface %s found, skipping", interface);
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 4e0b567..41673e3 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -83,6 +83,7 @@ struct pa_bluetooth_device {
     char *alias;
     char *address;
     uint32_t class_of_device;
+    pa_hashmap *uuids;
 
     pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
 };

commit 877f0a7d5458ffcef8fe175eafe4d8286f59cbd9
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:34 2013 -0300

    bluetooth: Handle PropertiesChanged for org.bluez.Adapter1

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index c761ecd..7b348e6 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -757,6 +757,37 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
+    } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", "PropertiesChanged")) {
+        DBusMessageIter arg_i;
+        const char *iface;
+
+        if (!y->objects_listed)
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
+
+        if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) {
+            pa_log_error("Invalid signature found in PropertiesChanged");
+            goto fail;
+        }
+
+        dbus_message_iter_get_basic(&arg_i, &iface);
+
+        pa_assert_se(dbus_message_iter_next(&arg_i));
+        pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
+
+        if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE)) {
+            pa_bluetooth_adapter *a;
+
+            pa_log_debug("Properties changed in adapter %s", dbus_message_get_path(m));
+
+            if (!(a = pa_hashmap_get(y->adapters, dbus_message_get_path(m)))) {
+                pa_log_warn("Properties changed in unknown adapter");
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            }
+
+            parse_adapter_properties(a, &arg_i, true);
+        }
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
     }
 
 fail:
@@ -1277,6 +1308,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
             "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
             "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
             "member='InterfacesRemoved'",
+            "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+            ",arg0='" BLUEZ_ADAPTER_INTERFACE "'",
             NULL) < 0) {
         pa_log_error("Failed to add D-Bus matches: %s", err.message);
         goto fail;
@@ -1340,6 +1373,8 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
                 "member='InterfacesAdded'",
                 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
                 "member='InterfacesRemoved'",
+                "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
+                "member='PropertiesChanged',arg0='" BLUEZ_ADAPTER_INTERFACE "'",
                 NULL);
 
         if (y->filter_added)

commit a59177e666a2a7ca05700e7e27e49d04bb3b3be3
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:33 2013 -0300

    bluetooth: Register endpoints with BlueZ 5 adapter

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 435bd93..c761ecd 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -40,9 +40,12 @@
 #define BLUEZ_SERVICE "org.bluez"
 #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1"
 #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1"
+#define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1"
 #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
 #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
 
+#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
+
 #define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
 #define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
 
@@ -471,6 +474,75 @@ static void parse_adapter_properties(pa_bluetooth_adapter *a, DBusMessageIter *i
     }
 }
 
+static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
+    DBusMessage *r;
+    pa_dbus_pending *p;
+    pa_bluetooth_discovery *y;
+    char *endpoint;
+
+    pa_assert(pending);
+    pa_assert_se(p = userdata);
+    pa_assert_se(y = p->context_data);
+    pa_assert_se(endpoint = p->call_data);
+    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+    if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
+        pa_log_info("Couldn't register endpoint %s because it is disabled in BlueZ", endpoint);
+        goto finish;
+    }
+
+    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+        pa_log_error(BLUEZ_MEDIA_INTERFACE ".RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r),
+                     pa_dbus_get_error_message(r));
+        goto finish;
+    }
+
+finish:
+    dbus_message_unref(r);
+
+    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
+    pa_dbus_pending_free(p);
+
+    pa_xfree(endpoint);
+}
+
+static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
+    DBusMessage *m;
+    DBusMessageIter i, d;
+    uint8_t codec = 0;
+
+    pa_log_debug("Registering %s on adapter %s", endpoint, path);
+
+    pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));
+
+    dbus_message_iter_init_append(m, &i);
+    dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint);
+    dbus_message_iter_open_container(&i, 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, &d);
+    pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
+    pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
+
+    if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) || pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) {
+        a2dp_sbc_t capabilities;
+
+        capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO |
+                                    SBC_CHANNEL_MODE_JOINT_STEREO;
+        capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 |
+                                 SBC_SAMPLING_FREQ_48000;
+        capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
+        capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
+        capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
+        capabilities.min_bitpool = MIN_BITPOOL;
+        capabilities.max_bitpool = MAX_BITPOOL;
+
+        pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
+    }
+
+    dbus_message_iter_close_container(&i, &d);
+
+    send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
+}
+
 static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
     DBusMessageIter element_i;
     const char *path;
@@ -510,7 +582,8 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
             if (!a->address)
                 return;
 
-            /* TODO: register endpoints with adapter */
+            register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE);
+            register_endpoint(y, path, A2DP_SINK_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK);
 
         } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
             pa_bluetooth_device *d;

commit 42dccb6b379f446bc430380153e55bcdc1838ea2
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:32 2013 -0300

    bluetooth: Parse BlueZ 5 adapter properties

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 5725690..435bd93 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -104,6 +104,31 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusM
     return p;
 }
 
+static const char *check_variant_property(DBusMessageIter *i) {
+    const char *key;
+
+    pa_assert(i);
+
+    if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
+        pa_log_error("Property name not a string.");
+        return NULL;
+    }
+
+    dbus_message_iter_get_basic(i, &key);
+
+    if (!dbus_message_iter_next(i)) {
+        pa_log_error("Property value missing");
+        return NULL;
+    }
+
+    if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
+        pa_log_error("Property value not a variant.");
+        return NULL;
+    }
+
+    return key;
+}
+
 pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
                                                    pa_bluetooth_profile_t p, const uint8_t *config, size_t size) {
     pa_bluetooth_transport *t;
@@ -404,6 +429,48 @@ static void adapter_remove_all(pa_bluetooth_discovery *y) {
         adapter_free(a);
 }
 
+static void parse_adapter_properties(pa_bluetooth_adapter *a, DBusMessageIter *i, bool is_property_change) {
+    DBusMessageIter element_i;
+
+    pa_assert(a);
+
+    dbus_message_iter_recurse(i, &element_i);
+
+    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+        DBusMessageIter dict_i, variant_i;
+        const char *key;
+
+        dbus_message_iter_recurse(&element_i, &dict_i);
+
+        key = check_variant_property(&dict_i);
+        if (key == NULL) {
+            pa_log_error("Received invalid property for adapter %s", a->path);
+            return;
+        }
+
+        dbus_message_iter_recurse(&dict_i, &variant_i);
+
+        if (dbus_message_iter_get_arg_type(&variant_i) == DBUS_TYPE_STRING && pa_streq(key, "Address")) {
+            char *value;
+
+            if (is_property_change) {
+                pa_log_warn("Adapter property 'Address' expected to be constant but changed for %s, ignoring", a->path);
+                return;
+            }
+
+            if (a->address) {
+                pa_log_warn("Adapter %s received a duplicate 'Address' property, ignoring", a->path);
+                return;
+            }
+
+            dbus_message_iter_get_basic(&variant_i, &value);
+            a->address = pa_xstrdup(value);
+        }
+
+        dbus_message_iter_next(&element_i);
+    }
+}
+
 static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
     DBusMessageIter element_i;
     const char *path;
@@ -439,7 +506,11 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
 
             pa_log_debug("Adapter %s found", path);
 
-            /* TODO: parse adapter properties and register endpoints */
+            parse_adapter_properties(a, &iface_i, false);
+            if (!a->address)
+                return;
+
+            /* TODO: register endpoints with adapter */
 
         } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
             pa_bluetooth_device *d;

commit f1d8958fb6b9e452ade21a14cddd2e53c528a564
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:31 2013 -0300

    bluetooth: Parse BlueZ 5 D-Bus interfaces
    
    Parse the arguments of the InterfacesAdded signal and the
    GetManagedObjects() reply.
    
    This code is based on previous work by Mikel Astiz.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 36e3352..5725690 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -404,6 +404,67 @@ static void adapter_remove_all(pa_bluetooth_discovery *y) {
         adapter_free(a);
 }
 
+static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
+    DBusMessageIter element_i;
+    const char *path;
+
+    pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
+    dbus_message_iter_get_basic(dict_i, &path);
+
+    pa_assert_se(dbus_message_iter_next(dict_i));
+    pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_ARRAY);
+
+    dbus_message_iter_recurse(dict_i, &element_i);
+
+    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+        DBusMessageIter iface_i;
+        const char *interface;
+
+        dbus_message_iter_recurse(&element_i, &iface_i);
+
+        pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_STRING);
+        dbus_message_iter_get_basic(&iface_i, &interface);
+
+        pa_assert_se(dbus_message_iter_next(&iface_i));
+        pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY);
+
+        if (pa_streq(interface, BLUEZ_ADAPTER_INTERFACE)) {
+            pa_bluetooth_adapter *a;
+
+            if ((a = pa_hashmap_get(y->adapters, path))) {
+                pa_log_error("Found duplicated D-Bus path for device %s", path);
+                return;
+            } else
+                a = adapter_create(y, path);
+
+            pa_log_debug("Adapter %s found", path);
+
+            /* TODO: parse adapter properties and register endpoints */
+
+        } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
+            pa_bluetooth_device *d;
+
+            if ((d = pa_hashmap_get(y->devices, path))) {
+                if (d->device_info_valid != 0) {
+                    pa_log_error("Found duplicated D-Bus path for device %s", path);
+                    return;
+                }
+            } else
+                d = device_create(y, path);
+
+            pa_log_debug("Device %s found", d->path);
+
+            /* TODO: parse device properties */
+
+        } else
+            pa_log_debug("Unknown interface %s found, skipping", interface);
+
+        dbus_message_iter_next(&element_i);
+    }
+
+    return;
+}
+
 static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
     pa_dbus_pending *p;
     pa_bluetooth_discovery *y;
@@ -435,7 +496,7 @@ static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata)
 
         dbus_message_iter_recurse(&element_i, &dict_i);
 
-        /* TODO: parse interfaces and properties */
+        parse_interfaces_and_properties(y, &dict_i);
 
         dbus_message_iter_next(&element_i);
     }
@@ -514,7 +575,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
             goto fail;
         }
 
-        /* TODO: parse interfaces and properties */
+        parse_interfaces_and_properties(y, &arg_i);
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
     } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")) {

commit 878eccb6802b2f32aa23182ed2ece9f0e09384b0
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:30 2013 -0300

    bluetooth: Handle InterfacesAdded and InterfacesRemoved
    
    This code is based on previous work by Mikel Astiz.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 9e14951..36e3352 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -38,6 +38,8 @@
 #include "bluez5-util.h"
 
 #define BLUEZ_SERVICE "org.bluez"
+#define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1"
+#define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1"
 #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
 #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
 
@@ -501,6 +503,55 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
         }
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")) {
+        DBusMessageIter arg_i;
+
+        if (!y->objects_listed)
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
+
+        if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) {
+            pa_log_error("Invalid signature found in InterfacesAdded");
+            goto fail;
+        }
+
+        /* TODO: parse interfaces and properties */
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")) {
+        const char *p;
+        DBusMessageIter arg_i;
+        DBusMessageIter element_i;
+
+        if (!y->objects_listed)
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
+
+        if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oas")) {
+            pa_log_error("Invalid signature found in InterfacesRemoved");
+            goto fail;
+        }
+
+        dbus_message_iter_get_basic(&arg_i, &p);
+
+        pa_assert_se(dbus_message_iter_next(&arg_i));
+        pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
+
+        dbus_message_iter_recurse(&arg_i, &element_i);
+
+        while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) {
+            const char *iface;
+
+            dbus_message_iter_get_basic(&element_i, &iface);
+
+            if (pa_streq(iface, BLUEZ_DEVICE_INTERFACE))
+                device_remove(y, p);
+            else if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE))
+                adapter_remove(y, p);
+
+            dbus_message_iter_next(&element_i);
+        }
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
     }
 
 fail:
@@ -1018,6 +1069,9 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     if (pa_dbus_add_matches(conn, &err,
             "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
             ",arg0='" BLUEZ_SERVICE "'",
+            "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
+            "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
+            "member='InterfacesRemoved'",
             NULL) < 0) {
         pa_log_error("Failed to add D-Bus matches: %s", err.message);
         goto fail;
@@ -1077,6 +1131,10 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
             pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
                 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
                 "arg0='" BLUEZ_SERVICE "'",
+                "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
+                "member='InterfacesAdded'",
+                "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
+                "member='InterfacesRemoved'",
                 NULL);
 
         if (y->filter_added)

commit 1e58120a5ca51d01c334ad467a357599e61b4c26
Author: João Paulo Rechi Vita <jprvita at gmail.com>
Date:   Tue Sep 24 19:45:29 2013 -0300

    bluetooth: Create a function to remove only one adapter object

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2a5b1ab..9e14951 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -364,6 +364,33 @@ static pa_bluetooth_adapter* adapter_create(pa_bluetooth_discovery *y, const cha
     return a;
 }
 
+static void adapter_free(pa_bluetooth_adapter *a) {
+    pa_bluetooth_device *d;
+    void *state;
+
+    pa_assert(a);
+    pa_assert(a->discovery);
+
+    PA_HASHMAP_FOREACH(d, a->discovery->devices, state)
+        if (d->adapter == a)
+            d->adapter = NULL;
+
+    pa_xfree(a->path);
+    pa_xfree(a->address);
+    pa_xfree(a);
+}
+
+static void adapter_remove(pa_bluetooth_discovery *y, const char *path) {
+    pa_bluetooth_adapter *a;
+
+    if (!(a = pa_hashmap_remove(y->adapters, path)))
+        pa_log_warn("Unknown adapter removed %s", path);
+    else {
+        pa_log_debug("Adapter %s removed", path);
+        adapter_free(a);
+    }
+}
+
 static void adapter_remove_all(pa_bluetooth_discovery *y) {
     pa_bluetooth_adapter *a;
 
@@ -371,11 +398,8 @@ static void adapter_remove_all(pa_bluetooth_discovery *y) {
 
     /* When this function is called all devices have already been freed */
 
-    while ((a = pa_hashmap_steal_first(y->adapters))) {
-        pa_xfree(a->path);
-        pa_xfree(a->address);
-        pa_xfree(a);
-    }
+    while ((a = pa_hashmap_steal_first(y->adapters)))
+        adapter_free(a);
 }
 
 static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {

commit b8edddddd3c340e5a90dcf3f61482d33aeb20f0c
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:28 2013 -0300

    bluetooth: Get managed objects
    
    Get objects from the BlueZ 5 object manager. This code is based on
    previous work by Mikel Astiz.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index ba55366..2a5b1ab 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -76,6 +76,7 @@ struct pa_bluetooth_discovery {
     pa_dbus_connection *connection;
     bool filter_added;
     bool matches_added;
+    bool objects_listed;
     pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
     pa_hashmap *adapters;
     pa_hashmap *devices;
@@ -377,6 +378,61 @@ static void adapter_remove_all(pa_bluetooth_discovery *y) {
     }
 }
 
+static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
+    pa_dbus_pending *p;
+    pa_bluetooth_discovery *y;
+    DBusMessage *r;
+    DBusMessageIter arg_i, element_i;
+
+    pa_assert_se(p = userdata);
+    pa_assert_se(y = p->context_data);
+    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+    if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+        pa_log_warn("BlueZ D-Bus ObjectManager not available");
+        goto finish;
+    }
+
+    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+        pa_log_error("GetManagedObjects() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+        goto finish;
+    }
+
+    if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) {
+        pa_log_error("Invalid reply signature for GetManagedObjects()");
+        goto finish;
+    }
+
+    dbus_message_iter_recurse(&arg_i, &element_i);
+    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+        DBusMessageIter dict_i;
+
+        dbus_message_iter_recurse(&element_i, &dict_i);
+
+        /* TODO: parse interfaces and properties */
+
+        dbus_message_iter_next(&element_i);
+    }
+
+    y->objects_listed = true;
+
+finish:
+    dbus_message_unref(r);
+
+    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
+    pa_dbus_pending_free(p);
+}
+
+static void get_managed_objects(pa_bluetooth_discovery *y) {
+    DBusMessage *m;
+
+    pa_assert(y);
+
+    pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, "/", "org.freedesktop.DBus.ObjectManager",
+                                                  "GetManagedObjects"));
+    send_and_add_to_pending(y, m, get_managed_objects_reply, NULL);
+}
+
 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
     pa_assert(y);
     pa_assert(PA_REFCNT_VALUE(y) > 0);
@@ -411,11 +467,12 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
                 pa_log_debug("Bluetooth daemon disappeared");
                 device_remove_all(y);
                 adapter_remove_all(y);
+                y->objects_listed = false;
             }
 
             if (new_owner && *new_owner) {
                 pa_log_debug("Bluetooth daemon appeared");
-                /* TODO: get managed objects */
+                get_managed_objects(y);
             }
         }
 
@@ -946,6 +1003,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
     endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
 
+    get_managed_objects(y);
+
     return y;
 
 fail:

commit d186d0f1056dafefa935d243e2f6b0fb4d7bdfcf
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:27 2013 -0300

    bluetooth: Add utility function to send D-Bus messages with reply

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index fd66e72..ba55366 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -80,8 +80,27 @@ struct pa_bluetooth_discovery {
     pa_hashmap *adapters;
     pa_hashmap *devices;
     pa_hashmap *transports;
+
+    PA_LLIST_HEAD(pa_dbus_pending, pending);
 };
 
+static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m,
+                                                                  DBusPendingCallNotifyFunction func, void *call_data) {
+    pa_dbus_pending *p;
+    DBusPendingCall *call;
+
+    pa_assert(y);
+    pa_assert(m);
+
+    pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
+
+    p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data);
+    PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
+    dbus_pending_call_set_notify(call, func, p, NULL);
+
+    return p;
+}
+
 pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
                                                    pa_bluetooth_profile_t p, const uint8_t *config, size_t size) {
     pa_bluetooth_transport *t;
@@ -892,6 +911,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     y->adapters = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
     y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
     y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
 
     for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
         pa_hook_init(&y->hooks[i], y);
@@ -951,6 +971,8 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
     if (PA_REFCNT_DEC(y) > 0)
         return;
 
+    pa_dbus_free_pending_list(&y->pending);
+
     if (y->devices) {
         device_remove_all(y);
         pa_hashmap_free(y->devices);

commit 63108c5cc8fa01766eb571b1f7a4e43bc83ae335
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:26 2013 -0300

    bluetooth: Implement org.bluez.MediaEndpoint1.ClearConfiguration()

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 60ee047..fd66e72 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -760,11 +760,32 @@ fail:
 }
 
 static DBusMessage *endpoint_clear_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
+    pa_bluetooth_discovery *y = userdata;
+    pa_bluetooth_transport *t;
     DBusMessage *r;
+    DBusError err;
+    const char *path;
 
-    pa_assert_se(r = dbus_message_new_error(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented",
-                                            "Method not implemented"));
+    dbus_error_init(&err);
+
+    if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
+        pa_log_error("Endpoint ClearConfiguration(): %s", err.message);
+        dbus_error_free(&err);
+        goto fail;
+    }
 
+    if ((t = pa_hashmap_get(y->transports, path))) {
+        pa_log_debug("Clearing transport %s profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
+        transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED);
+        pa_bluetooth_transport_free(t);
+    }
+
+    pa_assert_se(r = dbus_message_new_method_return(m));
+
+    return r;
+
+fail:
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to clear configuration"));
     return r;
 }
 

commit 2594909338cb93d6348c0a48f88acec22041fecb
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:25 2013 -0300

    bluetooth: Implement org.bluez.MediaEndpoint1.SelectConfiguration()

diff --git a/src/Makefile.am b/src/Makefile.am
index 689090f..b5814bf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2051,7 +2051,8 @@ module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
 # Bluetooth BlueZ 5 sink / source
 libbluez5_util_la_SOURCES = \
 		modules/bluetooth/bluez5-util.c \
-		modules/bluetooth/bluez5-util.h
+		modules/bluetooth/bluez5-util.h \
+		modules/bluetooth/a2dp-codecs.h
 libbluez5_util_la_LDFLAGS = -avoid-version
 libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
 libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2646763..60ee047 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -409,6 +409,48 @@ fail:
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
+static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
+    /* These bitpool values were chosen based on the A2DP spec recommendation */
+    switch (freq) {
+        case SBC_SAMPLING_FREQ_16000:
+        case SBC_SAMPLING_FREQ_32000:
+            return 53;
+
+        case SBC_SAMPLING_FREQ_44100:
+
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                    return 31;
+
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return 53;
+            }
+
+            pa_log_warn("Invalid channel mode %u", mode);
+            return 53;
+
+        case SBC_SAMPLING_FREQ_48000:
+
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                    return 29;
+
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return 51;
+            }
+
+            pa_log_warn("Invalid channel mode %u", mode);
+            return 51;
+    }
+
+    pa_log_warn("Invalid sampling freq %u", freq);
+    return 53;
+}
+
 const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
     switch(profile) {
         case PA_BLUETOOTH_PROFILE_A2DP_SINK:
@@ -589,11 +631,131 @@ fail2:
 }
 
 static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
+    pa_bluetooth_discovery *y = userdata;
+    a2dp_sbc_t *cap, config;
+    uint8_t *pconf = (uint8_t *) &config;
+    int i, size;
     DBusMessage *r;
+    DBusError err;
 
-    pa_assert_se(r = dbus_message_new_error(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented",
-                                            "Method not implemented"));
+    static const struct {
+        uint32_t rate;
+        uint8_t cap;
+    } freq_table[] = {
+        { 16000U, SBC_SAMPLING_FREQ_16000 },
+        { 32000U, SBC_SAMPLING_FREQ_32000 },
+        { 44100U, SBC_SAMPLING_FREQ_44100 },
+        { 48000U, SBC_SAMPLING_FREQ_48000 }
+    };
 
+    dbus_error_init(&err);
+
+    if (!dbus_message_get_args(m, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
+        pa_log_error("Endpoint SelectConfiguration(): %s", err.message);
+        dbus_error_free(&err);
+        goto fail;
+    }
+
+    if (size != sizeof(config)) {
+        pa_log_error("Capabilities array has invalid size");
+        goto fail;
+    }
+
+    pa_zero(config);
+
+    /* Find the lowest freq that is at least as high as the requested sampling rate */
+    for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
+        if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
+            config.frequency = freq_table[i].cap;
+            break;
+        }
+
+    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
+        for (--i; i >= 0; i--) {
+            if (cap->frequency & freq_table[i].cap) {
+                config.frequency = freq_table[i].cap;
+                break;
+            }
+        }
+
+        if (i < 0) {
+            pa_log_error("Not suitable sample rate");
+            goto fail;
+        }
+    }
+
+    pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
+
+    if (y->core->default_sample_spec.channels <= 1) {
+        if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
+            config.channel_mode = SBC_CHANNEL_MODE_MONO;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+            config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
+            config.channel_mode = SBC_CHANNEL_MODE_STEREO;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+            config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+        else {
+            pa_log_error("No supported channel modes");
+            goto fail;
+        }
+    }
+
+    if (y->core->default_sample_spec.channels >= 2) {
+        if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+            config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
+            config.channel_mode = SBC_CHANNEL_MODE_STEREO;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+            config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
+            config.channel_mode = SBC_CHANNEL_MODE_MONO;
+        else {
+            pa_log_error("No supported channel modes");
+            goto fail;
+        }
+    }
+
+    if (cap->block_length & SBC_BLOCK_LENGTH_16)
+        config.block_length = SBC_BLOCK_LENGTH_16;
+    else if (cap->block_length & SBC_BLOCK_LENGTH_12)
+        config.block_length = SBC_BLOCK_LENGTH_12;
+    else if (cap->block_length & SBC_BLOCK_LENGTH_8)
+        config.block_length = SBC_BLOCK_LENGTH_8;
+    else if (cap->block_length & SBC_BLOCK_LENGTH_4)
+        config.block_length = SBC_BLOCK_LENGTH_4;
+    else {
+        pa_log_error("No supported block lengths");
+        goto fail;
+    }
+
+    if (cap->subbands & SBC_SUBBANDS_8)
+        config.subbands = SBC_SUBBANDS_8;
+    else if (cap->subbands & SBC_SUBBANDS_4)
+        config.subbands = SBC_SUBBANDS_4;
+    else {
+        pa_log_error("No supported subbands");
+        goto fail;
+    }
+
+    if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
+        config.allocation_method = SBC_ALLOCATION_LOUDNESS;
+    else if (cap->allocation_method & SBC_ALLOCATION_SNR)
+        config.allocation_method = SBC_ALLOCATION_SNR;
+
+    config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
+    config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
+
+    if (config.min_bitpool > config.max_bitpool)
+        goto fail;
+
+    pa_assert_se(r = dbus_message_new_method_return(m));
+    pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID));
+
+    return r;
+
+fail:
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to select configuration"));
     return r;
 }
 

commit 449d6cb1e28598a3a751c72fb67dff51357bc3d2
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:24 2013 -0300

    bluetooth: Implement org.bluez.MediaEndpoint1.SetConfiguration()

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index e38b7c7..2646763 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -33,6 +33,8 @@
 #include <pulsecore/refcnt.h>
 #include <pulsecore/shared.h>
 
+#include "a2dp-codecs.h"
+
 #include "bluez5-util.h"
 
 #define BLUEZ_SERVICE "org.bluez"
@@ -407,12 +409,182 @@ fail:
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
+const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
+    switch(profile) {
+        case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+            return "a2dp_sink";
+        case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+            return "a2dp_source";
+        case PA_BLUETOOTH_PROFILE_OFF:
+            return "off";
+    }
+
+    return NULL;
+}
+
 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
+    pa_bluetooth_discovery *y = userdata;
+    pa_bluetooth_device *d;
+    pa_bluetooth_transport *t;
+    const char *sender, *path, *endpoint_path, *dev_path = NULL, *uuid = NULL;
+    const uint8_t *config = NULL;
+    int size = 0;
+    pa_bluetooth_profile_t p = PA_BLUETOOTH_PROFILE_OFF;
+    DBusMessageIter args, props;
     DBusMessage *r;
 
-    pa_assert_se(r = dbus_message_new_error(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented",
-                                            "Method not implemented"));
+    if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
+        pa_log_error("Invalid signature for method SetConfiguration()");
+        goto fail2;
+    }
+
+    dbus_message_iter_get_basic(&args, &path);
+
+    if (pa_hashmap_get(y->transports, path)) {
+        pa_log_error("Endpoint SetConfiguration(): Transport %s is already configured.", path);
+        goto fail2;
+    }
+
+    pa_assert_se(dbus_message_iter_next(&args));
+
+    dbus_message_iter_recurse(&args, &props);
+    if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+        goto fail;
+
+    /* Read transport properties */
+    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 (pa_streq(key, "UUID")) {
+            if (var != DBUS_TYPE_STRING) {
+                pa_log_error("Property %s of wrong type %c", key, (char)var);
+                goto fail;
+            }
+
+            dbus_message_iter_get_basic(&value, &uuid);
+
+            endpoint_path = dbus_message_get_path(m);
+            if (pa_streq(endpoint_path, A2DP_SOURCE_ENDPOINT)) {
+                if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
+                    p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
+            } else if (pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
+                if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
+                    p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
+            }
+
+            if (p == PA_BLUETOOTH_PROFILE_OFF) {
+                pa_log_error("UUID %s of transport %s incompatible with endpoint %s", uuid, path, endpoint_path);
+                goto fail;
+            }
+        } else if (pa_streq(key, "Device")) {
+            if (var != DBUS_TYPE_OBJECT_PATH) {
+                pa_log_error("Property %s of wrong type %c", key, (char)var);
+                goto fail;
+            }
+
+            dbus_message_iter_get_basic(&value, &dev_path);
+        } else if (pa_streq(key, "Configuration")) {
+            DBusMessageIter array;
+            a2dp_sbc_t *c;
+
+            if (var != DBUS_TYPE_ARRAY) {
+                pa_log_error("Property %s of wrong type %c", key, (char)var);
+                goto fail;
+            }
+
+            dbus_message_iter_recurse(&value, &array);
+            var = dbus_message_iter_get_arg_type(&array);
+            if (var != DBUS_TYPE_BYTE) {
+                pa_log_error("%s is an array of wrong type %c", key, (char)var);
+                goto fail;
+            }
+
+            dbus_message_iter_get_fixed_array(&array, &config, &size);
+            if (size != sizeof(a2dp_sbc_t)) {
+                pa_log_error("Configuration array of invalid size");
+                goto fail;
+            }
+
+            c = (a2dp_sbc_t *) config;
+
+            if (c->frequency != SBC_SAMPLING_FREQ_16000 && c->frequency != SBC_SAMPLING_FREQ_32000 &&
+                c->frequency != SBC_SAMPLING_FREQ_44100 && c->frequency != SBC_SAMPLING_FREQ_48000) {
+                pa_log_error("Invalid sampling frequency in configuration");
+                goto fail;
+            }
+
+            if (c->channel_mode != SBC_CHANNEL_MODE_MONO && c->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
+                c->channel_mode != SBC_CHANNEL_MODE_STEREO && c->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
+                pa_log_error("Invalid channel mode in configuration");
+                goto fail;
+            }
+
+            if (c->allocation_method != SBC_ALLOCATION_SNR && c->allocation_method != SBC_ALLOCATION_LOUDNESS) {
+                pa_log_error("Invalid allocation method in configuration");
+                goto fail;
+            }
+
+            if (c->subbands != SBC_SUBBANDS_4 && c->subbands != SBC_SUBBANDS_8) {
+                pa_log_error("Invalid SBC subbands in configuration");
+                goto fail;
+            }
+
+            if (c->block_length != SBC_BLOCK_LENGTH_4 && c->block_length != SBC_BLOCK_LENGTH_8 &&
+                c->block_length != SBC_BLOCK_LENGTH_12 && c->block_length != SBC_BLOCK_LENGTH_16) {
+                pa_log_error("Invalid block length in configuration");
+                goto fail;
+            }
+        }
+
+        dbus_message_iter_next(&props);
+    }
+
+    if ((d = pa_hashmap_get(y->devices, dev_path))) {
+        if (d->device_info_valid == -1) {
+            pa_log_error("Information about device %s is invalid", dev_path);
+            goto fail2;
+        }
+    } else {
+        /* InterfacesAdded signal is probably on it's way, device_info_valid is kept as 0. */
+        pa_log_warn("SetConfiguration() received for unknown device %s", dev_path);
+        d = device_create(y, dev_path);
+    }
+
+    if (d->transports[p] != NULL) {
+        pa_log_error("Cannot configure transport %s because profile %s is already used", path, pa_bluetooth_profile_to_string(p));
+        goto fail2;
+    }
+
+    sender = dbus_message_get_sender(m);
+
+    pa_assert_se(r = dbus_message_new_method_return(m));
+    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
+    dbus_message_unref(r);
+
+    d->transports[p] = t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
+    t->acquire = bluez5_transport_acquire_cb;
+    t->release = bluez5_transport_release_cb;
+    pa_bluetooth_transport_put(t);
+
+    pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
+
+    return NULL;
+
+fail:
+    pa_log_error("Endpoint SetConfiguration(): invalid arguments");
 
+fail2:
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to set configuration"));
     return r;
 }
 
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 57419f2..4e0b567 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -24,6 +24,9 @@
 
 #include <pulsecore/core.h>
 
+#define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
+#define PA_BLUETOOTH_UUID_A2DP_SINK   "0000110b-0000-1000-8000-00805f9b34fb"
+
 typedef struct pa_bluetooth_transport pa_bluetooth_transport;
 typedef struct pa_bluetooth_device pa_bluetooth_device;
 typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;
@@ -103,6 +106,8 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d
 
 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
 
+const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile);
+
 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y);

commit aff6fa79ba92752644cd57fcd83ad225b649df54
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:23 2013 -0300

    bluetooth: Register MediaEndpoint1 objects with D-Bus

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 9ca4b9e..e38b7c7 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -36,8 +36,37 @@
 #include "bluez5-util.h"
 
 #define BLUEZ_SERVICE "org.bluez"
+#define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
 #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
 
+#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
+#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+
+#define ENDPOINT_INTROSPECT_XML                                         \
+    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
+    "<node>"                                                            \
+    " <interface name=\"" BLUEZ_MEDIA_ENDPOINT_INTERFACE "\">"          \
+    "  <method name=\"SetConfiguration\">"                              \
+    "   <arg name=\"transport\" direction=\"in\" type=\"o\"/>"          \
+    "   <arg name=\"properties\" direction=\"in\" type=\"ay\"/>"        \
+    "  </method>"                                                       \
+    "  <method name=\"SelectConfiguration\">"                           \
+    "   <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>"      \
+    "   <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>"    \
+    "  </method>"                                                       \
+    "  <method name=\"ClearConfiguration\">"                            \
+    "   <arg name=\"transport\" direction=\"in\" type=\"o\"/>"          \
+    "  </method>"                                                       \
+    "  <method name=\"Release\">"                                       \
+    "  </method>"                                                       \
+    " </interface>"                                                     \
+    " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
+    "  <method name=\"Introspect\">"                                    \
+    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
+    "  </method>"                                                       \
+    " </interface>"                                                     \
+    "</node>"
+
 struct pa_bluetooth_discovery {
     PA_REFCNT_DECLARE;
 
@@ -378,6 +407,121 @@ fail:
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
+static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
+    DBusMessage *r;
+
+    pa_assert_se(r = dbus_message_new_error(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented",
+                                            "Method not implemented"));
+
+    return r;
+}
+
+static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
+    DBusMessage *r;
+
+    pa_assert_se(r = dbus_message_new_error(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented",
+                                            "Method not implemented"));
+
+    return r;
+}
+
+static DBusMessage *endpoint_clear_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
+    DBusMessage *r;
+
+    pa_assert_se(r = dbus_message_new_error(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented",
+                                            "Method not implemented"));
+
+    return r;
+}
+
+static DBusMessage *endpoint_release(DBusConnection *conn, DBusMessage *m, void *userdata) {
+    DBusMessage *r;
+
+    pa_assert_se(r = dbus_message_new_error(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented",
+                                            "Method not implemented"));
+
+    return r;
+}
+
+static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
+    struct pa_bluetooth_discovery *y = userdata;
+    DBusMessage *r = NULL;
+    const char *path, *interface, *member;
+
+    pa_assert(y);
+
+    path = dbus_message_get_path(m);
+    interface = dbus_message_get_interface(m);
+    member = dbus_message_get_member(m);
+
+    pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
+
+    if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT))
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+        const char *xml = ENDPOINT_INTROSPECT_XML;
+
+        pa_assert_se(r = dbus_message_new_method_return(m));
+        pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
+
+    } else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"))
+        r = endpoint_set_configuration(c, m, userdata);
+    else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration"))
+        r = endpoint_select_configuration(c, m, userdata);
+    else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration"))
+        r = endpoint_clear_configuration(c, m, userdata);
+    else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "Release"))
+        r = endpoint_release(c, m, userdata);
+    else
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    if (r) {
+        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
+        dbus_message_unref(r);
+    }
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
+    static const DBusObjectPathVTable vtable_endpoint = {
+        .message_function = endpoint_handler,
+    };
+
+    pa_assert(y);
+
+    switch(profile) {
+        case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+            pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT,
+                                                              &vtable_endpoint, y));
+            break;
+        case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+            pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT,
+                                                              &vtable_endpoint, y));
+            break;
+        default:
+            pa_assert_not_reached();
+            break;
+    }
+}
+
+static void endpoint_done(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
+    pa_assert(y);
+
+    switch(profile) {
+        case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+            dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
+            break;
+        case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+            dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
+            break;
+        default:
+            pa_assert_not_reached();
+            break;
+    }
+}
+
 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     pa_bluetooth_discovery *y;
     DBusError err;
@@ -424,6 +568,9 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     }
     y->matches_added = true;
 
+    endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
+    endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
+
     return y;
 
 fail:
@@ -475,6 +622,9 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
         if (y->filter_added)
             dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
 
+        endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
+        endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
+
         pa_dbus_connection_unref(y->connection);
     }
 

commit e78e7e698ce0f127b4415e854e2db3e66775bf57
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:22 2013 -0300

    bluetooth: Create pa_bluetooth_transport for BlueZ 5 support
    
    Create the pa_bluetooth_transport structure to store information about
    the bluetooth transport and utility functions to manipulate this
    structure. The acquire() and release() operations are function pointers
    in the pa_bluetooth_transport structure to make possible for different
    transport backends to provide different implementations of these
    operations. Thre is also a userdata field for the transport backend
    provide data for the acquire/release functions.
    
    This commit also creates a new function
    pa_bluetooth_device_any_transport_connected() to check if there is any
    audio connection between the host and a remote device.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 6196c11..9ca4b9e 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -36,6 +36,7 @@
 #include "bluez5-util.h"
 
 #define BLUEZ_SERVICE "org.bluez"
+#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
 
 struct pa_bluetooth_discovery {
     PA_REFCNT_DECLARE;
@@ -47,8 +48,165 @@ struct pa_bluetooth_discovery {
     pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
     pa_hashmap *adapters;
     pa_hashmap *devices;
+    pa_hashmap *transports;
 };
 
+pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
+                                                   pa_bluetooth_profile_t p, const uint8_t *config, size_t size) {
+    pa_bluetooth_transport *t;
+
+    t = pa_xnew0(pa_bluetooth_transport, 1);
+    t->device = d;
+    t->owner = pa_xstrdup(owner);
+    t->path = pa_xstrdup(path);
+    t->profile = p;
+    t->config_size = size;
+
+    if (size > 0) {
+        t->config = pa_xnew(uint8_t, size);
+        memcpy(t->config, config, size);
+    }
+
+    pa_assert_se(pa_hashmap_put(d->discovery->transports, t->path, t) >= 0);
+
+    return t;
+}
+
+static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
+    switch(state) {
+        case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
+            return "disconnected";
+        case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
+            return "idle";
+        case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
+            return "playing";
+    }
+
+    return "invalid";
+}
+
+static void transport_state_changed(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state) {
+    bool old_any_connected;
+
+    pa_assert(t);
+
+    if (t->state == state)
+        return;
+
+    old_any_connected = pa_bluetooth_device_any_transport_connected(t->device);
+
+    pa_log_debug("Transport %s state changed from %s to %s",
+                 t->path, transport_state_to_string(t->state), transport_state_to_string(state));
+
+    t->state = state;
+    if (state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+        t->device->transports[t->profile] = NULL;
+
+    pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
+
+    if (old_any_connected != pa_bluetooth_device_any_transport_connected(t->device))
+        pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
+}
+
+void pa_bluetooth_transport_put(pa_bluetooth_transport *t) {
+    transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_IDLE);
+}
+
+void pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
+    pa_assert(t);
+
+    pa_hashmap_remove(t->device->discovery->transports, t->path);
+    pa_xfree(t->owner);
+    pa_xfree(t->path);
+    pa_xfree(t->config);
+    pa_xfree(t);
+}
+
+static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
+    DBusMessage *m, *r;
+    DBusError err;
+    int ret;
+    uint16_t i, o;
+    const char *method = optional ? "TryAcquire" : "Acquire";
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, method));
+
+    dbus_error_init(&err);
+
+    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
+    if (!r) {
+        if (optional && pa_streq(err.name, "org.bluez.Error.NotAvailable"))
+            pa_log_info("Failed optional acquire of unavailable transport %s", t->path);
+        else
+            pa_log_error("Transport %s() failed for transport %s (%s)", method, t->path, err.message);
+
+        dbus_error_free(&err);
+        return -1;
+    }
+
+    if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o,
+                               DBUS_TYPE_INVALID)) {
+        pa_log_error("Failed to parse %s() reply: %s", method, err.message);
+        dbus_error_free(&err);
+        ret = -1;
+        goto finish;
+    }
+
+    if (imtu)
+        *imtu = i;
+
+    if (omtu)
+        *omtu = o;
+
+finish:
+    dbus_message_unref(r);
+    return ret;
+}
+
+static void bluez5_transport_release_cb(pa_bluetooth_transport *t) {
+    DBusMessage *m;
+    DBusError err;
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    dbus_error_init(&err);
+
+    if (t->state <= PA_BLUETOOTH_TRANSPORT_STATE_IDLE) {
+        pa_log_info("Transport %s auto-released by BlueZ or already released", t->path);
+        return;
+    }
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, "Release"));
+    dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
+
+    if (dbus_error_is_set(&err)) {
+        pa_log_error("Failed to release transport %s: %s", t->path, err.message);
+        dbus_error_free(&err);
+    } else
+        pa_log_info("Transport %s released", t->path);
+}
+
+bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
+    unsigned i;
+
+    pa_assert(d);
+
+    if (d->device_info_valid != 1)
+        return false;
+
+    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
+        if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+            return true;
+
+    return false;
+}
+
 static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) {
     pa_bluetooth_device *d;
 
@@ -95,8 +253,20 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d
 }
 
 static void device_free(pa_bluetooth_device *d) {
+    unsigned i;
+
     pa_assert(d);
 
+    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
+        pa_bluetooth_transport *t;
+
+        if (!(t = d->transports[i]))
+            continue;
+
+        transport_state_changed(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED);
+        pa_bluetooth_transport_free(t);
+    }
+
     d->discovery = NULL;
     d->adapter = NULL;
     pa_xfree(d->path);
@@ -222,6 +392,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     y->core = c;
     y->adapters = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
     y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
 
     for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
         pa_hook_init(&y->hooks[i], y);
@@ -288,6 +459,11 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
         pa_hashmap_free(y->adapters);
     }
 
+    if (y->transports) {
+        pa_assert(pa_hashmap_isempty(y->transports));
+        pa_hashmap_free(y->transports);
+    }
+
     if (y->connection) {
 
         if (y->matches_added)
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 00784f6..57419f2 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -24,15 +24,51 @@
 
 #include <pulsecore/core.h>
 
+typedef struct pa_bluetooth_transport pa_bluetooth_transport;
 typedef struct pa_bluetooth_device pa_bluetooth_device;
 typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;
 typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
 
 typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED,          /* Call data: pa_bluetooth_device */
+    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
 
+typedef enum profile {
+    PA_BLUETOOTH_PROFILE_A2DP_SINK,
+    PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
+    PA_BLUETOOTH_PROFILE_OFF
+} pa_bluetooth_profile_t;
+#define PA_BLUETOOTH_PROFILE_COUNT PA_BLUETOOTH_PROFILE_OFF
+
+typedef enum pa_bluetooth_transport_state {
+    PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED,
+    PA_BLUETOOTH_TRANSPORT_STATE_IDLE,
+    PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
+} pa_bluetooth_transport_state_t;
+
+typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu);
+typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
+
+struct pa_bluetooth_transport {
+    pa_bluetooth_device *device;
+
+    char *owner;
+    char *path;
+    pa_bluetooth_profile_t profile;
+
+    uint8_t codec;
+    uint8_t *config;
+    size_t config_size;
+
+    pa_bluetooth_transport_state_t state;
+
+    pa_bluetooth_transport_acquire_cb acquire;
+    pa_bluetooth_transport_release_cb release;
+    void *userdata;
+};
+
 struct pa_bluetooth_device {
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_adapter *adapter;
@@ -44,6 +80,8 @@ struct pa_bluetooth_device {
     char *alias;
     char *address;
     uint32_t class_of_device;
+
+    pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
 };
 
 struct pa_bluetooth_adapter {
@@ -52,6 +90,14 @@ struct pa_bluetooth_adapter {
     char *address;
 };
 
+pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
+                                                   pa_bluetooth_profile_t p, const uint8_t *config, size_t size);
+
+void pa_bluetooth_transport_put(pa_bluetooth_transport *t);
+void pa_bluetooth_transport_free(pa_bluetooth_transport *t);
+
+bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d);
+
 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery *y, const char *path);
 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local);
 

commit c3efb24aa75993a8c97cbcb4d1a8184142d17e64
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Sep 24 19:45:21 2013 -0300

    bluetooth: Remove all devices and adapters when org.bluez goes away

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 63e3332..6196c11 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -189,7 +189,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
         if (pa_streq(name, BLUEZ_SERVICE)) {
             if (old_owner && *old_owner) {
                 pa_log_debug("Bluetooth daemon disappeared");
-                /* TODO: remove all devices */
+                device_remove_all(y);
+                adapter_remove_all(y);
             }
 
             if (new_owner && *new_owner) {

commit 9be71b58385f56696fcf86a651518593af8c02e7
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Wed Sep 18 16:16:58 2013 -0500

    bluetooth: Create pa_bluetooth_device for BlueZ 5 support
    
    Create a structure pa_bluetooth_device to store information about the
    bluetooth device and utility functions to manipulate this structure.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 87dc5d6..63e3332 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -46,8 +46,88 @@ struct pa_bluetooth_discovery {
     bool matches_added;
     pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
     pa_hashmap *adapters;
+    pa_hashmap *devices;
 };
 
+static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) {
+    pa_bluetooth_device *d;
+
+    pa_assert(y);
+    pa_assert(path);
+
+    d = pa_xnew0(pa_bluetooth_device, 1);
+    d->discovery = y;
+    d->path = pa_xstrdup(path);
+
+    pa_hashmap_put(y->devices, d->path, d);
+
+    return d;
+}
+
+pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery *y, const char *path) {
+    pa_bluetooth_device *d;
+
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+    pa_assert(path);
+
+    if ((d = pa_hashmap_get(y->devices, path)))
+        if (d->device_info_valid == 1)
+            return d;
+
+    return NULL;
+}
+
+pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local) {
+    pa_bluetooth_device *d;
+    void *state = NULL;
+
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+    pa_assert(remote);
+    pa_assert(local);
+
+    while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
+        if (pa_streq(d->address, remote) && pa_streq(d->adapter->address, local))
+            return d->device_info_valid == 1 ? d : NULL;
+
+    return NULL;
+}
+
+static void device_free(pa_bluetooth_device *d) {
+    pa_assert(d);
+
+    d->discovery = NULL;
+    d->adapter = NULL;
+    pa_xfree(d->path);
+    pa_xfree(d->alias);
+    pa_xfree(d->address);
+    pa_xfree(d);
+}
+
+static void device_remove(pa_bluetooth_discovery *y, const char *path) {
+    pa_bluetooth_device *d;
+
+    if (!(d = pa_hashmap_remove(y->devices, path)))
+        pa_log_warn("Unknown device removed %s", path);
+    else {
+        pa_log_debug("Device %s removed", path);
+        device_free(d);
+    }
+}
+
+static void device_remove_all(pa_bluetooth_discovery *y) {
+    pa_bluetooth_device *d;
+
+    pa_assert(y);
+
+    while ((d = pa_hashmap_steal_first(y->devices))) {
+        d->device_info_valid = -1;
+        pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d);
+        device_free(d);
+   }
+}
+
 static pa_bluetooth_adapter* adapter_create(pa_bluetooth_discovery *y, const char *path) {
     pa_bluetooth_adapter *a;
 
@@ -68,6 +148,8 @@ static void adapter_remove_all(pa_bluetooth_discovery *y) {
 
     pa_assert(y);
 
+    /* When this function is called all devices have already been freed */
+
     while ((a = pa_hashmap_steal_first(y->adapters))) {
         pa_xfree(a->path);
         pa_xfree(a->address);
@@ -138,6 +220,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     PA_REFCNT_INIT(y);
     y->core = c;
     y->adapters = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
 
     for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
         pa_hook_init(&y->hooks[i], y);
@@ -194,6 +277,11 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
     if (PA_REFCNT_DEC(y) > 0)
         return;
 
+    if (y->devices) {
+        device_remove_all(y);
+        pa_hashmap_free(y->devices);
+    }
+
     if (y->adapters) {
         adapter_remove_all(y);
         pa_hashmap_free(y->adapters);
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 1f2f702..00784f6 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -24,19 +24,37 @@
 
 #include <pulsecore/core.h>
 
+typedef struct pa_bluetooth_device pa_bluetooth_device;
 typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;
 typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
 
 typedef enum pa_bluetooth_hook {
+    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED,          /* Call data: pa_bluetooth_device */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
 
+struct pa_bluetooth_device {
+    pa_bluetooth_discovery *discovery;
+    pa_bluetooth_adapter *adapter;
+
+    int device_info_valid;      /* 0: no results yet; 1: good results; -1: bad results ... */
+
+    /* Device information */
+    char *path;
+    char *alias;
+    char *address;
+    uint32_t class_of_device;
+};
+
 struct pa_bluetooth_adapter {
     pa_bluetooth_discovery *discovery;
     char *path;
     char *address;
 };
 
+pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery *y, const char *path);
+pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local);
+
 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
 
 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);

commit 546da17cbe18692c28ec89c76504481c7b5d6b10
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Wed Sep 18 16:16:57 2013 -0500

    bluetooth: Create pa_bluetooth_adapter for BlueZ 5 support

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index bfb3359..87dc5d6 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -45,8 +45,36 @@ struct pa_bluetooth_discovery {
     bool filter_added;
     bool matches_added;
     pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
+    pa_hashmap *adapters;
 };
 
+static pa_bluetooth_adapter* adapter_create(pa_bluetooth_discovery *y, const char *path) {
+    pa_bluetooth_adapter *a;
+
+    pa_assert(y);
+    pa_assert(path);
+
+    a = pa_xnew0(pa_bluetooth_adapter, 1);
+    a->discovery = y;
+    a->path = pa_xstrdup(path);
+
+    pa_hashmap_put(y->adapters, a->path, a);
+
+    return a;
+}
+
+static void adapter_remove_all(pa_bluetooth_discovery *y) {
+    pa_bluetooth_adapter *a;
+
+    pa_assert(y);
+
+    while ((a = pa_hashmap_steal_first(y->adapters))) {
+        pa_xfree(a->path);
+        pa_xfree(a->address);
+        pa_xfree(a);
+    }
+}
+
 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
     pa_assert(y);
     pa_assert(PA_REFCNT_VALUE(y) > 0);
@@ -109,6 +137,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     y = pa_xnew0(pa_bluetooth_discovery, 1);
     PA_REFCNT_INIT(y);
     y->core = c;
+    y->adapters = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
 
     for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
         pa_hook_init(&y->hooks[i], y);
@@ -165,6 +194,11 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
     if (PA_REFCNT_DEC(y) > 0)
         return;
 
+    if (y->adapters) {
+        adapter_remove_all(y);
+        pa_hashmap_free(y->adapters);
+    }
+
     if (y->connection) {
 
         if (y->matches_added)
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index afbd8e9..1f2f702 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -24,12 +24,19 @@
 
 #include <pulsecore/core.h>
 
+typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;
 typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
 
 typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
 
+struct pa_bluetooth_adapter {
+    pa_bluetooth_discovery *discovery;
+    char *path;
+    char *address;
+};
+
 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
 
 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);

commit 19a23cef109dda8e1b0aa29fe3675b75e2ca2e6b
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Wed Sep 18 16:16:56 2013 -0500

    bluetooth: Create infrastrucure for bluetooth hooks
    
    Create a pa_bluetooth_hook enum type and API to get pa_hooks from the
    opaque pa_bluetooth_discovery struct.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 36f1b26..bfb3359 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -44,8 +44,16 @@ struct pa_bluetooth_discovery {
     pa_dbus_connection *connection;
     bool filter_added;
     bool matches_added;
+    pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
 };
 
+pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+    return &y->hooks[hook];
+}
+
 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
     pa_bluetooth_discovery *y;
     DBusError err;
@@ -93,6 +101,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     pa_bluetooth_discovery *y;
     DBusError err;
     DBusConnection *conn;
+    unsigned i;
 
     if ((y = pa_shared_get(c, "bluetooth-discovery")))
         return pa_bluetooth_discovery_ref(y);
@@ -101,6 +110,9 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     PA_REFCNT_INIT(y);
     y->core = c;
 
+    for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
+        pa_hook_init(&y->hooks[i], y);
+
     pa_shared_set(c, "bluetooth-discovery", y);
 
     dbus_error_init(&err);
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 56602bf..afbd8e9 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -26,6 +26,12 @@
 
 typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
 
+typedef enum pa_bluetooth_hook {
+    PA_BLUETOOTH_HOOK_MAX
+} pa_bluetooth_hook_t;
+
+pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
+
 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y);

commit dcc28a23f962890a497cd7012c794929df74d907
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Wed Sep 18 16:16:55 2013 -0500

    bluetooth: Track org.bluez for BlueZ 5

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 0f23bff..36f1b26 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -26,20 +26,73 @@
 #include <pulse/xmalloc.h>
 
 #include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-shared.h>
+#include <pulsecore/log.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/refcnt.h>
 #include <pulsecore/shared.h>
 
 #include "bluez5-util.h"
 
+#define BLUEZ_SERVICE "org.bluez"
+
 struct pa_bluetooth_discovery {
     PA_REFCNT_DECLARE;
 
     pa_core *core;
+    pa_dbus_connection *connection;
+    bool filter_added;
+    bool matches_added;
 };
 
+static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
+    pa_bluetooth_discovery *y;
+    DBusError err;
+
+    pa_assert(bus);
+    pa_assert(m);
+    pa_assert_se(y = userdata);
+
+    dbus_error_init(&err);
+
+    if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
+        const char *name, *old_owner, *new_owner;
+
+        if (!dbus_message_get_args(m, &err,
+                                   DBUS_TYPE_STRING, &name,
+                                   DBUS_TYPE_STRING, &old_owner,
+                                   DBUS_TYPE_STRING, &new_owner,
+                                   DBUS_TYPE_INVALID)) {
+            pa_log_error("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
+            goto fail;
+        }
+
+        if (pa_streq(name, BLUEZ_SERVICE)) {
+            if (old_owner && *old_owner) {
+                pa_log_debug("Bluetooth daemon disappeared");
+                /* TODO: remove all devices */
+            }
+
+            if (new_owner && *new_owner) {
+                pa_log_debug("Bluetooth daemon appeared");
+                /* TODO: get managed objects */
+            }
+        }
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+fail:
+    dbus_error_free(&err);
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     pa_bluetooth_discovery *y;
+    DBusError err;
+    DBusConnection *conn;
 
     if ((y = pa_shared_get(c, "bluetooth-discovery")))
         return pa_bluetooth_discovery_ref(y);
@@ -50,7 +103,38 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
 
     pa_shared_set(c, "bluetooth-discovery", y);
 
+    dbus_error_init(&err);
+
+    if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) {
+        pa_log_error("Failed to get D-Bus connection: %s", err.message);
+        goto fail;
+    }
+
+    conn = pa_dbus_connection_get(y->connection);
+
+    /* dynamic detection of bluetooth audio devices */
+    if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) {
+        pa_log_error("Failed to add filter function");
+        goto fail;
+    }
+    y->filter_added = true;
+
+    if (pa_dbus_add_matches(conn, &err,
+            "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
+            ",arg0='" BLUEZ_SERVICE "'",
+            NULL) < 0) {
+        pa_log_error("Failed to add D-Bus matches: %s", err.message);
+        goto fail;
+    }
+    y->matches_added = true;
+
     return y;
+
+fail:
+    pa_bluetooth_discovery_unref(y);
+    dbus_error_free(&err);
+
+    return NULL;
 }
 
 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
@@ -69,6 +153,20 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
     if (PA_REFCNT_DEC(y) > 0)
         return;
 
+    if (y->connection) {
+
+        if (y->matches_added)
+            pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
+                "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
+                "arg0='" BLUEZ_SERVICE "'",
+                NULL);
+
+        if (y->filter_added)
+            dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
+
+        pa_dbus_connection_unref(y->connection);
+    }
+
     pa_shared_remove(y->core, "bluetooth-discovery");
     pa_xfree(y);
 }

commit 0103cd13795448dea815f1cb3cbc6d1de484d4ff
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:54:00 2013 -0300

    bluetooth: Create pa_bluetooth_discovery for BlueZ 5
    
    pa_bluetooth_discovery is the struct that holds information about known
    Bluetooth audio devices and other information about the Bluetooth stack.
    
    This commit also creates bluez5-util.[ch], which will hold a lot of
    utility functions to help with the BlueZ 5 support.

diff --git a/src/Makefile.am b/src/Makefile.am
index 66ce834..689090f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1334,6 +1334,7 @@ endif
 
 if HAVE_BLUEZ_5
 modlibexec_LTLIBRARIES += \
+		libbluez5-util.la \
 		module-bluez5-discover.la
 endif
 
@@ -2048,9 +2049,16 @@ module_bluez4_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbl
 module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
 
 # Bluetooth BlueZ 5 sink / source
+libbluez5_util_la_SOURCES = \
+		modules/bluetooth/bluez5-util.c \
+		modules/bluetooth/bluez5-util.h
+libbluez5_util_la_LDFLAGS = -avoid-version
+libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
+libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+
 module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
 module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
-module_bluez5_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
+module_bluez5_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluez5-util.la
 module_bluez5_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
 
 # Apple Airtunes/RAOP
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
new file mode 100644
index 0000000..0f23bff
--- /dev/null
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -0,0 +1,74 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008-2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/shared.h>
+
+#include "bluez5-util.h"
+
+struct pa_bluetooth_discovery {
+    PA_REFCNT_DECLARE;
+
+    pa_core *core;
+};
+
+pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
+    pa_bluetooth_discovery *y;
+
+    if ((y = pa_shared_get(c, "bluetooth-discovery")))
+        return pa_bluetooth_discovery_ref(y);
+
+    y = pa_xnew0(pa_bluetooth_discovery, 1);
+    PA_REFCNT_INIT(y);
+    y->core = c;
+
+    pa_shared_set(c, "bluetooth-discovery", y);
+
+    return y;
+}
+
+pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+    PA_REFCNT_INC(y);
+
+    return y;
+}
+
+void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+    if (PA_REFCNT_DEC(y) > 0)
+        return;
+
+    pa_shared_remove(y->core, "bluetooth-discovery");
+    pa_xfree(y);
+}
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
new file mode 100644
index 0000000..56602bf
--- /dev/null
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -0,0 +1,32 @@
+#ifndef foobluez5utilhfoo
+#define foobluez5utilhfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008-2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#include <pulsecore/core.h>
+
+typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
+
+pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
+pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
+void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y);
+#endif
diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c
index 5dbe399..8cc0631 100644
--- a/src/modules/bluetooth/module-bluez5-discover.c
+++ b/src/modules/bluetooth/module-bluez5-discover.c
@@ -23,9 +23,12 @@
 #include <config.h>
 #endif
 
+#include <pulsecore/core.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/module.h>
 
+#include "bluez5-util.h"
+
 #include "module-bluez5-discover-symdef.h"
 
 PA_MODULE_AUTHOR("João Paulo Rechi Vita");
@@ -33,11 +36,41 @@ PA_MODULE_DESCRIPTION("Detect available BlueZ 5 Bluetooth audio devices and load
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(true);
 
+struct userdata {
+    pa_module *module;
+    pa_core *core;
+    pa_bluetooth_discovery *discovery;
+};
+
 int pa__init(pa_module *m) {
+    struct userdata *u;
+
     pa_assert(m);
+
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+    u->module = m;
+    u->core = m->core;
+
+    if (!(u->discovery = pa_bluetooth_discovery_get(u->core)))
+        goto fail;
+
     return 0;
+
+fail:
+    pa__done(m);
+    return -1;
 }
 
 void pa__done(pa_module *m) {
+    struct userdata *u;
+
     pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->discovery)
+        pa_bluetooth_discovery_unref(u->discovery);
+
+    pa_xfree(u);
 }

commit c706792df756eb2295996306d7ffaa9723dc4a43
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:59 2013 -0300

    bluetooth: Create stub for module-bluez5-discover

diff --git a/src/Makefile.am b/src/Makefile.am
index 773a0af..66ce834 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1332,6 +1332,11 @@ modlibexec_LTLIBRARIES += \
 		module-bluez4-device.la
 endif
 
+if HAVE_BLUEZ_5
+modlibexec_LTLIBRARIES += \
+		module-bluez5-discover.la
+endif
+
 if HAVE_OPENSSL
 modlibexec_LTLIBRARIES += \
 		libraop.la \
@@ -1421,6 +1426,7 @@ SYMDEF_FILES = \
 		module-bluetooth-policy-symdef.h \
 		module-bluez4-discover-symdef.h \
 		module-bluez4-device-symdef.h \
+		module-bluez5-discover-symdef.h \
 		module-raop-sink-symdef.h \
 		module-raop-discover-symdef.h \
 		module-gconf-symdef.h \
@@ -2041,6 +2047,12 @@ module_bluez4_device_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_bluez4_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbluez4-util.la
 module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
 
+# Bluetooth BlueZ 5 sink / source
+module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
+module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_bluez5_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
+module_bluez5_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+
 # Apple Airtunes/RAOP
 module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
 module_raop_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c
new file mode 100644
index 0000000..5dbe399
--- /dev/null
+++ b/src/modules/bluetooth/module-bluez5-discover.c
@@ -0,0 +1,43 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008-2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+#include <pulsecore/module.h>
+
+#include "module-bluez5-discover-symdef.h"
+
+PA_MODULE_AUTHOR("João Paulo Rechi Vita");
+PA_MODULE_DESCRIPTION("Detect available BlueZ 5 Bluetooth audio devices and load BlueZ 5 Bluetooth audio drivers");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+
+int pa__init(pa_module *m) {
+    pa_assert(m);
+    return 0;
+}
+
+void pa__done(pa_module *m) {
+    pa_assert(m);
+}

commit 795e488f2530663a8252ac19f4efd85afd33c903
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Fri Aug 16 09:40:54 2013 -0300

    build: Add support for enabling/disabling BlueZ 5 support

diff --git a/configure.ac b/configure.ac
index 574d80a..dd07a78 100644
--- a/configure.ac
+++ b/configure.ac
@@ -979,21 +979,33 @@ PA_MACHINE_ID_FALLBACK="${localstatedir}/lib/dbus/machine-id"
 AX_DEFINE_DIR(PA_MACHINE_ID_FALLBACK, PA_MACHINE_ID_FALLBACK,
 	      [Fallback machine-id file])
 
-#### BlueZ support (optional, dependent on D-Bus) ####
+#### BlueZ support (optional, dependent on D-Bus and SBC) ####
 
 AC_ARG_ENABLE([bluez4],
     AS_HELP_STRING([--disable-bluez4],[Disable optional BlueZ 4 support]))
+AC_ARG_ENABLE([bluez5],
+    AS_HELP_STRING([--disable-bluez5],[Disable optional BlueZ 5 support]))
 
-AS_IF([test "x$enable_bluez4" != "xno"],
+## SBC ##
+AS_IF([test "x$enable_bluez4" != "xno" || test "x$enable_bluez5" != "xno"],
     [PKG_CHECK_MODULES(SBC, [ sbc >= 1.0 ], HAVE_SBC=1, HAVE_SBC=0)],
     HAVE_SBC=0)
+
+## BlueZ 4 ##
 AS_IF([test "x$enable_bluez4" != "xno" && test "x$HAVE_DBUS" = "x1" && test "x$HAVE_SBC" = "x1"], HAVE_BLUEZ_4=1)
 AS_IF([test "x$enable_bluez4" = "xyes" && test "x$HAVE_BLUEZ_4" != "x1"],
     [AC_MSG_ERROR([*** BLUEZ 4 support not found (requires sbc and D-Bus)])])
 AC_SUBST(HAVE_BLUEZ_4)
 AM_CONDITIONAL([HAVE_BLUEZ_4], [test "x$HAVE_BLUEZ_4" = x1])
 
-AS_IF([test "x$HAVE_BLUEZ_4" = "x1"], HAVE_BLUEZ=1)
+## BlueZ 5 ##
+AS_IF([test "x$enable_bluez5" != "xno" && test "x$HAVE_DBUS" = "x1" && test "x$HAVE_SBC" = "x1"], HAVE_BLUEZ_5=1)
+AS_IF([test "x$enable_bluez5" = "xyes" && test "x$HAVE_BLUEZ_5" != "x1"],
+    [AC_MSG_ERROR([*** BLUEZ 5 support not found (requires sbc and D-Bus)])])
+AC_SUBST(HAVE_BLUEZ_5)
+AM_CONDITIONAL([HAVE_BLUEZ_5], [test "x$HAVE_BLUEZ_5" = x1])
+
+AS_IF([test "x$HAVE_BLUEZ_4" = "x1" || test "x$HAVE_BLUEZ_5" = "x1"], HAVE_BLUEZ=1)
 AC_SUBST(HAVE_BLUEZ)
 AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1])
 
@@ -1388,6 +1400,7 @@ AS_IF([test "x$HAVE_DBUS" = "x1"], ENABLE_DBUS=yes, ENABLE_DBUS=no)
 AS_IF([test "x$HAVE_UDEV" = "x1"], ENABLE_UDEV=yes, ENABLE_UDEV=no)
 AS_IF([test "x$HAVE_SYSTEMD" = "x1"], ENABLE_SYSTEMD=yes, ENABLE_SYSTEMD=no)
 AS_IF([test "x$HAVE_BLUEZ_4" = "x1"], ENABLE_BLUEZ_4=yes, ENABLE_BLUEZ_4=no)
+AS_IF([test "x$HAVE_BLUEZ_5" = "x1"], ENABLE_BLUEZ_5=yes, ENABLE_BLUEZ_5=no)
 AS_IF([test "x$HAVE_HAL_COMPAT" = "x1"], ENABLE_HAL_COMPAT=yes, ENABLE_HAL_COMPAT=no)
 AS_IF([test "x$HAVE_TCPWRAP" = "x1"], ENABLE_TCPWRAP=yes, ENABLE_TCPWRAP=no)
 AS_IF([test "x$HAVE_LIBSAMPLERATE" = "x1"], ENABLE_LIBSAMPLERATE=yes, ENABLE_LIBSAMPLERATE=no)
@@ -1439,6 +1452,7 @@ echo "
     Enable Xen PV driver:          ${ENABLE_XEN}
     Enable D-Bus:                  ${ENABLE_DBUS}
       Enable BlueZ 4:              ${ENABLE_BLUEZ_4}
+      Enable BlueZ 5:              ${ENABLE_BLUEZ_5}
     Enable udev:                   ${ENABLE_UDEV}
       Enable HAL->udev compat:     ${ENABLE_HAL_COMPAT}
     Enable systemd login:          ${ENABLE_SYSTEMD}

commit d7d3a1e2b2a0fca545ba179d08c82395af0215d1
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Fri Aug 16 09:31:45 2013 -0300

    build: Make the build of bluetooth modules BlueZ 4 specific

diff --git a/configure.ac b/configure.ac
index 15f2f99..574d80a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -981,21 +981,19 @@ AX_DEFINE_DIR(PA_MACHINE_ID_FALLBACK, PA_MACHINE_ID_FALLBACK,
 
 #### BlueZ support (optional, dependent on D-Bus) ####
 
-AC_ARG_ENABLE([bluez],
-    AS_HELP_STRING([--disable-bluez],[Disable optional BlueZ support]))
+AC_ARG_ENABLE([bluez4],
+    AS_HELP_STRING([--disable-bluez4],[Disable optional BlueZ 4 support]))
 
-AS_IF([test "x$enable_bluez" != "xno"],
-    [PKG_CHECK_MODULES(BLUEZ, [ bluez >= 4.99 ], HAVE_BLUEZ=1, HAVE_BLUEZ=0)],
-    HAVE_BLUEZ=0)
-AS_IF([test "x$enable_bluez" != "xno"],
+AS_IF([test "x$enable_bluez4" != "xno"],
     [PKG_CHECK_MODULES(SBC, [ sbc >= 1.0 ], HAVE_SBC=1, HAVE_SBC=0)],
     HAVE_SBC=0)
-AS_IF([test "x$HAVE_SBC" != "x1"], HAVE_BLUEZ=0)
-AS_IF([test "x$HAVE_DBUS" != "x1"], HAVE_BLUEZ=0)
-
-AS_IF([test "x$enable_bluez" = "xyes" && test "x$HAVE_BLUEZ" = "x0"],
-    [AC_MSG_ERROR([*** BLUEZ support not found (requires BlueZ, sbc, and D-Bus)])])
+AS_IF([test "x$enable_bluez4" != "xno" && test "x$HAVE_DBUS" = "x1" && test "x$HAVE_SBC" = "x1"], HAVE_BLUEZ_4=1)
+AS_IF([test "x$enable_bluez4" = "xyes" && test "x$HAVE_BLUEZ_4" != "x1"],
+    [AC_MSG_ERROR([*** BLUEZ 4 support not found (requires sbc and D-Bus)])])
+AC_SUBST(HAVE_BLUEZ_4)
+AM_CONDITIONAL([HAVE_BLUEZ_4], [test "x$HAVE_BLUEZ_4" = x1])
 
+AS_IF([test "x$HAVE_BLUEZ_4" = "x1"], HAVE_BLUEZ=1)
 AC_SUBST(HAVE_BLUEZ)
 AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1])
 
@@ -1389,7 +1387,7 @@ AS_IF([test "x$HAVE_XEN" = "x1"], ENABLE_XEN=yes, ENABLE_XEN=no)
 AS_IF([test "x$HAVE_DBUS" = "x1"], ENABLE_DBUS=yes, ENABLE_DBUS=no)
 AS_IF([test "x$HAVE_UDEV" = "x1"], ENABLE_UDEV=yes, ENABLE_UDEV=no)
 AS_IF([test "x$HAVE_SYSTEMD" = "x1"], ENABLE_SYSTEMD=yes, ENABLE_SYSTEMD=no)
-AS_IF([test "x$HAVE_BLUEZ" = "x1"], ENABLE_BLUEZ=yes, ENABLE_BLUEZ=no)
+AS_IF([test "x$HAVE_BLUEZ_4" = "x1"], ENABLE_BLUEZ_4=yes, ENABLE_BLUEZ_4=no)
 AS_IF([test "x$HAVE_HAL_COMPAT" = "x1"], ENABLE_HAL_COMPAT=yes, ENABLE_HAL_COMPAT=no)
 AS_IF([test "x$HAVE_TCPWRAP" = "x1"], ENABLE_TCPWRAP=yes, ENABLE_TCPWRAP=no)
 AS_IF([test "x$HAVE_LIBSAMPLERATE" = "x1"], ENABLE_LIBSAMPLERATE=yes, ENABLE_LIBSAMPLERATE=no)
@@ -1440,7 +1438,7 @@ echo "
     Enable LIRC:                   ${ENABLE_LIRC}
     Enable Xen PV driver:          ${ENABLE_XEN}
     Enable D-Bus:                  ${ENABLE_DBUS}
-      Enable BlueZ:                ${ENABLE_BLUEZ}
+      Enable BlueZ 4:              ${ENABLE_BLUEZ_4}
     Enable udev:                   ${ENABLE_UDEV}
       Enable HAL->udev compat:     ${ENABLE_HAL_COMPAT}
     Enable systemd login:          ${ENABLE_SYSTEMD}
diff --git a/src/Makefile.am b/src/Makefile.am
index a294b65..773a0af 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1322,7 +1322,11 @@ endif
 
 if HAVE_BLUEZ
 modlibexec_LTLIBRARIES += \
-		module-bluetooth-policy.la \
+		module-bluetooth-policy.la
+endif
+
+if HAVE_BLUEZ_4
+modlibexec_LTLIBRARIES += \
 		libbluez4-util.la \
 		module-bluez4-discover.la \
 		module-bluez4-device.la
@@ -2012,6 +2016,12 @@ gconf_helper_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulsecommon-
 gconf_helper_CFLAGS = $(AM_CFLAGS) $(GCONF_CFLAGS)
 gconf_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
 
+# Bluetooth policy
+module_bluetooth_policy_la_SOURCES = modules/bluetooth/module-bluetooth-policy.c
+module_bluetooth_policy_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_bluetooth_policy_la_LIBADD = $(MODULE_LIBADD)
+module_bluetooth_policy_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+
 # Bluetooth BlueZ 4 sink / source
 module_bluez4_discover_la_SOURCES = modules/bluetooth/module-bluez4-discover.c
 module_bluez4_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
@@ -2031,11 +2041,6 @@ module_bluez4_device_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_bluez4_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbluez4-util.la
 module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
 
-module_bluetooth_policy_la_SOURCES = modules/bluetooth/module-bluetooth-policy.c
-module_bluetooth_policy_la_LDFLAGS = $(MODULE_LDFLAGS)
-module_bluetooth_policy_la_LIBADD = $(MODULE_LIBADD)
-module_bluetooth_policy_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
-
 # Apple Airtunes/RAOP
 module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
 module_raop_sink_la_LDFLAGS = $(MODULE_LDFLAGS)

commit 4f972aa993954c92d3d09fa4047906b931aab1af
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Fri Aug 16 09:30:41 2013 -0300

    bluetooth: Remove module-bluetooth-proximity
    
    module-bluetooth-proximity has not worked for quite a while, since it
    uses pre-BlueZ4 APIs. Nobody complained since then, which is a good
    indication that it doesn't have much users. Even the original commit
    message refers to it more as a toy than as something of great use: "add
    new fun module that automatically mutes your audio devices when you
    leave with your bluetooth phone, and unmutes when you come back"
    
    Removing it we completely remove the dependency on libbluetooth.

diff --git a/LICENSE b/LICENSE
index 30833e5..226c4ce 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,16 +2,16 @@ All PulseAudio source files are licensed under the GNU Lesser General Public
 License. (see file LGPL for details)
 
 However, the server side has optional GPL dependencies.  These include the
-libsamplerate and gdbm (core libraries), LIRC (lirc module), FFTW (equalizer
-module) and bluez (bluetooth proximity helper program) libraries, although
-others may also be included in the future.  If PulseAudio is compiled with these
-optional components, this effectively downgrades the license of the server part
-to GPL (see the file GPL for details), exercising section 3 of the LGPL.  In
-such circumstances, you should treat the client library (libpulse) of PulseAudio
-as being LGPL licensed and the server part (libpulsecore) as being GPL licensed.
-Since the PulseAudio daemon, tests, various utilities/helpers and the modules
-link to libpulsecore and/or the afore mentioned optional GPL dependencies they
-are of course also GPL licensed also in this scenario.
+libsamplerate and gdbm (core libraries), LIRC (lirc module) and FFTW (equalizer
+module), although others may also be included in the future.  If PulseAudio is
+compiled with these optional components, this effectively downgrades the
+license of the server part to GPL (see the file GPL for details), exercising
+section 3 of the LGPL.  In such circumstances, you should treat the client
+library (libpulse) of PulseAudio as being LGPL licensed and the server part
+(libpulsecore) as being GPL licensed.  Since the PulseAudio daemon, tests,
+various utilities/helpers and the modules link to libpulsecore and/or the afore
+mentioned optional GPL dependencies they are of course also GPL licensed also
+in this scenario.
 
 In addition to this, if D-Bus support is enabled, the PulseAudio client library
 (libpulse) MAY need to be licensed under the GPL, depending on the license
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 17ebc1d..0b81018 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,7 +15,6 @@ src/modules/alsa/module-alsa-card.c
 src/modules/alsa/module-alsa-sink.c
 src/modules/alsa/module-alsa-source.c
 src/modules/bluetooth/module-bluetooth-device.c
-src/modules/bluetooth/module-bluetooth-proximity.c
 src/modules/bluetooth/proximity-helper.c
 src/modules/echo-cancel/module-echo-cancel.c
 src/modules/gconf/gconf-helper.c
diff --git a/src/.gitignore b/src/.gitignore
index 7fe8494..80f6f2c 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -7,7 +7,6 @@ TAGS
 .libs
 /Makefile
 /Makefile.in
-proximity-helper
 client.conf
 daemon.conf
 default.pa
diff --git a/src/Makefile.am b/src/Makefile.am
index 9113d83..a294b65 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1322,14 +1322,10 @@ endif
 
 if HAVE_BLUEZ
 modlibexec_LTLIBRARIES += \
-		module-bluetooth-proximity.la \
 		module-bluetooth-policy.la \
 		libbluez4-util.la \
 		module-bluez4-discover.la \
 		module-bluez4-device.la
-
-pulselibexec_PROGRAMS += \
-		proximity-helper
 endif
 
 if HAVE_OPENSSL
@@ -1418,7 +1414,6 @@ SYMDEF_FILES = \
 		module-hal-detect-symdef.h \
 		module-udev-detect-symdef.h \
 		module-systemd-login-symdef.h \
-		module-bluetooth-proximity-symdef.h \
 		module-bluetooth-policy-symdef.h \
 		module-bluez4-discover-symdef.h \
 		module-bluez4-device-symdef.h \
@@ -2017,17 +2012,6 @@ gconf_helper_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulsecommon-
 gconf_helper_CFLAGS = $(AM_CFLAGS) $(GCONF_CFLAGS)
 gconf_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
 
-# Bluetooth proximity
-module_bluetooth_proximity_la_SOURCES = modules/bluetooth/module-bluetooth-proximity.c
-module_bluetooth_proximity_la_LDFLAGS = $(MODULE_LDFLAGS)
-module_bluetooth_proximity_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
-module_bluetooth_proximity_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DPA_BT_PROXIMITY_HELPER=\"$(pulselibexecdir)/proximity-helper\"
-
-proximity_helper_SOURCES = modules/bluetooth/proximity-helper.c
-proximity_helper_LDADD = $(AM_LDADD) $(BLUEZ_LIBS)
-proximity_helper_CFLAGS = $(AM_CFLAGS) $(BLUEZ_CFLAGS)
-proximity_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
-
 # Bluetooth BlueZ 4 sink / source
 module_bluez4_discover_la_SOURCES = modules/bluetooth/module-bluez4-discover.c
 module_bluez4_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
@@ -2082,10 +2066,6 @@ else
 SYMLINK_PROGRAM=ln -sf
 endif
 install-exec-hook:
-if HAVE_BLUEZ
-	-chown root $(DESTDIR)$(pulselibexecdir)/proximity-helper
-	-chmod u+s $(DESTDIR)$(pulselibexecdir)/proximity-helper
-endif
 	$(SYMLINK_PROGRAM) pacat$(EXEEXT) $(DESTDIR)$(bindir)/parec$(EXEEXT)
 	$(SYMLINK_PROGRAM) pacat$(EXEEXT) $(DESTDIR)$(bindir)/pamon$(EXEEXT)
 	$(SYMLINK_PROGRAM) pacat$(EXEEXT) $(DESTDIR)$(bindir)/paplay$(EXEEXT)
diff --git a/src/modules/bluetooth/module-bluetooth-proximity.c b/src/modules/bluetooth/module-bluetooth-proximity.c
deleted file mode 100644
index 1110ceb..0000000
--- a/src/modules/bluetooth/module-bluetooth-proximity.c
+++ /dev/null
@@ -1,480 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2005-2006 Lennart Poettering
-
-  PulseAudio is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Lesser General Public License as published
-  by the Free Software Foundation; either version 2.1 of the License,
-  or (at your option) any later version.
-
-  PulseAudio 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 Lesser General Public License
-  along with PulseAudio; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-  USA.
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <signal.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include <pulse/xmalloc.h>
-#include <pulsecore/module.h>
-#include <pulsecore/log.h>
-#include <pulsecore/namereg.h>
-#include <pulsecore/sink.h>
-#include <pulsecore/modargs.h>
-#include <pulsecore/macro.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/core-error.h>
-#include <pulsecore/start-child.h>
-#include <pulsecore/dbus-shared.h>
-
-#include "module-bluetooth-proximity-symdef.h"
-
-PA_MODULE_AUTHOR("Lennart Poettering");
-PA_MODULE_DESCRIPTION("Bluetooth Proximity Volume Control");
-PA_MODULE_VERSION(PACKAGE_VERSION);
-PA_MODULE_LOAD_ONCE(true);
-PA_MODULE_USAGE(
-        "sink=<sink name> "
-        "hci=<hci device> "
-);
-
-#define DEFAULT_HCI "hci0"
-
-static const char* const valid_modargs[] = {
-    "sink",
-    "hci",
-    NULL,
-};
-
-struct bonding {
-    struct userdata *userdata;
-    char address[18];
-
-    pid_t pid;
-    int fd;
-
-    pa_io_event *io_event;
-
-    enum {
-        UNKNOWN,
-        FOUND,
-        NOT_FOUND
-    } state;
-};
-
-struct userdata {
-    pa_module *module;
-    pa_dbus_connection *dbus_connection;
-
-    char *sink_name;
-    char *hci, *hci_path;
-
-    pa_hashmap *bondings;
-
-    unsigned n_found;
-    unsigned n_unknown;
-
-    bool muted:1;
-    bool filter_added:1;
-};
-
-static void update_volume(struct userdata *u) {
-    pa_assert(u);
-
-    if (u->muted && u->n_found > 0) {
-        pa_sink *s;
-
-        u->muted = false;
-
-        if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK))) {
-            pa_log_warn("Sink device '%s' not available for unmuting.", pa_strnull(u->sink_name));
-            return;
-        }
-
-        pa_log_info("Found %u BT devices, unmuting.", u->n_found);
-        pa_sink_set_mute(s, false, false);
-
-    } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) {
-        pa_sink *s;
-
-        u->muted = true;
-
-        if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK))) {
-            pa_log_warn("Sink device '%s' not available for muting.", pa_strnull(u->sink_name));
-            return;
-        }
-
-        pa_log_info("No BT devices found, muting.");
-        pa_sink_set_mute(s, true, false);
-
-    } else
-        pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown);
-}
-
-static void bonding_free(struct bonding *b) {
-    pa_assert(b);
-
-    if (b->state == FOUND)
-        pa_assert_se(b->userdata->n_found-- >= 1);
-
-    if (b->state == UNKNOWN)
-        pa_assert_se(b->userdata->n_unknown-- >= 1);
-
-    if (b->pid != (pid_t) -1) {
-        kill(b->pid, SIGTERM);
-        waitpid(b->pid, NULL, 0);
-    }
-
-    if (b->fd >= 0)
-        pa_close(b->fd);
-
-    if (b->io_event)
-        b->userdata->module->core->mainloop->io_free(b->io_event);
-
-    pa_xfree(b);
-}
-
-static void io_event_cb(
-        pa_mainloop_api*a,
-        pa_io_event* e,
-        int fd,
-        pa_io_event_flags_t events,
-        void *userdata) {
-
-    struct bonding *b = userdata;
-    char x;
-    ssize_t r;
-
-    pa_assert(b);
-
-    if ((r = read(fd, &x, 1)) <= 0) {
-        pa_log_warn("Child watching '%s' died abnormally: %s", b->address, r == 0 ? "EOF" : pa_cstrerror(errno));
-
-        pa_assert_se(pa_hashmap_remove(b->userdata->bondings, b->address) == b);
-        bonding_free(b);
-        return;
-    }
-
-    pa_assert_se(r == 1);
-
-    if (b->state == UNKNOWN)
-        pa_assert_se(b->userdata->n_unknown-- >= 1);
-
-    if (x == '+') {
-        pa_assert(b->state == UNKNOWN || b->state == NOT_FOUND);
-
-        b->state = FOUND;
-        b->userdata->n_found++;
-
-        pa_log_info("Device '%s' is alive.", b->address);
-
-    } else {
-        pa_assert(x == '-');
-        pa_assert(b->state == UNKNOWN || b->state == FOUND);
-
-        if (b->state == FOUND)
-            b->userdata->n_found--;
-
-        b->state = NOT_FOUND;
-
-        pa_log_info("Device '%s' is dead.", b->address);
-    }
-
-    update_volume(b->userdata);
-}
-
-static struct bonding* bonding_new(struct userdata *u, const char *a) {
-    struct bonding *b = NULL;
-    DBusMessage *m = NULL, *r = NULL;
-    DBusError e;
-    const char *class;
-
-    pa_assert(u);
-    pa_assert(a);
-
-    pa_return_val_if_fail(strlen(a) == 17, NULL);
-    pa_return_val_if_fail(!pa_hashmap_get(u->bondings, a), NULL);
-
-    dbus_error_init(&e);
-
-    pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "GetRemoteMajorClass"));
-    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID));
-    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), m, -1, &e);
-
-    if (!r) {
-        pa_log("org.bluez.Adapter.GetRemoteMajorClass(%s) failed: %s", a, e.message);
-        goto fail;
-    }
-
-    if (!(dbus_message_get_args(r, &e, DBUS_TYPE_STRING, &class, DBUS_TYPE_INVALID))) {
-        pa_log("Malformed org.bluez.Adapter.GetRemoteMajorClass signal: %s", e.message);
-        goto fail;
-    }
-
-    if (!pa_streq(class, "phone")) {
-        pa_log_info("Found device '%s' of class '%s', ignoring.", a, class);
-        goto fail;
-    }
-
-    b = pa_xnew(struct bonding, 1);
-    b->userdata = u;
-    pa_strlcpy(b->address, a, sizeof(b->address));
-    b->pid = (pid_t) -1;
-    b->fd = -1;
-    b->io_event = NULL;
-    b->state = UNKNOWN;
-    u->n_unknown ++;
-
-    pa_log_info("Watching device '%s' of class '%s'.", b->address, class);
-
-    if ((b->fd = pa_start_child_for_read(PA_BT_PROXIMITY_HELPER, a, &b->pid)) < 0) {
-        pa_log("Failed to start helper tool.");
-        goto fail;
-    }
-
-    b->io_event = u->module->core->mainloop->io_new(
-            u->module->core->mainloop,
-            b->fd,
-            PA_IO_EVENT_INPUT,
-            io_event_cb,
-            b);
-
-    dbus_message_unref(m);
-    dbus_message_unref(r);
-
-    pa_hashmap_put(u->bondings, b->address, b);
-
-    return b;
-
-fail:
-    if (m)
-        dbus_message_unref(m);
-    if (r)
-        dbus_message_unref(r);
-
-    if (b)
-        bonding_free(b);
-
-    dbus_error_free(&e);
-    return NULL;
-}
-
-static void bonding_remove(struct userdata *u, const char *a) {
-    struct bonding *b;
-    pa_assert(u);
-
-    pa_return_if_fail((b = pa_hashmap_remove(u->bondings, a)));
-
-    pa_log_info("No longer watching device '%s'", b->address);
-    bonding_free(b);
-}
-
-static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *m, void *userdata) {
-    struct userdata *u = userdata;
-    DBusError e;
-
-    dbus_error_init(&e);
-
-    if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingCreated")) {
-        const char *a;
-
-        if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) {
-            pa_log("Malformed org.bluez.Adapter.BondingCreated signal: %s", e.message);
-            goto finish;
-        }
-
-        bonding_new(u, a);
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
-    } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingRemoved")) {
-
-        const char *a;
-
-        if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) {
-            pa_log("Malformed org.bluez.Adapter.BondingRemoved signal: %s", e.message);
-            goto finish;
-        }
-
-        bonding_remove(u, a);
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    }
-
-finish:
-
-    dbus_error_free(&e);
-
-    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-}
-
-static int update_matches(struct userdata *u, bool add) {
-    char *filter1, *filter2;
-    DBusError e;
-    int r = -1;
-
-    pa_assert(u);
-    dbus_error_init(&e);
-
-    filter1 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingCreated',path='%s'", u->hci_path);
-    filter2 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingRemoved',path='%s'", u->hci_path);
-
-    if (add) {
-        dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e);
-
-        if (dbus_error_is_set(&e)) {
-            pa_log("dbus_bus_add_match(%s) failed: %s", filter1, e.message);
-            goto finish;
-        }
-    } else
-        dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, NULL);
-
-    if (add) {
-        dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e);
-
-        if (dbus_error_is_set(&e)) {
-            pa_log("dbus_bus_add_match(%s) failed: %s", filter2, e.message);
-            dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, NULL);
-            goto finish;
-        }
-    } else
-        dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, NULL);
-
-    if (add) {
-        pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u, NULL));
-        u->filter_added = true;
-    } else if (u->filter_added)
-        dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u);
-
-    r = 0;
-
-finish:
-    pa_xfree(filter1);
-    pa_xfree(filter2);
-    dbus_error_free(&e);
-
-    return r;
-}
-
-int pa__init(pa_module*m) {
-    pa_modargs *ma = NULL;
-    struct userdata *u;
-    DBusError e;
-    DBusMessage *msg = NULL, *r = NULL;
-    DBusMessageIter iter, sub;
-
-    pa_assert(m);
-    dbus_error_init(&e);
-
-    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
-        pa_log("Failed to parse module arguments");
-        goto fail;
-    }
-
-    m->userdata = u = pa_xnew0(struct userdata, 1);
-    u->module = m;
-    u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
-    u->hci = pa_xstrdup(pa_modargs_get_value(ma, "hci", DEFAULT_HCI));
-    u->hci_path = pa_sprintf_malloc("/org/bluez/%s", u->hci);
-    u->bondings = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) bonding_free);
-
-    if (!(u->dbus_connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &e))) {
-        pa_log("Failed to get D-Bus connection: %s", e.message);
-        goto fail;
-    }
-
-    if (update_matches(u, true) < 0)
-        goto fail;
-
-    pa_assert_se(msg = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "ListBondings"));
-
-    if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), msg, -1, &e))) {
-        pa_log("org.bluez.Adapter.ListBondings failed: %s", e.message);
-        goto fail;
-    }
-
-    dbus_message_iter_init(r, &iter);
-
-    if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
-        pa_log("Malformed reply to org.bluez.Adapter.ListBondings.");
-        goto fail;
-    }
-
-    dbus_message_iter_recurse(&iter, &sub);
-
-    while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
-        const char *a = NULL;
-
-        dbus_message_iter_get_basic(&sub, &a);
-        bonding_new(u, a);
-
-        dbus_message_iter_next(&sub);
-    }
-
-    dbus_message_unref(r);
-    dbus_message_unref(msg);
-
-    pa_modargs_free(ma);
-
-    if (pa_hashmap_size(u->bondings) == 0)
-        pa_log_warn("Warning: no phone device bonded.");
-
-    update_volume(u);
-
-    return 0;
-
-fail:
-
-    if (ma)
-        pa_modargs_free(ma);
-
-    pa__done(m);
-
-    dbus_error_free(&e);
-
-    if (msg)
-        dbus_message_unref(msg);
-
-    if (r)
-        dbus_message_unref(r);
-
-    return -1;
-}
-
-void pa__done(pa_module*m) {
-    struct userdata *u;
-    pa_assert(m);
-
-    if (!(u = m->userdata))
-        return;
-
-    if (u->bondings)
-        pa_hashmap_free(u->bondings);
-
-    if (u->dbus_connection) {
-        update_matches(u, false);
-        pa_dbus_connection_unref(u->dbus_connection);
-    }
-
-    pa_xfree(u->sink_name);
-    pa_xfree(u->hci_path);
-    pa_xfree(u->hci);
-    pa_xfree(u);
-}
diff --git a/src/modules/bluetooth/proximity-helper.c b/src/modules/bluetooth/proximity-helper.c
deleted file mode 100644
index 3767f01..0000000
--- a/src/modules/bluetooth/proximity-helper.c
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Small SUID helper that allows us to ping a BT device. Borrows
- * heavily from bluez-utils' l2ping, which is licensed as GPL2+
- * and comes with a copyright like this:
- *
- *  Copyright (C) 2000-2001  Qualcomm Incorporated
- *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk at qualcomm.com>
- *  Copyright (C) 2002-2007  Marcel Holtmann <marcel at holtmann.org>
- *
- *
- *  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
-
-#undef NDEBUG
-
-#include <assert.h>
-#include <stdio.h>
-#include <errno.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <sys/time.h>
-#include <sys/select.h>
-
-#include <bluetooth/bluetooth.h>
-#include <bluetooth/hci.h>
-#include <bluetooth/hci_lib.h>
-#include <bluetooth/l2cap.h>
-
-#define PING_STRING "PulseAudio"
-#define IDENT 200
-#define TIMEOUT 4
-#define INTERVAL 2
-
-static void update_status(int found) {
-    static int status = -1;
-
-    if (!found && status != 0)
-        printf("-");
-    if (found && status <= 0)
-        printf("+");
-
-    fflush(stdout);
-    status = !!found;
-}
-
-int main(int argc, char *argv[]) {
-    struct sockaddr_l2 addr;
-    union {
-        l2cap_cmd_hdr hdr;
-        uint8_t buf[L2CAP_CMD_HDR_SIZE + sizeof(PING_STRING)];
-    }  packet;
-    int fd = -1;
-    uint8_t id = IDENT;
-    int connected = 0;
-
-    assert(argc == 2);
-
-    for (;;) {
-        fd_set fds;
-        struct timeval end;
-        ssize_t r;
-
-        if (!connected) {
-
-            if (fd >= 0)
-                close(fd);
-
-            if ((fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP)) < 0) {
-                fprintf(stderr, "socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP) failed: %s", strerror(errno));
-                goto finish;
-            }
-
-            memset(&addr, 0, sizeof(addr));
-            addr.l2_family = AF_BLUETOOTH;
-            bacpy(&addr.l2_bdaddr, BDADDR_ANY);
-
-            if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
-                fprintf(stderr, "bind() failed: %s", strerror(errno));
-                goto finish;
-            }
-
-            memset(&addr, 0, sizeof(addr));
-            addr.l2_family = AF_BLUETOOTH;
-            str2ba(argv[1], &addr.l2_bdaddr);
-
-            if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
-
-                if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) {
-                    update_status(0);
-                    sleep(INTERVAL);
-                    continue;
-                }
-
-                fprintf(stderr, "connect() failed: %s", strerror(errno));
-                goto finish;
-            }
-
-            connected = 1;
-        }
-
-        assert(connected);
-
-        memset(&packet, 0, sizeof(packet));
-        strcpy((char*) packet.buf + L2CAP_CMD_HDR_SIZE, PING_STRING);
-        packet.hdr.ident = id;
-        packet.hdr.len = htobs(sizeof(PING_STRING));
-        packet.hdr.code = L2CAP_ECHO_REQ;
-
-        if ((r = send(fd, &packet, sizeof(packet), 0)) < 0) {
-
-            if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) {
-                update_status(0);
-                connected = 0;
-                sleep(INTERVAL);
-                continue;
-            }
-
-            fprintf(stderr, "send() failed: %s", strerror(errno));
-            goto finish;
-        }
-
-        assert(r == sizeof(packet));
-
-        gettimeofday(&end, NULL);
-        end.tv_sec += TIMEOUT;
-
-        for (;;) {
-            struct timeval now, delta;
-
-            gettimeofday(&now, NULL);
-
-            if (timercmp(&end, &now, <=)) {
-                update_status(0);
-                connected = 0;
-                sleep(INTERVAL);
-                break;
-            }
-
-            timersub(&end, &now, &delta);
-
-            FD_ZERO(&fds);
-            FD_SET(fd, &fds);
-
-            if (select(fd+1, &fds, NULL, NULL, &delta) < 0) {
-                fprintf(stderr, "select() failed: %s", strerror(errno));
-                goto finish;
-            }
-
-            if ((r = recv(fd, &packet, sizeof(packet), 0)) <= 0) {
-
-                if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) {
-                    update_status(0);
-                    connected = 0;
-                    sleep(INTERVAL);
-                    break;
-                }
-
-                fprintf(stderr, "send() failed: %s", r == 0 ? "EOF" : strerror(errno));
-                goto finish;
-            }
-
-            assert(r >= L2CAP_CMD_HDR_SIZE);
-
-            if (packet.hdr.ident != id)
-                continue;
-
-            if (packet.hdr.code == L2CAP_ECHO_RSP || packet.hdr.code == L2CAP_COMMAND_REJ) {
-
-                if (++id >= 0xFF)
-                    id = IDENT;
-
-                update_status(1);
-                sleep(INTERVAL);
-                break;
-            }
-        }
-    }
-
-finish:
-
-    if (fd >= 0)
-        close(fd);
-
-    return 1;
-}

commit e647e74f329d1ac1557e7a361631a74e22c7e399
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Thu Aug 15 16:17:26 2013 -0300

    bluetooth: Prefix BlueZ 4 card profiles enum with PA_BLUEZ4_
    
    Also typedef the enum as pa_bluez4_profile_t.

diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
index a33a659..f047b73 100644
--- a/src/modules/bluetooth/bluez4-util.c
+++ b/src/modules/bluetooth/bluez4-util.c
@@ -95,38 +95,38 @@ static pa_bluez4_audio_state_t audio_state_from_string(const char* value) {
     return PA_BLUEZ4_AUDIO_STATE_INVALID;
 }
 
-const char *pa_bluez4_profile_to_string(enum profile profile) {
+const char *pa_bluez4_profile_to_string(pa_bluez4_profile_t profile) {
     switch(profile) {
-        case PROFILE_A2DP:
+        case PA_BLUEZ4_PROFILE_A2DP:
             return "a2dp";
-        case PROFILE_A2DP_SOURCE:
+        case PA_BLUEZ4_PROFILE_A2DP_SOURCE:
             return "a2dp_source";
-        case PROFILE_HSP:
+        case PA_BLUEZ4_PROFILE_HSP:
             return "hsp";
-        case PROFILE_HFGW:
+        case PA_BLUEZ4_PROFILE_HFGW:
             return "hfgw";
-        case PROFILE_OFF:
+        case PA_BLUEZ4_PROFILE_OFF:
             pa_assert_not_reached();
     }
 
     pa_assert_not_reached();
 }
 
-static int profile_from_interface(const char *interface, enum profile *p) {
+static int profile_from_interface(const char *interface, pa_bluez4_profile_t *p) {
     pa_assert(interface);
     pa_assert(p);
 
     if (pa_streq(interface, "org.bluez.AudioSink")) {
-        *p = PROFILE_A2DP;
+        *p = PA_BLUEZ4_PROFILE_A2DP;
         return 0;
     } else if (pa_streq(interface, "org.bluez.AudioSource")) {
-        *p = PROFILE_A2DP_SOURCE;
+        *p = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
         return 0;
     } else if (pa_streq(interface, "org.bluez.Headset")) {
-        *p = PROFILE_HSP;
+        *p = PA_BLUEZ4_PROFILE_HSP;
         return 0;
     } else if (pa_streq(interface, "org.bluez.HandsfreeGateway")) {
-        *p = PROFILE_HFGW;
+        *p = PA_BLUEZ4_PROFILE_HFGW;
         return 0;
     }
 
@@ -505,7 +505,7 @@ static int parse_audio_property(pa_bluez4_device *d, const char *interface, DBus
     const char *key;
     DBusMessageIter variant_i;
     bool is_audio_interface;
-    enum profile p = PROFILE_OFF;
+    pa_bluez4_profile_t p = PA_BLUEZ4_PROFILE_OFF;
 
     pa_assert(d);
     pa_assert(interface);
@@ -519,7 +519,7 @@ static int parse_audio_property(pa_bluez4_device *d, const char *interface, DBus
     if (key == NULL)
         return -1;
 
-    transport = p == PROFILE_OFF ? NULL : d->transports[p];
+    transport = p == PA_BLUEZ4_PROFILE_OFF ? NULL : d->transports[p];
 
     dbus_message_iter_recurse(i, &variant_i);
 
@@ -546,7 +546,7 @@ static int parse_audio_property(pa_bluez4_device *d, const char *interface, DBus
                     break;
                 }
 
-                pa_assert(p != PROFILE_OFF);
+                pa_assert(p != PA_BLUEZ4_PROFILE_OFF);
 
                 d->profile_state[p] = state;
 
@@ -1216,7 +1216,7 @@ void pa_bluez4_transport_set_microphone_gain(pa_bluez4_transport *t, uint16_t va
     dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
 
     pa_assert(t);
-    pa_assert(t->profile == PROFILE_HSP);
+    pa_assert(t->profile == PA_BLUEZ4_PROFILE_HSP);
 
     set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
                  "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
@@ -1226,7 +1226,7 @@ void pa_bluez4_transport_set_speaker_gain(pa_bluez4_transport *t, uint16_t value
     dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
 
     pa_assert(t);
-    pa_assert(t->profile == PROFILE_HSP);
+    pa_assert(t->profile == PA_BLUEZ4_PROFILE_HSP);
 
     set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
                  "SpeakerGain", DBUS_TYPE_UINT16, &gain);
@@ -1246,7 +1246,7 @@ static int setup_dbus(pa_bluez4_discovery *y) {
     return 0;
 }
 
-static pa_bluez4_transport *transport_new(pa_bluez4_device *d, const char *owner, const char *path, enum profile p,
+static pa_bluez4_transport *transport_new(pa_bluez4_device *d, const char *owner, const char *path, pa_bluez4_profile_t p,
                                              const uint8_t *config, int size) {
     pa_bluez4_transport *t;
 
@@ -1275,7 +1275,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     uint8_t *config = NULL;
     int size = 0;
     bool nrec = false;
-    enum profile p;
+    pa_bluez4_profile_t p;
     DBusMessageIter args, props;
     DBusMessage *r;
     bool old_any_connected;
@@ -1346,13 +1346,13 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
         goto fail;
 
     if (dbus_message_has_path(m, ENDPOINT_PATH_HFP_AG))
-        p = PROFILE_HSP;
+        p = PA_BLUEZ4_PROFILE_HSP;
     else if (dbus_message_has_path(m, ENDPOINT_PATH_HFP_HS))
-        p = PROFILE_HFGW;
+        p = PA_BLUEZ4_PROFILE_HFGW;
     else if (dbus_message_has_path(m, ENDPOINT_PATH_A2DP_SOURCE))
-        p = PROFILE_A2DP;
+        p = PA_BLUEZ4_PROFILE_A2DP;
     else
-        p = PROFILE_A2DP_SOURCE;
+        p = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
 
     if (d->transports[p] != NULL) {
         pa_log("Cannot configure transport %s because profile %d is already used", path, p);
diff --git a/src/modules/bluetooth/bluez4-util.h b/src/modules/bluetooth/bluez4-util.h
index b920545..9667975 100644
--- a/src/modules/bluetooth/bluez4-util.h
+++ b/src/modules/bluetooth/bluez4-util.h
@@ -57,15 +57,15 @@ struct pa_bluez4_uuid {
     PA_LLIST_FIELDS(pa_bluez4_uuid);
 };
 
-enum profile {
-    PROFILE_A2DP,
-    PROFILE_A2DP_SOURCE,
-    PROFILE_HSP,
-    PROFILE_HFGW,
-    PROFILE_OFF
-};
+typedef enum pa_bluez4_profile {
+    PA_BLUEZ4_PROFILE_A2DP,
+    PA_BLUEZ4_PROFILE_A2DP_SOURCE,
+    PA_BLUEZ4_PROFILE_HSP,
+    PA_BLUEZ4_PROFILE_HFGW,
+    PA_BLUEZ4_PROFILE_OFF
+} pa_bluez4_profile_t;
 
-#define PA_BLUEZ4_PROFILE_COUNT PROFILE_OFF
+#define PA_BLUEZ4_PROFILE_COUNT PA_BLUEZ4_PROFILE_OFF
 
 struct pa_bluez4_hook_uuid_data {
     pa_bluez4_device *device;
@@ -93,7 +93,7 @@ struct pa_bluez4_transport {
     pa_bluez4_device *device;
     char *owner;
     char *path;
-    enum profile profile;
+    pa_bluez4_profile_t profile;
     uint8_t codec;
     uint8_t *config;
     int config_size;
@@ -173,6 +173,6 @@ const char *pa_bluez4_form_factor_to_string(pa_bluez4_form_factor_t ff);
 char *pa_bluez4_cleanup_name(const char *name);
 
 bool pa_bluez4_uuid_has(pa_bluez4_uuid *uuids, const char *uuid);
-const char *pa_bluez4_profile_to_string(enum profile profile);
+const char *pa_bluez4_profile_to_string(pa_bluez4_profile_t profile);
 
 #endif
diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
index aca10d5..2f4f71e 100644
--- a/src/modules/bluetooth/module-bluez4-device.c
+++ b/src/modules/bluetooth/module-bluez4-device.c
@@ -184,7 +184,7 @@ struct userdata {
     struct a2dp_info a2dp;
     struct hsp_info hsp;
 
-    enum profile profile;
+    pa_bluez4_profile_t profile;
 
     pa_modargs *modargs;
 
@@ -203,7 +203,7 @@ enum {
 
 #define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC)
 
-#define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source))
+#define USE_SCO_OVER_PCM(u) (u->profile == PA_BLUEZ4_PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source))
 
 static int init_profile(struct userdata *u);
 
@@ -246,7 +246,7 @@ static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
 /* from IO thread, except in SCO over PCM */
 static void bt_transport_config_mtu(struct userdata *u) {
     /* Calculate block sizes */
-    if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
+    if (u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW) {
         u->read_block_size = u->read_link_mtu;
         u->write_block_size = u->write_link_mtu;
     } else {
@@ -265,14 +265,14 @@ static void bt_transport_config_mtu(struct userdata *u) {
     if (u->sink) {
         pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
         pa_sink_set_fixed_latency_within_thread(u->sink,
-                                                (u->profile == PROFILE_A2DP ?
+                                                (u->profile == PA_BLUEZ4_PROFILE_A2DP ?
                                                  FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
                                                 pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
     }
 
     if (u->source)
         pa_source_set_fixed_latency_within_thread(u->source,
-                                                  (u->profile == PROFILE_A2DP_SOURCE ?
+                                                  (u->profile == PA_BLUEZ4_PROFILE_A2DP_SOURCE ?
                                                    FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
                                                   pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
 }
@@ -296,7 +296,7 @@ static void setup_stream(struct userdata *u) {
 
     pa_log_debug("Stream properly set up, we're ready to roll!");
 
-    if (u->profile == PROFILE_A2DP)
+    if (u->profile == PA_BLUEZ4_PROFILE_A2DP)
         a2dp_set_bitpool(u, u->a2dp.max_bitpool);
 
     u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
@@ -555,7 +555,7 @@ static int hsp_process_render(struct userdata *u) {
     int ret = 0;
 
     pa_assert(u);
-    pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW);
     pa_assert(u->sink);
 
     /* First, render some data */
@@ -620,7 +620,7 @@ static int hsp_process_push(struct userdata *u) {
     pa_memchunk memchunk;
 
     pa_assert(u);
-    pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW);
     pa_assert(u->source);
     pa_assert(u->read_smoother);
 
@@ -738,7 +738,7 @@ static int a2dp_process_render(struct userdata *u) {
     int ret = 0;
 
     pa_assert(u);
-    pa_assert(u->profile == PROFILE_A2DP);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_A2DP);
     pa_assert(u->sink);
 
     /* First, render some data */
@@ -864,7 +864,7 @@ static int a2dp_process_push(struct userdata *u) {
     pa_memchunk memchunk;
 
     pa_assert(u);
-    pa_assert(u->profile == PROFILE_A2DP_SOURCE);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_A2DP_SOURCE);
     pa_assert(u->source);
     pa_assert(u->read_smoother);
 
@@ -1031,7 +1031,7 @@ static void thread_func(void *userdata) {
             if (pollfd && (pollfd->revents & POLLIN)) {
                 int n_read;
 
-                if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW)
+                if (u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW)
                     n_read = hsp_process_push(u);
                 else
                     n_read = a2dp_process_push(u);
@@ -1087,7 +1087,7 @@ static void thread_func(void *userdata) {
                                 pa_memblock_unref(tmp.memblock);
                                 u->write_index += skip_bytes;
 
-                                if (u->profile == PROFILE_A2DP)
+                                if (u->profile == PA_BLUEZ4_PROFILE_A2DP)
                                     a2dp_reduce_bitpool(u);
                             }
                         }
@@ -1103,7 +1103,7 @@ static void thread_func(void *userdata) {
                     if (u->write_index <= 0)
                         u->started_at = pa_rtclock_now();
 
-                    if (u->profile == PROFILE_A2DP) {
+                    if (u->profile == PA_BLUEZ4_PROFILE_A2DP) {
                         if ((n_written = a2dp_process_render(u)) < 0)
                             goto io_fail;
                     } else {
@@ -1201,13 +1201,13 @@ static pa_available_t transport_state_to_availability(pa_bluez4_transport_state_
         return PA_AVAILABLE_UNKNOWN;
 }
 
-static pa_direction_t get_profile_direction(enum profile p) {
+static pa_direction_t get_profile_direction(pa_bluez4_profile_t p) {
     static const pa_direction_t profile_direction[] = {
-        [PROFILE_A2DP] = PA_DIRECTION_OUTPUT,
-        [PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
-        [PROFILE_HSP] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PROFILE_HFGW] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PROFILE_OFF] = 0
+        [PA_BLUEZ4_PROFILE_A2DP] = PA_DIRECTION_OUTPUT,
+        [PA_BLUEZ4_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
+        [PA_BLUEZ4_PROFILE_HSP] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUEZ4_PROFILE_HFGW] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUEZ4_PROFILE_OFF] = 0
     };
 
     return profile_direction[p];
@@ -1252,7 +1252,7 @@ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t d
 static void handle_transport_state_change(struct userdata *u, struct pa_bluez4_transport *transport) {
     bool acquire = false;
     bool release = false;
-    enum profile profile;
+    pa_bluez4_profile_t profile;
     pa_card_profile *cp;
     pa_bluez4_transport_state_t state;
     pa_device_port *port;
@@ -1328,7 +1328,7 @@ static void sink_set_volume_cb(pa_sink *s) {
 
     pa_assert(u);
     pa_assert(u->sink == s);
-    pa_assert(u->profile == PROFILE_HSP);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HSP);
     pa_assert(u->transport);
 
     gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM);
@@ -1355,7 +1355,7 @@ static void source_set_volume_cb(pa_source *s) {
 
     pa_assert(u);
     pa_assert(u->source == s);
-    pa_assert(u->profile == PROFILE_HSP);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HSP);
     pa_assert(u->transport);
 
     gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM);
@@ -1560,7 +1560,7 @@ static int add_sink(struct userdata *u) {
         data.module = u->module;
         pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
         pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluez4_profile_to_string(u->profile));
-        if (u->profile == PROFILE_HSP)
+        if (u->profile == PA_BLUEZ4_PROFILE_HSP)
             pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
         data.card = u->card;
         data.name = get_name("sink", u->modargs, u->address, &b);
@@ -1575,15 +1575,15 @@ static int add_sink(struct userdata *u) {
 
         if (!u->transport_acquired)
             switch (u->profile) {
-                case PROFILE_A2DP:
-                case PROFILE_HSP:
+                case PA_BLUEZ4_PROFILE_A2DP:
+                case PA_BLUEZ4_PROFILE_HSP:
                     pa_assert_not_reached(); /* Profile switch should have failed */
                     break;
-                case PROFILE_HFGW:
+                case PA_BLUEZ4_PROFILE_HFGW:
                     data.suspend_cause = PA_SUSPEND_USER;
                     break;
-                case PROFILE_A2DP_SOURCE:
-                case PROFILE_OFF:
+                case PA_BLUEZ4_PROFILE_A2DP_SOURCE:
+                case PA_BLUEZ4_PROFILE_OFF:
                     pa_assert_not_reached();
             }
 
@@ -1600,7 +1600,7 @@ static int add_sink(struct userdata *u) {
         u->sink->set_port = sink_set_port_cb;
     }
 
-    if (u->profile == PROFILE_HSP) {
+    if (u->profile == PA_BLUEZ4_PROFILE_HSP) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
 
@@ -1630,7 +1630,7 @@ static int add_source(struct userdata *u) {
         data.module = u->module;
         pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
         pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluez4_profile_to_string(u->profile));
-        if (u->profile == PROFILE_HSP)
+        if (u->profile == PA_BLUEZ4_PROFILE_HSP)
             pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
 
         data.card = u->card;
@@ -1647,15 +1647,15 @@ static int add_source(struct userdata *u) {
 
         if (!u->transport_acquired)
             switch (u->profile) {
-                case PROFILE_HSP:
+                case PA_BLUEZ4_PROFILE_HSP:
                     pa_assert_not_reached(); /* Profile switch should have failed */
                     break;
-                case PROFILE_A2DP_SOURCE:
-                case PROFILE_HFGW:
+                case PA_BLUEZ4_PROFILE_A2DP_SOURCE:
+                case PA_BLUEZ4_PROFILE_HFGW:
                     data.suspend_cause = PA_SUSPEND_USER;
                     break;
-                case PROFILE_A2DP:
-                case PROFILE_OFF:
+                case PA_BLUEZ4_PROFILE_A2DP:
+                case PA_BLUEZ4_PROFILE_OFF:
                     pa_assert_not_reached();
             }
 
@@ -1672,12 +1672,12 @@ static int add_source(struct userdata *u) {
         u->source->set_port = source_set_port_cb;
     }
 
-    if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
+    if ((u->profile == PA_BLUEZ4_PROFILE_HSP) || (u->profile == PA_BLUEZ4_PROFILE_HFGW)) {
         pa_bluez4_transport *t = u->transport;
         pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
     }
 
-    if (u->profile == PROFILE_HSP) {
+    if (u->profile == PA_BLUEZ4_PROFILE_HSP) {
         pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
         u->source->n_volume_steps = 16;
 
@@ -1792,7 +1792,7 @@ static void bt_transport_config_a2dp(struct userdata *u) {
     a2dp->max_bitpool = config->max_bitpool;
 
     /* Set minimum bitpool for source to get the maximum possible block_size */
-    a2dp->sbc.bitpool = u->profile == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool;
+    a2dp->sbc.bitpool = u->profile == PA_BLUEZ4_PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool;
     a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
     a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
 
@@ -1801,7 +1801,7 @@ static void bt_transport_config_a2dp(struct userdata *u) {
 }
 
 static void bt_transport_config(struct userdata *u) {
-    if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
+    if (u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW) {
         u->sample_spec.format = PA_SAMPLE_S16LE;
         u->sample_spec.channels = 1;
         u->sample_spec.rate = 8000;
@@ -1829,7 +1829,7 @@ static int setup_transport(struct userdata *u) {
 
     pa_assert(u);
     pa_assert(!u->transport);
-    pa_assert(u->profile != PROFILE_OFF);
+    pa_assert(u->profile != PA_BLUEZ4_PROFILE_OFF);
 
     /* check if profile has a transport */
     t = u->device->transports[u->profile];
@@ -1840,7 +1840,7 @@ static int setup_transport(struct userdata *u) {
 
     u->transport = t;
 
-    if (u->profile == PROFILE_A2DP_SOURCE || u->profile == PROFILE_HFGW)
+    if (u->profile == PA_BLUEZ4_PROFILE_A2DP_SOURCE || u->profile == PA_BLUEZ4_PROFILE_HFGW)
         bt_transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
     else if (bt_transport_acquire(u, false) < 0)
         return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
@@ -1854,22 +1854,22 @@ static int setup_transport(struct userdata *u) {
 static int init_profile(struct userdata *u) {
     int r = 0;
     pa_assert(u);
-    pa_assert(u->profile != PROFILE_OFF);
+    pa_assert(u->profile != PA_BLUEZ4_PROFILE_OFF);
 
     if (setup_transport(u) < 0)
         return -1;
 
     pa_assert(u->transport);
 
-    if (u->profile == PROFILE_A2DP ||
-        u->profile == PROFILE_HSP ||
-        u->profile == PROFILE_HFGW)
+    if (u->profile == PA_BLUEZ4_PROFILE_A2DP ||
+        u->profile == PA_BLUEZ4_PROFILE_HSP ||
+        u->profile == PA_BLUEZ4_PROFILE_HFGW)
         if (add_sink(u) < 0)
             r = -1;
 
-    if (u->profile == PROFILE_HSP ||
-        u->profile == PROFILE_A2DP_SOURCE ||
-        u->profile == PROFILE_HFGW)
+    if (u->profile == PA_BLUEZ4_PROFILE_HSP ||
+        u->profile == PA_BLUEZ4_PROFILE_A2DP_SOURCE ||
+        u->profile == PA_BLUEZ4_PROFILE_HFGW)
         if (add_source(u) < 0)
             r = -1;
 
@@ -1912,7 +1912,7 @@ static void stop_thread(struct userdata *u) {
     }
 
     if (u->sink) {
-        if (u->profile == PROFILE_HSP) {
+        if (u->profile == PA_BLUEZ4_PROFILE_HSP) {
             k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
             pa_shared_remove(u->core, k);
             pa_xfree(k);
@@ -1923,7 +1923,7 @@ static void stop_thread(struct userdata *u) {
     }
 
     if (u->source) {
-        if (u->profile == PROFILE_HSP) {
+        if (u->profile == PA_BLUEZ4_PROFILE_HSP) {
             k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
             pa_shared_remove(u->core, k);
             pa_xfree(k);
@@ -2019,7 +2019,7 @@ static void restore_sco_volume_callbacks(struct userdata *u) {
 /* Run from main thread */
 static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
     struct userdata *u;
-    enum profile *d;
+    pa_bluez4_profile_t *d;
 
     pa_assert(c);
     pa_assert(new_profile);
@@ -2027,7 +2027,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
 
     d = PA_CARD_PROFILE_DATA(new_profile);
 
-    if (*d != PROFILE_OFF) {
+    if (*d != PA_BLUEZ4_PROFILE_OFF) {
         const pa_bluez4_device *device = u->device;
 
         if (!device->transports[*d] || device->transports[*d]->state == PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED) {
@@ -2047,7 +2047,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
     if (USE_SCO_OVER_PCM(u))
         save_sco_volume_callbacks(u);
 
-    if (u->profile != PROFILE_OFF)
+    if (u->profile != PA_BLUEZ4_PROFILE_OFF)
         if (init_profile(u) < 0)
             goto off;
 
@@ -2163,7 +2163,7 @@ static void create_card_ports(struct userdata *u, pa_hashmap *ports) {
 static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid, pa_hashmap *ports) {
     pa_device_port *input_port, *output_port;
     pa_card_profile *p = NULL;
-    enum profile *d;
+    pa_bluez4_profile_t *d;
 
     pa_assert(u->input_port_name);
     pa_assert(u->output_port_name);
@@ -2171,7 +2171,7 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
     pa_assert_se(output_port = pa_hashmap_get(ports, u->output_port_name));
 
     if (pa_streq(uuid, A2DP_SINK_UUID)) {
-        p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile));
+        p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 10;
         p->n_sinks = 1;
         p->n_sources = 0;
@@ -2180,9 +2180,9 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
         pa_hashmap_put(output_port->profiles, p->name, p);
 
         d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_A2DP;
+        *d = PA_BLUEZ4_PROFILE_A2DP;
     } else if (pa_streq(uuid, A2DP_SOURCE_UUID)) {
-        p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile));
+        p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 10;
         p->n_sinks = 0;
         p->n_sources = 1;
@@ -2191,9 +2191,9 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
         pa_hashmap_put(input_port->profiles, p->name, p);
 
         d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_A2DP_SOURCE;
+        *d = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
     } else if (pa_streq(uuid, HSP_HS_UUID) || pa_streq(uuid, HFP_HS_UUID)) {
-        p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile));
+        p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 20;
         p->n_sinks = 1;
         p->n_sources = 1;
@@ -2203,9 +2203,9 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
         pa_hashmap_put(output_port->profiles, p->name, p);
 
         d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_HSP;
+        *d = PA_BLUEZ4_PROFILE_HSP;
     } else if (pa_streq(uuid, HFP_AG_UUID)) {
-        p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(enum profile));
+        p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(pa_bluez4_profile_t));
         p->priority = 20;
         p->n_sinks = 1;
         p->n_sources = 1;
@@ -2215,7 +2215,7 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
         pa_hashmap_put(output_port->profiles, p->name, p);
 
         d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_HFGW;
+        *d = PA_BLUEZ4_PROFILE_HFGW;
     }
 
     if (p) {
@@ -2233,7 +2233,7 @@ static int add_card(struct userdata *u) {
     pa_card_new_data data;
     bool b;
     pa_card_profile *p;
-    enum profile *d;
+    pa_bluez4_profile_t *d;
     pa_bluez4_form_factor_t ff;
     char *n;
     const char *default_profile;
@@ -2290,10 +2290,10 @@ static int add_card(struct userdata *u) {
 
     pa_assert(!pa_hashmap_isempty(data.profiles));
 
-    p = pa_card_profile_new("off", _("Off"), sizeof(enum profile));
+    p = pa_card_profile_new("off", _("Off"), sizeof(pa_bluez4_profile_t));
     p->available = PA_AVAILABLE_YES;
     d = PA_CARD_PROFILE_DATA(p);
-    *d = PROFILE_OFF;
+    *d = PA_BLUEZ4_PROFILE_OFF;
     pa_hashmap_put(data.profiles, p->name, p);
 
     if ((default_profile = pa_modargs_get_value(u->modargs, "profile", NULL))) {
@@ -2316,7 +2316,7 @@ static int add_card(struct userdata *u) {
 
     d = PA_CARD_PROFILE_DATA(u->card->active_profile);
 
-    if (*d != PROFILE_OFF && (!device->transports[*d] ||
+    if (*d != PA_BLUEZ4_PROFILE_OFF && (!device->transports[*d] ||
                               device->transports[*d]->state == PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED)) {
         pa_log_warn("Default profile not connected, selecting off profile");
         u->card->active_profile = pa_hashmap_get(u->card->profiles, "off");
@@ -2524,7 +2524,7 @@ int pa__init(pa_module *m) {
     u->msg->parent.process_msg = device_process_msg;
     u->msg->card = u->card;
 
-    if (u->profile != PROFILE_OFF)
+    if (u->profile != PA_BLUEZ4_PROFILE_OFF)
         if (init_profile(u) < 0)
             goto off;
 

commit 75922e4667e442ade00305cad8d175b17391c7b8
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:55 2013 -0300

    bluetooth: Register BlueZ 4 endpoints in a separate object path
    
    We need diferent object paths for BlueZ 4 and BlueZ 5 endpoints to avoid
    crashing the daemon in the case both modules are loaded in parallel.

diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
index 4826a2d..a33a659 100644
--- a/src/modules/bluetooth/bluez4-util.c
+++ b/src/modules/bluetooth/bluez4-util.c
@@ -32,10 +32,10 @@
 #include "bluez4-util.h"
 #include "a2dp-codecs.h"
 
-#define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG"
-#define HFP_HS_ENDPOINT "/MediaEndpoint/HFPHS"
-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+#define ENDPOINT_PATH_HFP_AG "/MediaEndpoint/BlueZ4/HFPAG"
+#define ENDPOINT_PATH_HFP_HS "/MediaEndpoint/BlueZ4/HFPHS"
+#define ENDPOINT_PATH_A2DP_SOURCE "/MediaEndpoint/BlueZ4/A2DPSource"
+#define ENDPOINT_PATH_A2DP_SINK "/MediaEndpoint/BlueZ4/A2DPSink"
 
 #define ENDPOINT_INTROSPECT_XML                                         \
     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
@@ -862,10 +862,10 @@ static void found_adapter(pa_bluez4_discovery *y, const char *path) {
     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
     send_and_add_to_pending(y, m, get_properties_reply, NULL);
 
-    register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
-    register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
-    register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
-    register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
+    register_endpoint(y, path, ENDPOINT_PATH_HFP_AG, HFP_AG_UUID);
+    register_endpoint(y, path, ENDPOINT_PATH_HFP_HS, HFP_HS_UUID);
+    register_endpoint(y, path, ENDPOINT_PATH_A2DP_SOURCE, A2DP_SOURCE_UUID);
+    register_endpoint(y, path, ENDPOINT_PATH_A2DP_SINK, A2DP_SINK_UUID);
 }
 
 static void list_adapters(pa_bluez4_discovery *y) {
@@ -1345,11 +1345,11 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     if (!d)
         goto fail;
 
-    if (dbus_message_has_path(m, HFP_AG_ENDPOINT))
+    if (dbus_message_has_path(m, ENDPOINT_PATH_HFP_AG))
         p = PROFILE_HSP;
-    else if (dbus_message_has_path(m, HFP_HS_ENDPOINT))
+    else if (dbus_message_has_path(m, ENDPOINT_PATH_HFP_HS))
         p = PROFILE_HFGW;
-    else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT))
+    else if (dbus_message_has_path(m, ENDPOINT_PATH_A2DP_SOURCE))
         p = PROFILE_A2DP;
     else
         p = PROFILE_A2DP_SOURCE;
@@ -1501,7 +1501,7 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage
         goto fail;
     }
 
-    if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT))
+    if (dbus_message_has_path(m, ENDPOINT_PATH_HFP_AG) || dbus_message_has_path(m, ENDPOINT_PATH_HFP_HS))
         goto done;
 
     pa_assert(size == sizeof(config));
@@ -1614,8 +1614,8 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
 
     dbus_error_init(&e);
 
-    if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) &&
-        !pa_streq(path, HFP_HS_ENDPOINT))
+    if (!pa_streq(path, ENDPOINT_PATH_A2DP_SOURCE) && !pa_streq(path, ENDPOINT_PATH_A2DP_SINK)
+            && !pa_streq(path, ENDPOINT_PATH_HFP_AG) && !pa_streq(path, ENDPOINT_PATH_HFP_HS))
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
     if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
@@ -1701,10 +1701,10 @@ pa_bluez4_discovery* pa_bluez4_discovery_get(pa_core *c) {
         goto fail;
     }
 
-    pa_assert_se(dbus_connection_register_object_path(conn, HFP_AG_ENDPOINT, &vtable_endpoint, y));
-    pa_assert_se(dbus_connection_register_object_path(conn, HFP_HS_ENDPOINT, &vtable_endpoint, y));
-    pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
-    pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
+    pa_assert_se(dbus_connection_register_object_path(conn, ENDPOINT_PATH_HFP_AG, &vtable_endpoint, y));
+    pa_assert_se(dbus_connection_register_object_path(conn, ENDPOINT_PATH_HFP_HS, &vtable_endpoint, y));
+    pa_assert_se(dbus_connection_register_object_path(conn, ENDPOINT_PATH_A2DP_SOURCE, &vtable_endpoint, y));
+    pa_assert_se(dbus_connection_register_object_path(conn, ENDPOINT_PATH_A2DP_SINK, &vtable_endpoint, y));
 
     list_adapters(y);
 
@@ -1750,10 +1750,10 @@ void pa_bluez4_discovery_unref(pa_bluez4_discovery *y) {
     }
 
     if (y->connection) {
-        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT);
-        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT);
-        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
-        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), ENDPOINT_PATH_HFP_AG);
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), ENDPOINT_PATH_HFP_HS);
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), ENDPOINT_PATH_A2DP_SOURCE);
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), ENDPOINT_PATH_A2DP_SINK);
         pa_dbus_remove_matches(
             pa_dbus_connection_get(y->connection),
             "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"

commit 0f84fef04d8e6610cd7eb9f3c7430153b65414dc
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:54 2013 -0300

    bluetooth: Name BlueZ 4 symbols accordingly
    
    We need diferent symbol prefixing for the current BlueZ 4 support and
    the new BlueZ 5 support which is about to enter the codebase, to avoid
    symbol clashing and crashing the daemon in the case both modules are
    loaded at the same time.
    
    This commit replaces all pa_bluetooth_ and pa_bt_ (for consistency)
    prefixes with pa_bluez4_, for both lower-case and upper-case, what was
    done with the following sed commands:
    
     $ sed -i s/pa_bluetooth_/pa_bluez4_/g src/modules/bluetooth/*bluez4*
     $ sed -i s/PA_BLUETOOTH_/PA_BLUEZ4_/g src/modules/bluetooth/*bluez4*
     $ sed -i s/pa_bt_/pa_bluez4_/g src/modules/bluetooth/*bluez4*
     $ sed -i s/PA_BT_/PA_BLUEZ4_/g src/modules/bluetooth/*bluez4*

diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
index 1d38f0d..4826a2d 100644
--- a/src/modules/bluetooth/bluez4-util.c
+++ b/src/modules/bluetooth/bluez4-util.c
@@ -61,7 +61,7 @@
     " </interface>"                                                     \
     "</node>"
 
-struct pa_bluetooth_discovery {
+struct pa_bluez4_discovery {
     PA_REFCNT_DECLARE;
 
     pa_core *core;
@@ -70,32 +70,32 @@ struct pa_bluetooth_discovery {
     bool adapters_listed;
     pa_hashmap *devices;
     pa_hashmap *transports;
-    pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
+    pa_hook hooks[PA_BLUEZ4_HOOK_MAX];
     bool filter_added;
 };
 
 static void get_properties_reply(DBusPendingCall *pending, void *userdata);
-static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
+static pa_dbus_pending* send_and_add_to_pending(pa_bluez4_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
                                                 void *call_data);
-static void found_adapter(pa_bluetooth_discovery *y, const char *path);
-static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path);
+static void found_adapter(pa_bluez4_discovery *y, const char *path);
+static pa_bluez4_device *found_device(pa_bluez4_discovery *y, const char* path);
 
-static pa_bt_audio_state_t audio_state_from_string(const char* value) {
+static pa_bluez4_audio_state_t audio_state_from_string(const char* value) {
     pa_assert(value);
 
     if (pa_streq(value, "disconnected"))
-        return PA_BT_AUDIO_STATE_DISCONNECTED;
+        return PA_BLUEZ4_AUDIO_STATE_DISCONNECTED;
     else if (pa_streq(value, "connecting"))
-        return PA_BT_AUDIO_STATE_CONNECTING;
+        return PA_BLUEZ4_AUDIO_STATE_CONNECTING;
     else if (pa_streq(value, "connected"))
-        return PA_BT_AUDIO_STATE_CONNECTED;
+        return PA_BLUEZ4_AUDIO_STATE_CONNECTED;
     else if (pa_streq(value, "playing"))
-        return PA_BT_AUDIO_STATE_PLAYING;
+        return PA_BLUEZ4_AUDIO_STATE_PLAYING;
 
-    return PA_BT_AUDIO_STATE_INVALID;
+    return PA_BLUEZ4_AUDIO_STATE_INVALID;
 }
 
-const char *pa_bt_profile_to_string(enum profile profile) {
+const char *pa_bluez4_profile_to_string(enum profile profile) {
     switch(profile) {
         case PROFILE_A2DP:
             return "a2dp";
@@ -133,46 +133,46 @@ static int profile_from_interface(const char *interface, enum profile *p) {
     return -1;
 }
 
-static pa_bluetooth_transport_state_t audio_state_to_transport_state(pa_bt_audio_state_t state) {
+static pa_bluez4_transport_state_t audio_state_to_transport_state(pa_bluez4_audio_state_t state) {
     switch (state) {
-        case PA_BT_AUDIO_STATE_INVALID: /* Typically if state hasn't been received yet */
-        case PA_BT_AUDIO_STATE_DISCONNECTED:
-        case PA_BT_AUDIO_STATE_CONNECTING:
-            return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
-        case PA_BT_AUDIO_STATE_CONNECTED:
-            return PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
-        case PA_BT_AUDIO_STATE_PLAYING:
-            return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
+        case PA_BLUEZ4_AUDIO_STATE_INVALID: /* Typically if state hasn't been received yet */
+        case PA_BLUEZ4_AUDIO_STATE_DISCONNECTED:
+        case PA_BLUEZ4_AUDIO_STATE_CONNECTING:
+            return PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED;
+        case PA_BLUEZ4_AUDIO_STATE_CONNECTED:
+            return PA_BLUEZ4_TRANSPORT_STATE_IDLE;
+        case PA_BLUEZ4_AUDIO_STATE_PLAYING:
+            return PA_BLUEZ4_TRANSPORT_STATE_PLAYING;
     }
 
     pa_assert_not_reached();
 }
 
-static pa_bluetooth_uuid *uuid_new(const char *uuid) {
-    pa_bluetooth_uuid *u;
+static pa_bluez4_uuid *uuid_new(const char *uuid) {
+    pa_bluez4_uuid *u;
 
-    u = pa_xnew(pa_bluetooth_uuid, 1);
+    u = pa_xnew(pa_bluez4_uuid, 1);
     u->uuid = pa_xstrdup(uuid);
-    PA_LLIST_INIT(pa_bluetooth_uuid, u);
+    PA_LLIST_INIT(pa_bluez4_uuid, u);
 
     return u;
 }
 
-static void uuid_free(pa_bluetooth_uuid *u) {
+static void uuid_free(pa_bluez4_uuid *u) {
     pa_assert(u);
 
     pa_xfree(u->uuid);
     pa_xfree(u);
 }
 
-static pa_bluetooth_device* device_new(pa_bluetooth_discovery *discovery, const char *path) {
-    pa_bluetooth_device *d;
+static pa_bluez4_device* device_new(pa_bluez4_discovery *discovery, const char *path) {
+    pa_bluez4_device *d;
     unsigned i;
 
     pa_assert(discovery);
     pa_assert(path);
 
-    d = pa_xnew0(pa_bluetooth_device, 1);
+    d = pa_xnew0(pa_bluez4_device, 1);
 
     d->discovery = discovery;
     d->dead = false;
@@ -183,20 +183,20 @@ static pa_bluetooth_device* device_new(pa_bluetooth_discovery *discovery, const
     d->path = pa_xstrdup(path);
     d->paired = -1;
     d->alias = NULL;
-    PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
+    PA_LLIST_HEAD_INIT(pa_bluez4_uuid, d->uuids);
     d->address = NULL;
     d->class = -1;
     d->trusted = -1;
 
-    d->audio_state = PA_BT_AUDIO_STATE_INVALID;
+    d->audio_state = PA_BLUEZ4_AUDIO_STATE_INVALID;
 
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
-        d->profile_state[i] = PA_BT_AUDIO_STATE_INVALID;
+    for (i = 0; i < PA_BLUEZ4_PROFILE_COUNT; i++)
+        d->profile_state[i] = PA_BLUEZ4_AUDIO_STATE_INVALID;
 
     return d;
 }
 
-static void transport_free(pa_bluetooth_transport *t) {
+static void transport_free(pa_bluez4_transport *t) {
     pa_assert(t);
 
     pa_xfree(t->owner);
@@ -205,26 +205,26 @@ static void transport_free(pa_bluetooth_transport *t) {
     pa_xfree(t);
 }
 
-static void device_free(pa_bluetooth_device *d) {
-    pa_bluetooth_uuid *u;
-    pa_bluetooth_transport *t;
+static void device_free(pa_bluez4_device *d) {
+    pa_bluez4_uuid *u;
+    pa_bluez4_transport *t;
     unsigned i;
 
     pa_assert(d);
 
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
+    for (i = 0; i < PA_BLUEZ4_PROFILE_COUNT; i++) {
         if (!(t = d->transports[i]))
             continue;
 
         d->transports[i] = NULL;
         pa_hashmap_remove(d->discovery->transports, t->path);
-        t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
-        pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
+        t->state = PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED;
+        pa_hook_fire(&d->discovery->hooks[PA_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED], t);
         transport_free(t);
     }
 
     while ((u = d->uuids)) {
-        PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
+        PA_LLIST_REMOVE(pa_bluez4_uuid, d->uuids, u);
         uuid_free(u);
     }
 
@@ -260,7 +260,7 @@ static const char *check_variant_property(DBusMessageIter *i) {
     return key;
 }
 
-static int parse_manager_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) {
+static int parse_manager_property(pa_bluez4_discovery *y, DBusMessageIter *i, bool is_property_change) {
     const char *key;
     DBusMessageIter variant_i;
 
@@ -303,7 +303,7 @@ static int parse_manager_property(pa_bluetooth_discovery *y, DBusMessageIter *i,
     return 0;
 }
 
-static int parse_adapter_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) {
+static int parse_adapter_property(pa_bluez4_discovery *y, DBusMessageIter *i, bool is_property_change) {
     const char *key;
     DBusMessageIter variant_i;
 
@@ -343,7 +343,7 @@ static int parse_adapter_property(pa_bluetooth_discovery *y, DBusMessageIter *i,
     return 0;
 }
 
-static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
+static int parse_device_property(pa_bluez4_device *d, DBusMessageIter *i, bool is_property_change) {
     const char *key;
     DBusMessageIter variant_i;
 
@@ -427,23 +427,23 @@ static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, boo
                 bool has_audio = false;
 
                 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
-                    pa_bluetooth_uuid *node;
+                    pa_bluez4_uuid *node;
                     const char *value;
-                    struct pa_bluetooth_hook_uuid_data uuiddata;
+                    struct pa_bluez4_hook_uuid_data uuiddata;
 
                     dbus_message_iter_get_basic(&ai, &value);
 
-                    if (pa_bluetooth_uuid_has(d->uuids, value)) {
+                    if (pa_bluez4_uuid_has(d->uuids, value)) {
                         dbus_message_iter_next(&ai);
                         continue;
                     }
 
                     node = uuid_new(value);
-                    PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
+                    PA_LLIST_PREPEND(pa_bluez4_uuid, d->uuids, node);
 
                     uuiddata.device = d;
                     uuiddata.uuid = value;
-                    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata);
+                    pa_hook_fire(&d->discovery->hooks[PA_BLUEZ4_HOOK_DEVICE_UUID_ADDED], &uuiddata);
 
                     /* Vudentz said the interfaces are here when the UUIDs are announced */
                     if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
@@ -487,21 +487,21 @@ static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, boo
     return 0;
 }
 
-static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
+static const char *transport_state_to_string(pa_bluez4_transport_state_t state) {
     switch (state) {
-        case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
+        case PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED:
             return "disconnected";
-        case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
+        case PA_BLUEZ4_TRANSPORT_STATE_IDLE:
             return "idle";
-        case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
+        case PA_BLUEZ4_TRANSPORT_STATE_PLAYING:
             return "playing";
     }
 
     pa_assert_not_reached();
 }
 
-static int parse_audio_property(pa_bluetooth_device *d, const char *interface, DBusMessageIter *i, bool is_property_change) {
-    pa_bluetooth_transport *transport;
+static int parse_audio_property(pa_bluez4_device *d, const char *interface, DBusMessageIter *i, bool is_property_change) {
+    pa_bluez4_transport *transport;
     const char *key;
     DBusMessageIter variant_i;
     bool is_audio_interface;
@@ -533,12 +533,12 @@ static int parse_audio_property(pa_bluetooth_device *d, const char *interface, D
             dbus_message_iter_get_basic(&variant_i, &value);
 
             if (pa_streq(key, "State")) {
-                pa_bt_audio_state_t state = audio_state_from_string(value);
-                pa_bluetooth_transport_state_t old_state;
+                pa_bluez4_audio_state_t state = audio_state_from_string(value);
+                pa_bluez4_transport_state_t old_state;
 
                 pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d->path, interface, value);
 
-                if (state == PA_BT_AUDIO_STATE_INVALID)
+                if (state == PA_BLUEZ4_AUDIO_STATE_INVALID)
                     return -1;
 
                 if (is_audio_interface) {
@@ -558,10 +558,10 @@ static int parse_audio_property(pa_bluetooth_device *d, const char *interface, D
 
                 if (transport->state != old_state) {
                     pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport->path,
-                                 pa_bt_profile_to_string(transport->profile), transport_state_to_string(old_state),
+                                 pa_bluez4_profile_to_string(transport->profile), transport_state_to_string(old_state),
                                  transport_state_to_string(transport->state));
 
-                    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], transport);
+                    pa_hook_fire(&d->discovery->hooks[PA_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED], transport);
                 }
             }
 
@@ -587,7 +587,7 @@ static int parse_audio_property(pa_bluetooth_device *d, const char *interface, D
                     break;
 
                 transport->microphone_gain = gain;
-                pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED], transport);
+                pa_hook_fire(&d->discovery->hooks[PA_BLUEZ4_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED], transport);
             } else if (pa_streq(key, "SpeakerGain")) {
                 uint16_t gain;
 
@@ -602,7 +602,7 @@ static int parse_audio_property(pa_bluetooth_device *d, const char *interface, D
                     break;
 
                 transport->speaker_gain = gain;
-                pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED], transport);
+                pa_hook_fire(&d->discovery->hooks[PA_BLUEZ4_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED], transport);
             }
 
             break;
@@ -612,18 +612,18 @@ static int parse_audio_property(pa_bluetooth_device *d, const char *interface, D
     return 0;
 }
 
-static void run_callback(pa_bluetooth_device *d, bool dead) {
+static void run_callback(pa_bluez4_device *d, bool dead) {
     pa_assert(d);
 
     if (d->device_info_valid != 1)
         return;
 
     d->dead = dead;
-    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d);
+    pa_hook_fire(&d->discovery->hooks[PA_BLUEZ4_HOOK_DEVICE_CONNECTION_CHANGED], d);
 }
 
-static void remove_all_devices(pa_bluetooth_discovery *y) {
-    pa_bluetooth_device *d;
+static void remove_all_devices(pa_bluez4_discovery *y) {
+    pa_bluez4_device *d;
 
     pa_assert(y);
 
@@ -633,9 +633,9 @@ static void remove_all_devices(pa_bluetooth_discovery *y) {
     }
 }
 
-static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) {
+static pa_bluez4_device *found_device(pa_bluez4_discovery *y, const char* path) {
     DBusMessage *m;
-    pa_bluetooth_device *d;
+    pa_bluez4_device *d;
 
     pa_assert(y);
     pa_assert(path);
@@ -660,8 +660,8 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
     DBusMessage *r;
     DBusMessageIter arg_i, element_i;
     pa_dbus_pending *p;
-    pa_bluetooth_device *d;
-    pa_bluetooth_discovery *y;
+    pa_bluez4_device *d;
+    pa_bluez4_discovery *y;
     int valid;
     bool old_any_connected;
 
@@ -687,7 +687,7 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
     pa_assert(p->call_data == d);
 
     if (d != NULL)
-        old_any_connected = pa_bluetooth_device_any_audio_connected(d);
+        old_any_connected = pa_bluez4_device_any_audio_connected(d);
 
     valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
 
@@ -745,7 +745,7 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
     }
 
 finish:
-    if (d != NULL && old_any_connected != pa_bluetooth_device_any_audio_connected(d))
+    if (d != NULL && old_any_connected != pa_bluez4_device_any_audio_connected(d))
         run_callback(d, false);
 
 finish2:
@@ -755,7 +755,7 @@ finish2:
     pa_dbus_pending_free(p);
 }
 
-static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
+static pa_dbus_pending* send_and_add_to_pending(pa_bluez4_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
                                                 void *call_data) {
     pa_dbus_pending *p;
     DBusPendingCall *call;
@@ -775,7 +775,7 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusM
 static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
     DBusMessage *r;
     pa_dbus_pending *p;
-    pa_bluetooth_discovery *y;
+    pa_bluez4_discovery *y;
     char *endpoint;
 
     pa_assert(pending);
@@ -790,7 +790,7 @@ static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
         goto finish;
     }
 
-    if (dbus_message_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) {
+    if (dbus_message_is_error(r, PA_BLUEZ4_ERROR_NOT_SUPPORTED)) {
         pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint);
         goto finish;
     }
@@ -810,7 +810,7 @@ finish:
     pa_xfree(endpoint);
 }
 
-static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
+static void register_endpoint(pa_bluez4_discovery *y, const char *path, const char *endpoint, const char *uuid) {
     DBusMessage *m;
     DBusMessageIter i, d;
     uint8_t codec = 0;
@@ -856,7 +856,7 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
     send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
 }
 
-static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
+static void found_adapter(pa_bluez4_discovery *y, const char *path) {
     DBusMessage *m;
 
     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
@@ -868,7 +868,7 @@ static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
     register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
 }
 
-static void list_adapters(pa_bluetooth_discovery *y) {
+static void list_adapters(pa_bluez4_discovery *y) {
     DBusMessage *m;
     pa_assert(y);
 
@@ -876,7 +876,7 @@ static void list_adapters(pa_bluetooth_discovery *y) {
     send_and_add_to_pending(y, m, get_properties_reply, NULL);
 }
 
-static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
+static int transport_parse_property(pa_bluez4_transport *t, DBusMessageIter *i) {
     const char *key;
     DBusMessageIter variant_i;
 
@@ -896,7 +896,7 @@ static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *
             if (pa_streq(key, "NREC") && t->nrec != value) {
                 t->nrec = value;
                 pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t->path, t->nrec ? "True" : "False");
-                pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED], t);
+                pa_hook_fire(&t->device->discovery->hooks[PA_BLUEZ4_HOOK_TRANSPORT_NREC_CHANGED], t);
             }
 
             break;
@@ -908,7 +908,7 @@ static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *
 
 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
     DBusError err;
-    pa_bluetooth_discovery *y;
+    pa_bluez4_discovery *y;
 
     pa_assert(bus);
     pa_assert(m);
@@ -924,7 +924,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
 
     if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
         const char *path;
-        pa_bluetooth_device *d;
+        pa_bluez4_device *d;
 
         if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
             pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
@@ -978,11 +978,11 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
                dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
                dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
 
-        pa_bluetooth_device *d;
+        pa_bluez4_device *d;
 
         if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
             DBusMessageIter arg_i;
-            bool old_any_connected = pa_bluetooth_device_any_audio_connected(d);
+            bool old_any_connected = pa_bluez4_device_any_audio_connected(d);
 
             if (!dbus_message_iter_init(m, &arg_i)) {
                 pa_log("Failed to parse PropertyChanged for device %s", d->path);
@@ -996,7 +996,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
             } else if (parse_audio_property(d, dbus_message_get_interface(m), &arg_i, true) < 0)
                 goto fail;
 
-            if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
+            if (old_any_connected != pa_bluez4_device_any_audio_connected(d))
                 run_callback(d, false);
         }
 
@@ -1029,7 +1029,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
     } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
-        pa_bluetooth_transport *t;
+        pa_bluez4_transport *t;
         DBusMessageIter arg_i;
 
         if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
@@ -1052,8 +1052,8 @@ fail:
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
-pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
-    pa_bluetooth_device *d;
+pa_bluez4_device* pa_bluez4_discovery_get_by_address(pa_bluez4_discovery *y, const char* address) {
+    pa_bluez4_device *d;
     void *state = NULL;
 
     pa_assert(y);
@@ -1067,8 +1067,8 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discover
     return NULL;
 }
 
-pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
-    pa_bluetooth_device *d;
+pa_bluez4_device* pa_bluez4_discovery_get_by_path(pa_bluez4_discovery *y, const char* path) {
+    pa_bluez4_device *d;
 
     pa_assert(y);
     pa_assert(PA_REFCNT_VALUE(y) > 0);
@@ -1081,7 +1081,7 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *
     return NULL;
 }
 
-bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
+bool pa_bluez4_device_any_audio_connected(const pa_bluez4_device *d) {
     unsigned i;
 
     pa_assert(d);
@@ -1089,7 +1089,7 @@ bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
     if (d->dead || d->device_info_valid != 1)
         return false;
 
-    if (d->audio_state == PA_BT_AUDIO_STATE_INVALID)
+    if (d->audio_state == PA_BLUEZ4_AUDIO_STATE_INVALID)
         return false;
 
     /* Make sure audio_state is *not* in CONNECTING state before we fire the
@@ -1105,17 +1105,17 @@ bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
      * connected. Waiting until the Audio interface gets connected means that
      * both headset profiles will be connected when the device module is
      * loaded. */
-    if (d->audio_state == PA_BT_AUDIO_STATE_CONNECTING)
+    if (d->audio_state == PA_BLUEZ4_AUDIO_STATE_CONNECTING)
         return false;
 
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
-        if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+    for (i = 0; i < PA_BLUEZ4_PROFILE_COUNT; i++)
+        if (d->transports[i] && d->transports[i]->state != PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED)
             return true;
 
     return false;
 }
 
-int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
+int pa_bluez4_transport_acquire(pa_bluez4_transport *t, bool optional, size_t *imtu, size_t *omtu) {
     const char *accesstype = "rw";
     DBusMessage *m, *r;
     DBusError err;
@@ -1134,7 +1134,7 @@ int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, siz
            suspended in the meantime, so we can't really guarantee that the
            stream will not be requested until BlueZ's API supports this
            atomically. */
-        if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
+        if (t->state < PA_BLUEZ4_TRANSPORT_STATE_PLAYING) {
             pa_log_info("Failed optional acquire of transport %s", t->path);
             return -1;
         }
@@ -1170,7 +1170,7 @@ fail:
     return ret;
 }
 
-void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
+void pa_bluez4_transport_release(pa_bluez4_transport *t) {
     const char *accesstype = "rw";
     DBusMessage *m;
     DBusError err;
@@ -1192,7 +1192,7 @@ void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
         pa_log_info("Transport %s released", t->path);
 }
 
-static void set_property(pa_bluetooth_discovery *y, const char *bus, const char *path, const char *interface,
+static void set_property(pa_bluez4_discovery *y, const char *bus, const char *path, const char *interface,
                          const char *prop_name, int prop_type, void *prop_value) {
     DBusMessage *m;
     DBusMessageIter i;
@@ -1212,7 +1212,7 @@ static void set_property(pa_bluetooth_discovery *y, const char *bus, const char
     dbus_message_unref(m);
 }
 
-void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value) {
+void pa_bluez4_transport_set_microphone_gain(pa_bluez4_transport *t, uint16_t value) {
     dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
 
     pa_assert(t);
@@ -1222,7 +1222,7 @@ void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint1
                  "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
 }
 
-void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value) {
+void pa_bluez4_transport_set_speaker_gain(pa_bluez4_transport *t, uint16_t value) {
     dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
 
     pa_assert(t);
@@ -1232,7 +1232,7 @@ void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t
                  "SpeakerGain", DBUS_TYPE_UINT16, &gain);
 }
 
-static int setup_dbus(pa_bluetooth_discovery *y) {
+static int setup_dbus(pa_bluez4_discovery *y) {
     DBusError err;
 
     dbus_error_init(&err);
@@ -1246,11 +1246,11 @@ static int setup_dbus(pa_bluetooth_discovery *y) {
     return 0;
 }
 
-static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char *owner, const char *path, enum profile p,
+static pa_bluez4_transport *transport_new(pa_bluez4_device *d, const char *owner, const char *path, enum profile p,
                                              const uint8_t *config, int size) {
-    pa_bluetooth_transport *t;
+    pa_bluez4_transport *t;
 
-    t = pa_xnew0(pa_bluetooth_transport, 1);
+    t = pa_xnew0(pa_bluez4_transport, 1);
     t->device = d;
     t->owner = pa_xstrdup(owner);
     t->path = pa_xstrdup(path);
@@ -1268,9 +1268,9 @@ static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char
 }
 
 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
-    pa_bluetooth_discovery *y = userdata;
-    pa_bluetooth_device *d;
-    pa_bluetooth_transport *t;
+    pa_bluez4_discovery *y = userdata;
+    pa_bluez4_device *d;
+    pa_bluez4_transport *t;
     const char *sender, *path, *dev_path = NULL, *uuid = NULL;
     uint8_t *config = NULL;
     int size = 0;
@@ -1359,7 +1359,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
         goto fail2;
     }
 
-    old_any_connected = pa_bluetooth_device_any_audio_connected(d);
+    old_any_connected = pa_bluez4_device_any_audio_connected(d);
 
     sender = dbus_message_get_sender(m);
 
@@ -1376,7 +1376,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
     dbus_message_unref(r);
 
-    if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
+    if (old_any_connected != pa_bluez4_device_any_audio_connected(d))
         run_callback(d, false);
 
     return NULL;
@@ -1391,8 +1391,8 @@ fail2:
 }
 
 static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
-    pa_bluetooth_discovery *y = userdata;
-    pa_bluetooth_transport *t;
+    pa_bluez4_discovery *y = userdata;
+    pa_bluez4_transport *t;
     DBusMessage *r;
     DBusError e;
     const char *path;
@@ -1406,15 +1406,15 @@ static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage
     }
 
     if ((t = pa_hashmap_get(y->transports, path))) {
-        bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
+        bool old_any_connected = pa_bluez4_device_any_audio_connected(t->device);
 
         pa_log_debug("Clearing transport %s profile %d", t->path, t->profile);
         t->device->transports[t->profile] = NULL;
         pa_hashmap_remove(y->transports, t->path);
-        t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
-        pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
+        t->state = PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED;
+        pa_hook_fire(&y->hooks[PA_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED], t);
 
-        if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
+        if (old_any_connected != pa_bluez4_device_any_audio_connected(t->device))
             run_callback(t->device, false);
 
         transport_free(t);
@@ -1476,7 +1476,7 @@ static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
 }
 
 static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
-    pa_bluetooth_discovery *y = userdata;
+    pa_bluez4_discovery *y = userdata;
     a2dp_sbc_t *cap, config;
     uint8_t *pconf = (uint8_t *) &config;
     int i, size;
@@ -1599,7 +1599,7 @@ fail:
 }
 
 static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
-    struct pa_bluetooth_discovery *y = userdata;
+    struct pa_bluez4_discovery *y = userdata;
     DBusMessage *r = NULL;
     DBusError e;
     const char *path, *interface, *member;
@@ -1641,9 +1641,9 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
     return DBUS_HANDLER_RESULT_HANDLED;
 }
 
-pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
+pa_bluez4_discovery* pa_bluez4_discovery_get(pa_core *c) {
     DBusError err;
-    pa_bluetooth_discovery *y;
+    pa_bluez4_discovery *y;
     DBusConnection *conn;
     unsigned i;
     static const DBusObjectPathVTable vtable_endpoint = {
@@ -1655,16 +1655,16 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     dbus_error_init(&err);
 
     if ((y = pa_shared_get(c, "bluez4-discovery")))
-        return pa_bluetooth_discovery_ref(y);
+        return pa_bluez4_discovery_ref(y);
 
-    y = pa_xnew0(pa_bluetooth_discovery, 1);
+    y = pa_xnew0(pa_bluez4_discovery, 1);
     PA_REFCNT_INIT(y);
     y->core = c;
     y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
     y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
     PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
 
-    for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
+    for (i = 0; i < PA_BLUEZ4_HOOK_MAX; i++)
         pa_hook_init(&y->hooks[i], y);
 
     pa_shared_set(c, "bluez4-discovery", y);
@@ -1712,14 +1712,14 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
 
 fail:
     if (y)
-        pa_bluetooth_discovery_unref(y);
+        pa_bluez4_discovery_unref(y);
 
     dbus_error_free(&err);
 
     return NULL;
 }
 
-pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
+pa_bluez4_discovery* pa_bluez4_discovery_ref(pa_bluez4_discovery *y) {
     pa_assert(y);
     pa_assert(PA_REFCNT_VALUE(y) > 0);
 
@@ -1728,7 +1728,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
     return y;
 }
 
-void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
+void pa_bluez4_discovery_unref(pa_bluez4_discovery *y) {
     unsigned i;
 
     pa_assert(y);
@@ -1777,7 +1777,7 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
         pa_dbus_connection_unref(y->connection);
     }
 
-    for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
+    for (i = 0; i < PA_BLUEZ4_HOOK_MAX; i++)
         pa_hook_done(&y->hooks[i]);
 
     if (y->core)
@@ -1786,26 +1786,26 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
     pa_xfree(y);
 }
 
-pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
+pa_hook* pa_bluez4_discovery_hook(pa_bluez4_discovery *y, pa_bluez4_hook_t hook) {
     pa_assert(y);
     pa_assert(PA_REFCNT_VALUE(y) > 0);
 
     return &y->hooks[hook];
 }
 
-pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) {
+pa_bluez4_form_factor_t pa_bluez4_get_form_factor(uint32_t class) {
     unsigned major, minor;
-    pa_bt_form_factor_t r;
-
-    static const pa_bt_form_factor_t table[] = {
-        [1] = PA_BT_FORM_FACTOR_HEADSET,
-        [2] = PA_BT_FORM_FACTOR_HANDSFREE,
-        [4] = PA_BT_FORM_FACTOR_MICROPHONE,
-        [5] = PA_BT_FORM_FACTOR_SPEAKER,
-        [6] = PA_BT_FORM_FACTOR_HEADPHONE,
-        [7] = PA_BT_FORM_FACTOR_PORTABLE,
-        [8] = PA_BT_FORM_FACTOR_CAR,
-        [10] = PA_BT_FORM_FACTOR_HIFI
+    pa_bluez4_form_factor_t r;
+
+    static const pa_bluez4_form_factor_t table[] = {
+        [1] = PA_BLUEZ4_FORM_FACTOR_HEADSET,
+        [2] = PA_BLUEZ4_FORM_FACTOR_HANDSFREE,
+        [4] = PA_BLUEZ4_FORM_FACTOR_MICROPHONE,
+        [5] = PA_BLUEZ4_FORM_FACTOR_SPEAKER,
+        [6] = PA_BLUEZ4_FORM_FACTOR_HEADPHONE,
+        [7] = PA_BLUEZ4_FORM_FACTOR_PORTABLE,
+        [8] = PA_BLUEZ4_FORM_FACTOR_CAR,
+        [10] = PA_BLUEZ4_FORM_FACTOR_HIFI
     };
 
     /*
@@ -1817,15 +1817,15 @@ pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) {
 
     switch (major) {
         case 2:
-            return PA_BT_FORM_FACTOR_PHONE;
+            return PA_BLUEZ4_FORM_FACTOR_PHONE;
         case 4:
             break;
         default:
             pa_log_debug("Unknown Bluetooth major device class %u", major);
-            return PA_BT_FORM_FACTOR_UNKNOWN;
+            return PA_BLUEZ4_FORM_FACTOR_UNKNOWN;
     }
 
-    r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BT_FORM_FACTOR_UNKNOWN;
+    r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BLUEZ4_FORM_FACTOR_UNKNOWN;
 
     if (!r)
         pa_log_debug("Unknown Bluetooth minor device class %u", minor);
@@ -1833,34 +1833,34 @@ pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) {
     return r;
 }
 
-const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff) {
+const char *pa_bluez4_form_factor_to_string(pa_bluez4_form_factor_t ff) {
     switch (ff) {
-        case PA_BT_FORM_FACTOR_UNKNOWN:
+        case PA_BLUEZ4_FORM_FACTOR_UNKNOWN:
             return "unknown";
-        case PA_BT_FORM_FACTOR_HEADSET:
+        case PA_BLUEZ4_FORM_FACTOR_HEADSET:
             return "headset";
-        case PA_BT_FORM_FACTOR_HANDSFREE:
+        case PA_BLUEZ4_FORM_FACTOR_HANDSFREE:
             return "hands-free";
-        case PA_BT_FORM_FACTOR_MICROPHONE:
+        case PA_BLUEZ4_FORM_FACTOR_MICROPHONE:
             return "microphone";
-        case PA_BT_FORM_FACTOR_SPEAKER:
+        case PA_BLUEZ4_FORM_FACTOR_SPEAKER:
             return "speaker";
-        case PA_BT_FORM_FACTOR_HEADPHONE:
+        case PA_BLUEZ4_FORM_FACTOR_HEADPHONE:
             return "headphone";
-        case PA_BT_FORM_FACTOR_PORTABLE:
+        case PA_BLUEZ4_FORM_FACTOR_PORTABLE:
             return "portable";
-        case PA_BT_FORM_FACTOR_CAR:
+        case PA_BLUEZ4_FORM_FACTOR_CAR:
             return "car";
-        case PA_BT_FORM_FACTOR_HIFI:
+        case PA_BLUEZ4_FORM_FACTOR_HIFI:
             return "hifi";
-        case PA_BT_FORM_FACTOR_PHONE:
+        case PA_BLUEZ4_FORM_FACTOR_PHONE:
             return "phone";
     }
 
     pa_assert_not_reached();
 }
 
-char *pa_bluetooth_cleanup_name(const char *name) {
+char *pa_bluez4_cleanup_name(const char *name) {
     char *t, *s, *d;
     bool space = false;
 
@@ -1891,7 +1891,7 @@ char *pa_bluetooth_cleanup_name(const char *name) {
     return t;
 }
 
-bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
+bool pa_bluez4_uuid_has(pa_bluez4_uuid *uuids, const char *uuid) {
     pa_assert(uuid);
 
     while (uuids) {
diff --git a/src/modules/bluetooth/bluez4-util.h b/src/modules/bluetooth/bluez4-util.h
index 4570a10..b920545 100644
--- a/src/modules/bluetooth/bluez4-util.h
+++ b/src/modules/bluetooth/bluez4-util.h
@@ -27,7 +27,7 @@
 #include <pulsecore/llist.h>
 #include <pulsecore/macro.h>
 
-#define PA_BLUETOOTH_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
+#define PA_BLUEZ4_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
 
 /* UUID copied from bluez/audio/device.h */
 #define GENERIC_AUDIO_UUID      "00001203-0000-1000-8000-00805f9b34fb"
@@ -45,16 +45,16 @@
 
 #define HSP_MAX_GAIN 15
 
-typedef struct pa_bluetooth_uuid pa_bluetooth_uuid;
-typedef struct pa_bluetooth_device pa_bluetooth_device;
-typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
-typedef struct pa_bluetooth_transport pa_bluetooth_transport;
+typedef struct pa_bluez4_uuid pa_bluez4_uuid;
+typedef struct pa_bluez4_device pa_bluez4_device;
+typedef struct pa_bluez4_discovery pa_bluez4_discovery;
+typedef struct pa_bluez4_transport pa_bluez4_transport;
 
 struct userdata;
 
-struct pa_bluetooth_uuid {
+struct pa_bluez4_uuid {
     char *uuid;
-    PA_LLIST_FIELDS(pa_bluetooth_uuid);
+    PA_LLIST_FIELDS(pa_bluez4_uuid);
 };
 
 enum profile {
@@ -65,32 +65,32 @@ enum profile {
     PROFILE_OFF
 };
 
-#define PA_BLUETOOTH_PROFILE_COUNT PROFILE_OFF
+#define PA_BLUEZ4_PROFILE_COUNT PROFILE_OFF
 
-struct pa_bluetooth_hook_uuid_data {
-    pa_bluetooth_device *device;
+struct pa_bluez4_hook_uuid_data {
+    pa_bluez4_device *device;
     const char *uuid;
 };
 
-/* Hook data: pa_bluetooth_discovery pointer. */
-typedef enum pa_bluetooth_hook {
-    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */
-    PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED, /* Call data: pa_bluetooth_hook_uuid_data */
-    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED, /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_MAX
-} pa_bluetooth_hook_t;
-
-typedef enum pa_bluetooth_transport_state {
-    PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED,
-    PA_BLUETOOTH_TRANSPORT_STATE_IDLE, /* Connected but not playing */
-    PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
-} pa_bluetooth_transport_state_t;
-
-struct pa_bluetooth_transport {
-    pa_bluetooth_device *device;
+/* Hook data: pa_bluez4_discovery pointer. */
+typedef enum pa_bluez4_hook {
+    PA_BLUEZ4_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluez4_device */
+    PA_BLUEZ4_HOOK_DEVICE_UUID_ADDED, /* Call data: pa_bluez4_hook_uuid_data */
+    PA_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluez4_transport */
+    PA_BLUEZ4_HOOK_TRANSPORT_NREC_CHANGED, /* Call data: pa_bluez4_transport */
+    PA_BLUEZ4_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluez4_transport */
+    PA_BLUEZ4_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluez4_transport */
+    PA_BLUEZ4_HOOK_MAX
+} pa_bluez4_hook_t;
+
+typedef enum pa_bluez4_transport_state {
+    PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED,
+    PA_BLUEZ4_TRANSPORT_STATE_IDLE, /* Connected but not playing */
+    PA_BLUEZ4_TRANSPORT_STATE_PLAYING
+} pa_bluez4_transport_state_t;
+
+struct pa_bluez4_transport {
+    pa_bluez4_device *device;
     char *owner;
     char *path;
     enum profile profile;
@@ -98,23 +98,23 @@ struct pa_bluetooth_transport {
     uint8_t *config;
     int config_size;
 
-    pa_bluetooth_transport_state_t state;
+    pa_bluez4_transport_state_t state;
     bool nrec;
     uint16_t microphone_gain; /* Used for HSP/HFP */
     uint16_t speaker_gain; /* Used for HSP/HFP */
 };
 
 /* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */
-typedef enum pa_bt_audio_state {
-    PA_BT_AUDIO_STATE_INVALID = -1,
-    PA_BT_AUDIO_STATE_DISCONNECTED,
-    PA_BT_AUDIO_STATE_CONNECTING,
-    PA_BT_AUDIO_STATE_CONNECTED,
-    PA_BT_AUDIO_STATE_PLAYING
-} pa_bt_audio_state_t;
-
-struct pa_bluetooth_device {
-    pa_bluetooth_discovery *discovery;
+typedef enum pa_bluez4_audio_state {
+    PA_BLUEZ4_AUDIO_STATE_INVALID = -1,
+    PA_BLUEZ4_AUDIO_STATE_DISCONNECTED,
+    PA_BLUEZ4_AUDIO_STATE_CONNECTING,
+    PA_BLUEZ4_AUDIO_STATE_CONNECTED,
+    PA_BLUEZ4_AUDIO_STATE_PLAYING
+} pa_bluez4_audio_state_t;
+
+struct pa_bluez4_device {
+    pa_bluez4_discovery *discovery;
     bool dead;
 
     int device_info_valid;      /* 0: no results yet; 1: good results; -1: bad results ... */
@@ -122,57 +122,57 @@ struct pa_bluetooth_device {
     /* Device information */
     char *name;
     char *path;
-    pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
+    pa_bluez4_transport *transports[PA_BLUEZ4_PROFILE_COUNT];
     int paired;
     char *alias;
-    PA_LLIST_HEAD(pa_bluetooth_uuid, uuids);
+    PA_LLIST_HEAD(pa_bluez4_uuid, uuids);
     char *address;
     int class;
     int trusted;
 
     /* Audio state */
-    pa_bt_audio_state_t audio_state;
+    pa_bluez4_audio_state_t audio_state;
 
     /* AudioSink, AudioSource, Headset and HandsfreeGateway states */
-    pa_bt_audio_state_t profile_state[PA_BLUETOOTH_PROFILE_COUNT];
+    pa_bluez4_audio_state_t profile_state[PA_BLUEZ4_PROFILE_COUNT];
 };
 
-pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
-pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
-void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *d);
+pa_bluez4_discovery* pa_bluez4_discovery_get(pa_core *core);
+pa_bluez4_discovery* pa_bluez4_discovery_ref(pa_bluez4_discovery *y);
+void pa_bluez4_discovery_unref(pa_bluez4_discovery *d);
 
-pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path);
-pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address);
+pa_bluez4_device* pa_bluez4_discovery_get_by_path(pa_bluez4_discovery *d, const char* path);
+pa_bluez4_device* pa_bluez4_discovery_get_by_address(pa_bluez4_discovery *d, const char* address);
 
-bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d);
+bool pa_bluez4_device_any_audio_connected(const pa_bluez4_device *d);
 
-int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu);
-void pa_bluetooth_transport_release(pa_bluetooth_transport *t);
+int pa_bluez4_transport_acquire(pa_bluez4_transport *t, bool optional, size_t *imtu, size_t *omtu);
+void pa_bluez4_transport_release(pa_bluez4_transport *t);
 
-void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value);
-void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value);
+void pa_bluez4_transport_set_microphone_gain(pa_bluez4_transport *t, uint16_t value);
+void pa_bluez4_transport_set_speaker_gain(pa_bluez4_transport *t, uint16_t value);
 
-pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
+pa_hook* pa_bluez4_discovery_hook(pa_bluez4_discovery *y, pa_bluez4_hook_t hook);
 
-typedef enum pa_bt_form_factor {
-    PA_BT_FORM_FACTOR_UNKNOWN,
-    PA_BT_FORM_FACTOR_HEADSET,
-    PA_BT_FORM_FACTOR_HANDSFREE,
-    PA_BT_FORM_FACTOR_MICROPHONE,
-    PA_BT_FORM_FACTOR_SPEAKER,
-    PA_BT_FORM_FACTOR_HEADPHONE,
-    PA_BT_FORM_FACTOR_PORTABLE,
-    PA_BT_FORM_FACTOR_CAR,
-    PA_BT_FORM_FACTOR_HIFI,
-    PA_BT_FORM_FACTOR_PHONE,
-} pa_bt_form_factor_t;
+typedef enum pa_bluez4_form_factor {
+    PA_BLUEZ4_FORM_FACTOR_UNKNOWN,
+    PA_BLUEZ4_FORM_FACTOR_HEADSET,
+    PA_BLUEZ4_FORM_FACTOR_HANDSFREE,
+    PA_BLUEZ4_FORM_FACTOR_MICROPHONE,
+    PA_BLUEZ4_FORM_FACTOR_SPEAKER,
+    PA_BLUEZ4_FORM_FACTOR_HEADPHONE,
+    PA_BLUEZ4_FORM_FACTOR_PORTABLE,
+    PA_BLUEZ4_FORM_FACTOR_CAR,
+    PA_BLUEZ4_FORM_FACTOR_HIFI,
+    PA_BLUEZ4_FORM_FACTOR_PHONE,
+} pa_bluez4_form_factor_t;
 
-pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class);
-const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff);
+pa_bluez4_form_factor_t pa_bluez4_get_form_factor(uint32_t class);
+const char *pa_bluez4_form_factor_to_string(pa_bluez4_form_factor_t ff);
 
-char *pa_bluetooth_cleanup_name(const char *name);
+char *pa_bluez4_cleanup_name(const char *name);
 
-bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid);
-const char *pa_bt_profile_to_string(enum profile profile);
+bool pa_bluez4_uuid_has(pa_bluez4_uuid *uuids, const char *uuid);
+const char *pa_bluez4_profile_to_string(enum profile profile);
 
 #endif
diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
index 2d1224f..aca10d5 100644
--- a/src/modules/bluetooth/module-bluez4-device.c
+++ b/src/modules/bluetooth/module-bluez4-device.c
@@ -135,11 +135,11 @@ struct userdata {
     pa_core *core;
     pa_module *module;
 
-    pa_bluetooth_device *device;
+    pa_bluez4_device *device;
     pa_hook_slot *uuid_added_slot;
     char *address;
     char *path;
-    pa_bluetooth_transport *transport;
+    pa_bluez4_transport *transport;
     bool transport_acquired;
     pa_hook_slot *discovery_slot;
     pa_hook_slot *sink_state_changed_slot;
@@ -149,7 +149,7 @@ struct userdata {
     pa_hook_slot *transport_microphone_changed_slot;
     pa_hook_slot *transport_speaker_changed_slot;
 
-    pa_bluetooth_discovery *discovery;
+    pa_bluez4_discovery *discovery;
     bool auto_connect;
 
     char *output_port_name;
@@ -351,7 +351,7 @@ static void bt_transport_release(struct userdata *u) {
 
     pa_log_debug("Releasing transport %s", u->transport->path);
 
-    pa_bluetooth_transport_release(u->transport);
+    pa_bluez4_transport_release(u->transport);
 
     u->transport_acquired = false;
 
@@ -366,7 +366,7 @@ static int bt_transport_acquire(struct userdata *u, bool optional) {
 
     pa_log_debug("Acquiring transport %s", u->transport->path);
 
-    u->stream_fd = pa_bluetooth_transport_acquire(u->transport, optional, &u->read_link_mtu, &u->write_link_mtu);
+    u->stream_fd = pa_bluez4_transport_acquire(u->transport, optional, &u->read_link_mtu, &u->write_link_mtu);
     if (u->stream_fd < 0) {
         if (!optional)
             pa_log("Failed to acquire transport %s", u->transport->path);
@@ -1192,10 +1192,10 @@ finish:
     pa_log_debug("IO thread shutting down");
 }
 
-static pa_available_t transport_state_to_availability(pa_bluetooth_transport_state_t state) {
-    if (state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+static pa_available_t transport_state_to_availability(pa_bluez4_transport_state_t state) {
+    if (state == PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED)
         return PA_AVAILABLE_NO;
-    else if (state >= PA_BLUETOOTH_TRANSPORT_STATE_PLAYING)
+    else if (state >= PA_BLUEZ4_TRANSPORT_STATE_PLAYING)
         return PA_AVAILABLE_YES;
     else
         return PA_AVAILABLE_UNKNOWN;
@@ -1221,8 +1221,8 @@ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t d
     pa_assert(u);
     pa_assert(u->device);
 
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
-        pa_bluetooth_transport *transport;
+    for (i = 0; i < PA_BLUEZ4_PROFILE_COUNT; i++) {
+        pa_bluez4_transport *transport;
 
         if (!(get_profile_direction(i) & direction))
             continue;
@@ -1231,16 +1231,16 @@ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t d
             continue;
 
         switch(transport->state) {
-            case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
+            case PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED:
                 continue;
 
-            case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
+            case PA_BLUEZ4_TRANSPORT_STATE_IDLE:
                 if (result == PA_AVAILABLE_NO)
                     result = PA_AVAILABLE_UNKNOWN;
 
                 break;
 
-            case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
+            case PA_BLUEZ4_TRANSPORT_STATE_PLAYING:
                 return PA_AVAILABLE_YES;
         }
     }
@@ -1249,12 +1249,12 @@ static pa_available_t get_port_availability(struct userdata *u, pa_direction_t d
 }
 
 /* Run from main thread */
-static void handle_transport_state_change(struct userdata *u, struct pa_bluetooth_transport *transport) {
+static void handle_transport_state_change(struct userdata *u, struct pa_bluez4_transport *transport) {
     bool acquire = false;
     bool release = false;
     enum profile profile;
     pa_card_profile *cp;
-    pa_bluetooth_transport_state_t state;
+    pa_bluez4_transport_state_t state;
     pa_device_port *port;
 
     pa_assert(u);
@@ -1264,7 +1264,7 @@ static void handle_transport_state_change(struct userdata *u, struct pa_bluetoot
     state = transport->state;
 
     /* Update profile availability */
-    if (!(cp = pa_hashmap_get(u->card->profiles, pa_bt_profile_to_string(profile))))
+    if (!(cp = pa_hashmap_get(u->card->profiles, pa_bluez4_profile_to_string(profile))))
         return;
 
     pa_card_profile_set_available(cp, transport_state_to_availability(state));
@@ -1277,8 +1277,8 @@ static void handle_transport_state_change(struct userdata *u, struct pa_bluetoot
     pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_INPUT));
 
     /* Acquire or release transport as needed */
-    acquire = (state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile);
-    release = (state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile);
+    acquire = (state == PA_BLUEZ4_TRANSPORT_STATE_PLAYING && u->profile == profile);
+    release = (state != PA_BLUEZ4_TRANSPORT_STATE_PLAYING && u->profile == profile);
 
     if (acquire)
         if (bt_transport_acquire(u, true) >= 0) {
@@ -1336,7 +1336,7 @@ static void sink_set_volume_cb(pa_sink *s) {
 
     pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
 
-    pa_bluetooth_transport_set_speaker_gain(u->transport, gain);
+    pa_bluez4_transport_set_speaker_gain(u->transport, gain);
 }
 
 /* Run from main thread */
@@ -1363,7 +1363,7 @@ static void source_set_volume_cb(pa_source *s) {
 
     pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
 
-    pa_bluetooth_transport_set_microphone_gain(u->transport, gain);
+    pa_bluez4_transport_set_microphone_gain(u->transport, gain);
 }
 
 /* Run from main thread */
@@ -1457,7 +1457,7 @@ static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct
     return PA_HOOK_OK;
 }
 
-static pa_hook_result_t transport_nrec_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+static pa_hook_result_t transport_nrec_changed_cb(pa_bluez4_discovery *y, pa_bluez4_transport *t, struct userdata *u) {
     pa_proplist *p;
 
     pa_assert(t);
@@ -1474,7 +1474,7 @@ static pa_hook_result_t transport_nrec_changed_cb(pa_bluetooth_discovery *y, pa_
     return PA_HOOK_OK;
 }
 
-static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t,
+static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluez4_discovery *y, pa_bluez4_transport *t,
                                                              struct userdata *u) {
     pa_cvolume v;
 
@@ -1493,7 +1493,7 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }
 
-static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t,
+static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluez4_discovery *y, pa_bluez4_transport *t,
                                                           struct userdata *u) {
     pa_cvolume v;
 
@@ -1548,7 +1548,7 @@ static int add_sink(struct userdata *u) {
 
         u->sink = u->hsp.sco_sink;
         p = pa_proplist_new();
-        pa_proplist_sets(p, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
+        pa_proplist_sets(p, "bluetooth.protocol", pa_bluez4_profile_to_string(u->profile));
         pa_proplist_update(u->sink->proplist, PA_UPDATE_MERGE, p);
         pa_proplist_free(p);
     } else {
@@ -1559,7 +1559,7 @@ static int add_sink(struct userdata *u) {
         data.driver = __FILE__;
         data.module = u->module;
         pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
-        pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
+        pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluez4_profile_to_string(u->profile));
         if (u->profile == PROFILE_HSP)
             pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
         data.card = u->card;
@@ -1620,7 +1620,7 @@ static int add_source(struct userdata *u) {
 
     if (USE_SCO_OVER_PCM(u)) {
         u->source = u->hsp.sco_source;
-        pa_proplist_sets(u->source->proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
+        pa_proplist_sets(u->source->proplist, "bluetooth.protocol", pa_bluez4_profile_to_string(u->profile));
     } else {
         pa_source_new_data data;
         bool b;
@@ -1629,7 +1629,7 @@ static int add_source(struct userdata *u) {
         data.driver = __FILE__;
         data.module = u->module;
         pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
-        pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
+        pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluez4_profile_to_string(u->profile));
         if (u->profile == PROFILE_HSP)
             pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
 
@@ -1673,7 +1673,7 @@ static int add_source(struct userdata *u) {
     }
 
     if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
-        pa_bluetooth_transport *t = u->transport;
+        pa_bluez4_transport *t = u->transport;
         pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
     }
 
@@ -1690,7 +1690,7 @@ static int add_source(struct userdata *u) {
 }
 
 static void bt_transport_config_a2dp(struct userdata *u) {
-    const pa_bluetooth_transport *t;
+    const pa_bluez4_transport *t;
     struct a2dp_info *a2dp = &u->a2dp;
     a2dp_sbc_t *config;
 
@@ -1810,11 +1810,11 @@ static void bt_transport_config(struct userdata *u) {
 }
 
 /* Run from main thread */
-static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+static pa_hook_result_t transport_state_changed_cb(pa_bluez4_discovery *y, pa_bluez4_transport *t, struct userdata *u) {
     pa_assert(t);
     pa_assert(u);
 
-    if (t == u->transport && t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+    if (t == u->transport && t->state == PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED)
         pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
 
     if (t->device == u->device)
@@ -1825,7 +1825,7 @@ static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa
 
 /* Run from main thread */
 static int setup_transport(struct userdata *u) {
-    pa_bluetooth_transport *t;
+    pa_bluez4_transport *t;
 
     pa_assert(u);
     pa_assert(!u->transport);
@@ -1833,7 +1833,7 @@ static int setup_transport(struct userdata *u) {
 
     /* check if profile has a transport */
     t = u->device->transports[u->profile];
-    if (!t || t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
+    if (!t || t->state == PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED) {
         pa_log_warn("Profile has no transport");
         return -1;
     }
@@ -2028,9 +2028,9 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
     d = PA_CARD_PROFILE_DATA(new_profile);
 
     if (*d != PROFILE_OFF) {
-        const pa_bluetooth_device *device = u->device;
+        const pa_bluez4_device *device = u->device;
 
-        if (!device->transports[*d] || device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
+        if (!device->transports[*d] || device->transports[*d]->state == PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED) {
             pa_log_warn("Profile not connected, refused to switch profile to %s", new_profile->name);
             return -PA_ERR_IO;
         }
@@ -2078,51 +2078,51 @@ static void create_card_ports(struct userdata *u, pa_hashmap *ports) {
     pa_assert(ports);
     pa_assert(u->device);
 
-    switch (pa_bluetooth_get_form_factor(u->device->class)) {
-        case PA_BT_FORM_FACTOR_UNKNOWN:
+    switch (pa_bluez4_get_form_factor(u->device->class)) {
+        case PA_BLUEZ4_FORM_FACTOR_UNKNOWN:
             break;
 
-        case PA_BT_FORM_FACTOR_HEADSET:
+        case PA_BLUEZ4_FORM_FACTOR_HEADSET:
             name_prefix = "headset";
             input_description = output_description = _("Headset");
             break;
 
-        case PA_BT_FORM_FACTOR_HANDSFREE:
+        case PA_BLUEZ4_FORM_FACTOR_HANDSFREE:
             name_prefix = "handsfree";
             input_description = output_description = _("Handsfree");
             break;
 
-        case PA_BT_FORM_FACTOR_MICROPHONE:
+        case PA_BLUEZ4_FORM_FACTOR_MICROPHONE:
             name_prefix = "microphone";
             input_description = _("Microphone");
             break;
 
-        case PA_BT_FORM_FACTOR_SPEAKER:
+        case PA_BLUEZ4_FORM_FACTOR_SPEAKER:
             name_prefix = "speaker";
             output_description = _("Speaker");
             break;
 
-        case PA_BT_FORM_FACTOR_HEADPHONE:
+        case PA_BLUEZ4_FORM_FACTOR_HEADPHONE:
             name_prefix = "headphone";
             output_description = _("Headphone");
             break;
 
-        case PA_BT_FORM_FACTOR_PORTABLE:
+        case PA_BLUEZ4_FORM_FACTOR_PORTABLE:
             name_prefix = "portable";
             input_description = output_description = _("Portable");
             break;
 
-        case PA_BT_FORM_FACTOR_CAR:
+        case PA_BLUEZ4_FORM_FACTOR_CAR:
             name_prefix = "car";
             input_description = output_description = _("Car");
             break;
 
-        case PA_BT_FORM_FACTOR_HIFI:
+        case PA_BLUEZ4_FORM_FACTOR_HIFI:
             name_prefix = "hifi";
             input_description = output_description = _("HiFi");
             break;
 
-        case PA_BT_FORM_FACTOR_PHONE:
+        case PA_BLUEZ4_FORM_FACTOR_PHONE:
             name_prefix = "phone";
             input_description = output_description = _("Phone");
             break;
@@ -2219,7 +2219,7 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
     }
 
     if (p) {
-        pa_bluetooth_transport *t;
+        pa_bluez4_transport *t;
 
         if ((t = u->device->transports[*d]))
             p->available = transport_state_to_availability(t->state);
@@ -2234,11 +2234,11 @@ static int add_card(struct userdata *u) {
     bool b;
     pa_card_profile *p;
     enum profile *d;
-    pa_bt_form_factor_t ff;
+    pa_bluez4_form_factor_t ff;
     char *n;
     const char *default_profile;
-    const pa_bluetooth_device *device;
-    const pa_bluetooth_uuid *uuid;
+    const pa_bluez4_device *device;
+    const pa_bluez4_uuid *uuid;
 
     pa_assert(u);
     pa_assert(u->device);
@@ -2249,7 +2249,7 @@ static int add_card(struct userdata *u) {
     data.driver = __FILE__;
     data.module = u->module;
 
-    n = pa_bluetooth_cleanup_name(device->alias);
+    n = pa_bluez4_cleanup_name(device->alias);
     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, n);
     pa_xfree(n);
     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, device->address);
@@ -2257,8 +2257,8 @@ static int add_card(struct userdata *u) {
     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "sound");
     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_BUS, "bluetooth");
 
-    if ((ff = pa_bluetooth_get_form_factor(device->class)) != PA_BT_FORM_FACTOR_UNKNOWN)
-        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, pa_bt_form_factor_to_string(ff));
+    if ((ff = pa_bluez4_get_form_factor(device->class)) != PA_BLUEZ4_FORM_FACTOR_UNKNOWN)
+        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, pa_bluez4_form_factor_to_string(ff));
 
     pa_proplist_sets(data.proplist, "bluez.path", device->path);
     pa_proplist_setf(data.proplist, "bluez.class", "0x%06x", (unsigned) device->class);
@@ -2317,7 +2317,7 @@ static int add_card(struct userdata *u) {
     d = PA_CARD_PROFILE_DATA(u->card->active_profile);
 
     if (*d != PROFILE_OFF && (!device->transports[*d] ||
-                              device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)) {
+                              device->transports[*d]->state == PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED)) {
         pa_log_warn("Default profile not connected, selecting off profile");
         u->card->active_profile = pa_hashmap_get(u->card->profiles, "off");
         u->card->save_profile = false;
@@ -2333,8 +2333,8 @@ static int add_card(struct userdata *u) {
 }
 
 /* Run from main thread */
-static pa_bluetooth_device* find_device(struct userdata *u, const char *address, const char *path) {
-    pa_bluetooth_device *d = NULL;
+static pa_bluez4_device* find_device(struct userdata *u, const char *address, const char *path) {
+    pa_bluez4_device *d = NULL;
 
     pa_assert(u);
 
@@ -2344,7 +2344,7 @@ static pa_bluetooth_device* find_device(struct userdata *u, const char *address,
     }
 
     if (path) {
-        if (!(d = pa_bluetooth_discovery_get_by_path(u->discovery, path))) {
+        if (!(d = pa_bluez4_discovery_get_by_path(u->discovery, path))) {
             pa_log_error("%s is not a valid BlueZ audio device.", path);
             return NULL;
         }
@@ -2355,7 +2355,7 @@ static pa_bluetooth_device* find_device(struct userdata *u, const char *address,
         }
 
     } else {
-        if (!(d = pa_bluetooth_discovery_get_by_address(u->discovery, address))) {
+        if (!(d = pa_bluez4_discovery_get_by_address(u->discovery, address))) {
             pa_log_error("%s is not known.", address);
             return NULL;
         }
@@ -2370,7 +2370,7 @@ static pa_bluetooth_device* find_device(struct userdata *u, const char *address,
 }
 
 /* Run from main thread */
-static pa_hook_result_t uuid_added_cb(pa_bluetooth_discovery *y, const struct pa_bluetooth_hook_uuid_data *data,
+static pa_hook_result_t uuid_added_cb(pa_bluez4_discovery *y, const struct pa_bluez4_hook_uuid_data *data,
                                       struct userdata *u) {
     pa_card_profile *p;
 
@@ -2398,7 +2398,7 @@ static pa_hook_result_t uuid_added_cb(pa_bluetooth_discovery *y, const struct pa
 }
 
 /* Run from main thread */
-static pa_hook_result_t discovery_hook_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
+static pa_hook_result_t discovery_hook_cb(pa_bluez4_discovery *y, const pa_bluez4_device *d, struct userdata *u) {
     pa_assert(u);
     pa_assert(d);
 
@@ -2407,7 +2407,7 @@ static pa_hook_result_t discovery_hook_cb(pa_bluetooth_discovery *y, const pa_bl
 
     if (d->dead)
         pa_log_debug("Device %s removed: unloading module", d->path);
-    else if (!pa_bluetooth_device_any_audio_connected(d))
+    else if (!pa_bluez4_device_any_audio_connected(d))
         pa_log_debug("Unloading module, because device %s doesn't have any audio profiles connected anymore.", d->path);
     else
         return PA_HOOK_OK;
@@ -2422,7 +2422,7 @@ int pa__init(pa_module *m) {
     uint32_t channels;
     struct userdata *u;
     const char *address, *path;
-    pa_bluetooth_device *device;
+    pa_bluez4_device *device;
 
     pa_assert(m);
 
@@ -2474,7 +2474,7 @@ int pa__init(pa_module *m) {
     address = pa_modargs_get_value(ma, "address", NULL);
     path = pa_modargs_get_value(ma, "path", NULL);
 
-    if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))
+    if (!(u->discovery = pa_bluez4_discovery_get(m->core)))
         goto fail;
 
     if (!(device = find_device(u, address, path)))
@@ -2483,11 +2483,11 @@ int pa__init(pa_module *m) {
     u->device = device;
 
     u->discovery_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
+        pa_hook_connect(pa_bluez4_discovery_hook(u->discovery, PA_BLUEZ4_HOOK_DEVICE_CONNECTION_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) discovery_hook_cb, u);
 
     u->uuid_added_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED),
+        pa_hook_connect(pa_bluez4_discovery_hook(u->discovery, PA_BLUEZ4_HOOK_DEVICE_UUID_ADDED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) uuid_added_cb, u);
 
     u->sink_state_changed_slot =
@@ -2499,19 +2499,19 @@ int pa__init(pa_module *m) {
                         PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_cb, u);
 
     u->transport_state_changed_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED),
+        pa_hook_connect(pa_bluez4_discovery_hook(u->discovery, PA_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u);
 
     u->transport_nrec_changed_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED),
+        pa_hook_connect(pa_bluez4_discovery_hook(u->discovery, PA_BLUEZ4_HOOK_TRANSPORT_NREC_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) transport_nrec_changed_cb, u);
 
     u->transport_microphone_changed_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED),
+        pa_hook_connect(pa_bluez4_discovery_hook(u->discovery, PA_BLUEZ4_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
 
     u->transport_speaker_changed_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED),
+        pa_hook_connect(pa_bluez4_discovery_hook(u->discovery, PA_BLUEZ4_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) transport_speaker_gain_changed_cb, u);
 
     /* Add the card structure. This will also initialize the default profile */
@@ -2617,7 +2617,7 @@ void pa__done(pa_module *m) {
     pa_xfree(u->path);
 
     if (u->discovery)
-        pa_bluetooth_discovery_unref(u->discovery);
+        pa_bluez4_discovery_unref(u->discovery);
 
     pa_xfree(u);
 }
diff --git a/src/modules/bluetooth/module-bluez4-discover.c b/src/modules/bluetooth/module-bluez4-discover.c
index e5b30f5..7673ba7 100644
--- a/src/modules/bluetooth/module-bluez4-discover.c
+++ b/src/modules/bluetooth/module-bluez4-discover.c
@@ -55,7 +55,7 @@ struct userdata {
     pa_module *module;
     pa_modargs *modargs;
     pa_core *core;
-    pa_bluetooth_discovery *discovery;
+    pa_bluez4_discovery *discovery;
     pa_hook_slot *slot;
     pa_hashmap *hashmap;
 };
@@ -65,7 +65,7 @@ struct module_info {
     uint32_t module;
 };
 
-static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
+static pa_hook_result_t load_module_for_device(pa_bluez4_discovery *y, const pa_bluez4_device *d, struct userdata *u) {
     struct module_info *mi;
 
     pa_assert(u);
@@ -73,7 +73,7 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const
 
     mi = pa_hashmap_get(u->hashmap, d->path);
 
-    if (pa_bluetooth_device_any_audio_connected(d)) {
+    if (pa_bluez4_device_any_audio_connected(d)) {
 
         if (!mi) {
             pa_module *m = NULL;
@@ -145,10 +145,10 @@ int pa__init(pa_module* m) {
     ma = NULL;
     u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
 
-    if (!(u->discovery = pa_bluetooth_discovery_get(u->core)))
+    if (!(u->discovery = pa_bluez4_discovery_get(u->core)))
         goto fail;
 
-    u->slot = pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
+    u->slot = pa_hook_connect(pa_bluez4_discovery_hook(u->discovery, PA_BLUEZ4_HOOK_DEVICE_CONNECTION_CHANGED),
                               PA_HOOK_NORMAL, (pa_hook_cb_t) load_module_for_device, u);
 
     return 0;
@@ -174,7 +174,7 @@ void pa__done(pa_module* m) {
         pa_hook_slot_free(u->slot);
 
     if (u->discovery)
-        pa_bluetooth_discovery_unref(u->discovery);
+        pa_bluez4_discovery_unref(u->discovery);
 
     if (u->hashmap) {
         struct module_info *mi;

commit 438094baa41ce21fa9425882a15df476b5ab776c
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:53 2013 -0300

    bluetooth: Rename shared struct to make it BlueZ 4 specific

diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
index f755069..1d38f0d 100644
--- a/src/modules/bluetooth/bluez4-util.c
+++ b/src/modules/bluetooth/bluez4-util.c
@@ -1654,7 +1654,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
 
     dbus_error_init(&err);
 
-    if ((y = pa_shared_get(c, "bluetooth-discovery")))
+    if ((y = pa_shared_get(c, "bluez4-discovery")))
         return pa_bluetooth_discovery_ref(y);
 
     y = pa_xnew0(pa_bluetooth_discovery, 1);
@@ -1667,7 +1667,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
         pa_hook_init(&y->hooks[i], y);
 
-    pa_shared_set(c, "bluetooth-discovery", y);
+    pa_shared_set(c, "bluez4-discovery", y);
 
     if (setup_dbus(y) < 0)
         goto fail;
@@ -1781,7 +1781,7 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
         pa_hook_done(&y->hooks[i]);
 
     if (y->core)
-        pa_shared_remove(y->core, "bluetooth-discovery");
+        pa_shared_remove(y->core, "bluez4-discovery");
 
     pa_xfree(y);
 }

commit e414b5828b43bc3817eeea0f4a2017c5356bf857
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:52 2013 -0300

    bluetooth: Rename bluetooth modules to BlueZ 4

diff --git a/src/Makefile.am b/src/Makefile.am
index 8392953..9113d83 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1322,11 +1322,11 @@ endif
 
 if HAVE_BLUEZ
 modlibexec_LTLIBRARIES += \
-		libbluetooth-util.la \
 		module-bluetooth-proximity.la \
-		module-bluetooth-discover.la \
 		module-bluetooth-policy.la \
-		module-bluetooth-device.la
+		libbluez4-util.la \
+		module-bluez4-discover.la \
+		module-bluez4-device.la
 
 pulselibexec_PROGRAMS += \
 		proximity-helper
@@ -1419,9 +1419,9 @@ SYMDEF_FILES = \
 		module-udev-detect-symdef.h \
 		module-systemd-login-symdef.h \
 		module-bluetooth-proximity-symdef.h \
-		module-bluetooth-discover-symdef.h \
 		module-bluetooth-policy-symdef.h \
-		module-bluetooth-device-symdef.h \
+		module-bluez4-discover-symdef.h \
+		module-bluez4-device-symdef.h \
 		module-raop-sink-symdef.h \
 		module-raop-discover-symdef.h \
 		module-gconf-symdef.h \
@@ -2028,25 +2028,24 @@ proximity_helper_LDADD = $(AM_LDADD) $(BLUEZ_LIBS)
 proximity_helper_CFLAGS = $(AM_CFLAGS) $(BLUEZ_CFLAGS)
 proximity_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
 
-# Bluetooth sink / source
-module_bluetooth_discover_la_SOURCES = modules/bluetooth/module-bluetooth-discover.c
-module_bluetooth_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
-module_bluetooth_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluetooth-util.la
-module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+# Bluetooth BlueZ 4 sink / source
+module_bluez4_discover_la_SOURCES = modules/bluetooth/module-bluez4-discover.c
+module_bluez4_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_bluez4_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluez4-util.la
+module_bluez4_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
 
-
-libbluetooth_util_la_SOURCES = \
+libbluez4_util_la_SOURCES = \
 		modules/bluetooth/a2dp-codecs.h \
-		modules/bluetooth/bluetooth-util.c \
-		modules/bluetooth/bluetooth-util.h
-libbluetooth_util_la_LDFLAGS = -avoid-version
-libbluetooth_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
-libbluetooth_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
-
-module_bluetooth_device_la_SOURCES = modules/bluetooth/module-bluetooth-device.c modules/bluetooth/rtp.h
-module_bluetooth_device_la_LDFLAGS = $(MODULE_LDFLAGS)
-module_bluetooth_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbluetooth-util.la
-module_bluetooth_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
+		modules/bluetooth/bluez4-util.c \
+		modules/bluetooth/bluez4-util.h
+libbluez4_util_la_LDFLAGS = -avoid-version
+libbluez4_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
+libbluez4_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+
+module_bluez4_device_la_SOURCES = modules/bluetooth/module-bluez4-device.c modules/bluetooth/rtp.h
+module_bluez4_device_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_bluez4_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbluez4-util.la
+module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
 
 module_bluetooth_policy_la_SOURCES = modules/bluetooth/module-bluetooth-policy.c
 module_bluetooth_policy_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
deleted file mode 100644
index 49831e6..0000000
--- a/src/modules/bluetooth/bluetooth-util.c
+++ /dev/null
@@ -1,1905 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2008-2013 João Paulo Rechi Vita
-
-  PulseAudio is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Lesser General Public License as
-  published by the Free Software Foundation; either version 2.1 of the
-  License, or (at your option) any later version.
-
-  PulseAudio 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 Lesser General Public
-  License along with PulseAudio; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-  USA.
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <pulse/xmalloc.h>
-
-#include <pulsecore/core-util.h>
-#include <pulsecore/shared.h>
-#include <pulsecore/dbus-shared.h>
-
-#include "bluetooth-util.h"
-#include "a2dp-codecs.h"
-
-#define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG"
-#define HFP_HS_ENDPOINT "/MediaEndpoint/HFPHS"
-#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
-#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
-
-#define ENDPOINT_INTROSPECT_XML                                         \
-    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
-    "<node>"                                                            \
-    " <interface name=\"org.bluez.MediaEndpoint\">"                     \
-    "  <method name=\"SetConfiguration\">"                              \
-    "   <arg name=\"transport\" direction=\"in\" type=\"o\"/>"          \
-    "   <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>"     \
-    "  </method>"                                                       \
-    "  <method name=\"SelectConfiguration\">"                           \
-    "   <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>"      \
-    "   <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>"    \
-    "  </method>"                                                       \
-    "  <method name=\"ClearConfiguration\">"                            \
-    "  </method>"                                                       \
-    "  <method name=\"Release\">"                                       \
-    "  </method>"                                                       \
-    " </interface>"                                                     \
-    " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
-    "  <method name=\"Introspect\">"                                    \
-    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
-    "  </method>"                                                       \
-    " </interface>"                                                     \
-    "</node>"
-
-struct pa_bluetooth_discovery {
-    PA_REFCNT_DECLARE;
-
-    pa_core *core;
-    pa_dbus_connection *connection;
-    PA_LLIST_HEAD(pa_dbus_pending, pending);
-    bool adapters_listed;
-    pa_hashmap *devices;
-    pa_hashmap *transports;
-    pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
-    bool filter_added;
-};
-
-static void get_properties_reply(DBusPendingCall *pending, void *userdata);
-static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
-                                                void *call_data);
-static void found_adapter(pa_bluetooth_discovery *y, const char *path);
-static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path);
-
-static pa_bt_audio_state_t audio_state_from_string(const char* value) {
-    pa_assert(value);
-
-    if (pa_streq(value, "disconnected"))
-        return PA_BT_AUDIO_STATE_DISCONNECTED;
-    else if (pa_streq(value, "connecting"))
-        return PA_BT_AUDIO_STATE_CONNECTING;
-    else if (pa_streq(value, "connected"))
-        return PA_BT_AUDIO_STATE_CONNECTED;
-    else if (pa_streq(value, "playing"))
-        return PA_BT_AUDIO_STATE_PLAYING;
-
-    return PA_BT_AUDIO_STATE_INVALID;
-}
-
-const char *pa_bt_profile_to_string(enum profile profile) {
-    switch(profile) {
-        case PROFILE_A2DP:
-            return "a2dp";
-        case PROFILE_A2DP_SOURCE:
-            return "a2dp_source";
-        case PROFILE_HSP:
-            return "hsp";
-        case PROFILE_HFGW:
-            return "hfgw";
-        case PROFILE_OFF:
-            pa_assert_not_reached();
-    }
-
-    pa_assert_not_reached();
-}
-
-static int profile_from_interface(const char *interface, enum profile *p) {
-    pa_assert(interface);
-    pa_assert(p);
-
-    if (pa_streq(interface, "org.bluez.AudioSink")) {
-        *p = PROFILE_A2DP;
-        return 0;
-    } else if (pa_streq(interface, "org.bluez.AudioSource")) {
-        *p = PROFILE_A2DP_SOURCE;
-        return 0;
-    } else if (pa_streq(interface, "org.bluez.Headset")) {
-        *p = PROFILE_HSP;
-        return 0;
-    } else if (pa_streq(interface, "org.bluez.HandsfreeGateway")) {
-        *p = PROFILE_HFGW;
-        return 0;
-    }
-
-    return -1;
-}
-
-static pa_bluetooth_transport_state_t audio_state_to_transport_state(pa_bt_audio_state_t state) {
-    switch (state) {
-        case PA_BT_AUDIO_STATE_INVALID: /* Typically if state hasn't been received yet */
-        case PA_BT_AUDIO_STATE_DISCONNECTED:
-        case PA_BT_AUDIO_STATE_CONNECTING:
-            return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
-        case PA_BT_AUDIO_STATE_CONNECTED:
-            return PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
-        case PA_BT_AUDIO_STATE_PLAYING:
-            return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
-    }
-
-    pa_assert_not_reached();
-}
-
-static pa_bluetooth_uuid *uuid_new(const char *uuid) {
-    pa_bluetooth_uuid *u;
-
-    u = pa_xnew(pa_bluetooth_uuid, 1);
-    u->uuid = pa_xstrdup(uuid);
-    PA_LLIST_INIT(pa_bluetooth_uuid, u);
-
-    return u;
-}
-
-static void uuid_free(pa_bluetooth_uuid *u) {
-    pa_assert(u);
-
-    pa_xfree(u->uuid);
-    pa_xfree(u);
-}
-
-static pa_bluetooth_device* device_new(pa_bluetooth_discovery *discovery, const char *path) {
-    pa_bluetooth_device *d;
-    unsigned i;
-
-    pa_assert(discovery);
-    pa_assert(path);
-
-    d = pa_xnew0(pa_bluetooth_device, 1);
-
-    d->discovery = discovery;
-    d->dead = false;
-
-    d->device_info_valid = 0;
-
-    d->name = NULL;
-    d->path = pa_xstrdup(path);
-    d->paired = -1;
-    d->alias = NULL;
-    PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
-    d->address = NULL;
-    d->class = -1;
-    d->trusted = -1;
-
-    d->audio_state = PA_BT_AUDIO_STATE_INVALID;
-
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
-        d->profile_state[i] = PA_BT_AUDIO_STATE_INVALID;
-
-    return d;
-}
-
-static void transport_free(pa_bluetooth_transport *t) {
-    pa_assert(t);
-
-    pa_xfree(t->owner);
-    pa_xfree(t->path);
-    pa_xfree(t->config);
-    pa_xfree(t);
-}
-
-static void device_free(pa_bluetooth_device *d) {
-    pa_bluetooth_uuid *u;
-    pa_bluetooth_transport *t;
-    unsigned i;
-
-    pa_assert(d);
-
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
-        if (!(t = d->transports[i]))
-            continue;
-
-        d->transports[i] = NULL;
-        pa_hashmap_remove(d->discovery->transports, t->path);
-        t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
-        pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
-        transport_free(t);
-    }
-
-    while ((u = d->uuids)) {
-        PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
-        uuid_free(u);
-    }
-
-    pa_xfree(d->name);
-    pa_xfree(d->path);
-    pa_xfree(d->alias);
-    pa_xfree(d->address);
-    pa_xfree(d);
-}
-
-static const char *check_variant_property(DBusMessageIter *i) {
-    const char *key;
-
-    pa_assert(i);
-
-    if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
-        pa_log("Property name not a string.");
-        return NULL;
-    }
-
-    dbus_message_iter_get_basic(i, &key);
-
-    if (!dbus_message_iter_next(i)) {
-        pa_log("Property value missing");
-        return NULL;
-    }
-
-    if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
-        pa_log("Property value not a variant.");
-        return NULL;
-    }
-
-    return key;
-}
-
-static int parse_manager_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) {
-    const char *key;
-    DBusMessageIter variant_i;
-
-    pa_assert(y);
-
-    key = check_variant_property(i);
-    if (key == NULL)
-        return -1;
-
-    dbus_message_iter_recurse(i, &variant_i);
-
-    switch (dbus_message_iter_get_arg_type(&variant_i)) {
-
-        case DBUS_TYPE_ARRAY: {
-
-            DBusMessageIter ai;
-            dbus_message_iter_recurse(&variant_i, &ai);
-
-            if (pa_streq(key, "Adapters")) {
-                y->adapters_listed = true;
-
-                if (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_OBJECT_PATH)
-                    break;
-
-                while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
-                    const char *value;
-
-                    dbus_message_iter_get_basic(&ai, &value);
-
-                    found_adapter(y, value);
-
-                    dbus_message_iter_next(&ai);
-                }
-            }
-
-            break;
-        }
-    }
-
-    return 0;
-}
-
-static int parse_adapter_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) {
-    const char *key;
-    DBusMessageIter variant_i;
-
-    pa_assert(y);
-
-    key = check_variant_property(i);
-    if (key == NULL)
-        return -1;
-
-    dbus_message_iter_recurse(i, &variant_i);
-
-    switch (dbus_message_iter_get_arg_type(&variant_i)) {
-
-        case DBUS_TYPE_ARRAY: {
-
-            DBusMessageIter ai;
-            dbus_message_iter_recurse(&variant_i, &ai);
-
-            if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_OBJECT_PATH &&
-                pa_streq(key, "Devices")) {
-
-                while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
-                    const char *value;
-
-                    dbus_message_iter_get_basic(&ai, &value);
-
-                    found_device(y, value);
-
-                    dbus_message_iter_next(&ai);
-                }
-            }
-
-            break;
-        }
-    }
-
-    return 0;
-}
-
-static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
-    const char *key;
-    DBusMessageIter variant_i;
-
-    pa_assert(d);
-
-    key = check_variant_property(i);
-    if (key == NULL)
-        return -1;
-
-    dbus_message_iter_recurse(i, &variant_i);
-
-/*     pa_log_debug("Parsing property org.bluez.Device.%s", key); */
-
-    switch (dbus_message_iter_get_arg_type(&variant_i)) {
-
-        case DBUS_TYPE_STRING: {
-
-            const char *value;
-            dbus_message_iter_get_basic(&variant_i, &value);
-
-            if (pa_streq(key, "Name")) {
-                pa_xfree(d->name);
-                d->name = pa_xstrdup(value);
-            } else if (pa_streq(key, "Alias")) {
-                pa_xfree(d->alias);
-                d->alias = pa_xstrdup(value);
-            } else if (pa_streq(key, "Address")) {
-                if (is_property_change) {
-                    pa_log("Device property 'Address' expected to be constant but changed for %s", d->path);
-                    return -1;
-                }
-
-                if (d->address) {
-                    pa_log("Device %s: Received a duplicate Address property.", d->path);
-                    return -1;
-                }
-
-                d->address = pa_xstrdup(value);
-            }
-
-/*             pa_log_debug("Value %s", value); */
-
-            break;
-        }
-
-        case DBUS_TYPE_BOOLEAN: {
-
-            dbus_bool_t value;
-            dbus_message_iter_get_basic(&variant_i, &value);
-
-            if (pa_streq(key, "Paired"))
-                d->paired = !!value;
-            else if (pa_streq(key, "Trusted"))
-                d->trusted = !!value;
-
-/*             pa_log_debug("Value %s", pa_yes_no(value)); */
-
-            break;
-        }
-
-        case DBUS_TYPE_UINT32: {
-
-            uint32_t value;
-            dbus_message_iter_get_basic(&variant_i, &value);
-
-            if (pa_streq(key, "Class"))
-                d->class = (int) value;
-
-/*             pa_log_debug("Value %u", (unsigned) value); */
-
-            break;
-        }
-
-        case DBUS_TYPE_ARRAY: {
-
-            DBusMessageIter ai;
-            dbus_message_iter_recurse(&variant_i, &ai);
-
-            if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && pa_streq(key, "UUIDs")) {
-                DBusMessage *m;
-                bool has_audio = false;
-
-                while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
-                    pa_bluetooth_uuid *node;
-                    const char *value;
-                    struct pa_bluetooth_hook_uuid_data uuiddata;
-
-                    dbus_message_iter_get_basic(&ai, &value);
-
-                    if (pa_bluetooth_uuid_has(d->uuids, value)) {
-                        dbus_message_iter_next(&ai);
-                        continue;
-                    }
-
-                    node = uuid_new(value);
-                    PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
-
-                    uuiddata.device = d;
-                    uuiddata.uuid = value;
-                    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata);
-
-                    /* Vudentz said the interfaces are here when the UUIDs are announced */
-                    if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
-                        pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway",
-                                                                      "GetProperties"));
-                        send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
-                        has_audio = true;
-                    } else if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
-                        pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset",
-                                                                      "GetProperties"));
-                        send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
-                        has_audio = true;
-                    } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
-                        pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink",
-                                                                      "GetProperties"));
-                        send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
-                        has_audio = true;
-                    } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
-                        pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource",
-                                                                      "GetProperties"));
-                        send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
-                        has_audio = true;
-                    }
-
-                    dbus_message_iter_next(&ai);
-                }
-
-                /* this might eventually be racy if .Audio is not there yet, but
-                   the State change will come anyway later, so this call is for
-                   cold-detection mostly */
-                if (has_audio) {
-                    pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
-                    send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
-                }
-            }
-
-            break;
-        }
-    }
-
-    return 0;
-}
-
-static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
-    switch (state) {
-        case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
-            return "disconnected";
-        case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
-            return "idle";
-        case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
-            return "playing";
-    }
-
-    pa_assert_not_reached();
-}
-
-static int parse_audio_property(pa_bluetooth_device *d, const char *interface, DBusMessageIter *i, bool is_property_change) {
-    pa_bluetooth_transport *transport;
-    const char *key;
-    DBusMessageIter variant_i;
-    bool is_audio_interface;
-    enum profile p = PROFILE_OFF;
-
-    pa_assert(d);
-    pa_assert(interface);
-    pa_assert(i);
-
-    if (!(is_audio_interface = pa_streq(interface, "org.bluez.Audio")))
-        if (profile_from_interface(interface, &p) < 0)
-            return 0; /* Interface not known so silently ignore property */
-
-    key = check_variant_property(i);
-    if (key == NULL)
-        return -1;
-
-    transport = p == PROFILE_OFF ? NULL : d->transports[p];
-
-    dbus_message_iter_recurse(i, &variant_i);
-
-/*     pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
-
-    switch (dbus_message_iter_get_arg_type(&variant_i)) {
-
-        case DBUS_TYPE_STRING: {
-
-            const char *value;
-            dbus_message_iter_get_basic(&variant_i, &value);
-
-            if (pa_streq(key, "State")) {
-                pa_bt_audio_state_t state = audio_state_from_string(value);
-                pa_bluetooth_transport_state_t old_state;
-
-                pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d->path, interface, value);
-
-                if (state == PA_BT_AUDIO_STATE_INVALID)
-                    return -1;
-
-                if (is_audio_interface) {
-                    d->audio_state = state;
-                    break;
-                }
-
-                pa_assert(p != PROFILE_OFF);
-
-                d->profile_state[p] = state;
-
-                if (!transport)
-                    break;
-
-                old_state = transport->state;
-                transport->state = audio_state_to_transport_state(state);
-
-                if (transport->state != old_state) {
-                    pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport->path,
-                                 pa_bt_profile_to_string(transport->profile), transport_state_to_string(old_state),
-                                 transport_state_to_string(transport->state));
-
-                    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], transport);
-                }
-            }
-
-            break;
-        }
-
-        case DBUS_TYPE_UINT16: {
-            uint16_t value;
-
-            dbus_message_iter_get_basic(&variant_i, &value);
-
-            if (pa_streq(key, "MicrophoneGain")) {
-                uint16_t gain;
-
-                pa_log_debug("dbus: property '%s' changed to value '%u'", key, value);
-
-                if (!transport) {
-                    pa_log("Volume change does not have an associated transport");
-                    return -1;
-                }
-
-                if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->microphone_gain)
-                    break;
-
-                transport->microphone_gain = gain;
-                pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED], transport);
-            } else if (pa_streq(key, "SpeakerGain")) {
-                uint16_t gain;
-
-                pa_log_debug("dbus: property '%s' changed to value '%u'", key, value);
-
-                if (!transport) {
-                    pa_log("Volume change does not have an associated transport");
-                    return -1;
-                }
-
-                if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->speaker_gain)
-                    break;
-
-                transport->speaker_gain = gain;
-                pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED], transport);
-            }
-
-            break;
-        }
-    }
-
-    return 0;
-}
-
-static void run_callback(pa_bluetooth_device *d, bool dead) {
-    pa_assert(d);
-
-    if (d->device_info_valid != 1)
-        return;
-
-    d->dead = dead;
-    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d);
-}
-
-static void remove_all_devices(pa_bluetooth_discovery *y) {
-    pa_bluetooth_device *d;
-
-    pa_assert(y);
-
-    while ((d = pa_hashmap_steal_first(y->devices))) {
-        run_callback(d, true);
-        device_free(d);
-    }
-}
-
-static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) {
-    DBusMessage *m;
-    pa_bluetooth_device *d;
-
-    pa_assert(y);
-    pa_assert(path);
-
-    d = pa_hashmap_get(y->devices, path);
-    if (d)
-        return d;
-
-    d = device_new(y, path);
-
-    pa_hashmap_put(y->devices, d->path, d);
-
-    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
-    send_and_add_to_pending(y, m, get_properties_reply, d);
-
-    /* Before we read the other properties (Audio, AudioSink, AudioSource,
-     * Headset) we wait that the UUID is read */
-    return d;
-}
-
-static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
-    DBusMessage *r;
-    DBusMessageIter arg_i, element_i;
-    pa_dbus_pending *p;
-    pa_bluetooth_device *d;
-    pa_bluetooth_discovery *y;
-    int valid;
-    bool old_any_connected;
-
-    pa_assert_se(p = userdata);
-    pa_assert_se(y = p->context_data);
-    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
-
-/*     pa_log_debug("Got %s.GetProperties response for %s", */
-/*                  dbus_message_get_interface(p->message), */
-/*                  dbus_message_get_path(p->message)); */
-
-    /* We don't use p->call_data here right-away since the device
-     * might already be invalidated at this point */
-
-    if (dbus_message_has_interface(p->message, "org.bluez.Manager") ||
-        dbus_message_has_interface(p->message, "org.bluez.Adapter"))
-        d = NULL;
-    else if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message)))) {
-        pa_log_warn("Received GetProperties() reply from unknown device: %s (device removed?)", dbus_message_get_path(p->message));
-        goto finish2;
-    }
-
-    pa_assert(p->call_data == d);
-
-    if (d != NULL)
-        old_any_connected = pa_bluetooth_device_any_audio_connected(d);
-
-    valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
-
-    if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
-        d->device_info_valid = valid;
-
-    if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
-        pa_log_debug("Bluetooth daemon is apparently not available.");
-        remove_all_devices(y);
-        goto finish2;
-    }
-
-    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
-        pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p->message), dbus_message_get_error_name(r),
-               pa_dbus_get_error_message(r));
-        goto finish;
-    }
-
-    if (!dbus_message_iter_init(r, &arg_i)) {
-        pa_log("GetProperties reply has no arguments.");
-        goto finish;
-    }
-
-    if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
-        pa_log("GetProperties argument is not an array.");
-        goto finish;
-    }
-
-    dbus_message_iter_recurse(&arg_i, &element_i);
-    while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
-
-        if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
-            DBusMessageIter dict_i;
-
-            dbus_message_iter_recurse(&element_i, &dict_i);
-
-            if (dbus_message_has_interface(p->message, "org.bluez.Manager")) {
-                if (parse_manager_property(y, &dict_i, false) < 0)
-                    goto finish;
-
-            } else if (dbus_message_has_interface(p->message, "org.bluez.Adapter")) {
-                if (parse_adapter_property(y, &dict_i, false) < 0)
-                    goto finish;
-
-            } else if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
-                if (parse_device_property(d, &dict_i, false) < 0)
-                    goto finish;
-
-            } else if (parse_audio_property(d, dbus_message_get_interface(p->message), &dict_i, false) < 0)
-                goto finish;
-
-        }
-
-        dbus_message_iter_next(&element_i);
-    }
-
-finish:
-    if (d != NULL && old_any_connected != pa_bluetooth_device_any_audio_connected(d))
-        run_callback(d, false);
-
-finish2:
-    dbus_message_unref(r);
-
-    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
-    pa_dbus_pending_free(p);
-}
-
-static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
-                                                void *call_data) {
-    pa_dbus_pending *p;
-    DBusPendingCall *call;
-
-    pa_assert(y);
-    pa_assert(m);
-
-    pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
-
-    p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data);
-    PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
-    dbus_pending_call_set_notify(call, func, p, NULL);
-
-    return p;
-}
-
-static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
-    DBusMessage *r;
-    pa_dbus_pending *p;
-    pa_bluetooth_discovery *y;
-    char *endpoint;
-
-    pa_assert(pending);
-    pa_assert_se(p = userdata);
-    pa_assert_se(y = p->context_data);
-    pa_assert_se(endpoint = p->call_data);
-    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
-
-    if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
-        pa_log_debug("Bluetooth daemon is apparently not available.");
-        remove_all_devices(y);
-        goto finish;
-    }
-
-    if (dbus_message_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) {
-        pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint);
-        goto finish;
-    }
-
-    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
-        pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r),
-               pa_dbus_get_error_message(r));
-        goto finish;
-    }
-
-finish:
-    dbus_message_unref(r);
-
-    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
-    pa_dbus_pending_free(p);
-
-    pa_xfree(endpoint);
-}
-
-static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
-    DBusMessage *m;
-    DBusMessageIter i, d;
-    uint8_t codec = 0;
-
-    pa_log_debug("Registering %s on adapter %s.", endpoint, path);
-
-    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint"));
-
-    dbus_message_iter_init_append(m, &i);
-
-    dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint);
-
-    dbus_message_iter_open_container(&i, 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,
-                                    &d);
-
-    pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
-
-    pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
-
-    if (pa_streq(uuid, HFP_AG_UUID) || pa_streq(uuid, HFP_HS_UUID)) {
-        uint8_t capability = 0;
-        pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1);
-    } else {
-        a2dp_sbc_t capabilities;
-
-        capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL |
-                                    SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO;
-        capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 |
-                                 SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000;
-        capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
-        capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
-        capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 |
-                                    SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
-        capabilities.min_bitpool = MIN_BITPOOL;
-        capabilities.max_bitpool = MAX_BITPOOL;
-
-        pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
-    }
-
-    dbus_message_iter_close_container(&i, &d);
-
-    send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
-}
-
-static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
-    DBusMessage *m;
-
-    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
-    send_and_add_to_pending(y, m, get_properties_reply, NULL);
-
-    register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
-    register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
-    register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
-    register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
-}
-
-static void list_adapters(pa_bluetooth_discovery *y) {
-    DBusMessage *m;
-    pa_assert(y);
-
-    pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties"));
-    send_and_add_to_pending(y, m, get_properties_reply, NULL);
-}
-
-static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
-    const char *key;
-    DBusMessageIter variant_i;
-
-    key = check_variant_property(i);
-    if (key == NULL)
-        return -1;
-
-    dbus_message_iter_recurse(i, &variant_i);
-
-    switch (dbus_message_iter_get_arg_type(&variant_i)) {
-
-        case DBUS_TYPE_BOOLEAN: {
-
-            dbus_bool_t value;
-            dbus_message_iter_get_basic(&variant_i, &value);
-
-            if (pa_streq(key, "NREC") && t->nrec != value) {
-                t->nrec = value;
-                pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t->path, t->nrec ? "True" : "False");
-                pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED], t);
-            }
-
-            break;
-         }
-    }
-
-    return 0;
-}
-
-static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
-    DBusError err;
-    pa_bluetooth_discovery *y;
-
-    pa_assert(bus);
-    pa_assert(m);
-
-    pa_assert_se(y = userdata);
-
-    dbus_error_init(&err);
-
-    pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
-            dbus_message_get_interface(m),
-            dbus_message_get_path(m),
-            dbus_message_get_member(m));
-
-    if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
-        const char *path;
-        pa_bluetooth_device *d;
-
-        if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
-            pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
-            goto fail;
-        }
-
-        pa_log_debug("Device %s removed", path);
-
-        if ((d = pa_hashmap_remove(y->devices, path))) {
-            run_callback(d, true);
-            device_free(d);
-        }
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
-    } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
-        const char *path;
-
-        if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
-            pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message);
-            goto fail;
-        }
-
-        pa_log_debug("Device %s created", path);
-
-        found_device(y, path);
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
-    } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
-        const char *path;
-
-        if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
-            pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message);
-            goto fail;
-        }
-
-        if (!y->adapters_listed) {
-            pa_log_debug("Ignoring 'AdapterAdded' because initial adapter list has not been received yet.");
-            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-        }
-
-        pa_log_debug("Adapter %s created", path);
-
-        found_adapter(y, path);
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
-    } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
-               dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
-               dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
-               dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
-               dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
-               dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
-
-        pa_bluetooth_device *d;
-
-        if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
-            DBusMessageIter arg_i;
-            bool old_any_connected = pa_bluetooth_device_any_audio_connected(d);
-
-            if (!dbus_message_iter_init(m, &arg_i)) {
-                pa_log("Failed to parse PropertyChanged for device %s", d->path);
-                goto fail;
-            }
-
-            if (dbus_message_has_interface(m, "org.bluez.Device")) {
-                if (parse_device_property(d, &arg_i, true) < 0)
-                    goto fail;
-
-            } else if (parse_audio_property(d, dbus_message_get_interface(m), &arg_i, true) < 0)
-                goto fail;
-
-            if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
-                run_callback(d, false);
-        }
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
-    } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
-        const char *name, *old_owner, *new_owner;
-
-        if (!dbus_message_get_args(m, &err,
-                                   DBUS_TYPE_STRING, &name,
-                                   DBUS_TYPE_STRING, &old_owner,
-                                   DBUS_TYPE_STRING, &new_owner,
-                                   DBUS_TYPE_INVALID)) {
-            pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
-            goto fail;
-        }
-
-        if (pa_streq(name, "org.bluez")) {
-            if (old_owner && *old_owner) {
-                pa_log_debug("Bluetooth daemon disappeared.");
-                remove_all_devices(y);
-                y->adapters_listed = false;
-            }
-
-            if (new_owner && *new_owner) {
-                pa_log_debug("Bluetooth daemon appeared.");
-                list_adapters(y);
-            }
-        }
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
-        pa_bluetooth_transport *t;
-        DBusMessageIter arg_i;
-
-        if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
-            goto fail;
-
-        if (!dbus_message_iter_init(m, &arg_i)) {
-            pa_log("Failed to parse PropertyChanged for transport %s", t->path);
-            goto fail;
-        }
-
-        if (transport_parse_property(t, &arg_i) < 0)
-            goto fail;
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    }
-
-fail:
-    dbus_error_free(&err);
-
-    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-}
-
-pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
-    pa_bluetooth_device *d;
-    void *state = NULL;
-
-    pa_assert(y);
-    pa_assert(PA_REFCNT_VALUE(y) > 0);
-    pa_assert(address);
-
-    while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
-        if (pa_streq(d->address, address))
-            return d->device_info_valid == 1 ? d : NULL;
-
-    return NULL;
-}
-
-pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
-    pa_bluetooth_device *d;
-
-    pa_assert(y);
-    pa_assert(PA_REFCNT_VALUE(y) > 0);
-    pa_assert(path);
-
-    if ((d = pa_hashmap_get(y->devices, path)))
-        if (d->device_info_valid == 1)
-            return d;
-
-    return NULL;
-}
-
-bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
-    unsigned i;
-
-    pa_assert(d);
-
-    if (d->dead || d->device_info_valid != 1)
-        return false;
-
-    if (d->audio_state == PA_BT_AUDIO_STATE_INVALID)
-        return false;
-
-    /* Make sure audio_state is *not* in CONNECTING state before we fire the
-     * hook to report the new device state. This is actually very important in
-     * order to make module-card-restore work well with headsets: if the headset
-     * supports both HSP and A2DP, one of those profiles is connected first and
-     * then the other, and lastly the Audio interface becomes connected.
-     * Checking only audio_state means that this function will return false at
-     * the time when only the first connection has been made. This is good,
-     * because otherwise, if the first connection is for HSP and we would
-     * already load a new device module instance, and module-card-restore tries
-     * to restore the A2DP profile, that would fail because A2DP is not yet
-     * connected. Waiting until the Audio interface gets connected means that
-     * both headset profiles will be connected when the device module is
-     * loaded. */
-    if (d->audio_state == PA_BT_AUDIO_STATE_CONNECTING)
-        return false;
-
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
-        if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
-            return true;
-
-    return false;
-}
-
-int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
-    const char *accesstype = "rw";
-    DBusMessage *m, *r;
-    DBusError err;
-    int ret;
-    uint16_t i, o;
-
-    pa_assert(t);
-    pa_assert(t->device);
-    pa_assert(t->device->discovery);
-
-    if (optional) {
-        /* FIXME: we are trying to acquire the transport only if the stream is
-           playing, without actually initiating the stream request from our side
-           (which is typically undesireable specially for hfgw use-cases.
-           However this approach is racy, since the stream could have been
-           suspended in the meantime, so we can't really guarantee that the
-           stream will not be requested until BlueZ's API supports this
-           atomically. */
-        if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
-            pa_log_info("Failed optional acquire of transport %s", t->path);
-            return -1;
-        }
-    }
-
-    dbus_error_init(&err);
-
-    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Acquire"));
-    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
-    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
-
-    if (!r) {
-        dbus_error_free(&err);
-        return -1;
-    }
-
-    if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o,
-                               DBUS_TYPE_INVALID)) {
-        pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message);
-        ret = -1;
-        dbus_error_free(&err);
-        goto fail;
-    }
-
-    if (imtu)
-        *imtu = i;
-
-    if (omtu)
-        *omtu = o;
-
-fail:
-    dbus_message_unref(r);
-    return ret;
-}
-
-void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
-    const char *accesstype = "rw";
-    DBusMessage *m;
-    DBusError err;
-
-    pa_assert(t);
-    pa_assert(t->device);
-    pa_assert(t->device->discovery);
-
-    dbus_error_init(&err);
-
-    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release"));
-    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
-    dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
-
-    if (dbus_error_is_set(&err)) {
-        pa_log("Failed to release transport %s: %s", t->path, err.message);
-        dbus_error_free(&err);
-    } else
-        pa_log_info("Transport %s released", t->path);
-}
-
-static void set_property(pa_bluetooth_discovery *y, const char *bus, const char *path, const char *interface,
-                         const char *prop_name, int prop_type, void *prop_value) {
-    DBusMessage *m;
-    DBusMessageIter i;
-
-    pa_assert(y);
-    pa_assert(path);
-    pa_assert(interface);
-    pa_assert(prop_name);
-
-    pa_assert_se(m = dbus_message_new_method_call(bus, path, interface, "SetProperty"));
-    dbus_message_iter_init_append(m, &i);
-    dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name);
-    pa_dbus_append_basic_variant(&i, prop_type, prop_value);
-
-    dbus_message_set_no_reply(m, true);
-    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL));
-    dbus_message_unref(m);
-}
-
-void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value) {
-    dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
-
-    pa_assert(t);
-    pa_assert(t->profile == PROFILE_HSP);
-
-    set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
-                 "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
-}
-
-void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value) {
-    dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
-
-    pa_assert(t);
-    pa_assert(t->profile == PROFILE_HSP);
-
-    set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
-                 "SpeakerGain", DBUS_TYPE_UINT16, &gain);
-}
-
-static int setup_dbus(pa_bluetooth_discovery *y) {
-    DBusError err;
-
-    dbus_error_init(&err);
-
-    if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) {
-        pa_log("Failed to get D-Bus connection: %s", err.message);
-        dbus_error_free(&err);
-        return -1;
-    }
-
-    return 0;
-}
-
-static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char *owner, const char *path, enum profile p,
-                                             const uint8_t *config, int size) {
-    pa_bluetooth_transport *t;
-
-    t = pa_xnew0(pa_bluetooth_transport, 1);
-    t->device = d;
-    t->owner = pa_xstrdup(owner);
-    t->path = pa_xstrdup(path);
-    t->profile = p;
-    t->config_size = size;
-
-    if (size > 0) {
-        t->config = pa_xnew(uint8_t, size);
-        memcpy(t->config, config, size);
-    }
-
-    t->state = audio_state_to_transport_state(d->profile_state[p]);
-
-    return t;
-}
-
-static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
-    pa_bluetooth_discovery *y = userdata;
-    pa_bluetooth_device *d;
-    pa_bluetooth_transport *t;
-    const char *sender, *path, *dev_path = NULL, *uuid = NULL;
-    uint8_t *config = NULL;
-    int size = 0;
-    bool nrec = false;
-    enum profile p;
-    DBusMessageIter args, props;
-    DBusMessage *r;
-    bool old_any_connected;
-
-    if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
-        pa_log("Invalid signature for method SetConfiguration");
-        goto fail2;
-    }
-
-    dbus_message_iter_get_basic(&args, &path);
-
-    if (pa_hashmap_get(y->transports, path)) {
-        pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path);
-        goto fail2;
-    }
-
-    pa_assert_se(dbus_message_iter_next(&args));
-
-    dbus_message_iter_recurse(&args, &props);
-    if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
-        goto fail;
-
-    /* Read transport properties */
-    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)
-                goto fail;
-
-            dbus_message_iter_get_basic(&value, &uuid);
-        } else if (strcasecmp(key, "Device") == 0) {
-            if (var != DBUS_TYPE_OBJECT_PATH)
-                goto fail;
-
-            dbus_message_iter_get_basic(&value, &dev_path);
-        } else if (strcasecmp(key, "NREC") == 0) {
-            dbus_bool_t tmp_boolean;
-            if (var != DBUS_TYPE_BOOLEAN)
-                goto fail;
-
-            dbus_message_iter_get_basic(&value, &tmp_boolean);
-            nrec = tmp_boolean;
-        } else if (strcasecmp(key, "Configuration") == 0) {
-            DBusMessageIter array;
-            if (var != DBUS_TYPE_ARRAY)
-                goto fail;
-
-            dbus_message_iter_recurse(&value, &array);
-            dbus_message_iter_get_fixed_array(&array, &config, &size);
-        }
-
-        dbus_message_iter_next(&props);
-    }
-
-    d = found_device(y, dev_path);
-    if (!d)
-        goto fail;
-
-    if (dbus_message_has_path(m, HFP_AG_ENDPOINT))
-        p = PROFILE_HSP;
-    else if (dbus_message_has_path(m, HFP_HS_ENDPOINT))
-        p = PROFILE_HFGW;
-    else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT))
-        p = PROFILE_A2DP;
-    else
-        p = PROFILE_A2DP_SOURCE;
-
-    if (d->transports[p] != NULL) {
-        pa_log("Cannot configure transport %s because profile %d is already used", path, p);
-        goto fail2;
-    }
-
-    old_any_connected = pa_bluetooth_device_any_audio_connected(d);
-
-    sender = dbus_message_get_sender(m);
-
-    t = transport_new(d, sender, path, p, config, size);
-    if (nrec)
-        t->nrec = nrec;
-
-    d->transports[p] = t;
-    pa_assert_se(pa_hashmap_put(y->transports, t->path, t) >= 0);
-
-    pa_log_debug("Transport %s profile %d available", t->path, t->profile);
-
-    pa_assert_se(r = dbus_message_new_method_return(m));
-    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
-    dbus_message_unref(r);
-
-    if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
-        run_callback(d, false);
-
-    return NULL;
-
-fail:
-    pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
-
-fail2:
-    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
-                                            "Unable to set configuration"));
-    return r;
-}
-
-static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
-    pa_bluetooth_discovery *y = userdata;
-    pa_bluetooth_transport *t;
-    DBusMessage *r;
-    DBusError e;
-    const char *path;
-
-    dbus_error_init(&e);
-
-    if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
-        pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message);
-        dbus_error_free(&e);
-        goto fail;
-    }
-
-    if ((t = pa_hashmap_get(y->transports, path))) {
-        bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
-
-        pa_log_debug("Clearing transport %s profile %d", t->path, t->profile);
-        t->device->transports[t->profile] = NULL;
-        pa_hashmap_remove(y->transports, t->path);
-        t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
-        pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
-
-        if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
-            run_callback(t->device, false);
-
-        transport_free(t);
-    }
-
-    pa_assert_se(r = dbus_message_new_method_return(m));
-
-    return r;
-
-fail:
-    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
-                                            "Unable to clear configuration"));
-    return r;
-}
-
-static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
-
-    switch (freq) {
-        case SBC_SAMPLING_FREQ_16000:
-        case SBC_SAMPLING_FREQ_32000:
-            return 53;
-
-        case SBC_SAMPLING_FREQ_44100:
-
-            switch (mode) {
-                case SBC_CHANNEL_MODE_MONO:
-                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                    return 31;
-
-                case SBC_CHANNEL_MODE_STEREO:
-                case SBC_CHANNEL_MODE_JOINT_STEREO:
-                    return 53;
-
-                default:
-                    pa_log_warn("Invalid channel mode %u", mode);
-                    return 53;
-            }
-
-        case SBC_SAMPLING_FREQ_48000:
-
-            switch (mode) {
-                case SBC_CHANNEL_MODE_MONO:
-                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-                    return 29;
-
-                case SBC_CHANNEL_MODE_STEREO:
-                case SBC_CHANNEL_MODE_JOINT_STEREO:
-                    return 51;
-
-                default:
-                    pa_log_warn("Invalid channel mode %u", mode);
-                    return 51;
-            }
-
-        default:
-            pa_log_warn("Invalid sampling freq %u", freq);
-            return 53;
-    }
-}
-
-static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
-    pa_bluetooth_discovery *y = userdata;
-    a2dp_sbc_t *cap, config;
-    uint8_t *pconf = (uint8_t *) &config;
-    int i, size;
-    DBusMessage *r;
-    DBusError e;
-
-    static const struct {
-        uint32_t rate;
-        uint8_t cap;
-    } freq_table[] = {
-        { 16000U, SBC_SAMPLING_FREQ_16000 },
-        { 32000U, SBC_SAMPLING_FREQ_32000 },
-        { 44100U, SBC_SAMPLING_FREQ_44100 },
-        { 48000U, SBC_SAMPLING_FREQ_48000 }
-    };
-
-    dbus_error_init(&e);
-
-    if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
-        pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message);
-        dbus_error_free(&e);
-        goto fail;
-    }
-
-    if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT))
-        goto done;
-
-    pa_assert(size == sizeof(config));
-
-    memset(&config, 0, sizeof(config));
-
-    /* Find the lowest freq that is at least as high as the requested
-     * sampling rate */
-    for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
-        if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
-            config.frequency = freq_table[i].cap;
-            break;
-        }
-
-    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
-        for (--i; i >= 0; i--) {
-            if (cap->frequency & freq_table[i].cap) {
-                config.frequency = freq_table[i].cap;
-                break;
-            }
-        }
-
-        if (i < 0) {
-            pa_log("Not suitable sample rate");
-            goto fail;
-        }
-    }
-
-    pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
-
-    if (y->core->default_sample_spec.channels <= 1) {
-        if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
-            config.channel_mode = SBC_CHANNEL_MODE_MONO;
-    }
-
-    if (y->core->default_sample_spec.channels >= 2) {
-        if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
-            config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
-            config.channel_mode = SBC_CHANNEL_MODE_STEREO;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
-            config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
-        else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) {
-            config.channel_mode = SBC_CHANNEL_MODE_MONO;
-        } else {
-            pa_log("No supported channel modes");
-            goto fail;
-        }
-    }
-
-    if (cap->block_length & SBC_BLOCK_LENGTH_16)
-        config.block_length = SBC_BLOCK_LENGTH_16;
-    else if (cap->block_length & SBC_BLOCK_LENGTH_12)
-        config.block_length = SBC_BLOCK_LENGTH_12;
-    else if (cap->block_length & SBC_BLOCK_LENGTH_8)
-        config.block_length = SBC_BLOCK_LENGTH_8;
-    else if (cap->block_length & SBC_BLOCK_LENGTH_4)
-        config.block_length = SBC_BLOCK_LENGTH_4;
-    else {
-        pa_log_error("No supported block lengths");
-        goto fail;
-    }
-
-    if (cap->subbands & SBC_SUBBANDS_8)
-        config.subbands = SBC_SUBBANDS_8;
-    else if (cap->subbands & SBC_SUBBANDS_4)
-        config.subbands = SBC_SUBBANDS_4;
-    else {
-        pa_log_error("No supported subbands");
-        goto fail;
-    }
-
-    if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
-        config.allocation_method = SBC_ALLOCATION_LOUDNESS;
-    else if (cap->allocation_method & SBC_ALLOCATION_SNR)
-        config.allocation_method = SBC_ALLOCATION_SNR;
-
-    config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
-    config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
-
-done:
-    pa_assert_se(r = dbus_message_new_method_return(m));
-
-    pa_assert_se(dbus_message_append_args(
-                                     r,
-                                     DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size,
-                                     DBUS_TYPE_INVALID));
-
-    return r;
-
-fail:
-    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
-                                            "Unable to select configuration"));
-    return r;
-}
-
-static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
-    struct pa_bluetooth_discovery *y = userdata;
-    DBusMessage *r = NULL;
-    DBusError e;
-    const char *path, *interface, *member;
-
-    pa_assert(y);
-
-    path = dbus_message_get_path(m);
-    interface = dbus_message_get_interface(m);
-    member = dbus_message_get_member(m);
-
-    pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
-
-    dbus_error_init(&e);
-
-    if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) &&
-        !pa_streq(path, HFP_HS_ENDPOINT))
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
-    if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
-        const char *xml = ENDPOINT_INTROSPECT_XML;
-
-        pa_assert_se(r = dbus_message_new_method_return(m));
-        pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
-
-    } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration"))
-        r = endpoint_set_configuration(c, m, userdata);
-    else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration"))
-        r = endpoint_select_configuration(c, m, userdata);
-    else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration"))
-        r = endpoint_clear_configuration(c, m, userdata);
-    else
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
-    if (r) {
-        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
-        dbus_message_unref(r);
-    }
-
-    return DBUS_HANDLER_RESULT_HANDLED;
-}
-
-pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
-    DBusError err;
-    pa_bluetooth_discovery *y;
-    DBusConnection *conn;
-    unsigned i;
-    static const DBusObjectPathVTable vtable_endpoint = {
-        .message_function = endpoint_handler,
-    };
-
-    pa_assert(c);
-
-    dbus_error_init(&err);
-
-    if ((y = pa_shared_get(c, "bluetooth-discovery")))
-        return pa_bluetooth_discovery_ref(y);
-
-    y = pa_xnew0(pa_bluetooth_discovery, 1);
-    PA_REFCNT_INIT(y);
-    y->core = c;
-    y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-    y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-    PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
-
-    for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
-        pa_hook_init(&y->hooks[i], y);
-
-    pa_shared_set(c, "bluetooth-discovery", y);
-
-    if (setup_dbus(y) < 0)
-        goto fail;
-
-    conn = pa_dbus_connection_get(y->connection);
-
-    /* dynamic detection of bluetooth audio devices */
-    if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) {
-        pa_log_error("Failed to add filter function");
-        goto fail;
-    }
-
-    y->filter_added = true;
-
-    if (pa_dbus_add_matches(
-                conn, &err,
-                "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
-                ",arg0='org.bluez'",
-                "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
-                "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
-                "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
-                "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
-                "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
-                "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
-                "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
-                "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
-                "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
-                "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
-                NULL) < 0) {
-        pa_log("Failed to add D-Bus matches: %s", err.message);
-        goto fail;
-    }
-
-    pa_assert_se(dbus_connection_register_object_path(conn, HFP_AG_ENDPOINT, &vtable_endpoint, y));
-    pa_assert_se(dbus_connection_register_object_path(conn, HFP_HS_ENDPOINT, &vtable_endpoint, y));
-    pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
-    pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
-
-    list_adapters(y);
-
-    return y;
-
-fail:
-    if (y)
-        pa_bluetooth_discovery_unref(y);
-
-    dbus_error_free(&err);
-
-    return NULL;
-}
-
-pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
-    pa_assert(y);
-    pa_assert(PA_REFCNT_VALUE(y) > 0);
-
-    PA_REFCNT_INC(y);
-
-    return y;
-}
-
-void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
-    unsigned i;
-
-    pa_assert(y);
-    pa_assert(PA_REFCNT_VALUE(y) > 0);
-
-    if (PA_REFCNT_DEC(y) > 0)
-        return;
-
-    pa_dbus_free_pending_list(&y->pending);
-
-    if (y->devices) {
-        remove_all_devices(y);
-        pa_hashmap_free(y->devices);
-    }
-
-    if (y->transports) {
-        pa_assert(pa_hashmap_isempty(y->transports));
-        pa_hashmap_free(y->transports);
-    }
-
-    if (y->connection) {
-        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT);
-        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT);
-        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
-        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
-        pa_dbus_remove_matches(
-            pa_dbus_connection_get(y->connection),
-            "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
-            ",arg0='org.bluez'",
-            "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
-            "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
-            "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
-            "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
-            "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
-            "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
-            "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
-            "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
-            "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
-            "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
-            "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
-            NULL);
-
-        if (y->filter_added)
-            dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
-
-        pa_dbus_connection_unref(y->connection);
-    }
-
-    for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
-        pa_hook_done(&y->hooks[i]);
-
-    if (y->core)
-        pa_shared_remove(y->core, "bluetooth-discovery");
-
-    pa_xfree(y);
-}
-
-pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
-    pa_assert(y);
-    pa_assert(PA_REFCNT_VALUE(y) > 0);
-
-    return &y->hooks[hook];
-}
-
-pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) {
-    unsigned major, minor;
-    pa_bt_form_factor_t r;
-
-    static const pa_bt_form_factor_t table[] = {
-        [1] = PA_BT_FORM_FACTOR_HEADSET,
-        [2] = PA_BT_FORM_FACTOR_HANDSFREE,
-        [4] = PA_BT_FORM_FACTOR_MICROPHONE,
-        [5] = PA_BT_FORM_FACTOR_SPEAKER,
-        [6] = PA_BT_FORM_FACTOR_HEADPHONE,
-        [7] = PA_BT_FORM_FACTOR_PORTABLE,
-        [8] = PA_BT_FORM_FACTOR_CAR,
-        [10] = PA_BT_FORM_FACTOR_HIFI
-    };
-
-    /*
-     * See Bluetooth Assigned Numbers:
-     * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
-     */
-    major = (class >> 8) & 0x1F;
-    minor = (class >> 2) & 0x3F;
-
-    switch (major) {
-        case 2:
-            return PA_BT_FORM_FACTOR_PHONE;
-        case 4:
-            break;
-        default:
-            pa_log_debug("Unknown Bluetooth major device class %u", major);
-            return PA_BT_FORM_FACTOR_UNKNOWN;
-    }
-
-    r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BT_FORM_FACTOR_UNKNOWN;
-
-    if (!r)
-        pa_log_debug("Unknown Bluetooth minor device class %u", minor);
-
-    return r;
-}
-
-const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff) {
-    switch (ff) {
-        case PA_BT_FORM_FACTOR_UNKNOWN:
-            return "unknown";
-        case PA_BT_FORM_FACTOR_HEADSET:
-            return "headset";
-        case PA_BT_FORM_FACTOR_HANDSFREE:
-            return "hands-free";
-        case PA_BT_FORM_FACTOR_MICROPHONE:
-            return "microphone";
-        case PA_BT_FORM_FACTOR_SPEAKER:
-            return "speaker";
-        case PA_BT_FORM_FACTOR_HEADPHONE:
-            return "headphone";
-        case PA_BT_FORM_FACTOR_PORTABLE:
-            return "portable";
-        case PA_BT_FORM_FACTOR_CAR:
-            return "car";
-        case PA_BT_FORM_FACTOR_HIFI:
-            return "hifi";
-        case PA_BT_FORM_FACTOR_PHONE:
-            return "phone";
-    }
-
-    pa_assert_not_reached();
-}
-
-char *pa_bluetooth_cleanup_name(const char *name) {
-    char *t, *s, *d;
-    bool space = false;
-
-    pa_assert(name);
-
-    while ((*name >= 1 && *name <= 32) || *name >= 127)
-        name++;
-
-    t = pa_xstrdup(name);
-
-    for (s = d = t; *s; s++) {
-
-        if (*s <= 32 || *s >= 127 || *s == '_') {
-            space = true;
-            continue;
-        }
-
-        if (space) {
-            *(d++) = ' ';
-            space = false;
-        }
-
-        *(d++) = *s;
-    }
-
-    *d = 0;
-
-    return t;
-}
-
-bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
-    pa_assert(uuid);
-
-    while (uuids) {
-        if (strcasecmp(uuids->uuid, uuid) == 0)
-            return true;
-
-        uuids = uuids->next;
-    }
-
-    return false;
-}
diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h
deleted file mode 100644
index c69ba71..0000000
--- a/src/modules/bluetooth/bluetooth-util.h
+++ /dev/null
@@ -1,178 +0,0 @@
-#ifndef foobluetoothutilhfoo
-#define foobluetoothutilhfoo
-
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2008-2013 João Paulo Rechi Vita
-
-  PulseAudio is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Lesser General Public License as
-  published by the Free Software Foundation; either version 2.1 of the
-  License, or (at your option) any later version.
-
-  PulseAudio 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 Lesser General Public
-  License along with PulseAudio; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-  USA.
-***/
-
-#include <dbus/dbus.h>
-
-#include <pulsecore/llist.h>
-#include <pulsecore/macro.h>
-
-#define PA_BLUETOOTH_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
-
-/* UUID copied from bluez/audio/device.h */
-#define GENERIC_AUDIO_UUID      "00001203-0000-1000-8000-00805f9b34fb"
-
-#define HSP_HS_UUID             "00001108-0000-1000-8000-00805f9b34fb"
-#define HSP_AG_UUID             "00001112-0000-1000-8000-00805f9b34fb"
-
-#define HFP_HS_UUID             "0000111e-0000-1000-8000-00805f9b34fb"
-#define HFP_AG_UUID             "0000111f-0000-1000-8000-00805f9b34fb"
-
-#define ADVANCED_AUDIO_UUID     "0000110d-0000-1000-8000-00805f9b34fb"
-
-#define A2DP_SOURCE_UUID        "0000110a-0000-1000-8000-00805f9b34fb"
-#define A2DP_SINK_UUID          "0000110b-0000-1000-8000-00805f9b34fb"
-
-#define HSP_MAX_GAIN 15
-
-typedef struct pa_bluetooth_uuid pa_bluetooth_uuid;
-typedef struct pa_bluetooth_device pa_bluetooth_device;
-typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
-typedef struct pa_bluetooth_transport pa_bluetooth_transport;
-
-struct userdata;
-
-struct pa_bluetooth_uuid {
-    char *uuid;
-    PA_LLIST_FIELDS(pa_bluetooth_uuid);
-};
-
-enum profile {
-    PROFILE_A2DP,
-    PROFILE_A2DP_SOURCE,
-    PROFILE_HSP,
-    PROFILE_HFGW,
-    PROFILE_OFF
-};
-
-#define PA_BLUETOOTH_PROFILE_COUNT PROFILE_OFF
-
-struct pa_bluetooth_hook_uuid_data {
-    pa_bluetooth_device *device;
-    const char *uuid;
-};
-
-/* Hook data: pa_bluetooth_discovery pointer. */
-typedef enum pa_bluetooth_hook {
-    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */
-    PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED, /* Call data: pa_bluetooth_hook_uuid_data */
-    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED, /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
-    PA_BLUETOOTH_HOOK_MAX
-} pa_bluetooth_hook_t;
-
-typedef enum pa_bluetooth_transport_state {
-    PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED,
-    PA_BLUETOOTH_TRANSPORT_STATE_IDLE, /* Connected but not playing */
-    PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
-} pa_bluetooth_transport_state_t;
-
-struct pa_bluetooth_transport {
-    pa_bluetooth_device *device;
-    char *owner;
-    char *path;
-    enum profile profile;
-    uint8_t codec;
-    uint8_t *config;
-    int config_size;
-
-    pa_bluetooth_transport_state_t state;
-    bool nrec;
-    uint16_t microphone_gain; /* Used for HSP/HFP */
-    uint16_t speaker_gain; /* Used for HSP/HFP */
-};
-
-/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */
-typedef enum pa_bt_audio_state {
-    PA_BT_AUDIO_STATE_INVALID = -1,
-    PA_BT_AUDIO_STATE_DISCONNECTED,
-    PA_BT_AUDIO_STATE_CONNECTING,
-    PA_BT_AUDIO_STATE_CONNECTED,
-    PA_BT_AUDIO_STATE_PLAYING
-} pa_bt_audio_state_t;
-
-struct pa_bluetooth_device {
-    pa_bluetooth_discovery *discovery;
-    bool dead;
-
-    int device_info_valid;      /* 0: no results yet; 1: good results; -1: bad results ... */
-
-    /* Device information */
-    char *name;
-    char *path;
-    pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
-    int paired;
-    char *alias;
-    PA_LLIST_HEAD(pa_bluetooth_uuid, uuids);
-    char *address;
-    int class;
-    int trusted;
-
-    /* Audio state */
-    pa_bt_audio_state_t audio_state;
-
-    /* AudioSink, AudioSource, Headset and HandsfreeGateway states */
-    pa_bt_audio_state_t profile_state[PA_BLUETOOTH_PROFILE_COUNT];
-};
-
-pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
-pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
-void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *d);
-
-pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path);
-pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address);
-
-bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d);
-
-int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu);
-void pa_bluetooth_transport_release(pa_bluetooth_transport *t);
-
-void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value);
-void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value);
-
-pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
-
-typedef enum pa_bt_form_factor {
-    PA_BT_FORM_FACTOR_UNKNOWN,
-    PA_BT_FORM_FACTOR_HEADSET,
-    PA_BT_FORM_FACTOR_HANDSFREE,
-    PA_BT_FORM_FACTOR_MICROPHONE,
-    PA_BT_FORM_FACTOR_SPEAKER,
-    PA_BT_FORM_FACTOR_HEADPHONE,
-    PA_BT_FORM_FACTOR_PORTABLE,
-    PA_BT_FORM_FACTOR_CAR,
-    PA_BT_FORM_FACTOR_HIFI,
-    PA_BT_FORM_FACTOR_PHONE,
-} pa_bt_form_factor_t;
-
-pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class);
-const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff);
-
-char *pa_bluetooth_cleanup_name(const char *name);
-
-bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid);
-const char *pa_bt_profile_to_string(enum profile profile);
-
-#endif
diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
new file mode 100644
index 0000000..f755069
--- /dev/null
+++ b/src/modules/bluetooth/bluez4-util.c
@@ -0,0 +1,1905 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008-2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/dbus-shared.h>
+
+#include "bluez4-util.h"
+#include "a2dp-codecs.h"
+
+#define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG"
+#define HFP_HS_ENDPOINT "/MediaEndpoint/HFPHS"
+#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
+#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
+
+#define ENDPOINT_INTROSPECT_XML                                         \
+    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
+    "<node>"                                                            \
+    " <interface name=\"org.bluez.MediaEndpoint\">"                     \
+    "  <method name=\"SetConfiguration\">"                              \
+    "   <arg name=\"transport\" direction=\"in\" type=\"o\"/>"          \
+    "   <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>"     \
+    "  </method>"                                                       \
+    "  <method name=\"SelectConfiguration\">"                           \
+    "   <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>"      \
+    "   <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>"    \
+    "  </method>"                                                       \
+    "  <method name=\"ClearConfiguration\">"                            \
+    "  </method>"                                                       \
+    "  <method name=\"Release\">"                                       \
+    "  </method>"                                                       \
+    " </interface>"                                                     \
+    " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
+    "  <method name=\"Introspect\">"                                    \
+    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
+    "  </method>"                                                       \
+    " </interface>"                                                     \
+    "</node>"
+
+struct pa_bluetooth_discovery {
+    PA_REFCNT_DECLARE;
+
+    pa_core *core;
+    pa_dbus_connection *connection;
+    PA_LLIST_HEAD(pa_dbus_pending, pending);
+    bool adapters_listed;
+    pa_hashmap *devices;
+    pa_hashmap *transports;
+    pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
+    bool filter_added;
+};
+
+static void get_properties_reply(DBusPendingCall *pending, void *userdata);
+static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
+                                                void *call_data);
+static void found_adapter(pa_bluetooth_discovery *y, const char *path);
+static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path);
+
+static pa_bt_audio_state_t audio_state_from_string(const char* value) {
+    pa_assert(value);
+
+    if (pa_streq(value, "disconnected"))
+        return PA_BT_AUDIO_STATE_DISCONNECTED;
+    else if (pa_streq(value, "connecting"))
+        return PA_BT_AUDIO_STATE_CONNECTING;
+    else if (pa_streq(value, "connected"))
+        return PA_BT_AUDIO_STATE_CONNECTED;
+    else if (pa_streq(value, "playing"))
+        return PA_BT_AUDIO_STATE_PLAYING;
+
+    return PA_BT_AUDIO_STATE_INVALID;
+}
+
+const char *pa_bt_profile_to_string(enum profile profile) {
+    switch(profile) {
+        case PROFILE_A2DP:
+            return "a2dp";
+        case PROFILE_A2DP_SOURCE:
+            return "a2dp_source";
+        case PROFILE_HSP:
+            return "hsp";
+        case PROFILE_HFGW:
+            return "hfgw";
+        case PROFILE_OFF:
+            pa_assert_not_reached();
+    }
+
+    pa_assert_not_reached();
+}
+
+static int profile_from_interface(const char *interface, enum profile *p) {
+    pa_assert(interface);
+    pa_assert(p);
+
+    if (pa_streq(interface, "org.bluez.AudioSink")) {
+        *p = PROFILE_A2DP;
+        return 0;
+    } else if (pa_streq(interface, "org.bluez.AudioSource")) {
+        *p = PROFILE_A2DP_SOURCE;
+        return 0;
+    } else if (pa_streq(interface, "org.bluez.Headset")) {
+        *p = PROFILE_HSP;
+        return 0;
+    } else if (pa_streq(interface, "org.bluez.HandsfreeGateway")) {
+        *p = PROFILE_HFGW;
+        return 0;
+    }
+
+    return -1;
+}
+
+static pa_bluetooth_transport_state_t audio_state_to_transport_state(pa_bt_audio_state_t state) {
+    switch (state) {
+        case PA_BT_AUDIO_STATE_INVALID: /* Typically if state hasn't been received yet */
+        case PA_BT_AUDIO_STATE_DISCONNECTED:
+        case PA_BT_AUDIO_STATE_CONNECTING:
+            return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
+        case PA_BT_AUDIO_STATE_CONNECTED:
+            return PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
+        case PA_BT_AUDIO_STATE_PLAYING:
+            return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
+    }
+
+    pa_assert_not_reached();
+}
+
+static pa_bluetooth_uuid *uuid_new(const char *uuid) {
+    pa_bluetooth_uuid *u;
+
+    u = pa_xnew(pa_bluetooth_uuid, 1);
+    u->uuid = pa_xstrdup(uuid);
+    PA_LLIST_INIT(pa_bluetooth_uuid, u);
+
+    return u;
+}
+
+static void uuid_free(pa_bluetooth_uuid *u) {
+    pa_assert(u);
+
+    pa_xfree(u->uuid);
+    pa_xfree(u);
+}
+
+static pa_bluetooth_device* device_new(pa_bluetooth_discovery *discovery, const char *path) {
+    pa_bluetooth_device *d;
+    unsigned i;
+
+    pa_assert(discovery);
+    pa_assert(path);
+
+    d = pa_xnew0(pa_bluetooth_device, 1);
+
+    d->discovery = discovery;
+    d->dead = false;
+
+    d->device_info_valid = 0;
+
+    d->name = NULL;
+    d->path = pa_xstrdup(path);
+    d->paired = -1;
+    d->alias = NULL;
+    PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
+    d->address = NULL;
+    d->class = -1;
+    d->trusted = -1;
+
+    d->audio_state = PA_BT_AUDIO_STATE_INVALID;
+
+    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
+        d->profile_state[i] = PA_BT_AUDIO_STATE_INVALID;
+
+    return d;
+}
+
+static void transport_free(pa_bluetooth_transport *t) {
+    pa_assert(t);
+
+    pa_xfree(t->owner);
+    pa_xfree(t->path);
+    pa_xfree(t->config);
+    pa_xfree(t);
+}
+
+static void device_free(pa_bluetooth_device *d) {
+    pa_bluetooth_uuid *u;
+    pa_bluetooth_transport *t;
+    unsigned i;
+
+    pa_assert(d);
+
+    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
+        if (!(t = d->transports[i]))
+            continue;
+
+        d->transports[i] = NULL;
+        pa_hashmap_remove(d->discovery->transports, t->path);
+        t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
+        pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
+        transport_free(t);
+    }
+
+    while ((u = d->uuids)) {
+        PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
+        uuid_free(u);
+    }
+
+    pa_xfree(d->name);
+    pa_xfree(d->path);
+    pa_xfree(d->alias);
+    pa_xfree(d->address);
+    pa_xfree(d);
+}
+
+static const char *check_variant_property(DBusMessageIter *i) {
+    const char *key;
+
+    pa_assert(i);
+
+    if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
+        pa_log("Property name not a string.");
+        return NULL;
+    }
+
+    dbus_message_iter_get_basic(i, &key);
+
+    if (!dbus_message_iter_next(i)) {
+        pa_log("Property value missing");
+        return NULL;
+    }
+
+    if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
+        pa_log("Property value not a variant.");
+        return NULL;
+    }
+
+    return key;
+}
+
+static int parse_manager_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) {
+    const char *key;
+    DBusMessageIter variant_i;
+
+    pa_assert(y);
+
+    key = check_variant_property(i);
+    if (key == NULL)
+        return -1;
+
+    dbus_message_iter_recurse(i, &variant_i);
+
+    switch (dbus_message_iter_get_arg_type(&variant_i)) {
+
+        case DBUS_TYPE_ARRAY: {
+
+            DBusMessageIter ai;
+            dbus_message_iter_recurse(&variant_i, &ai);
+
+            if (pa_streq(key, "Adapters")) {
+                y->adapters_listed = true;
+
+                if (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_OBJECT_PATH)
+                    break;
+
+                while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
+                    const char *value;
+
+                    dbus_message_iter_get_basic(&ai, &value);
+
+                    found_adapter(y, value);
+
+                    dbus_message_iter_next(&ai);
+                }
+            }
+
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static int parse_adapter_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) {
+    const char *key;
+    DBusMessageIter variant_i;
+
+    pa_assert(y);
+
+    key = check_variant_property(i);
+    if (key == NULL)
+        return -1;
+
+    dbus_message_iter_recurse(i, &variant_i);
+
+    switch (dbus_message_iter_get_arg_type(&variant_i)) {
+
+        case DBUS_TYPE_ARRAY: {
+
+            DBusMessageIter ai;
+            dbus_message_iter_recurse(&variant_i, &ai);
+
+            if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_OBJECT_PATH &&
+                pa_streq(key, "Devices")) {
+
+                while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
+                    const char *value;
+
+                    dbus_message_iter_get_basic(&ai, &value);
+
+                    found_device(y, value);
+
+                    dbus_message_iter_next(&ai);
+                }
+            }
+
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
+    const char *key;
+    DBusMessageIter variant_i;
+
+    pa_assert(d);
+
+    key = check_variant_property(i);
+    if (key == NULL)
+        return -1;
+
+    dbus_message_iter_recurse(i, &variant_i);
+
+/*     pa_log_debug("Parsing property org.bluez.Device.%s", key); */
+
+    switch (dbus_message_iter_get_arg_type(&variant_i)) {
+
+        case DBUS_TYPE_STRING: {
+
+            const char *value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Name")) {
+                pa_xfree(d->name);
+                d->name = pa_xstrdup(value);
+            } else if (pa_streq(key, "Alias")) {
+                pa_xfree(d->alias);
+                d->alias = pa_xstrdup(value);
+            } else if (pa_streq(key, "Address")) {
+                if (is_property_change) {
+                    pa_log("Device property 'Address' expected to be constant but changed for %s", d->path);
+                    return -1;
+                }
+
+                if (d->address) {
+                    pa_log("Device %s: Received a duplicate Address property.", d->path);
+                    return -1;
+                }
+
+                d->address = pa_xstrdup(value);
+            }
+
+/*             pa_log_debug("Value %s", value); */
+
+            break;
+        }
+
+        case DBUS_TYPE_BOOLEAN: {
+
+            dbus_bool_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Paired"))
+                d->paired = !!value;
+            else if (pa_streq(key, "Trusted"))
+                d->trusted = !!value;
+
+/*             pa_log_debug("Value %s", pa_yes_no(value)); */
+
+            break;
+        }
+
+        case DBUS_TYPE_UINT32: {
+
+            uint32_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Class"))
+                d->class = (int) value;
+
+/*             pa_log_debug("Value %u", (unsigned) value); */
+
+            break;
+        }
+
+        case DBUS_TYPE_ARRAY: {
+
+            DBusMessageIter ai;
+            dbus_message_iter_recurse(&variant_i, &ai);
+
+            if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && pa_streq(key, "UUIDs")) {
+                DBusMessage *m;
+                bool has_audio = false;
+
+                while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
+                    pa_bluetooth_uuid *node;
+                    const char *value;
+                    struct pa_bluetooth_hook_uuid_data uuiddata;
+
+                    dbus_message_iter_get_basic(&ai, &value);
+
+                    if (pa_bluetooth_uuid_has(d->uuids, value)) {
+                        dbus_message_iter_next(&ai);
+                        continue;
+                    }
+
+                    node = uuid_new(value);
+                    PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
+
+                    uuiddata.device = d;
+                    uuiddata.uuid = value;
+                    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata);
+
+                    /* Vudentz said the interfaces are here when the UUIDs are announced */
+                    if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
+                        pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway",
+                                                                      "GetProperties"));
+                        send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
+                        has_audio = true;
+                    } else if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
+                        pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset",
+                                                                      "GetProperties"));
+                        send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
+                        has_audio = true;
+                    } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
+                        pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink",
+                                                                      "GetProperties"));
+                        send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
+                        has_audio = true;
+                    } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
+                        pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource",
+                                                                      "GetProperties"));
+                        send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
+                        has_audio = true;
+                    }
+
+                    dbus_message_iter_next(&ai);
+                }
+
+                /* this might eventually be racy if .Audio is not there yet, but
+                   the State change will come anyway later, so this call is for
+                   cold-detection mostly */
+                if (has_audio) {
+                    pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
+                    send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
+                }
+            }
+
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
+    switch (state) {
+        case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
+            return "disconnected";
+        case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
+            return "idle";
+        case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
+            return "playing";
+    }
+
+    pa_assert_not_reached();
+}
+
+static int parse_audio_property(pa_bluetooth_device *d, const char *interface, DBusMessageIter *i, bool is_property_change) {
+    pa_bluetooth_transport *transport;
+    const char *key;
+    DBusMessageIter variant_i;
+    bool is_audio_interface;
+    enum profile p = PROFILE_OFF;
+
+    pa_assert(d);
+    pa_assert(interface);
+    pa_assert(i);
+
+    if (!(is_audio_interface = pa_streq(interface, "org.bluez.Audio")))
+        if (profile_from_interface(interface, &p) < 0)
+            return 0; /* Interface not known so silently ignore property */
+
+    key = check_variant_property(i);
+    if (key == NULL)
+        return -1;
+
+    transport = p == PROFILE_OFF ? NULL : d->transports[p];
+
+    dbus_message_iter_recurse(i, &variant_i);
+
+/*     pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
+
+    switch (dbus_message_iter_get_arg_type(&variant_i)) {
+
+        case DBUS_TYPE_STRING: {
+
+            const char *value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "State")) {
+                pa_bt_audio_state_t state = audio_state_from_string(value);
+                pa_bluetooth_transport_state_t old_state;
+
+                pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d->path, interface, value);
+
+                if (state == PA_BT_AUDIO_STATE_INVALID)
+                    return -1;
+
+                if (is_audio_interface) {
+                    d->audio_state = state;
+                    break;
+                }
+
+                pa_assert(p != PROFILE_OFF);
+
+                d->profile_state[p] = state;
+
+                if (!transport)
+                    break;
+
+                old_state = transport->state;
+                transport->state = audio_state_to_transport_state(state);
+
+                if (transport->state != old_state) {
+                    pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport->path,
+                                 pa_bt_profile_to_string(transport->profile), transport_state_to_string(old_state),
+                                 transport_state_to_string(transport->state));
+
+                    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], transport);
+                }
+            }
+
+            break;
+        }
+
+        case DBUS_TYPE_UINT16: {
+            uint16_t value;
+
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "MicrophoneGain")) {
+                uint16_t gain;
+
+                pa_log_debug("dbus: property '%s' changed to value '%u'", key, value);
+
+                if (!transport) {
+                    pa_log("Volume change does not have an associated transport");
+                    return -1;
+                }
+
+                if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->microphone_gain)
+                    break;
+
+                transport->microphone_gain = gain;
+                pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED], transport);
+            } else if (pa_streq(key, "SpeakerGain")) {
+                uint16_t gain;
+
+                pa_log_debug("dbus: property '%s' changed to value '%u'", key, value);
+
+                if (!transport) {
+                    pa_log("Volume change does not have an associated transport");
+                    return -1;
+                }
+
+                if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->speaker_gain)
+                    break;
+
+                transport->speaker_gain = gain;
+                pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED], transport);
+            }
+
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static void run_callback(pa_bluetooth_device *d, bool dead) {
+    pa_assert(d);
+
+    if (d->device_info_valid != 1)
+        return;
+
+    d->dead = dead;
+    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d);
+}
+
+static void remove_all_devices(pa_bluetooth_discovery *y) {
+    pa_bluetooth_device *d;
+
+    pa_assert(y);
+
+    while ((d = pa_hashmap_steal_first(y->devices))) {
+        run_callback(d, true);
+        device_free(d);
+    }
+}
+
+static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) {
+    DBusMessage *m;
+    pa_bluetooth_device *d;
+
+    pa_assert(y);
+    pa_assert(path);
+
+    d = pa_hashmap_get(y->devices, path);
+    if (d)
+        return d;
+
+    d = device_new(y, path);
+
+    pa_hashmap_put(y->devices, d->path, d);
+
+    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
+    send_and_add_to_pending(y, m, get_properties_reply, d);
+
+    /* Before we read the other properties (Audio, AudioSink, AudioSource,
+     * Headset) we wait that the UUID is read */
+    return d;
+}
+
+static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
+    DBusMessage *r;
+    DBusMessageIter arg_i, element_i;
+    pa_dbus_pending *p;
+    pa_bluetooth_device *d;
+    pa_bluetooth_discovery *y;
+    int valid;
+    bool old_any_connected;
+
+    pa_assert_se(p = userdata);
+    pa_assert_se(y = p->context_data);
+    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+/*     pa_log_debug("Got %s.GetProperties response for %s", */
+/*                  dbus_message_get_interface(p->message), */
+/*                  dbus_message_get_path(p->message)); */
+
+    /* We don't use p->call_data here right-away since the device
+     * might already be invalidated at this point */
+
+    if (dbus_message_has_interface(p->message, "org.bluez.Manager") ||
+        dbus_message_has_interface(p->message, "org.bluez.Adapter"))
+        d = NULL;
+    else if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message)))) {
+        pa_log_warn("Received GetProperties() reply from unknown device: %s (device removed?)", dbus_message_get_path(p->message));
+        goto finish2;
+    }
+
+    pa_assert(p->call_data == d);
+
+    if (d != NULL)
+        old_any_connected = pa_bluetooth_device_any_audio_connected(d);
+
+    valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
+
+    if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
+        d->device_info_valid = valid;
+
+    if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
+        pa_log_debug("Bluetooth daemon is apparently not available.");
+        remove_all_devices(y);
+        goto finish2;
+    }
+
+    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+        pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p->message), dbus_message_get_error_name(r),
+               pa_dbus_get_error_message(r));
+        goto finish;
+    }
+
+    if (!dbus_message_iter_init(r, &arg_i)) {
+        pa_log("GetProperties reply has no arguments.");
+        goto finish;
+    }
+
+    if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
+        pa_log("GetProperties argument is not an array.");
+        goto finish;
+    }
+
+    dbus_message_iter_recurse(&arg_i, &element_i);
+    while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
+
+        if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+            DBusMessageIter dict_i;
+
+            dbus_message_iter_recurse(&element_i, &dict_i);
+
+            if (dbus_message_has_interface(p->message, "org.bluez.Manager")) {
+                if (parse_manager_property(y, &dict_i, false) < 0)
+                    goto finish;
+
+            } else if (dbus_message_has_interface(p->message, "org.bluez.Adapter")) {
+                if (parse_adapter_property(y, &dict_i, false) < 0)
+                    goto finish;
+
+            } else if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
+                if (parse_device_property(d, &dict_i, false) < 0)
+                    goto finish;
+
+            } else if (parse_audio_property(d, dbus_message_get_interface(p->message), &dict_i, false) < 0)
+                goto finish;
+
+        }
+
+        dbus_message_iter_next(&element_i);
+    }
+
+finish:
+    if (d != NULL && old_any_connected != pa_bluetooth_device_any_audio_connected(d))
+        run_callback(d, false);
+
+finish2:
+    dbus_message_unref(r);
+
+    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
+    pa_dbus_pending_free(p);
+}
+
+static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
+                                                void *call_data) {
+    pa_dbus_pending *p;
+    DBusPendingCall *call;
+
+    pa_assert(y);
+    pa_assert(m);
+
+    pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
+
+    p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data);
+    PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
+    dbus_pending_call_set_notify(call, func, p, NULL);
+
+    return p;
+}
+
+static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
+    DBusMessage *r;
+    pa_dbus_pending *p;
+    pa_bluetooth_discovery *y;
+    char *endpoint;
+
+    pa_assert(pending);
+    pa_assert_se(p = userdata);
+    pa_assert_se(y = p->context_data);
+    pa_assert_se(endpoint = p->call_data);
+    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+    if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
+        pa_log_debug("Bluetooth daemon is apparently not available.");
+        remove_all_devices(y);
+        goto finish;
+    }
+
+    if (dbus_message_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) {
+        pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint);
+        goto finish;
+    }
+
+    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+        pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r),
+               pa_dbus_get_error_message(r));
+        goto finish;
+    }
+
+finish:
+    dbus_message_unref(r);
+
+    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
+    pa_dbus_pending_free(p);
+
+    pa_xfree(endpoint);
+}
+
+static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
+    DBusMessage *m;
+    DBusMessageIter i, d;
+    uint8_t codec = 0;
+
+    pa_log_debug("Registering %s on adapter %s.", endpoint, path);
+
+    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint"));
+
+    dbus_message_iter_init_append(m, &i);
+
+    dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint);
+
+    dbus_message_iter_open_container(&i, 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,
+                                    &d);
+
+    pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
+
+    pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
+
+    if (pa_streq(uuid, HFP_AG_UUID) || pa_streq(uuid, HFP_HS_UUID)) {
+        uint8_t capability = 0;
+        pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1);
+    } else {
+        a2dp_sbc_t capabilities;
+
+        capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL |
+                                    SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO;
+        capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 |
+                                 SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000;
+        capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
+        capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
+        capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 |
+                                    SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
+        capabilities.min_bitpool = MIN_BITPOOL;
+        capabilities.max_bitpool = MAX_BITPOOL;
+
+        pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
+    }
+
+    dbus_message_iter_close_container(&i, &d);
+
+    send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
+}
+
+static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
+    DBusMessage *m;
+
+    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
+    send_and_add_to_pending(y, m, get_properties_reply, NULL);
+
+    register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
+    register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
+    register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
+    register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
+}
+
+static void list_adapters(pa_bluetooth_discovery *y) {
+    DBusMessage *m;
+    pa_assert(y);
+
+    pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties"));
+    send_and_add_to_pending(y, m, get_properties_reply, NULL);
+}
+
+static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
+    const char *key;
+    DBusMessageIter variant_i;
+
+    key = check_variant_property(i);
+    if (key == NULL)
+        return -1;
+
+    dbus_message_iter_recurse(i, &variant_i);
+
+    switch (dbus_message_iter_get_arg_type(&variant_i)) {
+
+        case DBUS_TYPE_BOOLEAN: {
+
+            dbus_bool_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "NREC") && t->nrec != value) {
+                t->nrec = value;
+                pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t->path, t->nrec ? "True" : "False");
+                pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED], t);
+            }
+
+            break;
+         }
+    }
+
+    return 0;
+}
+
+static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
+    DBusError err;
+    pa_bluetooth_discovery *y;
+
+    pa_assert(bus);
+    pa_assert(m);
+
+    pa_assert_se(y = userdata);
+
+    dbus_error_init(&err);
+
+    pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
+            dbus_message_get_interface(m),
+            dbus_message_get_path(m),
+            dbus_message_get_member(m));
+
+    if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
+        const char *path;
+        pa_bluetooth_device *d;
+
+        if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
+            pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
+            goto fail;
+        }
+
+        pa_log_debug("Device %s removed", path);
+
+        if ((d = pa_hashmap_remove(y->devices, path))) {
+            run_callback(d, true);
+            device_free(d);
+        }
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
+        const char *path;
+
+        if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
+            pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message);
+            goto fail;
+        }
+
+        pa_log_debug("Device %s created", path);
+
+        found_device(y, path);
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
+        const char *path;
+
+        if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
+            pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message);
+            goto fail;
+        }
+
+        if (!y->adapters_listed) {
+            pa_log_debug("Ignoring 'AdapterAdded' because initial adapter list has not been received yet.");
+            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+
+        pa_log_debug("Adapter %s created", path);
+
+        found_adapter(y, path);
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
+               dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
+               dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
+               dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
+               dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
+               dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
+
+        pa_bluetooth_device *d;
+
+        if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
+            DBusMessageIter arg_i;
+            bool old_any_connected = pa_bluetooth_device_any_audio_connected(d);
+
+            if (!dbus_message_iter_init(m, &arg_i)) {
+                pa_log("Failed to parse PropertyChanged for device %s", d->path);
+                goto fail;
+            }
+
+            if (dbus_message_has_interface(m, "org.bluez.Device")) {
+                if (parse_device_property(d, &arg_i, true) < 0)
+                    goto fail;
+
+            } else if (parse_audio_property(d, dbus_message_get_interface(m), &arg_i, true) < 0)
+                goto fail;
+
+            if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
+                run_callback(d, false);
+        }
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
+        const char *name, *old_owner, *new_owner;
+
+        if (!dbus_message_get_args(m, &err,
+                                   DBUS_TYPE_STRING, &name,
+                                   DBUS_TYPE_STRING, &old_owner,
+                                   DBUS_TYPE_STRING, &new_owner,
+                                   DBUS_TYPE_INVALID)) {
+            pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
+            goto fail;
+        }
+
+        if (pa_streq(name, "org.bluez")) {
+            if (old_owner && *old_owner) {
+                pa_log_debug("Bluetooth daemon disappeared.");
+                remove_all_devices(y);
+                y->adapters_listed = false;
+            }
+
+            if (new_owner && *new_owner) {
+                pa_log_debug("Bluetooth daemon appeared.");
+                list_adapters(y);
+            }
+        }
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
+        pa_bluetooth_transport *t;
+        DBusMessageIter arg_i;
+
+        if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
+            goto fail;
+
+        if (!dbus_message_iter_init(m, &arg_i)) {
+            pa_log("Failed to parse PropertyChanged for transport %s", t->path);
+            goto fail;
+        }
+
+        if (transport_parse_property(t, &arg_i) < 0)
+            goto fail;
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+fail:
+    dbus_error_free(&err);
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
+    pa_bluetooth_device *d;
+    void *state = NULL;
+
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+    pa_assert(address);
+
+    while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
+        if (pa_streq(d->address, address))
+            return d->device_info_valid == 1 ? d : NULL;
+
+    return NULL;
+}
+
+pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
+    pa_bluetooth_device *d;
+
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+    pa_assert(path);
+
+    if ((d = pa_hashmap_get(y->devices, path)))
+        if (d->device_info_valid == 1)
+            return d;
+
+    return NULL;
+}
+
+bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
+    unsigned i;
+
+    pa_assert(d);
+
+    if (d->dead || d->device_info_valid != 1)
+        return false;
+
+    if (d->audio_state == PA_BT_AUDIO_STATE_INVALID)
+        return false;
+
+    /* Make sure audio_state is *not* in CONNECTING state before we fire the
+     * hook to report the new device state. This is actually very important in
+     * order to make module-card-restore work well with headsets: if the headset
+     * supports both HSP and A2DP, one of those profiles is connected first and
+     * then the other, and lastly the Audio interface becomes connected.
+     * Checking only audio_state means that this function will return false at
+     * the time when only the first connection has been made. This is good,
+     * because otherwise, if the first connection is for HSP and we would
+     * already load a new device module instance, and module-card-restore tries
+     * to restore the A2DP profile, that would fail because A2DP is not yet
+     * connected. Waiting until the Audio interface gets connected means that
+     * both headset profiles will be connected when the device module is
+     * loaded. */
+    if (d->audio_state == PA_BT_AUDIO_STATE_CONNECTING)
+        return false;
+
+    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
+        if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+            return true;
+
+    return false;
+}
+
+int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
+    const char *accesstype = "rw";
+    DBusMessage *m, *r;
+    DBusError err;
+    int ret;
+    uint16_t i, o;
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    if (optional) {
+        /* FIXME: we are trying to acquire the transport only if the stream is
+           playing, without actually initiating the stream request from our side
+           (which is typically undesireable specially for hfgw use-cases.
+           However this approach is racy, since the stream could have been
+           suspended in the meantime, so we can't really guarantee that the
+           stream will not be requested until BlueZ's API supports this
+           atomically. */
+        if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
+            pa_log_info("Failed optional acquire of transport %s", t->path);
+            return -1;
+        }
+    }
+
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Acquire"));
+    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
+    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
+
+    if (!r) {
+        dbus_error_free(&err);
+        return -1;
+    }
+
+    if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o,
+                               DBUS_TYPE_INVALID)) {
+        pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message);
+        ret = -1;
+        dbus_error_free(&err);
+        goto fail;
+    }
+
+    if (imtu)
+        *imtu = i;
+
+    if (omtu)
+        *omtu = o;
+
+fail:
+    dbus_message_unref(r);
+    return ret;
+}
+
+void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
+    const char *accesstype = "rw";
+    DBusMessage *m;
+    DBusError err;
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release"));
+    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
+    dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
+
+    if (dbus_error_is_set(&err)) {
+        pa_log("Failed to release transport %s: %s", t->path, err.message);
+        dbus_error_free(&err);
+    } else
+        pa_log_info("Transport %s released", t->path);
+}
+
+static void set_property(pa_bluetooth_discovery *y, const char *bus, const char *path, const char *interface,
+                         const char *prop_name, int prop_type, void *prop_value) {
+    DBusMessage *m;
+    DBusMessageIter i;
+
+    pa_assert(y);
+    pa_assert(path);
+    pa_assert(interface);
+    pa_assert(prop_name);
+
+    pa_assert_se(m = dbus_message_new_method_call(bus, path, interface, "SetProperty"));
+    dbus_message_iter_init_append(m, &i);
+    dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name);
+    pa_dbus_append_basic_variant(&i, prop_type, prop_value);
+
+    dbus_message_set_no_reply(m, true);
+    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL));
+    dbus_message_unref(m);
+}
+
+void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value) {
+    dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
+
+    pa_assert(t);
+    pa_assert(t->profile == PROFILE_HSP);
+
+    set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
+                 "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
+}
+
+void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value) {
+    dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
+
+    pa_assert(t);
+    pa_assert(t->profile == PROFILE_HSP);
+
+    set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
+                 "SpeakerGain", DBUS_TYPE_UINT16, &gain);
+}
+
+static int setup_dbus(pa_bluetooth_discovery *y) {
+    DBusError err;
+
+    dbus_error_init(&err);
+
+    if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) {
+        pa_log("Failed to get D-Bus connection: %s", err.message);
+        dbus_error_free(&err);
+        return -1;
+    }
+
+    return 0;
+}
+
+static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char *owner, const char *path, enum profile p,
+                                             const uint8_t *config, int size) {
+    pa_bluetooth_transport *t;
+
+    t = pa_xnew0(pa_bluetooth_transport, 1);
+    t->device = d;
+    t->owner = pa_xstrdup(owner);
+    t->path = pa_xstrdup(path);
+    t->profile = p;
+    t->config_size = size;
+
+    if (size > 0) {
+        t->config = pa_xnew(uint8_t, size);
+        memcpy(t->config, config, size);
+    }
+
+    t->state = audio_state_to_transport_state(d->profile_state[p]);
+
+    return t;
+}
+
+static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
+    pa_bluetooth_discovery *y = userdata;
+    pa_bluetooth_device *d;
+    pa_bluetooth_transport *t;
+    const char *sender, *path, *dev_path = NULL, *uuid = NULL;
+    uint8_t *config = NULL;
+    int size = 0;
+    bool nrec = false;
+    enum profile p;
+    DBusMessageIter args, props;
+    DBusMessage *r;
+    bool old_any_connected;
+
+    if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
+        pa_log("Invalid signature for method SetConfiguration");
+        goto fail2;
+    }
+
+    dbus_message_iter_get_basic(&args, &path);
+
+    if (pa_hashmap_get(y->transports, path)) {
+        pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path);
+        goto fail2;
+    }
+
+    pa_assert_se(dbus_message_iter_next(&args));
+
+    dbus_message_iter_recurse(&args, &props);
+    if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+        goto fail;
+
+    /* Read transport properties */
+    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)
+                goto fail;
+
+            dbus_message_iter_get_basic(&value, &uuid);
+        } else if (strcasecmp(key, "Device") == 0) {
+            if (var != DBUS_TYPE_OBJECT_PATH)
+                goto fail;
+
+            dbus_message_iter_get_basic(&value, &dev_path);
+        } else if (strcasecmp(key, "NREC") == 0) {
+            dbus_bool_t tmp_boolean;
+            if (var != DBUS_TYPE_BOOLEAN)
+                goto fail;
+
+            dbus_message_iter_get_basic(&value, &tmp_boolean);
+            nrec = tmp_boolean;
+        } else if (strcasecmp(key, "Configuration") == 0) {
+            DBusMessageIter array;
+            if (var != DBUS_TYPE_ARRAY)
+                goto fail;
+
+            dbus_message_iter_recurse(&value, &array);
+            dbus_message_iter_get_fixed_array(&array, &config, &size);
+        }
+
+        dbus_message_iter_next(&props);
+    }
+
+    d = found_device(y, dev_path);
+    if (!d)
+        goto fail;
+
+    if (dbus_message_has_path(m, HFP_AG_ENDPOINT))
+        p = PROFILE_HSP;
+    else if (dbus_message_has_path(m, HFP_HS_ENDPOINT))
+        p = PROFILE_HFGW;
+    else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT))
+        p = PROFILE_A2DP;
+    else
+        p = PROFILE_A2DP_SOURCE;
+
+    if (d->transports[p] != NULL) {
+        pa_log("Cannot configure transport %s because profile %d is already used", path, p);
+        goto fail2;
+    }
+
+    old_any_connected = pa_bluetooth_device_any_audio_connected(d);
+
+    sender = dbus_message_get_sender(m);
+
+    t = transport_new(d, sender, path, p, config, size);
+    if (nrec)
+        t->nrec = nrec;
+
+    d->transports[p] = t;
+    pa_assert_se(pa_hashmap_put(y->transports, t->path, t) >= 0);
+
+    pa_log_debug("Transport %s profile %d available", t->path, t->profile);
+
+    pa_assert_se(r = dbus_message_new_method_return(m));
+    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
+    dbus_message_unref(r);
+
+    if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
+        run_callback(d, false);
+
+    return NULL;
+
+fail:
+    pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
+
+fail2:
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
+                                            "Unable to set configuration"));
+    return r;
+}
+
+static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
+    pa_bluetooth_discovery *y = userdata;
+    pa_bluetooth_transport *t;
+    DBusMessage *r;
+    DBusError e;
+    const char *path;
+
+    dbus_error_init(&e);
+
+    if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
+        pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message);
+        dbus_error_free(&e);
+        goto fail;
+    }
+
+    if ((t = pa_hashmap_get(y->transports, path))) {
+        bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
+
+        pa_log_debug("Clearing transport %s profile %d", t->path, t->profile);
+        t->device->transports[t->profile] = NULL;
+        pa_hashmap_remove(y->transports, t->path);
+        t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
+        pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
+
+        if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
+            run_callback(t->device, false);
+
+        transport_free(t);
+    }
+
+    pa_assert_se(r = dbus_message_new_method_return(m));
+
+    return r;
+
+fail:
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
+                                            "Unable to clear configuration"));
+    return r;
+}
+
+static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
+
+    switch (freq) {
+        case SBC_SAMPLING_FREQ_16000:
+        case SBC_SAMPLING_FREQ_32000:
+            return 53;
+
+        case SBC_SAMPLING_FREQ_44100:
+
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                    return 31;
+
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return 53;
+
+                default:
+                    pa_log_warn("Invalid channel mode %u", mode);
+                    return 53;
+            }
+
+        case SBC_SAMPLING_FREQ_48000:
+
+            switch (mode) {
+                case SBC_CHANNEL_MODE_MONO:
+                case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                    return 29;
+
+                case SBC_CHANNEL_MODE_STEREO:
+                case SBC_CHANNEL_MODE_JOINT_STEREO:
+                    return 51;
+
+                default:
+                    pa_log_warn("Invalid channel mode %u", mode);
+                    return 51;
+            }
+
+        default:
+            pa_log_warn("Invalid sampling freq %u", freq);
+            return 53;
+    }
+}
+
+static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
+    pa_bluetooth_discovery *y = userdata;
+    a2dp_sbc_t *cap, config;
+    uint8_t *pconf = (uint8_t *) &config;
+    int i, size;
+    DBusMessage *r;
+    DBusError e;
+
+    static const struct {
+        uint32_t rate;
+        uint8_t cap;
+    } freq_table[] = {
+        { 16000U, SBC_SAMPLING_FREQ_16000 },
+        { 32000U, SBC_SAMPLING_FREQ_32000 },
+        { 44100U, SBC_SAMPLING_FREQ_44100 },
+        { 48000U, SBC_SAMPLING_FREQ_48000 }
+    };
+
+    dbus_error_init(&e);
+
+    if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
+        pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message);
+        dbus_error_free(&e);
+        goto fail;
+    }
+
+    if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT))
+        goto done;
+
+    pa_assert(size == sizeof(config));
+
+    memset(&config, 0, sizeof(config));
+
+    /* Find the lowest freq that is at least as high as the requested
+     * sampling rate */
+    for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
+        if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
+            config.frequency = freq_table[i].cap;
+            break;
+        }
+
+    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
+        for (--i; i >= 0; i--) {
+            if (cap->frequency & freq_table[i].cap) {
+                config.frequency = freq_table[i].cap;
+                break;
+            }
+        }
+
+        if (i < 0) {
+            pa_log("Not suitable sample rate");
+            goto fail;
+        }
+    }
+
+    pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
+
+    if (y->core->default_sample_spec.channels <= 1) {
+        if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
+            config.channel_mode = SBC_CHANNEL_MODE_MONO;
+    }
+
+    if (y->core->default_sample_spec.channels >= 2) {
+        if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+            config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
+            config.channel_mode = SBC_CHANNEL_MODE_STEREO;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+            config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+        else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) {
+            config.channel_mode = SBC_CHANNEL_MODE_MONO;
+        } else {
+            pa_log("No supported channel modes");
+            goto fail;
+        }
+    }
+
+    if (cap->block_length & SBC_BLOCK_LENGTH_16)
+        config.block_length = SBC_BLOCK_LENGTH_16;
+    else if (cap->block_length & SBC_BLOCK_LENGTH_12)
+        config.block_length = SBC_BLOCK_LENGTH_12;
+    else if (cap->block_length & SBC_BLOCK_LENGTH_8)
+        config.block_length = SBC_BLOCK_LENGTH_8;
+    else if (cap->block_length & SBC_BLOCK_LENGTH_4)
+        config.block_length = SBC_BLOCK_LENGTH_4;
+    else {
+        pa_log_error("No supported block lengths");
+        goto fail;
+    }
+
+    if (cap->subbands & SBC_SUBBANDS_8)
+        config.subbands = SBC_SUBBANDS_8;
+    else if (cap->subbands & SBC_SUBBANDS_4)
+        config.subbands = SBC_SUBBANDS_4;
+    else {
+        pa_log_error("No supported subbands");
+        goto fail;
+    }
+
+    if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
+        config.allocation_method = SBC_ALLOCATION_LOUDNESS;
+    else if (cap->allocation_method & SBC_ALLOCATION_SNR)
+        config.allocation_method = SBC_ALLOCATION_SNR;
+
+    config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
+    config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
+
+done:
+    pa_assert_se(r = dbus_message_new_method_return(m));
+
+    pa_assert_se(dbus_message_append_args(
+                                     r,
+                                     DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size,
+                                     DBUS_TYPE_INVALID));
+
+    return r;
+
+fail:
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
+                                            "Unable to select configuration"));
+    return r;
+}
+
+static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
+    struct pa_bluetooth_discovery *y = userdata;
+    DBusMessage *r = NULL;
+    DBusError e;
+    const char *path, *interface, *member;
+
+    pa_assert(y);
+
+    path = dbus_message_get_path(m);
+    interface = dbus_message_get_interface(m);
+    member = dbus_message_get_member(m);
+
+    pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
+
+    dbus_error_init(&e);
+
+    if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) &&
+        !pa_streq(path, HFP_HS_ENDPOINT))
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+        const char *xml = ENDPOINT_INTROSPECT_XML;
+
+        pa_assert_se(r = dbus_message_new_method_return(m));
+        pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
+
+    } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration"))
+        r = endpoint_set_configuration(c, m, userdata);
+    else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration"))
+        r = endpoint_select_configuration(c, m, userdata);
+    else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration"))
+        r = endpoint_clear_configuration(c, m, userdata);
+    else
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    if (r) {
+        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
+        dbus_message_unref(r);
+    }
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
+    DBusError err;
+    pa_bluetooth_discovery *y;
+    DBusConnection *conn;
+    unsigned i;
+    static const DBusObjectPathVTable vtable_endpoint = {
+        .message_function = endpoint_handler,
+    };
+
+    pa_assert(c);
+
+    dbus_error_init(&err);
+
+    if ((y = pa_shared_get(c, "bluetooth-discovery")))
+        return pa_bluetooth_discovery_ref(y);
+
+    y = pa_xnew0(pa_bluetooth_discovery, 1);
+    PA_REFCNT_INIT(y);
+    y->core = c;
+    y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
+
+    for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
+        pa_hook_init(&y->hooks[i], y);
+
+    pa_shared_set(c, "bluetooth-discovery", y);
+
+    if (setup_dbus(y) < 0)
+        goto fail;
+
+    conn = pa_dbus_connection_get(y->connection);
+
+    /* dynamic detection of bluetooth audio devices */
+    if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) {
+        pa_log_error("Failed to add filter function");
+        goto fail;
+    }
+
+    y->filter_added = true;
+
+    if (pa_dbus_add_matches(
+                conn, &err,
+                "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
+                ",arg0='org.bluez'",
+                "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
+                "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
+                "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
+                "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
+                NULL) < 0) {
+        pa_log("Failed to add D-Bus matches: %s", err.message);
+        goto fail;
+    }
+
+    pa_assert_se(dbus_connection_register_object_path(conn, HFP_AG_ENDPOINT, &vtable_endpoint, y));
+    pa_assert_se(dbus_connection_register_object_path(conn, HFP_HS_ENDPOINT, &vtable_endpoint, y));
+    pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
+    pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
+
+    list_adapters(y);
+
+    return y;
+
+fail:
+    if (y)
+        pa_bluetooth_discovery_unref(y);
+
+    dbus_error_free(&err);
+
+    return NULL;
+}
+
+pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+    PA_REFCNT_INC(y);
+
+    return y;
+}
+
+void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
+    unsigned i;
+
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+    if (PA_REFCNT_DEC(y) > 0)
+        return;
+
+    pa_dbus_free_pending_list(&y->pending);
+
+    if (y->devices) {
+        remove_all_devices(y);
+        pa_hashmap_free(y->devices);
+    }
+
+    if (y->transports) {
+        pa_assert(pa_hashmap_isempty(y->transports));
+        pa_hashmap_free(y->transports);
+    }
+
+    if (y->connection) {
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT);
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT);
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
+        pa_dbus_remove_matches(
+            pa_dbus_connection_get(y->connection),
+            "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
+            ",arg0='org.bluez'",
+            "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
+            "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
+            "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
+            "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
+            "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
+            "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
+            "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
+            "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
+            "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
+            "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
+            "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
+            NULL);
+
+        if (y->filter_added)
+            dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
+
+        pa_dbus_connection_unref(y->connection);
+    }
+
+    for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
+        pa_hook_done(&y->hooks[i]);
+
+    if (y->core)
+        pa_shared_remove(y->core, "bluetooth-discovery");
+
+    pa_xfree(y);
+}
+
+pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+    return &y->hooks[hook];
+}
+
+pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) {
+    unsigned major, minor;
+    pa_bt_form_factor_t r;
+
+    static const pa_bt_form_factor_t table[] = {
+        [1] = PA_BT_FORM_FACTOR_HEADSET,
+        [2] = PA_BT_FORM_FACTOR_HANDSFREE,
+        [4] = PA_BT_FORM_FACTOR_MICROPHONE,
+        [5] = PA_BT_FORM_FACTOR_SPEAKER,
+        [6] = PA_BT_FORM_FACTOR_HEADPHONE,
+        [7] = PA_BT_FORM_FACTOR_PORTABLE,
+        [8] = PA_BT_FORM_FACTOR_CAR,
+        [10] = PA_BT_FORM_FACTOR_HIFI
+    };
+
+    /*
+     * See Bluetooth Assigned Numbers:
+     * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
+     */
+    major = (class >> 8) & 0x1F;
+    minor = (class >> 2) & 0x3F;
+
+    switch (major) {
+        case 2:
+            return PA_BT_FORM_FACTOR_PHONE;
+        case 4:
+            break;
+        default:
+            pa_log_debug("Unknown Bluetooth major device class %u", major);
+            return PA_BT_FORM_FACTOR_UNKNOWN;
+    }
+
+    r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BT_FORM_FACTOR_UNKNOWN;
+
+    if (!r)
+        pa_log_debug("Unknown Bluetooth minor device class %u", minor);
+
+    return r;
+}
+
+const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff) {
+    switch (ff) {
+        case PA_BT_FORM_FACTOR_UNKNOWN:
+            return "unknown";
+        case PA_BT_FORM_FACTOR_HEADSET:
+            return "headset";
+        case PA_BT_FORM_FACTOR_HANDSFREE:
+            return "hands-free";
+        case PA_BT_FORM_FACTOR_MICROPHONE:
+            return "microphone";
+        case PA_BT_FORM_FACTOR_SPEAKER:
+            return "speaker";
+        case PA_BT_FORM_FACTOR_HEADPHONE:
+            return "headphone";
+        case PA_BT_FORM_FACTOR_PORTABLE:
+            return "portable";
+        case PA_BT_FORM_FACTOR_CAR:
+            return "car";
+        case PA_BT_FORM_FACTOR_HIFI:
+            return "hifi";
+        case PA_BT_FORM_FACTOR_PHONE:
+            return "phone";
+    }
+
+    pa_assert_not_reached();
+}
+
+char *pa_bluetooth_cleanup_name(const char *name) {
+    char *t, *s, *d;
+    bool space = false;
+
+    pa_assert(name);
+
+    while ((*name >= 1 && *name <= 32) || *name >= 127)
+        name++;
+
+    t = pa_xstrdup(name);
+
+    for (s = d = t; *s; s++) {
+
+        if (*s <= 32 || *s >= 127 || *s == '_') {
+            space = true;
+            continue;
+        }
+
+        if (space) {
+            *(d++) = ' ';
+            space = false;
+        }
+
+        *(d++) = *s;
+    }
+
+    *d = 0;
+
+    return t;
+}
+
+bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
+    pa_assert(uuid);
+
+    while (uuids) {
+        if (strcasecmp(uuids->uuid, uuid) == 0)
+            return true;
+
+        uuids = uuids->next;
+    }
+
+    return false;
+}
diff --git a/src/modules/bluetooth/bluez4-util.h b/src/modules/bluetooth/bluez4-util.h
new file mode 100644
index 0000000..4570a10
--- /dev/null
+++ b/src/modules/bluetooth/bluez4-util.h
@@ -0,0 +1,178 @@
+#ifndef foobluez4utilhfoo
+#define foobluez4utilhfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008-2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/llist.h>
+#include <pulsecore/macro.h>
+
+#define PA_BLUETOOTH_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
+
+/* UUID copied from bluez/audio/device.h */
+#define GENERIC_AUDIO_UUID      "00001203-0000-1000-8000-00805f9b34fb"
+
+#define HSP_HS_UUID             "00001108-0000-1000-8000-00805f9b34fb"
+#define HSP_AG_UUID             "00001112-0000-1000-8000-00805f9b34fb"
+
+#define HFP_HS_UUID             "0000111e-0000-1000-8000-00805f9b34fb"
+#define HFP_AG_UUID             "0000111f-0000-1000-8000-00805f9b34fb"
+
+#define ADVANCED_AUDIO_UUID     "0000110d-0000-1000-8000-00805f9b34fb"
+
+#define A2DP_SOURCE_UUID        "0000110a-0000-1000-8000-00805f9b34fb"
+#define A2DP_SINK_UUID          "0000110b-0000-1000-8000-00805f9b34fb"
+
+#define HSP_MAX_GAIN 15
+
+typedef struct pa_bluetooth_uuid pa_bluetooth_uuid;
+typedef struct pa_bluetooth_device pa_bluetooth_device;
+typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
+typedef struct pa_bluetooth_transport pa_bluetooth_transport;
+
+struct userdata;
+
+struct pa_bluetooth_uuid {
+    char *uuid;
+    PA_LLIST_FIELDS(pa_bluetooth_uuid);
+};
+
+enum profile {
+    PROFILE_A2DP,
+    PROFILE_A2DP_SOURCE,
+    PROFILE_HSP,
+    PROFILE_HFGW,
+    PROFILE_OFF
+};
+
+#define PA_BLUETOOTH_PROFILE_COUNT PROFILE_OFF
+
+struct pa_bluetooth_hook_uuid_data {
+    pa_bluetooth_device *device;
+    const char *uuid;
+};
+
+/* Hook data: pa_bluetooth_discovery pointer. */
+typedef enum pa_bluetooth_hook {
+    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */
+    PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED, /* Call data: pa_bluetooth_hook_uuid_data */
+    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED, /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_MAX
+} pa_bluetooth_hook_t;
+
+typedef enum pa_bluetooth_transport_state {
+    PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED,
+    PA_BLUETOOTH_TRANSPORT_STATE_IDLE, /* Connected but not playing */
+    PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
+} pa_bluetooth_transport_state_t;
+
+struct pa_bluetooth_transport {
+    pa_bluetooth_device *device;
+    char *owner;
+    char *path;
+    enum profile profile;
+    uint8_t codec;
+    uint8_t *config;
+    int config_size;
+
+    pa_bluetooth_transport_state_t state;
+    bool nrec;
+    uint16_t microphone_gain; /* Used for HSP/HFP */
+    uint16_t speaker_gain; /* Used for HSP/HFP */
+};
+
+/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */
+typedef enum pa_bt_audio_state {
+    PA_BT_AUDIO_STATE_INVALID = -1,
+    PA_BT_AUDIO_STATE_DISCONNECTED,
+    PA_BT_AUDIO_STATE_CONNECTING,
+    PA_BT_AUDIO_STATE_CONNECTED,
+    PA_BT_AUDIO_STATE_PLAYING
+} pa_bt_audio_state_t;
+
+struct pa_bluetooth_device {
+    pa_bluetooth_discovery *discovery;
+    bool dead;
+
+    int device_info_valid;      /* 0: no results yet; 1: good results; -1: bad results ... */
+
+    /* Device information */
+    char *name;
+    char *path;
+    pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
+    int paired;
+    char *alias;
+    PA_LLIST_HEAD(pa_bluetooth_uuid, uuids);
+    char *address;
+    int class;
+    int trusted;
+
+    /* Audio state */
+    pa_bt_audio_state_t audio_state;
+
+    /* AudioSink, AudioSource, Headset and HandsfreeGateway states */
+    pa_bt_audio_state_t profile_state[PA_BLUETOOTH_PROFILE_COUNT];
+};
+
+pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
+pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
+void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *d);
+
+pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path);
+pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address);
+
+bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d);
+
+int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu);
+void pa_bluetooth_transport_release(pa_bluetooth_transport *t);
+
+void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value);
+void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value);
+
+pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
+
+typedef enum pa_bt_form_factor {
+    PA_BT_FORM_FACTOR_UNKNOWN,
+    PA_BT_FORM_FACTOR_HEADSET,
+    PA_BT_FORM_FACTOR_HANDSFREE,
+    PA_BT_FORM_FACTOR_MICROPHONE,
+    PA_BT_FORM_FACTOR_SPEAKER,
+    PA_BT_FORM_FACTOR_HEADPHONE,
+    PA_BT_FORM_FACTOR_PORTABLE,
+    PA_BT_FORM_FACTOR_CAR,
+    PA_BT_FORM_FACTOR_HIFI,
+    PA_BT_FORM_FACTOR_PHONE,
+} pa_bt_form_factor_t;
+
+pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class);
+const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff);
+
+char *pa_bluetooth_cleanup_name(const char *name);
+
+bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid);
+const char *pa_bt_profile_to_string(enum profile profile);
+
+#endif
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
deleted file mode 100644
index 5da197b..0000000
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ /dev/null
@@ -1,2623 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2008-2013 João Paulo Rechi Vita
-  Copyright 2011-2013 BMW Car IT GmbH.
-
-  PulseAudio is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Lesser General Public License as
-  published by the Free Software Foundation; either version 2.1 of the
-  License, or (at your option) any later version.
-
-  PulseAudio 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 Lesser General Public
-  License along with PulseAudio; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-  USA.
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <string.h>
-#include <errno.h>
-#include <math.h>
-#include <linux/sockios.h>
-#include <arpa/inet.h>
-
-#include <pulse/rtclock.h>
-#include <pulse/sample.h>
-#include <pulse/timeval.h>
-#include <pulse/xmalloc.h>
-
-#include <pulsecore/i18n.h>
-#include <pulsecore/module.h>
-#include <pulsecore/modargs.h>
-#include <pulsecore/core-rtclock.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/core-error.h>
-#include <pulsecore/shared.h>
-#include <pulsecore/socket-util.h>
-#include <pulsecore/thread.h>
-#include <pulsecore/thread-mq.h>
-#include <pulsecore/poll.h>
-#include <pulsecore/rtpoll.h>
-#include <pulsecore/time-smoother.h>
-#include <pulsecore/namereg.h>
-
-#include <sbc/sbc.h>
-
-#include "module-bluetooth-device-symdef.h"
-#include "a2dp-codecs.h"
-#include "rtp.h"
-#include "bluetooth-util.h"
-
-#define BITPOOL_DEC_LIMIT 32
-#define BITPOOL_DEC_STEP 5
-
-PA_MODULE_AUTHOR("João Paulo Rechi Vita");
-PA_MODULE_DESCRIPTION("BlueZ 4 Bluetooth audio sink and source");
-PA_MODULE_VERSION(PACKAGE_VERSION);
-PA_MODULE_LOAD_ONCE(false);
-PA_MODULE_USAGE(
-        "name=<name for the card/sink/source, to be prefixed> "
-        "card_name=<name for the card> "
-        "card_properties=<properties for the card> "
-        "sink_name=<name for the sink> "
-        "sink_properties=<properties for the sink> "
-        "source_name=<name for the source> "
-        "source_properties=<properties for the source> "
-        "address=<address of the device> "
-        "profile=<a2dp|hsp|hfgw> "
-        "rate=<sample rate> "
-        "channels=<number of channels> "
-        "path=<device object path> "
-        "auto_connect=<automatically connect?> "
-        "sco_sink=<SCO over PCM sink name> "
-        "sco_source=<SCO over PCM source name>");
-
-/* TODO: not close fd when entering suspend mode in a2dp */
-
-static const char* const valid_modargs[] = {
-    "name",
-    "card_name",
-    "card_properties",
-    "sink_name",
-    "sink_properties",
-    "source_name",
-    "source_properties",
-    "address",
-    "profile",
-    "rate",
-    "channels",
-    "path",
-    "auto_connect",
-    "sco_sink",
-    "sco_source",
-    NULL
-};
-
-struct a2dp_info {
-    sbc_t sbc;                           /* Codec data */
-    bool sbc_initialized;                /* Keep track if the encoder is initialized */
-    size_t codesize, frame_length;       /* SBC Codesize, frame_length. We simply cache those values here */
-
-    void* buffer;                        /* Codec transfer buffer */
-    size_t buffer_size;                  /* Size of the buffer */
-
-    uint16_t seq_num;                    /* Cumulative packet sequence */
-    uint8_t min_bitpool;
-    uint8_t max_bitpool;
-};
-
-struct hsp_info {
-    pa_sink *sco_sink;
-    void (*sco_sink_set_volume)(pa_sink *s);
-    pa_source *sco_source;
-    void (*sco_source_set_volume)(pa_source *s);
-};
-
-struct bluetooth_msg {
-    pa_msgobject parent;
-    pa_card *card;
-};
-
-typedef struct bluetooth_msg bluetooth_msg;
-PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject);
-#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))
-
-struct userdata {
-    pa_core *core;
-    pa_module *module;
-
-    pa_bluetooth_device *device;
-    pa_hook_slot *uuid_added_slot;
-    char *address;
-    char *path;
-    pa_bluetooth_transport *transport;
-    bool transport_acquired;
-    pa_hook_slot *discovery_slot;
-    pa_hook_slot *sink_state_changed_slot;
-    pa_hook_slot *source_state_changed_slot;
-    pa_hook_slot *transport_state_changed_slot;
-    pa_hook_slot *transport_nrec_changed_slot;
-    pa_hook_slot *transport_microphone_changed_slot;
-    pa_hook_slot *transport_speaker_changed_slot;
-
-    pa_bluetooth_discovery *discovery;
-    bool auto_connect;
-
-    char *output_port_name;
-    char *input_port_name;
-
-    pa_card *card;
-    pa_sink *sink;
-    pa_source *source;
-
-    pa_thread_mq thread_mq;
-    pa_rtpoll *rtpoll;
-    pa_rtpoll_item *rtpoll_item;
-    pa_thread *thread;
-    bluetooth_msg *msg;
-
-    uint64_t read_index, write_index;
-    pa_usec_t started_at;
-    pa_smoother *read_smoother;
-
-    pa_memchunk write_memchunk;
-
-    pa_sample_spec sample_spec, requested_sample_spec;
-
-    int stream_fd;
-
-    size_t read_link_mtu;
-    size_t read_block_size;
-
-    size_t write_link_mtu;
-    size_t write_block_size;
-
-    struct a2dp_info a2dp;
-    struct hsp_info hsp;
-
-    enum profile profile;
-
-    pa_modargs *modargs;
-
-    int stream_write_type;
-};
-
-enum {
-    BLUETOOTH_MESSAGE_IO_THREAD_FAILED,
-    BLUETOOTH_MESSAGE_MAX
-};
-
-#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC)
-#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC)
-#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC)
-#define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC)
-
-#define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC)
-
-#define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source))
-
-static int init_profile(struct userdata *u);
-
-/* from IO thread */
-static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
-    struct a2dp_info *a2dp;
-
-    pa_assert(u);
-
-    a2dp = &u->a2dp;
-
-    if (a2dp->sbc.bitpool == bitpool)
-        return;
-
-    if (bitpool > a2dp->max_bitpool)
-        bitpool = a2dp->max_bitpool;
-    else if (bitpool < a2dp->min_bitpool)
-        bitpool = a2dp->min_bitpool;
-
-    a2dp->sbc.bitpool = bitpool;
-
-    a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
-    a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
-
-    pa_log_debug("Bitpool has changed to %u", a2dp->sbc.bitpool);
-
-    u->read_block_size =
-        (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-        / a2dp->frame_length * a2dp->codesize;
-
-    u->write_block_size =
-        (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-        / a2dp->frame_length * a2dp->codesize;
-
-    pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
-    pa_sink_set_fixed_latency_within_thread(u->sink,
-            FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
-}
-
-/* from IO thread, except in SCO over PCM */
-static void bt_transport_config_mtu(struct userdata *u) {
-    /* Calculate block sizes */
-    if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
-        u->read_block_size = u->read_link_mtu;
-        u->write_block_size = u->write_link_mtu;
-    } else {
-        u->read_block_size =
-            (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-            / u->a2dp.frame_length * u->a2dp.codesize;
-
-        u->write_block_size =
-            (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-            / u->a2dp.frame_length * u->a2dp.codesize;
-    }
-
-    if (USE_SCO_OVER_PCM(u))
-        return;
-
-    if (u->sink) {
-        pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
-        pa_sink_set_fixed_latency_within_thread(u->sink,
-                                                (u->profile == PROFILE_A2DP ?
-                                                 FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
-                                                pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
-    }
-
-    if (u->source)
-        pa_source_set_fixed_latency_within_thread(u->source,
-                                                  (u->profile == PROFILE_A2DP_SOURCE ?
-                                                   FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
-                                                  pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
-}
-
-/* from IO thread, except in SCO over PCM */
-
-static void setup_stream(struct userdata *u) {
-    struct pollfd *pollfd;
-    int one;
-
-    pa_log_info("Transport %s resuming", u->transport->path);
-
-    bt_transport_config_mtu(u);
-
-    pa_make_fd_nonblock(u->stream_fd);
-    pa_make_socket_low_delay(u->stream_fd);
-
-    one = 1;
-    if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0)
-        pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno));
-
-    pa_log_debug("Stream properly set up, we're ready to roll!");
-
-    if (u->profile == PROFILE_A2DP)
-        a2dp_set_bitpool(u, u->a2dp.max_bitpool);
-
-    u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
-    pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
-    pollfd->fd = u->stream_fd;
-    pollfd->events = pollfd->revents = 0;
-
-    u->read_index = u->write_index = 0;
-    u->started_at = 0;
-
-    if (u->source)
-        u->read_smoother = pa_smoother_new(
-                PA_USEC_PER_SEC,
-                PA_USEC_PER_SEC*2,
-                true,
-                true,
-                10,
-                pa_rtclock_now(),
-                true);
-}
-
-static void teardown_stream(struct userdata *u) {
-    if (u->rtpoll_item) {
-        pa_rtpoll_item_free(u->rtpoll_item);
-        u->rtpoll_item = NULL;
-    }
-
-    if (u->stream_fd >= 0) {
-        pa_close(u->stream_fd);
-        u->stream_fd = -1;
-    }
-
-    if (u->read_smoother) {
-        pa_smoother_free(u->read_smoother);
-        u->read_smoother = NULL;
-    }
-
-    if (u->write_memchunk.memblock) {
-        pa_memblock_unref(u->write_memchunk.memblock);
-        pa_memchunk_reset(&u->write_memchunk);
-    }
-
-    pa_log_debug("Audio stream torn down");
-}
-
-static void bt_transport_release(struct userdata *u) {
-    pa_assert(u->transport);
-
-    /* Ignore if already released */
-    if (!u->transport_acquired)
-        return;
-
-    pa_log_debug("Releasing transport %s", u->transport->path);
-
-    pa_bluetooth_transport_release(u->transport);
-
-    u->transport_acquired = false;
-
-    teardown_stream(u);
-}
-
-static int bt_transport_acquire(struct userdata *u, bool optional) {
-    pa_assert(u->transport);
-
-    if (u->transport_acquired)
-        return 0;
-
-    pa_log_debug("Acquiring transport %s", u->transport->path);
-
-    u->stream_fd = pa_bluetooth_transport_acquire(u->transport, optional, &u->read_link_mtu, &u->write_link_mtu);
-    if (u->stream_fd < 0) {
-        if (!optional)
-            pa_log("Failed to acquire transport %s", u->transport->path);
-        else
-            pa_log_info("Failed optional acquire of transport %s", u->transport->path);
-
-        return -1;
-    }
-
-    u->transport_acquired = true;
-    pa_log_info("Transport %s acquired: fd %d", u->transport->path, u->stream_fd);
-
-    return 0;
-}
-
-/* Run from IO thread */
-static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
-    struct userdata *u = PA_SINK(o)->userdata;
-    bool failed = false;
-    int r;
-
-    pa_assert(u->sink == PA_SINK(o));
-    pa_assert(u->transport);
-
-    switch (code) {
-
-        case PA_SINK_MESSAGE_SET_STATE:
-
-            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
-
-                case PA_SINK_SUSPENDED:
-                    /* Ignore if transition is PA_SINK_INIT->PA_SINK_SUSPENDED */
-                    if (!PA_SINK_IS_OPENED(u->sink->thread_info.state))
-                        break;
-
-                    /* Stop the device if the source is suspended as well */
-                    if (!u->source || u->source->state == PA_SOURCE_SUSPENDED)
-                        /* We deliberately ignore whether stopping
-                         * actually worked. Since the stream_fd is
-                         * closed it doesn't really matter */
-                        bt_transport_release(u);
-
-                    break;
-
-                case PA_SINK_IDLE:
-                case PA_SINK_RUNNING:
-                    if (u->sink->thread_info.state != PA_SINK_SUSPENDED)
-                        break;
-
-                    /* Resume the device if the source was suspended as well */
-                    if (!u->source || !PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
-                        if (bt_transport_acquire(u, false) < 0)
-                            failed = true;
-                        else
-                            setup_stream(u);
-                    }
-                    break;
-
-                case PA_SINK_UNLINKED:
-                case PA_SINK_INIT:
-                case PA_SINK_INVALID_STATE:
-                    ;
-            }
-            break;
-
-        case PA_SINK_MESSAGE_GET_LATENCY: {
-
-            if (u->read_smoother) {
-                pa_usec_t wi, ri;
-
-                ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
-                wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->sample_spec);
-
-                *((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
-            } else {
-                pa_usec_t ri, wi;
-
-                ri = pa_rtclock_now() - u->started_at;
-                wi = pa_bytes_to_usec(u->write_index, &u->sample_spec);
-
-                *((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
-            }
-
-            *((pa_usec_t*) data) += u->sink->thread_info.fixed_latency;
-            return 0;
-        }
-    }
-
-    r = pa_sink_process_msg(o, code, data, offset, chunk);
-
-    return (r < 0 || !failed) ? r : -1;
-}
-
-/* Run from IO thread */
-static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
-    struct userdata *u = PA_SOURCE(o)->userdata;
-    bool failed = false;
-    int r;
-
-    pa_assert(u->source == PA_SOURCE(o));
-    pa_assert(u->transport);
-
-    switch (code) {
-
-        case PA_SOURCE_MESSAGE_SET_STATE:
-
-            switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
-
-                case PA_SOURCE_SUSPENDED:
-                    /* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */
-                    if (!PA_SOURCE_IS_OPENED(u->source->thread_info.state))
-                        break;
-
-                    /* Stop the device if the sink is suspended as well */
-                    if (!u->sink || u->sink->state == PA_SINK_SUSPENDED)
-                        bt_transport_release(u);
-
-                    if (u->read_smoother)
-                        pa_smoother_pause(u->read_smoother, pa_rtclock_now());
-                    break;
-
-                case PA_SOURCE_IDLE:
-                case PA_SOURCE_RUNNING:
-                    if (u->source->thread_info.state != PA_SOURCE_SUSPENDED)
-                        break;
-
-                    /* Resume the device if the sink was suspended as well */
-                    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
-                        if (bt_transport_acquire(u, false) < 0)
-                            failed = true;
-                        else
-                            setup_stream(u);
-                    }
-                    /* We don't resume the smoother here. Instead we
-                     * wait until the first packet arrives */
-                    break;
-
-                case PA_SOURCE_UNLINKED:
-                case PA_SOURCE_INIT:
-                case PA_SOURCE_INVALID_STATE:
-                    ;
-            }
-            break;
-
-        case PA_SOURCE_MESSAGE_GET_LATENCY: {
-            pa_usec_t wi, ri;
-
-            if (u->read_smoother) {
-                wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
-                ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
-
-                *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->thread_info.fixed_latency;
-            } else
-                *((pa_usec_t*) data) = 0;
-
-            return 0;
-        }
-
-    }
-
-    r = pa_source_process_msg(o, code, data, offset, chunk);
-
-    return (r < 0 || !failed) ? r : -1;
-}
-
-/* Called from main thread context */
-static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
-    struct bluetooth_msg *u = BLUETOOTH_MSG(obj);
-
-    switch (code) {
-        case BLUETOOTH_MESSAGE_IO_THREAD_FAILED: {
-            if (u->card->module->unload_requested)
-                break;
-
-            pa_log_debug("Switching the profile to off due to IO thread failure.");
-
-            pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
-            break;
-        }
-    }
-    return 0;
-}
-
-/* Run from IO thread */
-static int hsp_process_render(struct userdata *u) {
-    int ret = 0;
-
-    pa_assert(u);
-    pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
-    pa_assert(u->sink);
-
-    /* First, render some data */
-    if (!u->write_memchunk.memblock)
-        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
-
-    pa_assert(u->write_memchunk.length == u->write_block_size);
-
-    for (;;) {
-        ssize_t l;
-        const void *p;
-
-        /* Now write that data to the socket. The socket is of type
-         * SEQPACKET, and we generated the data of the MTU size, so this
-         * should just work. */
-
-        p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
-        l = pa_write(u->stream_fd, p, u->write_memchunk.length, &u->stream_write_type);
-        pa_memblock_release(u->write_memchunk.memblock);
-
-        pa_assert(l != 0);
-
-        if (l < 0) {
-
-            if (errno == EINTR)
-                /* Retry right away if we got interrupted */
-                continue;
-
-            else if (errno == EAGAIN)
-                /* Hmm, apparently the socket was not writable, give up for now */
-                break;
-
-            pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno));
-            ret = -1;
-            break;
-        }
-
-        pa_assert((size_t) l <= u->write_memchunk.length);
-
-        if ((size_t) l != u->write_memchunk.length) {
-            pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
-                        (unsigned long long) l,
-                        (unsigned long long) u->write_memchunk.length);
-            ret = -1;
-            break;
-        }
-
-        u->write_index += (uint64_t) u->write_memchunk.length;
-        pa_memblock_unref(u->write_memchunk.memblock);
-        pa_memchunk_reset(&u->write_memchunk);
-
-        ret = 1;
-        break;
-    }
-
-    return ret;
-}
-
-/* Run from IO thread */
-static int hsp_process_push(struct userdata *u) {
-    int ret = 0;
-    pa_memchunk memchunk;
-
-    pa_assert(u);
-    pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
-    pa_assert(u->source);
-    pa_assert(u->read_smoother);
-
-    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
-    memchunk.index = memchunk.length = 0;
-
-    for (;;) {
-        ssize_t l;
-        void *p;
-        struct msghdr m;
-        struct cmsghdr *cm;
-        uint8_t aux[1024];
-        struct iovec iov;
-        bool found_tstamp = false;
-        pa_usec_t tstamp;
-
-        memset(&m, 0, sizeof(m));
-        memset(&aux, 0, sizeof(aux));
-        memset(&iov, 0, sizeof(iov));
-
-        m.msg_iov = &iov;
-        m.msg_iovlen = 1;
-        m.msg_control = aux;
-        m.msg_controllen = sizeof(aux);
-
-        p = pa_memblock_acquire(memchunk.memblock);
-        iov.iov_base = p;
-        iov.iov_len = pa_memblock_get_length(memchunk.memblock);
-        l = recvmsg(u->stream_fd, &m, 0);
-        pa_memblock_release(memchunk.memblock);
-
-        if (l <= 0) {
-
-            if (l < 0 && errno == EINTR)
-                /* Retry right away if we got interrupted */
-                continue;
-
-            else if (l < 0 && errno == EAGAIN)
-                /* Hmm, apparently the socket was not readable, give up for now. */
-                break;
-
-            pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
-            ret = -1;
-            break;
-        }
-
-        pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock));
-
-        /* In some rare occasions, we might receive packets of a very strange
-         * size. This could potentially be possible if the SCO packet was
-         * received partially over-the-air, or more probably due to hardware
-         * issues in our Bluetooth adapter. In these cases, in order to avoid
-         * an assertion failure due to unaligned data, just discard the whole
-         * packet */
-        if (!pa_frame_aligned(l, &u->sample_spec)) {
-            pa_log_warn("SCO packet received of unaligned size: %zu", l);
-            break;
-        }
-
-        memchunk.length = (size_t) l;
-        u->read_index += (uint64_t) l;
-
-        for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm))
-            if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
-                struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
-                pa_rtclock_from_wallclock(tv);
-                tstamp = pa_timeval_load(tv);
-                found_tstamp = true;
-                break;
-            }
-
-        if (!found_tstamp) {
-            pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
-            tstamp = pa_rtclock_now();
-        }
-
-        pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
-        pa_smoother_resume(u->read_smoother, tstamp, true);
-
-        pa_source_post(u->source, &memchunk);
-
-        ret = l;
-        break;
-    }
-
-    pa_memblock_unref(memchunk.memblock);
-
-    return ret;
-}
-
-/* Run from IO thread */
-static void a2dp_prepare_buffer(struct userdata *u) {
-    size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
-
-    pa_assert(u);
-
-    if (u->a2dp.buffer_size >= min_buffer_size)
-        return;
-
-    u->a2dp.buffer_size = 2 * min_buffer_size;
-    pa_xfree(u->a2dp.buffer);
-    u->a2dp.buffer = pa_xmalloc(u->a2dp.buffer_size);
-}
-
-/* Run from IO thread */
-static int a2dp_process_render(struct userdata *u) {
-    struct a2dp_info *a2dp;
-    struct rtp_header *header;
-    struct rtp_payload *payload;
-    size_t nbytes;
-    void *d;
-    const void *p;
-    size_t to_write, to_encode;
-    unsigned frame_count;
-    int ret = 0;
-
-    pa_assert(u);
-    pa_assert(u->profile == PROFILE_A2DP);
-    pa_assert(u->sink);
-
-    /* First, render some data */
-    if (!u->write_memchunk.memblock)
-        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
-
-    pa_assert(u->write_memchunk.length == u->write_block_size);
-
-    a2dp_prepare_buffer(u);
-
-    a2dp = &u->a2dp;
-    header = a2dp->buffer;
-    payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
-
-    frame_count = 0;
-
-    /* Try to create a packet of the full MTU */
-
-    p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
-    to_encode = u->write_memchunk.length;
-
-    d = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
-    to_write = a2dp->buffer_size - sizeof(*header) - sizeof(*payload);
-
-    while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
-        ssize_t written;
-        ssize_t encoded;
-
-        encoded = sbc_encode(&a2dp->sbc,
-                             p, to_encode,
-                             d, to_write,
-                             &written);
-
-        if (PA_UNLIKELY(encoded <= 0)) {
-            pa_log_error("SBC encoding error (%li)", (long) encoded);
-            pa_memblock_release(u->write_memchunk.memblock);
-            return -1;
-        }
-
-/*         pa_log_debug("SBC: encoded: %lu; written: %lu", (unsigned long) encoded, (unsigned long) written); */
-/*         pa_log_debug("SBC: codesize: %lu; frame_length: %lu", (unsigned long) a2dp->codesize, (unsigned long) a2dp->frame_length); */
-
-        pa_assert_fp((size_t) encoded <= to_encode);
-        pa_assert_fp((size_t) encoded == a2dp->codesize);
-
-        pa_assert_fp((size_t) written <= to_write);
-        pa_assert_fp((size_t) written == a2dp->frame_length);
-
-        p = (const uint8_t*) p + encoded;
-        to_encode -= encoded;
-
-        d = (uint8_t*) d + written;
-        to_write -= written;
-
-        frame_count++;
-    }
-
-    pa_memblock_release(u->write_memchunk.memblock);
-
-    pa_assert(to_encode == 0);
-
-    PA_ONCE_BEGIN {
-        pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&a2dp->sbc)));
-    } PA_ONCE_END;
-
-    /* write it to the fifo */
-    memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload));
-    header->v = 2;
-    header->pt = 1;
-    header->sequence_number = htons(a2dp->seq_num++);
-    header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));
-    header->ssrc = htonl(1);
-    payload->frame_count = frame_count;
-
-    nbytes = (uint8_t*) d - (uint8_t*) a2dp->buffer;
-
-    for (;;) {
-        ssize_t l;
-
-        l = pa_write(u->stream_fd, a2dp->buffer, nbytes, &u->stream_write_type);
-
-        pa_assert(l != 0);
-
-        if (l < 0) {
-
-            if (errno == EINTR)
-                /* Retry right away if we got interrupted */
-                continue;
-
-            else if (errno == EAGAIN)
-                /* Hmm, apparently the socket was not writable, give up for now */
-                break;
-
-            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
-            ret = -1;
-            break;
-        }
-
-        pa_assert((size_t) l <= nbytes);
-
-        if ((size_t) l != nbytes) {
-            pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
-                        (unsigned long long) l,
-                        (unsigned long long) nbytes);
-            ret = -1;
-            break;
-        }
-
-        u->write_index += (uint64_t) u->write_memchunk.length;
-        pa_memblock_unref(u->write_memchunk.memblock);
-        pa_memchunk_reset(&u->write_memchunk);
-
-        ret = 1;
-
-        break;
-    }
-
-    return ret;
-}
-
-static int a2dp_process_push(struct userdata *u) {
-    int ret = 0;
-    pa_memchunk memchunk;
-
-    pa_assert(u);
-    pa_assert(u->profile == PROFILE_A2DP_SOURCE);
-    pa_assert(u->source);
-    pa_assert(u->read_smoother);
-
-    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
-    memchunk.index = memchunk.length = 0;
-
-    for (;;) {
-        bool found_tstamp = false;
-        pa_usec_t tstamp;
-        struct a2dp_info *a2dp;
-        struct rtp_header *header;
-        struct rtp_payload *payload;
-        const void *p;
-        void *d;
-        ssize_t l;
-        size_t to_write, to_decode;
-
-        a2dp_prepare_buffer(u);
-
-        a2dp = &u->a2dp;
-        header = a2dp->buffer;
-        payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
-
-        l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type);
-
-        if (l <= 0) {
-
-            if (l < 0 && errno == EINTR)
-                /* Retry right away if we got interrupted */
-                continue;
-
-            else if (l < 0 && errno == EAGAIN)
-                /* Hmm, apparently the socket was not readable, give up for now. */
-                break;
-
-            pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
-            ret = -1;
-            break;
-        }
-
-        pa_assert((size_t) l <= a2dp->buffer_size);
-
-        u->read_index += (uint64_t) l;
-
-        /* TODO: get timestamp from rtp */
-        if (!found_tstamp) {
-            /* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */
-            tstamp = pa_rtclock_now();
-        }
-
-        pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
-        pa_smoother_resume(u->read_smoother, tstamp, true);
-
-        p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
-        to_decode = l - sizeof(*header) - sizeof(*payload);
-
-        d = pa_memblock_acquire(memchunk.memblock);
-        to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
-
-        while (PA_LIKELY(to_decode > 0)) {
-            size_t written;
-            ssize_t decoded;
-
-            decoded = sbc_decode(&a2dp->sbc,
-                                 p, to_decode,
-                                 d, to_write,
-                                 &written);
-
-            if (PA_UNLIKELY(decoded <= 0)) {
-                pa_log_error("SBC decoding error (%li)", (long) decoded);
-                pa_memblock_release(memchunk.memblock);
-                pa_memblock_unref(memchunk.memblock);
-                return -1;
-            }
-
-/*             pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */
-/*             pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */
-
-            /* Reset frame length, it can be changed due to bitpool change */
-            a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
-
-            pa_assert_fp((size_t) decoded <= to_decode);
-            pa_assert_fp((size_t) decoded == a2dp->frame_length);
-
-            pa_assert_fp((size_t) written == a2dp->codesize);
-
-            p = (const uint8_t*) p + decoded;
-            to_decode -= decoded;
-
-            d = (uint8_t*) d + written;
-            to_write -= written;
-        }
-
-        memchunk.length -= to_write;
-
-        pa_memblock_release(memchunk.memblock);
-
-        pa_source_post(u->source, &memchunk);
-
-        ret = l;
-        break;
-    }
-
-    pa_memblock_unref(memchunk.memblock);
-
-    return ret;
-}
-
-static void a2dp_reduce_bitpool(struct userdata *u) {
-    struct a2dp_info *a2dp;
-    uint8_t bitpool;
-
-    pa_assert(u);
-
-    a2dp = &u->a2dp;
-
-    /* Check if bitpool is already at its limit */
-    if (a2dp->sbc.bitpool <= BITPOOL_DEC_LIMIT)
-        return;
-
-    bitpool = a2dp->sbc.bitpool - BITPOOL_DEC_STEP;
-
-    if (bitpool < BITPOOL_DEC_LIMIT)
-        bitpool = BITPOOL_DEC_LIMIT;
-
-    a2dp_set_bitpool(u, bitpool);
-}
-
-static void thread_func(void *userdata) {
-    struct userdata *u = userdata;
-    unsigned do_write = 0;
-    unsigned pending_read_bytes = 0;
-    bool writable = false;
-
-    pa_assert(u);
-    pa_assert(u->transport);
-
-    pa_log_debug("IO Thread starting up");
-
-    if (u->core->realtime_scheduling)
-        pa_make_realtime(u->core->realtime_priority);
-
-    pa_thread_mq_install(&u->thread_mq);
-
-    /* Setup the stream only if the transport was already acquired */
-    if (u->transport_acquired)
-        setup_stream(u);
-
-    for (;;) {
-        struct pollfd *pollfd;
-        int ret;
-        bool disable_timer = true;
-
-        pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
-
-        if (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state)) {
-
-            /* We should send two blocks to the device before we expect
-             * a response. */
-
-            if (u->write_index == 0 && u->read_index <= 0)
-                do_write = 2;
-
-            if (pollfd && (pollfd->revents & POLLIN)) {
-                int n_read;
-
-                if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW)
-                    n_read = hsp_process_push(u);
-                else
-                    n_read = a2dp_process_push(u);
-
-                if (n_read < 0)
-                    goto io_fail;
-
-                /* We just read something, so we are supposed to write something, too */
-                pending_read_bytes += n_read;
-                do_write += pending_read_bytes / u->write_block_size;
-                pending_read_bytes = pending_read_bytes % u->write_block_size;
-            }
-        }
-
-        if (u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state)) {
-
-            if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
-                pa_sink_process_rewind(u->sink, 0);
-
-            if (pollfd) {
-                if (pollfd->revents & POLLOUT)
-                    writable = true;
-
-                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {
-                    pa_usec_t time_passed;
-                    pa_usec_t audio_sent;
-
-                    /* Hmm, there is no input stream we could synchronize
-                     * to. So let's do things by time */
-
-                    time_passed = pa_rtclock_now() - u->started_at;
-                    audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
-
-                    if (audio_sent <= time_passed) {
-                        pa_usec_t audio_to_send = time_passed - audio_sent;
-
-                        /* Never try to catch up for more than 100ms */
-                        if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) {
-                            pa_usec_t skip_usec;
-                            uint64_t skip_bytes;
-
-                            skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC;
-                            skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec);
-
-                            if (skip_bytes > 0) {
-                                pa_memchunk tmp;
-
-                                pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
-                                            (unsigned long long) skip_usec,
-                                            (unsigned long long) skip_bytes);
-
-                                pa_sink_render_full(u->sink, skip_bytes, &tmp);
-                                pa_memblock_unref(tmp.memblock);
-                                u->write_index += skip_bytes;
-
-                                if (u->profile == PROFILE_A2DP)
-                                    a2dp_reduce_bitpool(u);
-                            }
-                        }
-
-                        do_write = 1;
-                        pending_read_bytes = 0;
-                    }
-                }
-
-                if (writable && do_write > 0) {
-                    int n_written;
-
-                    if (u->write_index <= 0)
-                        u->started_at = pa_rtclock_now();
-
-                    if (u->profile == PROFILE_A2DP) {
-                        if ((n_written = a2dp_process_render(u)) < 0)
-                            goto io_fail;
-                    } else {
-                        if ((n_written = hsp_process_render(u)) < 0)
-                            goto io_fail;
-                    }
-
-                    if (n_written == 0)
-                        pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!");
-
-                    do_write -= n_written;
-                    writable = false;
-                }
-
-                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0) {
-                    pa_usec_t sleep_for;
-                    pa_usec_t time_passed, next_write_at;
-
-                    if (writable) {
-                        /* Hmm, there is no input stream we could synchronize
-                         * to. So let's estimate when we need to wake up the latest */
-                        time_passed = pa_rtclock_now() - u->started_at;
-                        next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
-                        sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
-                        /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
-                    } else
-                        /* drop stream every 500 ms */
-                        sleep_for = PA_USEC_PER_MSEC * 500;
-
-                    pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for);
-                    disable_timer = false;
-                }
-            }
-        }
-
-        if (disable_timer)
-            pa_rtpoll_set_timer_disabled(u->rtpoll);
-
-        /* Hmm, nothing to do. Let's sleep */
-        if (pollfd)
-            pollfd->events = (short) (((u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state) && !writable) ? POLLOUT : 0) |
-                                      (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state) ? POLLIN : 0));
-
-        if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0) {
-            pa_log_debug("pa_rtpoll_run failed with: %d", ret);
-            goto fail;
-        }
-        if (ret == 0) {
-            pa_log_debug("IO thread shutdown requested, stopping cleanly");
-            bt_transport_release(u);
-            goto finish;
-        }
-
-        pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
-
-        if (pollfd && (pollfd->revents & ~(POLLOUT|POLLIN))) {
-            pa_log_info("FD error: %s%s%s%s",
-                        pollfd->revents & POLLERR ? "POLLERR " :"",
-                        pollfd->revents & POLLHUP ? "POLLHUP " :"",
-                        pollfd->revents & POLLPRI ? "POLLPRI " :"",
-                        pollfd->revents & POLLNVAL ? "POLLNVAL " :"");
-            goto io_fail;
-        }
-
-        continue;
-
-io_fail:
-        /* In case of HUP, just tear down the streams */
-        if (!pollfd || (pollfd->revents & POLLHUP) == 0)
-            goto fail;
-
-        do_write = 0;
-        pending_read_bytes = 0;
-        writable = false;
-
-        teardown_stream(u);
-    }
-
-fail:
-    /* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */
-    pa_log_debug("IO thread failed");
-    pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_IO_THREAD_FAILED, NULL, 0, NULL, NULL);
-    pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
-
-finish:
-    pa_log_debug("IO thread shutting down");
-}
-
-static pa_available_t transport_state_to_availability(pa_bluetooth_transport_state_t state) {
-    if (state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
-        return PA_AVAILABLE_NO;
-    else if (state >= PA_BLUETOOTH_TRANSPORT_STATE_PLAYING)
-        return PA_AVAILABLE_YES;
-    else
-        return PA_AVAILABLE_UNKNOWN;
-}
-
-static pa_direction_t get_profile_direction(enum profile p) {
-    static const pa_direction_t profile_direction[] = {
-        [PROFILE_A2DP] = PA_DIRECTION_OUTPUT,
-        [PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
-        [PROFILE_HSP] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PROFILE_HFGW] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PROFILE_OFF] = 0
-    };
-
-    return profile_direction[p];
-}
-
-/* Run from main thread */
-static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) {
-    pa_available_t result = PA_AVAILABLE_NO;
-    unsigned i;
-
-    pa_assert(u);
-    pa_assert(u->device);
-
-    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
-        pa_bluetooth_transport *transport;
-
-        if (!(get_profile_direction(i) & direction))
-            continue;
-
-        if (!(transport = u->device->transports[i]))
-            continue;
-
-        switch(transport->state) {
-            case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
-                continue;
-
-            case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
-                if (result == PA_AVAILABLE_NO)
-                    result = PA_AVAILABLE_UNKNOWN;
-
-                break;
-
-            case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
-                return PA_AVAILABLE_YES;
-        }
-    }
-
-    return result;
-}
-
-/* Run from main thread */
-static void handle_transport_state_change(struct userdata *u, struct pa_bluetooth_transport *transport) {
-    bool acquire = false;
-    bool release = false;
-    enum profile profile;
-    pa_card_profile *cp;
-    pa_bluetooth_transport_state_t state;
-    pa_device_port *port;
-
-    pa_assert(u);
-    pa_assert(transport);
-
-    profile = transport->profile;
-    state = transport->state;
-
-    /* Update profile availability */
-    if (!(cp = pa_hashmap_get(u->card->profiles, pa_bt_profile_to_string(profile))))
-        return;
-
-    pa_card_profile_set_available(cp, transport_state_to_availability(state));
-
-    /* Update port availability */
-    pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
-    pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_OUTPUT));
-
-    pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
-    pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_INPUT));
-
-    /* Acquire or release transport as needed */
-    acquire = (state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile);
-    release = (state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile);
-
-    if (acquire)
-        if (bt_transport_acquire(u, true) >= 0) {
-            if (u->source) {
-                pa_log_debug("Resuming source %s, because the bluetooth audio state changed to 'playing'.", u->source->name);
-                pa_source_suspend(u->source, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
-            }
-
-            if (u->sink) {
-                pa_log_debug("Resuming sink %s, because the bluetooth audio state changed to 'playing'.", u->sink->name);
-                pa_sink_suspend(u->sink, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
-            }
-        }
-
-    if (release && u->transport_acquired) {
-        /* FIXME: this release is racy, since the audio stream might have
-           been set up again in the meantime (but not processed yet by PA).
-           BlueZ should probably release the transport automatically, and
-           in that case we would just mark the transport as released */
-
-        /* Remote side closed the stream so we consider it PA_SUSPEND_USER */
-        if (u->source) {
-            pa_log_debug("Suspending source %s, because the remote end closed the stream.", u->source->name);
-            pa_source_suspend(u->source, true, PA_SUSPEND_USER);
-        }
-
-        if (u->sink) {
-            pa_log_debug("Suspending sink %s, because the remote end closed the stream.", u->sink->name);
-            pa_sink_suspend(u->sink, true, PA_SUSPEND_USER);
-        }
-    }
-}
-
-/* Run from main thread */
-static void sink_set_volume_cb(pa_sink *s) {
-    uint16_t gain;
-    pa_volume_t volume;
-    struct userdata *u;
-    char *k;
-
-    pa_assert(s);
-    pa_assert(s->core);
-
-    k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s);
-    u = pa_shared_get(s->core, k);
-    pa_xfree(k);
-
-    pa_assert(u);
-    pa_assert(u->sink == s);
-    pa_assert(u->profile == PROFILE_HSP);
-    pa_assert(u->transport);
-
-    gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM);
-    volume = (pa_volume_t) round((double) gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
-
-    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
-
-    pa_bluetooth_transport_set_speaker_gain(u->transport, gain);
-}
-
-/* Run from main thread */
-static void source_set_volume_cb(pa_source *s) {
-    uint16_t gain;
-    pa_volume_t volume;
-    struct userdata *u;
-    char *k;
-
-    pa_assert(s);
-    pa_assert(s->core);
-
-    k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s);
-    u = pa_shared_get(s->core, k);
-    pa_xfree(k);
-
-    pa_assert(u);
-    pa_assert(u->source == s);
-    pa_assert(u->profile == PROFILE_HSP);
-    pa_assert(u->transport);
-
-    gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM);
-    volume = (pa_volume_t) round((double) gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
-
-    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
-
-    pa_bluetooth_transport_set_microphone_gain(u->transport, gain);
-}
-
-/* Run from main thread */
-static char *get_name(const char *type, pa_modargs *ma, const char *device_id, bool *namereg_fail) {
-    char *t;
-    const char *n;
-
-    pa_assert(type);
-    pa_assert(ma);
-    pa_assert(device_id);
-    pa_assert(namereg_fail);
-
-    t = pa_sprintf_malloc("%s_name", type);
-    n = pa_modargs_get_value(ma, t, NULL);
-    pa_xfree(t);
-
-    if (n) {
-        *namereg_fail = true;
-        return pa_xstrdup(n);
-    }
-
-    if ((n = pa_modargs_get_value(ma, "name", NULL)))
-        *namereg_fail = true;
-    else {
-        n = device_id;
-        *namereg_fail = false;
-    }
-
-    return pa_sprintf_malloc("bluez_%s.%s", type, n);
-}
-
-static int sco_over_pcm_state_update(struct userdata *u, bool changed) {
-    pa_assert(u);
-    pa_assert(USE_SCO_OVER_PCM(u));
-
-    if (PA_SINK_IS_OPENED(pa_sink_get_state(u->hsp.sco_sink)) ||
-        PA_SOURCE_IS_OPENED(pa_source_get_state(u->hsp.sco_source))) {
-
-        if (u->stream_fd >= 0)
-            return 0;
-
-        pa_log_debug("Resuming SCO over PCM");
-        if (init_profile(u) < 0) {
-            pa_log("Can't resume SCO over PCM");
-            return -1;
-        }
-
-        if (bt_transport_acquire(u, false) < 0)
-            return -1;
-
-        setup_stream(u);
-
-        return 0;
-    }
-
-    if (changed) {
-        if (u->stream_fd < 0)
-            return 0;
-
-        pa_log_debug("Closing SCO over PCM");
-
-        bt_transport_release(u);
-    }
-
-    return 0;
-}
-
-static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, struct userdata *u) {
-    pa_assert(c);
-    pa_sink_assert_ref(s);
-    pa_assert(u);
-
-    if (!USE_SCO_OVER_PCM(u) || s != u->hsp.sco_sink)
-        return PA_HOOK_OK;
-
-    sco_over_pcm_state_update(u, true);
-
-    return PA_HOOK_OK;
-}
-
-static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct userdata *u) {
-    pa_assert(c);
-    pa_source_assert_ref(s);
-    pa_assert(u);
-
-    if (!USE_SCO_OVER_PCM(u) || s != u->hsp.sco_source)
-        return PA_HOOK_OK;
-
-    sco_over_pcm_state_update(u, true);
-
-    return PA_HOOK_OK;
-}
-
-static pa_hook_result_t transport_nrec_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
-    pa_proplist *p;
-
-    pa_assert(t);
-    pa_assert(u);
-
-    if (t != u->transport)
-        return PA_HOOK_OK;
-
-    p = pa_proplist_new();
-    pa_proplist_sets(p, "bluetooth.nrec", t->nrec ? "1" : "0");
-    pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, p);
-    pa_proplist_free(p);
-
-    return PA_HOOK_OK;
-}
-
-static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t,
-                                                             struct userdata *u) {
-    pa_cvolume v;
-
-    pa_assert(t);
-    pa_assert(u);
-
-    if (t != u->transport)
-        return PA_HOOK_OK;
-
-    pa_assert(u->source);
-
-    pa_cvolume_set(&v, u->sample_spec.channels,
-                   (pa_volume_t) round((double) t->microphone_gain * PA_VOLUME_NORM / HSP_MAX_GAIN));
-    pa_source_volume_changed(u->source, &v);
-
-    return PA_HOOK_OK;
-}
-
-static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t,
-                                                          struct userdata *u) {
-    pa_cvolume v;
-
-    pa_assert(t);
-    pa_assert(u);
-
-    if (t != u->transport)
-        return PA_HOOK_OK;
-
-    pa_assert(u->sink);
-
-    pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) round((double) t->speaker_gain * PA_VOLUME_NORM / HSP_MAX_GAIN));
-    pa_sink_volume_changed(u->sink, &v);
-
-    return PA_HOOK_OK;
-}
-
-static void connect_ports(struct userdata *u, void *sink_or_source_new_data, pa_direction_t direction) {
-    pa_device_port *port;
-
-    if (direction == PA_DIRECTION_OUTPUT) {
-        pa_sink_new_data *sink_new_data = sink_or_source_new_data;
-
-        pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
-        pa_assert_se(pa_hashmap_put(sink_new_data->ports, port->name, port) >= 0);
-        pa_device_port_ref(port);
-    } else {
-        pa_source_new_data *source_new_data = sink_or_source_new_data;
-
-        pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
-        pa_assert_se(pa_hashmap_put(source_new_data->ports, port->name, port) >= 0);
-        pa_device_port_ref(port);
-    }
-}
-
-static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
-    return 0;
-}
-
-static int source_set_port_cb(pa_source *s, pa_device_port *p) {
-    return 0;
-}
-
-/* Run from main thread */
-static int add_sink(struct userdata *u) {
-    char *k;
-
-    pa_assert(u->transport);
-
-    if (USE_SCO_OVER_PCM(u)) {
-        pa_proplist *p;
-
-        u->sink = u->hsp.sco_sink;
-        p = pa_proplist_new();
-        pa_proplist_sets(p, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
-        pa_proplist_update(u->sink->proplist, PA_UPDATE_MERGE, p);
-        pa_proplist_free(p);
-    } else {
-        pa_sink_new_data data;
-        bool b;
-
-        pa_sink_new_data_init(&data);
-        data.driver = __FILE__;
-        data.module = u->module;
-        pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
-        pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
-        if (u->profile == PROFILE_HSP)
-            pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
-        data.card = u->card;
-        data.name = get_name("sink", u->modargs, u->address, &b);
-        data.namereg_fail = b;
-
-        if (pa_modargs_get_proplist(u->modargs, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
-            pa_log("Invalid properties");
-            pa_sink_new_data_done(&data);
-            return -1;
-        }
-        connect_ports(u, &data, PA_DIRECTION_OUTPUT);
-
-        if (!u->transport_acquired)
-            switch (u->profile) {
-                case PROFILE_A2DP:
-                case PROFILE_HSP:
-                    pa_assert_not_reached(); /* Profile switch should have failed */
-                    break;
-                case PROFILE_HFGW:
-                    data.suspend_cause = PA_SUSPEND_USER;
-                    break;
-                case PROFILE_A2DP_SOURCE:
-                case PROFILE_OFF:
-                    pa_assert_not_reached();
-            }
-
-        u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
-        pa_sink_new_data_done(&data);
-
-        if (!u->sink) {
-            pa_log_error("Failed to create sink");
-            return -1;
-        }
-
-        u->sink->userdata = u;
-        u->sink->parent.process_msg = sink_process_msg;
-        u->sink->set_port = sink_set_port_cb;
-    }
-
-    if (u->profile == PROFILE_HSP) {
-        pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
-        u->sink->n_volume_steps = 16;
-
-        k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
-        pa_shared_set(u->core, k, u);
-        pa_xfree(k);
-    }
-
-    return 0;
-}
-
-/* Run from main thread */
-static int add_source(struct userdata *u) {
-    char *k;
-
-    pa_assert(u->transport);
-
-    if (USE_SCO_OVER_PCM(u)) {
-        u->source = u->hsp.sco_source;
-        pa_proplist_sets(u->source->proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
-    } else {
-        pa_source_new_data data;
-        bool b;
-
-        pa_source_new_data_init(&data);
-        data.driver = __FILE__;
-        data.module = u->module;
-        pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
-        pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
-        if (u->profile == PROFILE_HSP)
-            pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
-
-        data.card = u->card;
-        data.name = get_name("source", u->modargs, u->address, &b);
-        data.namereg_fail = b;
-
-        if (pa_modargs_get_proplist(u->modargs, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
-            pa_log("Invalid properties");
-            pa_source_new_data_done(&data);
-            return -1;
-        }
-
-        connect_ports(u, &data, PA_DIRECTION_INPUT);
-
-        if (!u->transport_acquired)
-            switch (u->profile) {
-                case PROFILE_HSP:
-                    pa_assert_not_reached(); /* Profile switch should have failed */
-                    break;
-                case PROFILE_A2DP_SOURCE:
-                case PROFILE_HFGW:
-                    data.suspend_cause = PA_SUSPEND_USER;
-                    break;
-                case PROFILE_A2DP:
-                case PROFILE_OFF:
-                    pa_assert_not_reached();
-            }
-
-        u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
-        pa_source_new_data_done(&data);
-
-        if (!u->source) {
-            pa_log_error("Failed to create source");
-            return -1;
-        }
-
-        u->source->userdata = u;
-        u->source->parent.process_msg = source_process_msg;
-        u->source->set_port = source_set_port_cb;
-    }
-
-    if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
-        pa_bluetooth_transport *t = u->transport;
-        pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
-    }
-
-    if (u->profile == PROFILE_HSP) {
-        pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
-        u->source->n_volume_steps = 16;
-
-        k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
-        pa_shared_set(u->core, k, u);
-        pa_xfree(k);
-    }
-
-    return 0;
-}
-
-static void bt_transport_config_a2dp(struct userdata *u) {
-    const pa_bluetooth_transport *t;
-    struct a2dp_info *a2dp = &u->a2dp;
-    a2dp_sbc_t *config;
-
-    t = u->transport;
-    pa_assert(t);
-
-    config = (a2dp_sbc_t *) t->config;
-
-    u->sample_spec.format = PA_SAMPLE_S16LE;
-
-    if (a2dp->sbc_initialized)
-        sbc_reinit(&a2dp->sbc, 0);
-    else
-        sbc_init(&a2dp->sbc, 0);
-    a2dp->sbc_initialized = true;
-
-    switch (config->frequency) {
-        case SBC_SAMPLING_FREQ_16000:
-            a2dp->sbc.frequency = SBC_FREQ_16000;
-            u->sample_spec.rate = 16000U;
-            break;
-        case SBC_SAMPLING_FREQ_32000:
-            a2dp->sbc.frequency = SBC_FREQ_32000;
-            u->sample_spec.rate = 32000U;
-            break;
-        case SBC_SAMPLING_FREQ_44100:
-            a2dp->sbc.frequency = SBC_FREQ_44100;
-            u->sample_spec.rate = 44100U;
-            break;
-        case SBC_SAMPLING_FREQ_48000:
-            a2dp->sbc.frequency = SBC_FREQ_48000;
-            u->sample_spec.rate = 48000U;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
-
-    switch (config->channel_mode) {
-        case SBC_CHANNEL_MODE_MONO:
-            a2dp->sbc.mode = SBC_MODE_MONO;
-            u->sample_spec.channels = 1;
-            break;
-        case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-            a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL;
-            u->sample_spec.channels = 2;
-            break;
-        case SBC_CHANNEL_MODE_STEREO:
-            a2dp->sbc.mode = SBC_MODE_STEREO;
-            u->sample_spec.channels = 2;
-            break;
-        case SBC_CHANNEL_MODE_JOINT_STEREO:
-            a2dp->sbc.mode = SBC_MODE_JOINT_STEREO;
-            u->sample_spec.channels = 2;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
-
-    switch (config->allocation_method) {
-        case SBC_ALLOCATION_SNR:
-            a2dp->sbc.allocation = SBC_AM_SNR;
-            break;
-        case SBC_ALLOCATION_LOUDNESS:
-            a2dp->sbc.allocation = SBC_AM_LOUDNESS;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
-
-    switch (config->subbands) {
-        case SBC_SUBBANDS_4:
-            a2dp->sbc.subbands = SBC_SB_4;
-            break;
-        case SBC_SUBBANDS_8:
-            a2dp->sbc.subbands = SBC_SB_8;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
-
-    switch (config->block_length) {
-        case SBC_BLOCK_LENGTH_4:
-            a2dp->sbc.blocks = SBC_BLK_4;
-            break;
-        case SBC_BLOCK_LENGTH_8:
-            a2dp->sbc.blocks = SBC_BLK_8;
-            break;
-        case SBC_BLOCK_LENGTH_12:
-            a2dp->sbc.blocks = SBC_BLK_12;
-            break;
-        case SBC_BLOCK_LENGTH_16:
-            a2dp->sbc.blocks = SBC_BLK_16;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
-
-    a2dp->min_bitpool = config->min_bitpool;
-    a2dp->max_bitpool = config->max_bitpool;
-
-    /* Set minimum bitpool for source to get the maximum possible block_size */
-    a2dp->sbc.bitpool = u->profile == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool;
-    a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
-    a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
-
-    pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
-                a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool);
-}
-
-static void bt_transport_config(struct userdata *u) {
-    if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
-        u->sample_spec.format = PA_SAMPLE_S16LE;
-        u->sample_spec.channels = 1;
-        u->sample_spec.rate = 8000;
-    } else
-        bt_transport_config_a2dp(u);
-}
-
-/* Run from main thread */
-static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
-    pa_assert(t);
-    pa_assert(u);
-
-    if (t == u->transport && t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
-        pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
-
-    if (t->device == u->device)
-        handle_transport_state_change(u, t);
-
-    return PA_HOOK_OK;
-}
-
-/* Run from main thread */
-static int setup_transport(struct userdata *u) {
-    pa_bluetooth_transport *t;
-
-    pa_assert(u);
-    pa_assert(!u->transport);
-    pa_assert(u->profile != PROFILE_OFF);
-
-    /* check if profile has a transport */
-    t = u->device->transports[u->profile];
-    if (!t || t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
-        pa_log_warn("Profile has no transport");
-        return -1;
-    }
-
-    u->transport = t;
-
-    if (u->profile == PROFILE_A2DP_SOURCE || u->profile == PROFILE_HFGW)
-        bt_transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
-    else if (bt_transport_acquire(u, false) < 0)
-        return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
-
-    bt_transport_config(u);
-
-    return 0;
-}
-
-/* Run from main thread */
-static int init_profile(struct userdata *u) {
-    int r = 0;
-    pa_assert(u);
-    pa_assert(u->profile != PROFILE_OFF);
-
-    if (setup_transport(u) < 0)
-        return -1;
-
-    pa_assert(u->transport);
-
-    if (u->profile == PROFILE_A2DP ||
-        u->profile == PROFILE_HSP ||
-        u->profile == PROFILE_HFGW)
-        if (add_sink(u) < 0)
-            r = -1;
-
-    if (u->profile == PROFILE_HSP ||
-        u->profile == PROFILE_A2DP_SOURCE ||
-        u->profile == PROFILE_HFGW)
-        if (add_source(u) < 0)
-            r = -1;
-
-    return r;
-}
-
-/* Run from main thread */
-static void stop_thread(struct userdata *u) {
-    char *k;
-
-    pa_assert(u);
-
-    if (u->sink && !USE_SCO_OVER_PCM(u))
-        pa_sink_unlink(u->sink);
-
-    if (u->source && !USE_SCO_OVER_PCM(u))
-        pa_source_unlink(u->source);
-
-    if (u->thread) {
-        pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
-        pa_thread_free(u->thread);
-        u->thread = NULL;
-    }
-
-    if (u->rtpoll_item) {
-        pa_rtpoll_item_free(u->rtpoll_item);
-        u->rtpoll_item = NULL;
-    }
-
-    if (u->rtpoll) {
-        pa_thread_mq_done(&u->thread_mq);
-
-        pa_rtpoll_free(u->rtpoll);
-        u->rtpoll = NULL;
-    }
-
-    if (u->transport) {
-        bt_transport_release(u);
-        u->transport = NULL;
-    }
-
-    if (u->sink) {
-        if (u->profile == PROFILE_HSP) {
-            k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
-            pa_shared_remove(u->core, k);
-            pa_xfree(k);
-        }
-
-        pa_sink_unref(u->sink);
-        u->sink = NULL;
-    }
-
-    if (u->source) {
-        if (u->profile == PROFILE_HSP) {
-            k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
-            pa_shared_remove(u->core, k);
-            pa_xfree(k);
-        }
-
-        pa_source_unref(u->source);
-        u->source = NULL;
-    }
-
-    if (u->read_smoother) {
-        pa_smoother_free(u->read_smoother);
-        u->read_smoother = NULL;
-    }
-}
-
-/* Run from main thread */
-static int start_thread(struct userdata *u) {
-    pa_assert(u);
-    pa_assert(!u->thread);
-    pa_assert(!u->rtpoll);
-    pa_assert(!u->rtpoll_item);
-
-    u->rtpoll = pa_rtpoll_new();
-    pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll);
-
-    if (USE_SCO_OVER_PCM(u)) {
-        if (sco_over_pcm_state_update(u, false) < 0) {
-            char *k;
-
-            if (u->sink) {
-                k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
-                pa_shared_remove(u->core, k);
-                pa_xfree(k);
-                u->sink = NULL;
-            }
-            if (u->source) {
-                k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
-                pa_shared_remove(u->core, k);
-                pa_xfree(k);
-                u->source = NULL;
-            }
-            return -1;
-        }
-
-        pa_sink_ref(u->sink);
-        pa_source_ref(u->source);
-        /* FIXME: monitor stream_fd error */
-        return 0;
-    }
-
-    if (!(u->thread = pa_thread_new("bluetooth", thread_func, u))) {
-        pa_log_error("Failed to create IO thread");
-        return -1;
-    }
-
-    if (u->sink) {
-        pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
-        pa_sink_set_rtpoll(u->sink, u->rtpoll);
-        pa_sink_put(u->sink);
-
-        if (u->sink->set_volume)
-            u->sink->set_volume(u->sink);
-    }
-
-    if (u->source) {
-        pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
-        pa_source_set_rtpoll(u->source, u->rtpoll);
-        pa_source_put(u->source);
-
-        if (u->source->set_volume)
-            u->source->set_volume(u->source);
-    }
-
-    return 0;
-}
-
-static void save_sco_volume_callbacks(struct userdata *u) {
-    pa_assert(u);
-    pa_assert(USE_SCO_OVER_PCM(u));
-
-    u->hsp.sco_sink_set_volume = u->hsp.sco_sink->set_volume;
-    u->hsp.sco_source_set_volume = u->hsp.sco_source->set_volume;
-}
-
-static void restore_sco_volume_callbacks(struct userdata *u) {
-    pa_assert(u);
-    pa_assert(USE_SCO_OVER_PCM(u));
-
-    pa_sink_set_set_volume_callback(u->hsp.sco_sink, u->hsp.sco_sink_set_volume);
-    pa_source_set_set_volume_callback(u->hsp.sco_source, u->hsp.sco_source_set_volume);
-}
-
-/* Run from main thread */
-static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
-    struct userdata *u;
-    enum profile *d;
-
-    pa_assert(c);
-    pa_assert(new_profile);
-    pa_assert_se(u = c->userdata);
-
-    d = PA_CARD_PROFILE_DATA(new_profile);
-
-    if (*d != PROFILE_OFF) {
-        const pa_bluetooth_device *device = u->device;
-
-        if (!device->transports[*d] || device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
-            pa_log_warn("Profile not connected, refused to switch profile to %s", new_profile->name);
-            return -PA_ERR_IO;
-        }
-    }
-
-    stop_thread(u);
-
-    if (USE_SCO_OVER_PCM(u))
-        restore_sco_volume_callbacks(u);
-
-    u->profile = *d;
-    u->sample_spec = u->requested_sample_spec;
-
-    if (USE_SCO_OVER_PCM(u))
-        save_sco_volume_callbacks(u);
-
-    if (u->profile != PROFILE_OFF)
-        if (init_profile(u) < 0)
-            goto off;
-
-    if (u->sink || u->source)
-        if (start_thread(u) < 0)
-            goto off;
-
-    return 0;
-
-off:
-    stop_thread(u);
-
-    pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
-
-    return -PA_ERR_IO;
-}
-
-/* Run from main thread */
-static void create_card_ports(struct userdata *u, pa_hashmap *ports) {
-    pa_device_port *port;
-    pa_device_port_new_data port_data;
-
-    const char *name_prefix = NULL;
-    const char *input_description = NULL;
-    const char *output_description = NULL;
-
-    pa_assert(u);
-    pa_assert(ports);
-    pa_assert(u->device);
-
-    switch (pa_bluetooth_get_form_factor(u->device->class)) {
-        case PA_BT_FORM_FACTOR_UNKNOWN:
-            break;
-
-        case PA_BT_FORM_FACTOR_HEADSET:
-            name_prefix = "headset";
-            input_description = output_description = _("Headset");
-            break;
-
-        case PA_BT_FORM_FACTOR_HANDSFREE:
-            name_prefix = "handsfree";
-            input_description = output_description = _("Handsfree");
-            break;
-
-        case PA_BT_FORM_FACTOR_MICROPHONE:
-            name_prefix = "microphone";
-            input_description = _("Microphone");
-            break;
-
-        case PA_BT_FORM_FACTOR_SPEAKER:
-            name_prefix = "speaker";
-            output_description = _("Speaker");
-            break;
-
-        case PA_BT_FORM_FACTOR_HEADPHONE:
-            name_prefix = "headphone";
-            output_description = _("Headphone");
-            break;
-
-        case PA_BT_FORM_FACTOR_PORTABLE:
-            name_prefix = "portable";
-            input_description = output_description = _("Portable");
-            break;
-
-        case PA_BT_FORM_FACTOR_CAR:
-            name_prefix = "car";
-            input_description = output_description = _("Car");
-            break;
-
-        case PA_BT_FORM_FACTOR_HIFI:
-            name_prefix = "hifi";
-            input_description = output_description = _("HiFi");
-            break;
-
-        case PA_BT_FORM_FACTOR_PHONE:
-            name_prefix = "phone";
-            input_description = output_description = _("Phone");
-            break;
-    }
-
-    if (!name_prefix)
-        name_prefix = "unknown";
-
-    if (!output_description)
-        output_description = _("Bluetooth Output");
-
-    if (!input_description)
-        input_description = _("Bluetooth Input");
-
-    u->output_port_name = pa_sprintf_malloc("%s-output", name_prefix);
-    u->input_port_name = pa_sprintf_malloc("%s-input", name_prefix);
-
-    pa_device_port_new_data_init(&port_data);
-    pa_device_port_new_data_set_name(&port_data, u->output_port_name);
-    pa_device_port_new_data_set_description(&port_data, output_description);
-    pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_OUTPUT);
-    pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_OUTPUT));
-    pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0));
-    pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0);
-    pa_device_port_new_data_done(&port_data);
-
-    pa_device_port_new_data_init(&port_data);
-    pa_device_port_new_data_set_name(&port_data, u->input_port_name);
-    pa_device_port_new_data_set_description(&port_data, input_description);
-    pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_INPUT);
-    pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_INPUT));
-    pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0));
-    pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0);
-    pa_device_port_new_data_done(&port_data);
-}
-
-/* Run from main thread */
-static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid, pa_hashmap *ports) {
-    pa_device_port *input_port, *output_port;
-    pa_card_profile *p = NULL;
-    enum profile *d;
-
-    pa_assert(u->input_port_name);
-    pa_assert(u->output_port_name);
-    pa_assert_se(input_port = pa_hashmap_get(ports, u->input_port_name));
-    pa_assert_se(output_port = pa_hashmap_get(ports, u->output_port_name));
-
-    if (pa_streq(uuid, A2DP_SINK_UUID)) {
-        p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile));
-        p->priority = 10;
-        p->n_sinks = 1;
-        p->n_sources = 0;
-        p->max_sink_channels = 2;
-        p->max_source_channels = 0;
-        pa_hashmap_put(output_port->profiles, p->name, p);
-
-        d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_A2DP;
-    } else if (pa_streq(uuid, A2DP_SOURCE_UUID)) {
-        p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile));
-        p->priority = 10;
-        p->n_sinks = 0;
-        p->n_sources = 1;
-        p->max_sink_channels = 0;
-        p->max_source_channels = 2;
-        pa_hashmap_put(input_port->profiles, p->name, p);
-
-        d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_A2DP_SOURCE;
-    } else if (pa_streq(uuid, HSP_HS_UUID) || pa_streq(uuid, HFP_HS_UUID)) {
-        p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile));
-        p->priority = 20;
-        p->n_sinks = 1;
-        p->n_sources = 1;
-        p->max_sink_channels = 1;
-        p->max_source_channels = 1;
-        pa_hashmap_put(input_port->profiles, p->name, p);
-        pa_hashmap_put(output_port->profiles, p->name, p);
-
-        d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_HSP;
-    } else if (pa_streq(uuid, HFP_AG_UUID)) {
-        p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(enum profile));
-        p->priority = 20;
-        p->n_sinks = 1;
-        p->n_sources = 1;
-        p->max_sink_channels = 1;
-        p->max_source_channels = 1;
-        pa_hashmap_put(input_port->profiles, p->name, p);
-        pa_hashmap_put(output_port->profiles, p->name, p);
-
-        d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_HFGW;
-    }
-
-    if (p) {
-        pa_bluetooth_transport *t;
-
-        if ((t = u->device->transports[*d]))
-            p->available = transport_state_to_availability(t->state);
-    }
-
-    return p;
-}
-
-/* Run from main thread */
-static int add_card(struct userdata *u) {
-    pa_card_new_data data;
-    bool b;
-    pa_card_profile *p;
-    enum profile *d;
-    pa_bt_form_factor_t ff;
-    char *n;
-    const char *default_profile;
-    const pa_bluetooth_device *device;
-    const pa_bluetooth_uuid *uuid;
-
-    pa_assert(u);
-    pa_assert(u->device);
-
-    device = u->device;
-
-    pa_card_new_data_init(&data);
-    data.driver = __FILE__;
-    data.module = u->module;
-
-    n = pa_bluetooth_cleanup_name(device->alias);
-    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, n);
-    pa_xfree(n);
-    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, device->address);
-    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "bluez");
-    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "sound");
-    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_BUS, "bluetooth");
-
-    if ((ff = pa_bluetooth_get_form_factor(device->class)) != PA_BT_FORM_FACTOR_UNKNOWN)
-        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, pa_bt_form_factor_to_string(ff));
-
-    pa_proplist_sets(data.proplist, "bluez.path", device->path);
-    pa_proplist_setf(data.proplist, "bluez.class", "0x%06x", (unsigned) device->class);
-    pa_proplist_sets(data.proplist, "bluez.alias", device->alias);
-    data.name = get_name("card", u->modargs, device->address, &b);
-    data.namereg_fail = b;
-
-    if (pa_modargs_get_proplist(u->modargs, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
-        pa_log("Invalid properties");
-        pa_card_new_data_done(&data);
-        return -1;
-    }
-
-    create_card_ports(u, data.ports);
-
-    PA_LLIST_FOREACH(uuid, device->uuids) {
-        p = create_card_profile(u, uuid->uuid, data.ports);
-
-        if (!p)
-            continue;
-
-        if (pa_hashmap_get(data.profiles, p->name)) {
-            pa_card_profile_free(p);
-            continue;
-        }
-
-        pa_hashmap_put(data.profiles, p->name, p);
-    }
-
-    pa_assert(!pa_hashmap_isempty(data.profiles));
-
-    p = pa_card_profile_new("off", _("Off"), sizeof(enum profile));
-    p->available = PA_AVAILABLE_YES;
-    d = PA_CARD_PROFILE_DATA(p);
-    *d = PROFILE_OFF;
-    pa_hashmap_put(data.profiles, p->name, p);
-
-    if ((default_profile = pa_modargs_get_value(u->modargs, "profile", NULL))) {
-        if (pa_hashmap_get(data.profiles, default_profile))
-            pa_card_new_data_set_profile(&data, default_profile);
-        else
-            pa_log_warn("Profile '%s' not valid or not supported by device.", default_profile);
-    }
-
-    u->card = pa_card_new(u->core, &data);
-    pa_card_new_data_done(&data);
-
-    if (!u->card) {
-        pa_log("Failed to allocate card.");
-        return -1;
-    }
-
-    u->card->userdata = u;
-    u->card->set_profile = card_set_profile;
-
-    d = PA_CARD_PROFILE_DATA(u->card->active_profile);
-
-    if (*d != PROFILE_OFF && (!device->transports[*d] ||
-                              device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)) {
-        pa_log_warn("Default profile not connected, selecting off profile");
-        u->card->active_profile = pa_hashmap_get(u->card->profiles, "off");
-        u->card->save_profile = false;
-    }
-
-    d = PA_CARD_PROFILE_DATA(u->card->active_profile);
-    u->profile = *d;
-
-    if (USE_SCO_OVER_PCM(u))
-        save_sco_volume_callbacks(u);
-
-    return 0;
-}
-
-/* Run from main thread */
-static pa_bluetooth_device* find_device(struct userdata *u, const char *address, const char *path) {
-    pa_bluetooth_device *d = NULL;
-
-    pa_assert(u);
-
-    if (!address && !path) {
-        pa_log_error("Failed to get device address/path from module arguments.");
-        return NULL;
-    }
-
-    if (path) {
-        if (!(d = pa_bluetooth_discovery_get_by_path(u->discovery, path))) {
-            pa_log_error("%s is not a valid BlueZ audio device.", path);
-            return NULL;
-        }
-
-        if (address && !(pa_streq(d->address, address))) {
-            pa_log_error("Passed path %s address %s != %s don't match.", path, d->address, address);
-            return NULL;
-        }
-
-    } else {
-        if (!(d = pa_bluetooth_discovery_get_by_address(u->discovery, address))) {
-            pa_log_error("%s is not known.", address);
-            return NULL;
-        }
-    }
-
-    if (d) {
-        u->address = pa_xstrdup(d->address);
-        u->path = pa_xstrdup(d->path);
-    }
-
-    return d;
-}
-
-/* Run from main thread */
-static pa_hook_result_t uuid_added_cb(pa_bluetooth_discovery *y, const struct pa_bluetooth_hook_uuid_data *data,
-                                      struct userdata *u) {
-    pa_card_profile *p;
-
-    pa_assert(data);
-    pa_assert(data->device);
-    pa_assert(data->uuid);
-    pa_assert(u);
-
-    if (data->device != u->device)
-        return PA_HOOK_OK;
-
-    p = create_card_profile(u, data->uuid, u->card->ports);
-
-    if (!p)
-        return PA_HOOK_OK;
-
-    if (pa_hashmap_get(u->card->profiles, p->name)) {
-        pa_card_profile_free(p);
-        return PA_HOOK_OK;
-    }
-
-    pa_card_add_profile(u->card, p);
-
-    return PA_HOOK_OK;
-}
-
-/* Run from main thread */
-static pa_hook_result_t discovery_hook_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
-    pa_assert(u);
-    pa_assert(d);
-
-    if (d != u->device)
-        return PA_HOOK_OK;
-
-    if (d->dead)
-        pa_log_debug("Device %s removed: unloading module", d->path);
-    else if (!pa_bluetooth_device_any_audio_connected(d))
-        pa_log_debug("Unloading module, because device %s doesn't have any audio profiles connected anymore.", d->path);
-    else
-        return PA_HOOK_OK;
-
-    pa_module_unload(u->core, u->module, true);
-
-    return PA_HOOK_OK;
-}
-
-int pa__init(pa_module *m) {
-    pa_modargs *ma;
-    uint32_t channels;
-    struct userdata *u;
-    const char *address, *path;
-    pa_bluetooth_device *device;
-
-    pa_assert(m);
-
-    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
-        pa_log_error("Failed to parse module arguments");
-        goto fail;
-    }
-
-    m->userdata = u = pa_xnew0(struct userdata, 1);
-    u->module = m;
-    u->core = m->core;
-    u->stream_fd = -1;
-    u->sample_spec = m->core->default_sample_spec;
-    u->modargs = ma;
-
-    if (pa_modargs_get_value(ma, "sco_sink", NULL) &&
-        !(u->hsp.sco_sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_sink", NULL), PA_NAMEREG_SINK))) {
-        pa_log("SCO sink not found");
-        goto fail;
-    }
-
-    if (pa_modargs_get_value(ma, "sco_source", NULL) &&
-        !(u->hsp.sco_source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_source", NULL), PA_NAMEREG_SOURCE))) {
-        pa_log("SCO source not found");
-        goto fail;
-    }
-
-    if (pa_modargs_get_value_u32(ma, "rate", &u->sample_spec.rate) < 0 ||
-        u->sample_spec.rate <= 0 || u->sample_spec.rate > PA_RATE_MAX) {
-        pa_log_error("Failed to get rate from module arguments");
-        goto fail;
-    }
-
-    u->auto_connect = true;
-    if (pa_modargs_get_value_boolean(ma, "auto_connect", &u->auto_connect)) {
-        pa_log("Failed to parse auto_connect= argument");
-        goto fail;
-    }
-
-    channels = u->sample_spec.channels;
-    if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 ||
-        channels <= 0 || channels > PA_CHANNELS_MAX) {
-        pa_log_error("Failed to get channels from module arguments");
-        goto fail;
-    }
-    u->sample_spec.channels = (uint8_t) channels;
-    u->requested_sample_spec = u->sample_spec;
-
-    address = pa_modargs_get_value(ma, "address", NULL);
-    path = pa_modargs_get_value(ma, "path", NULL);
-
-    if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))
-        goto fail;
-
-    if (!(device = find_device(u, address, path)))
-        goto fail;
-
-    u->device = device;
-
-    u->discovery_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
-                        PA_HOOK_NORMAL, (pa_hook_cb_t) discovery_hook_cb, u);
-
-    u->uuid_added_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED),
-                        PA_HOOK_NORMAL, (pa_hook_cb_t) uuid_added_cb, u);
-
-    u->sink_state_changed_slot =
-        pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED],
-                        PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_cb, u);
-
-    u->source_state_changed_slot =
-        pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED],
-                        PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_cb, u);
-
-    u->transport_state_changed_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED),
-                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u);
-
-    u->transport_nrec_changed_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED),
-                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_nrec_changed_cb, u);
-
-    u->transport_microphone_changed_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED),
-                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
-
-    u->transport_speaker_changed_slot =
-        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED),
-                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_speaker_gain_changed_cb, u);
-
-    /* Add the card structure. This will also initialize the default profile */
-    if (add_card(u) < 0)
-        goto fail;
-
-    if (!(u->msg = pa_msgobject_new(bluetooth_msg)))
-        goto fail;
-
-    u->msg->parent.process_msg = device_process_msg;
-    u->msg->card = u->card;
-
-    if (u->profile != PROFILE_OFF)
-        if (init_profile(u) < 0)
-            goto off;
-
-    if (u->sink || u->source)
-        if (start_thread(u) < 0)
-            goto off;
-
-    return 0;
-
-off:
-    stop_thread(u);
-
-    pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
-
-    return 0;
-
-fail:
-
-    pa__done(m);
-
-    return -1;
-}
-
-int pa__get_n_used(pa_module *m) {
-    struct userdata *u;
-
-    pa_assert(m);
-    pa_assert_se(u = m->userdata);
-
-    return
-        (u->sink ? pa_sink_linked_by(u->sink) : 0) +
-        (u->source ? pa_source_linked_by(u->source) : 0);
-}
-
-void pa__done(pa_module *m) {
-    struct userdata *u;
-
-    pa_assert(m);
-
-    if (!(u = m->userdata))
-        return;
-
-    stop_thread(u);
-
-    if (u->discovery_slot)
-        pa_hook_slot_free(u->discovery_slot);
-
-    if (u->uuid_added_slot)
-        pa_hook_slot_free(u->uuid_added_slot);
-
-    if (u->sink_state_changed_slot)
-        pa_hook_slot_free(u->sink_state_changed_slot);
-
-    if (u->source_state_changed_slot)
-        pa_hook_slot_free(u->source_state_changed_slot);
-
-    if (u->transport_state_changed_slot)
-        pa_hook_slot_free(u->transport_state_changed_slot);
-
-    if (u->transport_nrec_changed_slot)
-        pa_hook_slot_free(u->transport_nrec_changed_slot);
-
-    if (u->transport_microphone_changed_slot)
-        pa_hook_slot_free(u->transport_microphone_changed_slot);
-
-    if (u->transport_speaker_changed_slot)
-        pa_hook_slot_free(u->transport_speaker_changed_slot);
-
-    if (USE_SCO_OVER_PCM(u))
-        restore_sco_volume_callbacks(u);
-
-    if (u->msg)
-        pa_xfree(u->msg);
-
-    if (u->card)
-        pa_card_free(u->card);
-
-    if (u->a2dp.buffer)
-        pa_xfree(u->a2dp.buffer);
-
-    sbc_finish(&u->a2dp.sbc);
-
-    if (u->modargs)
-        pa_modargs_free(u->modargs);
-
-    pa_xfree(u->output_port_name);
-    pa_xfree(u->input_port_name);
-
-    pa_xfree(u->address);
-    pa_xfree(u->path);
-
-    if (u->discovery)
-        pa_bluetooth_discovery_unref(u->discovery);
-
-    pa_xfree(u);
-}
diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c
deleted file mode 100644
index d89890b..0000000
--- a/src/modules/bluetooth/module-bluetooth-discover.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  Copyright 2008-2013 João Paulo Rechi Vita
-
-  PulseAudio is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Lesser General Public License as
-  published by the Free Software Foundation; either version 2.1 of the
-  License, or (at your option) any later version.
-
-  PulseAudio 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 Lesser General Public
-  License along with PulseAudio; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-  USA.
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <pulse/xmalloc.h>
-#include <pulsecore/module.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/modargs.h>
-#include <pulsecore/macro.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/dbus-shared.h>
-
-#include "module-bluetooth-discover-symdef.h"
-#include "bluetooth-util.h"
-
-PA_MODULE_AUTHOR("João Paulo Rechi Vita");
-PA_MODULE_DESCRIPTION("Detect available BlueZ 4 Bluetooth audio devices and load BlueZ 4 Bluetooth audio drivers");
-PA_MODULE_VERSION(PACKAGE_VERSION);
-PA_MODULE_USAGE("sco_sink=<name of sink> "
-                "sco_source=<name of source> ");
-PA_MODULE_LOAD_ONCE(true);
-
-static const char* const valid_modargs[] = {
-    "sco_sink",
-    "sco_source",
-    "async", /* deprecated */
-    NULL
-};
-
-struct userdata {
-    pa_module *module;
-    pa_modargs *modargs;
-    pa_core *core;
-    pa_bluetooth_discovery *discovery;
-    pa_hook_slot *slot;
-    pa_hashmap *hashmap;
-};
-
-struct module_info {
-    char *path;
-    uint32_t module;
-};
-
-static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
-    struct module_info *mi;
-
-    pa_assert(u);
-    pa_assert(d);
-
-    mi = pa_hashmap_get(u->hashmap, d->path);
-
-    if (pa_bluetooth_device_any_audio_connected(d)) {
-
-        if (!mi) {
-            pa_module *m = NULL;
-            char *args;
-
-            /* Oh, awesome, a new device has shown up and been connected! */
-
-            args = pa_sprintf_malloc("address=\"%s\" path=\"%s\"", d->address, d->path);
-
-            if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) &&
-                pa_modargs_get_value(u->modargs, "sco_source", NULL)) {
-                char *tmp;
-
-                tmp = pa_sprintf_malloc("%s sco_sink=\"%s\" sco_source=\"%s\"", args,
-                                        pa_modargs_get_value(u->modargs, "sco_sink", NULL),
-                                        pa_modargs_get_value(u->modargs, "sco_source", NULL));
-                pa_xfree(args);
-                args = tmp;
-            }
-
-            pa_log_debug("Loading module-bluetooth-device %s", args);
-            m = pa_module_load(u->module->core, "module-bluetooth-device", args);
-            pa_xfree(args);
-
-            if (m) {
-                mi = pa_xnew(struct module_info, 1);
-                mi->module = m->index;
-                mi->path = pa_xstrdup(d->path);
-
-                pa_hashmap_put(u->hashmap, mi->path, mi);
-            } else
-                pa_log_debug("Failed to load module for device %s", d->path);
-        }
-
-    } else {
-
-        if (mi) {
-
-            /* Hmm, disconnection? Then the module unloads itself */
-
-            pa_log_debug("Unregistering module for %s", d->path);
-            pa_hashmap_remove(u->hashmap, mi->path);
-            pa_xfree(mi->path);
-            pa_xfree(mi);
-        }
-    }
-
-    return PA_HOOK_OK;
-}
-
-int pa__init(pa_module* m) {
-    struct userdata *u;
-    pa_modargs *ma = NULL;
-
-    pa_assert(m);
-
-    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
-        pa_log("Failed to parse module arguments");
-        goto fail;
-    }
-
-    if (pa_modargs_get_value(ma, "async", NULL))
-        pa_log_warn("The 'async' argument is deprecated and does nothing.");
-
-    m->userdata = u = pa_xnew0(struct userdata, 1);
-    u->module = m;
-    u->core = m->core;
-    u->modargs = ma;
-    ma = NULL;
-    u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-
-    if (!(u->discovery = pa_bluetooth_discovery_get(u->core)))
-        goto fail;
-
-    u->slot = pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
-                              PA_HOOK_NORMAL, (pa_hook_cb_t) load_module_for_device, u);
-
-    return 0;
-
-fail:
-    pa__done(m);
-
-    if (ma)
-        pa_modargs_free(ma);
-
-    return -1;
-}
-
-void pa__done(pa_module* m) {
-    struct userdata *u;
-
-    pa_assert(m);
-
-    if (!(u = m->userdata))
-        return;
-
-    if (u->slot)
-        pa_hook_slot_free(u->slot);
-
-    if (u->discovery)
-        pa_bluetooth_discovery_unref(u->discovery);
-
-    if (u->hashmap) {
-        struct module_info *mi;
-
-        while ((mi = pa_hashmap_steal_first(u->hashmap))) {
-            pa_xfree(mi->path);
-            pa_xfree(mi);
-        }
-
-        pa_hashmap_free(u->hashmap);
-    }
-
-    if (u->modargs)
-        pa_modargs_free(u->modargs);
-
-    pa_xfree(u);
-}
diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
new file mode 100644
index 0000000..2d1224f
--- /dev/null
+++ b/src/modules/bluetooth/module-bluez4-device.c
@@ -0,0 +1,2623 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008-2013 João Paulo Rechi Vita
+  Copyright 2011-2013 BMW Car IT GmbH.
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#include <linux/sockios.h>
+#include <arpa/inet.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/sample.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/i18n.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/poll.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/time-smoother.h>
+#include <pulsecore/namereg.h>
+
+#include <sbc/sbc.h>
+
+#include "module-bluez4-device-symdef.h"
+#include "a2dp-codecs.h"
+#include "rtp.h"
+#include "bluez4-util.h"
+
+#define BITPOOL_DEC_LIMIT 32
+#define BITPOOL_DEC_STEP 5
+
+PA_MODULE_AUTHOR("João Paulo Rechi Vita");
+PA_MODULE_DESCRIPTION("BlueZ 4 Bluetooth audio sink and source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(false);
+PA_MODULE_USAGE(
+        "name=<name for the card/sink/source, to be prefixed> "
+        "card_name=<name for the card> "
+        "card_properties=<properties for the card> "
+        "sink_name=<name for the sink> "
+        "sink_properties=<properties for the sink> "
+        "source_name=<name for the source> "
+        "source_properties=<properties for the source> "
+        "address=<address of the device> "
+        "profile=<a2dp|hsp|hfgw> "
+        "rate=<sample rate> "
+        "channels=<number of channels> "
+        "path=<device object path> "
+        "auto_connect=<automatically connect?> "
+        "sco_sink=<SCO over PCM sink name> "
+        "sco_source=<SCO over PCM source name>");
+
+/* TODO: not close fd when entering suspend mode in a2dp */
+
+static const char* const valid_modargs[] = {
+    "name",
+    "card_name",
+    "card_properties",
+    "sink_name",
+    "sink_properties",
+    "source_name",
+    "source_properties",
+    "address",
+    "profile",
+    "rate",
+    "channels",
+    "path",
+    "auto_connect",
+    "sco_sink",
+    "sco_source",
+    NULL
+};
+
+struct a2dp_info {
+    sbc_t sbc;                           /* Codec data */
+    bool sbc_initialized;                /* Keep track if the encoder is initialized */
+    size_t codesize, frame_length;       /* SBC Codesize, frame_length. We simply cache those values here */
+
+    void* buffer;                        /* Codec transfer buffer */
+    size_t buffer_size;                  /* Size of the buffer */
+
+    uint16_t seq_num;                    /* Cumulative packet sequence */
+    uint8_t min_bitpool;
+    uint8_t max_bitpool;
+};
+
+struct hsp_info {
+    pa_sink *sco_sink;
+    void (*sco_sink_set_volume)(pa_sink *s);
+    pa_source *sco_source;
+    void (*sco_source_set_volume)(pa_source *s);
+};
+
+struct bluetooth_msg {
+    pa_msgobject parent;
+    pa_card *card;
+};
+
+typedef struct bluetooth_msg bluetooth_msg;
+PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject);
+#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))
+
+struct userdata {
+    pa_core *core;
+    pa_module *module;
+
+    pa_bluetooth_device *device;
+    pa_hook_slot *uuid_added_slot;
+    char *address;
+    char *path;
+    pa_bluetooth_transport *transport;
+    bool transport_acquired;
+    pa_hook_slot *discovery_slot;
+    pa_hook_slot *sink_state_changed_slot;
+    pa_hook_slot *source_state_changed_slot;
+    pa_hook_slot *transport_state_changed_slot;
+    pa_hook_slot *transport_nrec_changed_slot;
+    pa_hook_slot *transport_microphone_changed_slot;
+    pa_hook_slot *transport_speaker_changed_slot;
+
+    pa_bluetooth_discovery *discovery;
+    bool auto_connect;
+
+    char *output_port_name;
+    char *input_port_name;
+
+    pa_card *card;
+    pa_sink *sink;
+    pa_source *source;
+
+    pa_thread_mq thread_mq;
+    pa_rtpoll *rtpoll;
+    pa_rtpoll_item *rtpoll_item;
+    pa_thread *thread;
+    bluetooth_msg *msg;
+
+    uint64_t read_index, write_index;
+    pa_usec_t started_at;
+    pa_smoother *read_smoother;
+
+    pa_memchunk write_memchunk;
+
+    pa_sample_spec sample_spec, requested_sample_spec;
+
+    int stream_fd;
+
+    size_t read_link_mtu;
+    size_t read_block_size;
+
+    size_t write_link_mtu;
+    size_t write_block_size;
+
+    struct a2dp_info a2dp;
+    struct hsp_info hsp;
+
+    enum profile profile;
+
+    pa_modargs *modargs;
+
+    int stream_write_type;
+};
+
+enum {
+    BLUETOOTH_MESSAGE_IO_THREAD_FAILED,
+    BLUETOOTH_MESSAGE_MAX
+};
+
+#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC)
+
+#define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC)
+
+#define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source))
+
+static int init_profile(struct userdata *u);
+
+/* from IO thread */
+static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
+    struct a2dp_info *a2dp;
+
+    pa_assert(u);
+
+    a2dp = &u->a2dp;
+
+    if (a2dp->sbc.bitpool == bitpool)
+        return;
+
+    if (bitpool > a2dp->max_bitpool)
+        bitpool = a2dp->max_bitpool;
+    else if (bitpool < a2dp->min_bitpool)
+        bitpool = a2dp->min_bitpool;
+
+    a2dp->sbc.bitpool = bitpool;
+
+    a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
+    a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
+
+    pa_log_debug("Bitpool has changed to %u", a2dp->sbc.bitpool);
+
+    u->read_block_size =
+        (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+        / a2dp->frame_length * a2dp->codesize;
+
+    u->write_block_size =
+        (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+        / a2dp->frame_length * a2dp->codesize;
+
+    pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
+    pa_sink_set_fixed_latency_within_thread(u->sink,
+            FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+}
+
+/* from IO thread, except in SCO over PCM */
+static void bt_transport_config_mtu(struct userdata *u) {
+    /* Calculate block sizes */
+    if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
+        u->read_block_size = u->read_link_mtu;
+        u->write_block_size = u->write_link_mtu;
+    } else {
+        u->read_block_size =
+            (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+            / u->a2dp.frame_length * u->a2dp.codesize;
+
+        u->write_block_size =
+            (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+            / u->a2dp.frame_length * u->a2dp.codesize;
+    }
+
+    if (USE_SCO_OVER_PCM(u))
+        return;
+
+    if (u->sink) {
+        pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
+        pa_sink_set_fixed_latency_within_thread(u->sink,
+                                                (u->profile == PROFILE_A2DP ?
+                                                 FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
+                                                pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+    }
+
+    if (u->source)
+        pa_source_set_fixed_latency_within_thread(u->source,
+                                                  (u->profile == PROFILE_A2DP_SOURCE ?
+                                                   FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
+                                                  pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
+}
+
+/* from IO thread, except in SCO over PCM */
+
+static void setup_stream(struct userdata *u) {
+    struct pollfd *pollfd;
+    int one;
+
+    pa_log_info("Transport %s resuming", u->transport->path);
+
+    bt_transport_config_mtu(u);
+
+    pa_make_fd_nonblock(u->stream_fd);
+    pa_make_socket_low_delay(u->stream_fd);
+
+    one = 1;
+    if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0)
+        pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno));
+
+    pa_log_debug("Stream properly set up, we're ready to roll!");
+
+    if (u->profile == PROFILE_A2DP)
+        a2dp_set_bitpool(u, u->a2dp.max_bitpool);
+
+    u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
+    pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+    pollfd->fd = u->stream_fd;
+    pollfd->events = pollfd->revents = 0;
+
+    u->read_index = u->write_index = 0;
+    u->started_at = 0;
+
+    if (u->source)
+        u->read_smoother = pa_smoother_new(
+                PA_USEC_PER_SEC,
+                PA_USEC_PER_SEC*2,
+                true,
+                true,
+                10,
+                pa_rtclock_now(),
+                true);
+}
+
+static void teardown_stream(struct userdata *u) {
+    if (u->rtpoll_item) {
+        pa_rtpoll_item_free(u->rtpoll_item);
+        u->rtpoll_item = NULL;
+    }
+
+    if (u->stream_fd >= 0) {
+        pa_close(u->stream_fd);
+        u->stream_fd = -1;
+    }
+
+    if (u->read_smoother) {
+        pa_smoother_free(u->read_smoother);
+        u->read_smoother = NULL;
+    }
+
+    if (u->write_memchunk.memblock) {
+        pa_memblock_unref(u->write_memchunk.memblock);
+        pa_memchunk_reset(&u->write_memchunk);
+    }
+
+    pa_log_debug("Audio stream torn down");
+}
+
+static void bt_transport_release(struct userdata *u) {
+    pa_assert(u->transport);
+
+    /* Ignore if already released */
+    if (!u->transport_acquired)
+        return;
+
+    pa_log_debug("Releasing transport %s", u->transport->path);
+
+    pa_bluetooth_transport_release(u->transport);
+
+    u->transport_acquired = false;
+
+    teardown_stream(u);
+}
+
+static int bt_transport_acquire(struct userdata *u, bool optional) {
+    pa_assert(u->transport);
+
+    if (u->transport_acquired)
+        return 0;
+
+    pa_log_debug("Acquiring transport %s", u->transport->path);
+
+    u->stream_fd = pa_bluetooth_transport_acquire(u->transport, optional, &u->read_link_mtu, &u->write_link_mtu);
+    if (u->stream_fd < 0) {
+        if (!optional)
+            pa_log("Failed to acquire transport %s", u->transport->path);
+        else
+            pa_log_info("Failed optional acquire of transport %s", u->transport->path);
+
+        return -1;
+    }
+
+    u->transport_acquired = true;
+    pa_log_info("Transport %s acquired: fd %d", u->transport->path, u->stream_fd);
+
+    return 0;
+}
+
+/* Run from IO thread */
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+    struct userdata *u = PA_SINK(o)->userdata;
+    bool failed = false;
+    int r;
+
+    pa_assert(u->sink == PA_SINK(o));
+    pa_assert(u->transport);
+
+    switch (code) {
+
+        case PA_SINK_MESSAGE_SET_STATE:
+
+            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+
+                case PA_SINK_SUSPENDED:
+                    /* Ignore if transition is PA_SINK_INIT->PA_SINK_SUSPENDED */
+                    if (!PA_SINK_IS_OPENED(u->sink->thread_info.state))
+                        break;
+
+                    /* Stop the device if the source is suspended as well */
+                    if (!u->source || u->source->state == PA_SOURCE_SUSPENDED)
+                        /* We deliberately ignore whether stopping
+                         * actually worked. Since the stream_fd is
+                         * closed it doesn't really matter */
+                        bt_transport_release(u);
+
+                    break;
+
+                case PA_SINK_IDLE:
+                case PA_SINK_RUNNING:
+                    if (u->sink->thread_info.state != PA_SINK_SUSPENDED)
+                        break;
+
+                    /* Resume the device if the source was suspended as well */
+                    if (!u->source || !PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
+                        if (bt_transport_acquire(u, false) < 0)
+                            failed = true;
+                        else
+                            setup_stream(u);
+                    }
+                    break;
+
+                case PA_SINK_UNLINKED:
+                case PA_SINK_INIT:
+                case PA_SINK_INVALID_STATE:
+                    ;
+            }
+            break;
+
+        case PA_SINK_MESSAGE_GET_LATENCY: {
+
+            if (u->read_smoother) {
+                pa_usec_t wi, ri;
+
+                ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
+                wi = pa_bytes_to_usec(u->write_index + u->write_block_size, &u->sample_spec);
+
+                *((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
+            } else {
+                pa_usec_t ri, wi;
+
+                ri = pa_rtclock_now() - u->started_at;
+                wi = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+
+                *((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
+            }
+
+            *((pa_usec_t*) data) += u->sink->thread_info.fixed_latency;
+            return 0;
+        }
+    }
+
+    r = pa_sink_process_msg(o, code, data, offset, chunk);
+
+    return (r < 0 || !failed) ? r : -1;
+}
+
+/* Run from IO thread */
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+    struct userdata *u = PA_SOURCE(o)->userdata;
+    bool failed = false;
+    int r;
+
+    pa_assert(u->source == PA_SOURCE(o));
+    pa_assert(u->transport);
+
+    switch (code) {
+
+        case PA_SOURCE_MESSAGE_SET_STATE:
+
+            switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
+
+                case PA_SOURCE_SUSPENDED:
+                    /* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */
+                    if (!PA_SOURCE_IS_OPENED(u->source->thread_info.state))
+                        break;
+
+                    /* Stop the device if the sink is suspended as well */
+                    if (!u->sink || u->sink->state == PA_SINK_SUSPENDED)
+                        bt_transport_release(u);
+
+                    if (u->read_smoother)
+                        pa_smoother_pause(u->read_smoother, pa_rtclock_now());
+                    break;
+
+                case PA_SOURCE_IDLE:
+                case PA_SOURCE_RUNNING:
+                    if (u->source->thread_info.state != PA_SOURCE_SUSPENDED)
+                        break;
+
+                    /* Resume the device if the sink was suspended as well */
+                    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+                        if (bt_transport_acquire(u, false) < 0)
+                            failed = true;
+                        else
+                            setup_stream(u);
+                    }
+                    /* We don't resume the smoother here. Instead we
+                     * wait until the first packet arrives */
+                    break;
+
+                case PA_SOURCE_UNLINKED:
+                case PA_SOURCE_INIT:
+                case PA_SOURCE_INVALID_STATE:
+                    ;
+            }
+            break;
+
+        case PA_SOURCE_MESSAGE_GET_LATENCY: {
+            pa_usec_t wi, ri;
+
+            if (u->read_smoother) {
+                wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
+                ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
+
+                *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->thread_info.fixed_latency;
+            } else
+                *((pa_usec_t*) data) = 0;
+
+            return 0;
+        }
+
+    }
+
+    r = pa_source_process_msg(o, code, data, offset, chunk);
+
+    return (r < 0 || !failed) ? r : -1;
+}
+
+/* Called from main thread context */
+static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+    struct bluetooth_msg *u = BLUETOOTH_MSG(obj);
+
+    switch (code) {
+        case BLUETOOTH_MESSAGE_IO_THREAD_FAILED: {
+            if (u->card->module->unload_requested)
+                break;
+
+            pa_log_debug("Switching the profile to off due to IO thread failure.");
+
+            pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+            break;
+        }
+    }
+    return 0;
+}
+
+/* Run from IO thread */
+static int hsp_process_render(struct userdata *u) {
+    int ret = 0;
+
+    pa_assert(u);
+    pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
+    pa_assert(u->sink);
+
+    /* First, render some data */
+    if (!u->write_memchunk.memblock)
+        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
+
+    pa_assert(u->write_memchunk.length == u->write_block_size);
+
+    for (;;) {
+        ssize_t l;
+        const void *p;
+
+        /* Now write that data to the socket. The socket is of type
+         * SEQPACKET, and we generated the data of the MTU size, so this
+         * should just work. */
+
+        p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
+        l = pa_write(u->stream_fd, p, u->write_memchunk.length, &u->stream_write_type);
+        pa_memblock_release(u->write_memchunk.memblock);
+
+        pa_assert(l != 0);
+
+        if (l < 0) {
+
+            if (errno == EINTR)
+                /* Retry right away if we got interrupted */
+                continue;
+
+            else if (errno == EAGAIN)
+                /* Hmm, apparently the socket was not writable, give up for now */
+                break;
+
+            pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno));
+            ret = -1;
+            break;
+        }
+
+        pa_assert((size_t) l <= u->write_memchunk.length);
+
+        if ((size_t) l != u->write_memchunk.length) {
+            pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
+                        (unsigned long long) l,
+                        (unsigned long long) u->write_memchunk.length);
+            ret = -1;
+            break;
+        }
+
+        u->write_index += (uint64_t) u->write_memchunk.length;
+        pa_memblock_unref(u->write_memchunk.memblock);
+        pa_memchunk_reset(&u->write_memchunk);
+
+        ret = 1;
+        break;
+    }
+
+    return ret;
+}
+
+/* Run from IO thread */
+static int hsp_process_push(struct userdata *u) {
+    int ret = 0;
+    pa_memchunk memchunk;
+
+    pa_assert(u);
+    pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
+    pa_assert(u->source);
+    pa_assert(u->read_smoother);
+
+    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
+    memchunk.index = memchunk.length = 0;
+
+    for (;;) {
+        ssize_t l;
+        void *p;
+        struct msghdr m;
+        struct cmsghdr *cm;
+        uint8_t aux[1024];
+        struct iovec iov;
+        bool found_tstamp = false;
+        pa_usec_t tstamp;
+
+        memset(&m, 0, sizeof(m));
+        memset(&aux, 0, sizeof(aux));
+        memset(&iov, 0, sizeof(iov));
+
+        m.msg_iov = &iov;
+        m.msg_iovlen = 1;
+        m.msg_control = aux;
+        m.msg_controllen = sizeof(aux);
+
+        p = pa_memblock_acquire(memchunk.memblock);
+        iov.iov_base = p;
+        iov.iov_len = pa_memblock_get_length(memchunk.memblock);
+        l = recvmsg(u->stream_fd, &m, 0);
+        pa_memblock_release(memchunk.memblock);
+
+        if (l <= 0) {
+
+            if (l < 0 && errno == EINTR)
+                /* Retry right away if we got interrupted */
+                continue;
+
+            else if (l < 0 && errno == EAGAIN)
+                /* Hmm, apparently the socket was not readable, give up for now. */
+                break;
+
+            pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
+            ret = -1;
+            break;
+        }
+
+        pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock));
+
+        /* In some rare occasions, we might receive packets of a very strange
+         * size. This could potentially be possible if the SCO packet was
+         * received partially over-the-air, or more probably due to hardware
+         * issues in our Bluetooth adapter. In these cases, in order to avoid
+         * an assertion failure due to unaligned data, just discard the whole
+         * packet */
+        if (!pa_frame_aligned(l, &u->sample_spec)) {
+            pa_log_warn("SCO packet received of unaligned size: %zu", l);
+            break;
+        }
+
+        memchunk.length = (size_t) l;
+        u->read_index += (uint64_t) l;
+
+        for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm))
+            if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
+                struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
+                pa_rtclock_from_wallclock(tv);
+                tstamp = pa_timeval_load(tv);
+                found_tstamp = true;
+                break;
+            }
+
+        if (!found_tstamp) {
+            pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
+            tstamp = pa_rtclock_now();
+        }
+
+        pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+        pa_smoother_resume(u->read_smoother, tstamp, true);
+
+        pa_source_post(u->source, &memchunk);
+
+        ret = l;
+        break;
+    }
+
+    pa_memblock_unref(memchunk.memblock);
+
+    return ret;
+}
+
+/* Run from IO thread */
+static void a2dp_prepare_buffer(struct userdata *u) {
+    size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
+
+    pa_assert(u);
+
+    if (u->a2dp.buffer_size >= min_buffer_size)
+        return;
+
+    u->a2dp.buffer_size = 2 * min_buffer_size;
+    pa_xfree(u->a2dp.buffer);
+    u->a2dp.buffer = pa_xmalloc(u->a2dp.buffer_size);
+}
+
+/* Run from IO thread */
+static int a2dp_process_render(struct userdata *u) {
+    struct a2dp_info *a2dp;
+    struct rtp_header *header;
+    struct rtp_payload *payload;
+    size_t nbytes;
+    void *d;
+    const void *p;
+    size_t to_write, to_encode;
+    unsigned frame_count;
+    int ret = 0;
+
+    pa_assert(u);
+    pa_assert(u->profile == PROFILE_A2DP);
+    pa_assert(u->sink);
+
+    /* First, render some data */
+    if (!u->write_memchunk.memblock)
+        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
+
+    pa_assert(u->write_memchunk.length == u->write_block_size);
+
+    a2dp_prepare_buffer(u);
+
+    a2dp = &u->a2dp;
+    header = a2dp->buffer;
+    payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
+
+    frame_count = 0;
+
+    /* Try to create a packet of the full MTU */
+
+    p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
+    to_encode = u->write_memchunk.length;
+
+    d = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
+    to_write = a2dp->buffer_size - sizeof(*header) - sizeof(*payload);
+
+    while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
+        ssize_t written;
+        ssize_t encoded;
+
+        encoded = sbc_encode(&a2dp->sbc,
+                             p, to_encode,
+                             d, to_write,
+                             &written);
+
+        if (PA_UNLIKELY(encoded <= 0)) {
+            pa_log_error("SBC encoding error (%li)", (long) encoded);
+            pa_memblock_release(u->write_memchunk.memblock);
+            return -1;
+        }
+
+/*         pa_log_debug("SBC: encoded: %lu; written: %lu", (unsigned long) encoded, (unsigned long) written); */
+/*         pa_log_debug("SBC: codesize: %lu; frame_length: %lu", (unsigned long) a2dp->codesize, (unsigned long) a2dp->frame_length); */
+
+        pa_assert_fp((size_t) encoded <= to_encode);
+        pa_assert_fp((size_t) encoded == a2dp->codesize);
+
+        pa_assert_fp((size_t) written <= to_write);
+        pa_assert_fp((size_t) written == a2dp->frame_length);
+
+        p = (const uint8_t*) p + encoded;
+        to_encode -= encoded;
+
+        d = (uint8_t*) d + written;
+        to_write -= written;
+
+        frame_count++;
+    }
+
+    pa_memblock_release(u->write_memchunk.memblock);
+
+    pa_assert(to_encode == 0);
+
+    PA_ONCE_BEGIN {
+        pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&a2dp->sbc)));
+    } PA_ONCE_END;
+
+    /* write it to the fifo */
+    memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload));
+    header->v = 2;
+    header->pt = 1;
+    header->sequence_number = htons(a2dp->seq_num++);
+    header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));
+    header->ssrc = htonl(1);
+    payload->frame_count = frame_count;
+
+    nbytes = (uint8_t*) d - (uint8_t*) a2dp->buffer;
+
+    for (;;) {
+        ssize_t l;
+
+        l = pa_write(u->stream_fd, a2dp->buffer, nbytes, &u->stream_write_type);
+
+        pa_assert(l != 0);
+
+        if (l < 0) {
+
+            if (errno == EINTR)
+                /* Retry right away if we got interrupted */
+                continue;
+
+            else if (errno == EAGAIN)
+                /* Hmm, apparently the socket was not writable, give up for now */
+                break;
+
+            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
+            ret = -1;
+            break;
+        }
+
+        pa_assert((size_t) l <= nbytes);
+
+        if ((size_t) l != nbytes) {
+            pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
+                        (unsigned long long) l,
+                        (unsigned long long) nbytes);
+            ret = -1;
+            break;
+        }
+
+        u->write_index += (uint64_t) u->write_memchunk.length;
+        pa_memblock_unref(u->write_memchunk.memblock);
+        pa_memchunk_reset(&u->write_memchunk);
+
+        ret = 1;
+
+        break;
+    }
+
+    return ret;
+}
+
+static int a2dp_process_push(struct userdata *u) {
+    int ret = 0;
+    pa_memchunk memchunk;
+
+    pa_assert(u);
+    pa_assert(u->profile == PROFILE_A2DP_SOURCE);
+    pa_assert(u->source);
+    pa_assert(u->read_smoother);
+
+    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
+    memchunk.index = memchunk.length = 0;
+
+    for (;;) {
+        bool found_tstamp = false;
+        pa_usec_t tstamp;
+        struct a2dp_info *a2dp;
+        struct rtp_header *header;
+        struct rtp_payload *payload;
+        const void *p;
+        void *d;
+        ssize_t l;
+        size_t to_write, to_decode;
+
+        a2dp_prepare_buffer(u);
+
+        a2dp = &u->a2dp;
+        header = a2dp->buffer;
+        payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
+
+        l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type);
+
+        if (l <= 0) {
+
+            if (l < 0 && errno == EINTR)
+                /* Retry right away if we got interrupted */
+                continue;
+
+            else if (l < 0 && errno == EAGAIN)
+                /* Hmm, apparently the socket was not readable, give up for now. */
+                break;
+
+            pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
+            ret = -1;
+            break;
+        }
+
+        pa_assert((size_t) l <= a2dp->buffer_size);
+
+        u->read_index += (uint64_t) l;
+
+        /* TODO: get timestamp from rtp */
+        if (!found_tstamp) {
+            /* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */
+            tstamp = pa_rtclock_now();
+        }
+
+        pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+        pa_smoother_resume(u->read_smoother, tstamp, true);
+
+        p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
+        to_decode = l - sizeof(*header) - sizeof(*payload);
+
+        d = pa_memblock_acquire(memchunk.memblock);
+        to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
+
+        while (PA_LIKELY(to_decode > 0)) {
+            size_t written;
+            ssize_t decoded;
+
+            decoded = sbc_decode(&a2dp->sbc,
+                                 p, to_decode,
+                                 d, to_write,
+                                 &written);
+
+            if (PA_UNLIKELY(decoded <= 0)) {
+                pa_log_error("SBC decoding error (%li)", (long) decoded);
+                pa_memblock_release(memchunk.memblock);
+                pa_memblock_unref(memchunk.memblock);
+                return -1;
+            }
+
+/*             pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */
+/*             pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */
+
+            /* Reset frame length, it can be changed due to bitpool change */
+            a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
+
+            pa_assert_fp((size_t) decoded <= to_decode);
+            pa_assert_fp((size_t) decoded == a2dp->frame_length);
+
+            pa_assert_fp((size_t) written == a2dp->codesize);
+
+            p = (const uint8_t*) p + decoded;
+            to_decode -= decoded;
+
+            d = (uint8_t*) d + written;
+            to_write -= written;
+        }
+
+        memchunk.length -= to_write;
+
+        pa_memblock_release(memchunk.memblock);
+
+        pa_source_post(u->source, &memchunk);
+
+        ret = l;
+        break;
+    }
+
+    pa_memblock_unref(memchunk.memblock);
+
+    return ret;
+}
+
+static void a2dp_reduce_bitpool(struct userdata *u) {
+    struct a2dp_info *a2dp;
+    uint8_t bitpool;
+
+    pa_assert(u);
+
+    a2dp = &u->a2dp;
+
+    /* Check if bitpool is already at its limit */
+    if (a2dp->sbc.bitpool <= BITPOOL_DEC_LIMIT)
+        return;
+
+    bitpool = a2dp->sbc.bitpool - BITPOOL_DEC_STEP;
+
+    if (bitpool < BITPOOL_DEC_LIMIT)
+        bitpool = BITPOOL_DEC_LIMIT;
+
+    a2dp_set_bitpool(u, bitpool);
+}
+
+static void thread_func(void *userdata) {
+    struct userdata *u = userdata;
+    unsigned do_write = 0;
+    unsigned pending_read_bytes = 0;
+    bool writable = false;
+
+    pa_assert(u);
+    pa_assert(u->transport);
+
+    pa_log_debug("IO Thread starting up");
+
+    if (u->core->realtime_scheduling)
+        pa_make_realtime(u->core->realtime_priority);
+
+    pa_thread_mq_install(&u->thread_mq);
+
+    /* Setup the stream only if the transport was already acquired */
+    if (u->transport_acquired)
+        setup_stream(u);
+
+    for (;;) {
+        struct pollfd *pollfd;
+        int ret;
+        bool disable_timer = true;
+
+        pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
+
+        if (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state)) {
+
+            /* We should send two blocks to the device before we expect
+             * a response. */
+
+            if (u->write_index == 0 && u->read_index <= 0)
+                do_write = 2;
+
+            if (pollfd && (pollfd->revents & POLLIN)) {
+                int n_read;
+
+                if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW)
+                    n_read = hsp_process_push(u);
+                else
+                    n_read = a2dp_process_push(u);
+
+                if (n_read < 0)
+                    goto io_fail;
+
+                /* We just read something, so we are supposed to write something, too */
+                pending_read_bytes += n_read;
+                do_write += pending_read_bytes / u->write_block_size;
+                pending_read_bytes = pending_read_bytes % u->write_block_size;
+            }
+        }
+
+        if (u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state)) {
+
+            if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
+                pa_sink_process_rewind(u->sink, 0);
+
+            if (pollfd) {
+                if (pollfd->revents & POLLOUT)
+                    writable = true;
+
+                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {
+                    pa_usec_t time_passed;
+                    pa_usec_t audio_sent;
+
+                    /* Hmm, there is no input stream we could synchronize
+                     * to. So let's do things by time */
+
+                    time_passed = pa_rtclock_now() - u->started_at;
+                    audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+
+                    if (audio_sent <= time_passed) {
+                        pa_usec_t audio_to_send = time_passed - audio_sent;
+
+                        /* Never try to catch up for more than 100ms */
+                        if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) {
+                            pa_usec_t skip_usec;
+                            uint64_t skip_bytes;
+
+                            skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC;
+                            skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec);
+
+                            if (skip_bytes > 0) {
+                                pa_memchunk tmp;
+
+                                pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
+                                            (unsigned long long) skip_usec,
+                                            (unsigned long long) skip_bytes);
+
+                                pa_sink_render_full(u->sink, skip_bytes, &tmp);
+                                pa_memblock_unref(tmp.memblock);
+                                u->write_index += skip_bytes;
+
+                                if (u->profile == PROFILE_A2DP)
+                                    a2dp_reduce_bitpool(u);
+                            }
+                        }
+
+                        do_write = 1;
+                        pending_read_bytes = 0;
+                    }
+                }
+
+                if (writable && do_write > 0) {
+                    int n_written;
+
+                    if (u->write_index <= 0)
+                        u->started_at = pa_rtclock_now();
+
+                    if (u->profile == PROFILE_A2DP) {
+                        if ((n_written = a2dp_process_render(u)) < 0)
+                            goto io_fail;
+                    } else {
+                        if ((n_written = hsp_process_render(u)) < 0)
+                            goto io_fail;
+                    }
+
+                    if (n_written == 0)
+                        pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!");
+
+                    do_write -= n_written;
+                    writable = false;
+                }
+
+                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0) {
+                    pa_usec_t sleep_for;
+                    pa_usec_t time_passed, next_write_at;
+
+                    if (writable) {
+                        /* Hmm, there is no input stream we could synchronize
+                         * to. So let's estimate when we need to wake up the latest */
+                        time_passed = pa_rtclock_now() - u->started_at;
+                        next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+                        sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
+                        /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
+                    } else
+                        /* drop stream every 500 ms */
+                        sleep_for = PA_USEC_PER_MSEC * 500;
+
+                    pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for);
+                    disable_timer = false;
+                }
+            }
+        }
+
+        if (disable_timer)
+            pa_rtpoll_set_timer_disabled(u->rtpoll);
+
+        /* Hmm, nothing to do. Let's sleep */
+        if (pollfd)
+            pollfd->events = (short) (((u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state) && !writable) ? POLLOUT : 0) |
+                                      (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state) ? POLLIN : 0));
+
+        if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0) {
+            pa_log_debug("pa_rtpoll_run failed with: %d", ret);
+            goto fail;
+        }
+        if (ret == 0) {
+            pa_log_debug("IO thread shutdown requested, stopping cleanly");
+            bt_transport_release(u);
+            goto finish;
+        }
+
+        pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
+
+        if (pollfd && (pollfd->revents & ~(POLLOUT|POLLIN))) {
+            pa_log_info("FD error: %s%s%s%s",
+                        pollfd->revents & POLLERR ? "POLLERR " :"",
+                        pollfd->revents & POLLHUP ? "POLLHUP " :"",
+                        pollfd->revents & POLLPRI ? "POLLPRI " :"",
+                        pollfd->revents & POLLNVAL ? "POLLNVAL " :"");
+            goto io_fail;
+        }
+
+        continue;
+
+io_fail:
+        /* In case of HUP, just tear down the streams */
+        if (!pollfd || (pollfd->revents & POLLHUP) == 0)
+            goto fail;
+
+        do_write = 0;
+        pending_read_bytes = 0;
+        writable = false;
+
+        teardown_stream(u);
+    }
+
+fail:
+    /* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */
+    pa_log_debug("IO thread failed");
+    pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_IO_THREAD_FAILED, NULL, 0, NULL, NULL);
+    pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+    pa_log_debug("IO thread shutting down");
+}
+
+static pa_available_t transport_state_to_availability(pa_bluetooth_transport_state_t state) {
+    if (state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+        return PA_AVAILABLE_NO;
+    else if (state >= PA_BLUETOOTH_TRANSPORT_STATE_PLAYING)
+        return PA_AVAILABLE_YES;
+    else
+        return PA_AVAILABLE_UNKNOWN;
+}
+
+static pa_direction_t get_profile_direction(enum profile p) {
+    static const pa_direction_t profile_direction[] = {
+        [PROFILE_A2DP] = PA_DIRECTION_OUTPUT,
+        [PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
+        [PROFILE_HSP] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PROFILE_HFGW] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PROFILE_OFF] = 0
+    };
+
+    return profile_direction[p];
+}
+
+/* Run from main thread */
+static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) {
+    pa_available_t result = PA_AVAILABLE_NO;
+    unsigned i;
+
+    pa_assert(u);
+    pa_assert(u->device);
+
+    for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
+        pa_bluetooth_transport *transport;
+
+        if (!(get_profile_direction(i) & direction))
+            continue;
+
+        if (!(transport = u->device->transports[i]))
+            continue;
+
+        switch(transport->state) {
+            case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
+                continue;
+
+            case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
+                if (result == PA_AVAILABLE_NO)
+                    result = PA_AVAILABLE_UNKNOWN;
+
+                break;
+
+            case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
+                return PA_AVAILABLE_YES;
+        }
+    }
+
+    return result;
+}
+
+/* Run from main thread */
+static void handle_transport_state_change(struct userdata *u, struct pa_bluetooth_transport *transport) {
+    bool acquire = false;
+    bool release = false;
+    enum profile profile;
+    pa_card_profile *cp;
+    pa_bluetooth_transport_state_t state;
+    pa_device_port *port;
+
+    pa_assert(u);
+    pa_assert(transport);
+
+    profile = transport->profile;
+    state = transport->state;
+
+    /* Update profile availability */
+    if (!(cp = pa_hashmap_get(u->card->profiles, pa_bt_profile_to_string(profile))))
+        return;
+
+    pa_card_profile_set_available(cp, transport_state_to_availability(state));
+
+    /* Update port availability */
+    pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
+    pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_OUTPUT));
+
+    pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
+    pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_INPUT));
+
+    /* Acquire or release transport as needed */
+    acquire = (state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile);
+    release = (state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile);
+
+    if (acquire)
+        if (bt_transport_acquire(u, true) >= 0) {
+            if (u->source) {
+                pa_log_debug("Resuming source %s, because the bluetooth audio state changed to 'playing'.", u->source->name);
+                pa_source_suspend(u->source, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+            }
+
+            if (u->sink) {
+                pa_log_debug("Resuming sink %s, because the bluetooth audio state changed to 'playing'.", u->sink->name);
+                pa_sink_suspend(u->sink, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+            }
+        }
+
+    if (release && u->transport_acquired) {
+        /* FIXME: this release is racy, since the audio stream might have
+           been set up again in the meantime (but not processed yet by PA).
+           BlueZ should probably release the transport automatically, and
+           in that case we would just mark the transport as released */
+
+        /* Remote side closed the stream so we consider it PA_SUSPEND_USER */
+        if (u->source) {
+            pa_log_debug("Suspending source %s, because the remote end closed the stream.", u->source->name);
+            pa_source_suspend(u->source, true, PA_SUSPEND_USER);
+        }
+
+        if (u->sink) {
+            pa_log_debug("Suspending sink %s, because the remote end closed the stream.", u->sink->name);
+            pa_sink_suspend(u->sink, true, PA_SUSPEND_USER);
+        }
+    }
+}
+
+/* Run from main thread */
+static void sink_set_volume_cb(pa_sink *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+    char *k;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s);
+    u = pa_shared_get(s->core, k);
+    pa_xfree(k);
+
+    pa_assert(u);
+    pa_assert(u->sink == s);
+    pa_assert(u->profile == PROFILE_HSP);
+    pa_assert(u->transport);
+
+    gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM);
+    volume = (pa_volume_t) round((double) gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+
+    pa_bluetooth_transport_set_speaker_gain(u->transport, gain);
+}
+
+/* Run from main thread */
+static void source_set_volume_cb(pa_source *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+    char *k;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s);
+    u = pa_shared_get(s->core, k);
+    pa_xfree(k);
+
+    pa_assert(u);
+    pa_assert(u->source == s);
+    pa_assert(u->profile == PROFILE_HSP);
+    pa_assert(u->transport);
+
+    gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM);
+    volume = (pa_volume_t) round((double) gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+
+    pa_bluetooth_transport_set_microphone_gain(u->transport, gain);
+}
+
+/* Run from main thread */
+static char *get_name(const char *type, pa_modargs *ma, const char *device_id, bool *namereg_fail) {
+    char *t;
+    const char *n;
+
+    pa_assert(type);
+    pa_assert(ma);
+    pa_assert(device_id);
+    pa_assert(namereg_fail);
+
+    t = pa_sprintf_malloc("%s_name", type);
+    n = pa_modargs_get_value(ma, t, NULL);
+    pa_xfree(t);
+
+    if (n) {
+        *namereg_fail = true;
+        return pa_xstrdup(n);
+    }
+
+    if ((n = pa_modargs_get_value(ma, "name", NULL)))
+        *namereg_fail = true;
+    else {
+        n = device_id;
+        *namereg_fail = false;
+    }
+
+    return pa_sprintf_malloc("bluez_%s.%s", type, n);
+}
+
+static int sco_over_pcm_state_update(struct userdata *u, bool changed) {
+    pa_assert(u);
+    pa_assert(USE_SCO_OVER_PCM(u));
+
+    if (PA_SINK_IS_OPENED(pa_sink_get_state(u->hsp.sco_sink)) ||
+        PA_SOURCE_IS_OPENED(pa_source_get_state(u->hsp.sco_source))) {
+
+        if (u->stream_fd >= 0)
+            return 0;
+
+        pa_log_debug("Resuming SCO over PCM");
+        if (init_profile(u) < 0) {
+            pa_log("Can't resume SCO over PCM");
+            return -1;
+        }
+
+        if (bt_transport_acquire(u, false) < 0)
+            return -1;
+
+        setup_stream(u);
+
+        return 0;
+    }
+
+    if (changed) {
+        if (u->stream_fd < 0)
+            return 0;
+
+        pa_log_debug("Closing SCO over PCM");
+
+        bt_transport_release(u);
+    }
+
+    return 0;
+}
+
+static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, struct userdata *u) {
+    pa_assert(c);
+    pa_sink_assert_ref(s);
+    pa_assert(u);
+
+    if (!USE_SCO_OVER_PCM(u) || s != u->hsp.sco_sink)
+        return PA_HOOK_OK;
+
+    sco_over_pcm_state_update(u, true);
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct userdata *u) {
+    pa_assert(c);
+    pa_source_assert_ref(s);
+    pa_assert(u);
+
+    if (!USE_SCO_OVER_PCM(u) || s != u->hsp.sco_source)
+        return PA_HOOK_OK;
+
+    sco_over_pcm_state_update(u, true);
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t transport_nrec_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_proplist *p;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
+    p = pa_proplist_new();
+    pa_proplist_sets(p, "bluetooth.nrec", t->nrec ? "1" : "0");
+    pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, p);
+    pa_proplist_free(p);
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t,
+                                                             struct userdata *u) {
+    pa_cvolume v;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
+    pa_assert(u->source);
+
+    pa_cvolume_set(&v, u->sample_spec.channels,
+                   (pa_volume_t) round((double) t->microphone_gain * PA_VOLUME_NORM / HSP_MAX_GAIN));
+    pa_source_volume_changed(u->source, &v);
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t,
+                                                          struct userdata *u) {
+    pa_cvolume v;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
+    pa_assert(u->sink);
+
+    pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) round((double) t->speaker_gain * PA_VOLUME_NORM / HSP_MAX_GAIN));
+    pa_sink_volume_changed(u->sink, &v);
+
+    return PA_HOOK_OK;
+}
+
+static void connect_ports(struct userdata *u, void *sink_or_source_new_data, pa_direction_t direction) {
+    pa_device_port *port;
+
+    if (direction == PA_DIRECTION_OUTPUT) {
+        pa_sink_new_data *sink_new_data = sink_or_source_new_data;
+
+        pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
+        pa_assert_se(pa_hashmap_put(sink_new_data->ports, port->name, port) >= 0);
+        pa_device_port_ref(port);
+    } else {
+        pa_source_new_data *source_new_data = sink_or_source_new_data;
+
+        pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
+        pa_assert_se(pa_hashmap_put(source_new_data->ports, port->name, port) >= 0);
+        pa_device_port_ref(port);
+    }
+}
+
+static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
+    return 0;
+}
+
+static int source_set_port_cb(pa_source *s, pa_device_port *p) {
+    return 0;
+}
+
+/* Run from main thread */
+static int add_sink(struct userdata *u) {
+    char *k;
+
+    pa_assert(u->transport);
+
+    if (USE_SCO_OVER_PCM(u)) {
+        pa_proplist *p;
+
+        u->sink = u->hsp.sco_sink;
+        p = pa_proplist_new();
+        pa_proplist_sets(p, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
+        pa_proplist_update(u->sink->proplist, PA_UPDATE_MERGE, p);
+        pa_proplist_free(p);
+    } else {
+        pa_sink_new_data data;
+        bool b;
+
+        pa_sink_new_data_init(&data);
+        data.driver = __FILE__;
+        data.module = u->module;
+        pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
+        pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
+        if (u->profile == PROFILE_HSP)
+            pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
+        data.card = u->card;
+        data.name = get_name("sink", u->modargs, u->address, &b);
+        data.namereg_fail = b;
+
+        if (pa_modargs_get_proplist(u->modargs, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+            pa_log("Invalid properties");
+            pa_sink_new_data_done(&data);
+            return -1;
+        }
+        connect_ports(u, &data, PA_DIRECTION_OUTPUT);
+
+        if (!u->transport_acquired)
+            switch (u->profile) {
+                case PROFILE_A2DP:
+                case PROFILE_HSP:
+                    pa_assert_not_reached(); /* Profile switch should have failed */
+                    break;
+                case PROFILE_HFGW:
+                    data.suspend_cause = PA_SUSPEND_USER;
+                    break;
+                case PROFILE_A2DP_SOURCE:
+                case PROFILE_OFF:
+                    pa_assert_not_reached();
+            }
+
+        u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
+        pa_sink_new_data_done(&data);
+
+        if (!u->sink) {
+            pa_log_error("Failed to create sink");
+            return -1;
+        }
+
+        u->sink->userdata = u;
+        u->sink->parent.process_msg = sink_process_msg;
+        u->sink->set_port = sink_set_port_cb;
+    }
+
+    if (u->profile == PROFILE_HSP) {
+        pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+        u->sink->n_volume_steps = 16;
+
+        k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
+        pa_shared_set(u->core, k, u);
+        pa_xfree(k);
+    }
+
+    return 0;
+}
+
+/* Run from main thread */
+static int add_source(struct userdata *u) {
+    char *k;
+
+    pa_assert(u->transport);
+
+    if (USE_SCO_OVER_PCM(u)) {
+        u->source = u->hsp.sco_source;
+        pa_proplist_sets(u->source->proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
+    } else {
+        pa_source_new_data data;
+        bool b;
+
+        pa_source_new_data_init(&data);
+        data.driver = __FILE__;
+        data.module = u->module;
+        pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
+        pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile));
+        if (u->profile == PROFILE_HSP)
+            pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
+
+        data.card = u->card;
+        data.name = get_name("source", u->modargs, u->address, &b);
+        data.namereg_fail = b;
+
+        if (pa_modargs_get_proplist(u->modargs, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+            pa_log("Invalid properties");
+            pa_source_new_data_done(&data);
+            return -1;
+        }
+
+        connect_ports(u, &data, PA_DIRECTION_INPUT);
+
+        if (!u->transport_acquired)
+            switch (u->profile) {
+                case PROFILE_HSP:
+                    pa_assert_not_reached(); /* Profile switch should have failed */
+                    break;
+                case PROFILE_A2DP_SOURCE:
+                case PROFILE_HFGW:
+                    data.suspend_cause = PA_SUSPEND_USER;
+                    break;
+                case PROFILE_A2DP:
+                case PROFILE_OFF:
+                    pa_assert_not_reached();
+            }
+
+        u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
+        pa_source_new_data_done(&data);
+
+        if (!u->source) {
+            pa_log_error("Failed to create source");
+            return -1;
+        }
+
+        u->source->userdata = u;
+        u->source->parent.process_msg = source_process_msg;
+        u->source->set_port = source_set_port_cb;
+    }
+
+    if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
+        pa_bluetooth_transport *t = u->transport;
+        pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
+    }
+
+    if (u->profile == PROFILE_HSP) {
+        pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
+        u->source->n_volume_steps = 16;
+
+        k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
+        pa_shared_set(u->core, k, u);
+        pa_xfree(k);
+    }
+
+    return 0;
+}
+
+static void bt_transport_config_a2dp(struct userdata *u) {
+    const pa_bluetooth_transport *t;
+    struct a2dp_info *a2dp = &u->a2dp;
+    a2dp_sbc_t *config;
+
+    t = u->transport;
+    pa_assert(t);
+
+    config = (a2dp_sbc_t *) t->config;
+
+    u->sample_spec.format = PA_SAMPLE_S16LE;
+
+    if (a2dp->sbc_initialized)
+        sbc_reinit(&a2dp->sbc, 0);
+    else
+        sbc_init(&a2dp->sbc, 0);
+    a2dp->sbc_initialized = true;
+
+    switch (config->frequency) {
+        case SBC_SAMPLING_FREQ_16000:
+            a2dp->sbc.frequency = SBC_FREQ_16000;
+            u->sample_spec.rate = 16000U;
+            break;
+        case SBC_SAMPLING_FREQ_32000:
+            a2dp->sbc.frequency = SBC_FREQ_32000;
+            u->sample_spec.rate = 32000U;
+            break;
+        case SBC_SAMPLING_FREQ_44100:
+            a2dp->sbc.frequency = SBC_FREQ_44100;
+            u->sample_spec.rate = 44100U;
+            break;
+        case SBC_SAMPLING_FREQ_48000:
+            a2dp->sbc.frequency = SBC_FREQ_48000;
+            u->sample_spec.rate = 48000U;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->channel_mode) {
+        case SBC_CHANNEL_MODE_MONO:
+            a2dp->sbc.mode = SBC_MODE_MONO;
+            u->sample_spec.channels = 1;
+            break;
+        case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+            a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+            u->sample_spec.channels = 2;
+            break;
+        case SBC_CHANNEL_MODE_STEREO:
+            a2dp->sbc.mode = SBC_MODE_STEREO;
+            u->sample_spec.channels = 2;
+            break;
+        case SBC_CHANNEL_MODE_JOINT_STEREO:
+            a2dp->sbc.mode = SBC_MODE_JOINT_STEREO;
+            u->sample_spec.channels = 2;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->allocation_method) {
+        case SBC_ALLOCATION_SNR:
+            a2dp->sbc.allocation = SBC_AM_SNR;
+            break;
+        case SBC_ALLOCATION_LOUDNESS:
+            a2dp->sbc.allocation = SBC_AM_LOUDNESS;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->subbands) {
+        case SBC_SUBBANDS_4:
+            a2dp->sbc.subbands = SBC_SB_4;
+            break;
+        case SBC_SUBBANDS_8:
+            a2dp->sbc.subbands = SBC_SB_8;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    switch (config->block_length) {
+        case SBC_BLOCK_LENGTH_4:
+            a2dp->sbc.blocks = SBC_BLK_4;
+            break;
+        case SBC_BLOCK_LENGTH_8:
+            a2dp->sbc.blocks = SBC_BLK_8;
+            break;
+        case SBC_BLOCK_LENGTH_12:
+            a2dp->sbc.blocks = SBC_BLK_12;
+            break;
+        case SBC_BLOCK_LENGTH_16:
+            a2dp->sbc.blocks = SBC_BLK_16;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    a2dp->min_bitpool = config->min_bitpool;
+    a2dp->max_bitpool = config->max_bitpool;
+
+    /* Set minimum bitpool for source to get the maximum possible block_size */
+    a2dp->sbc.bitpool = u->profile == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool;
+    a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
+    a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
+
+    pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
+                a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool);
+}
+
+static void bt_transport_config(struct userdata *u) {
+    if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
+        u->sample_spec.format = PA_SAMPLE_S16LE;
+        u->sample_spec.channels = 1;
+        u->sample_spec.rate = 8000;
+    } else
+        bt_transport_config_a2dp(u);
+}
+
+/* Run from main thread */
+static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t == u->transport && t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+        pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+
+    if (t->device == u->device)
+        handle_transport_state_change(u, t);
+
+    return PA_HOOK_OK;
+}
+
+/* Run from main thread */
+static int setup_transport(struct userdata *u) {
+    pa_bluetooth_transport *t;
+
+    pa_assert(u);
+    pa_assert(!u->transport);
+    pa_assert(u->profile != PROFILE_OFF);
+
+    /* check if profile has a transport */
+    t = u->device->transports[u->profile];
+    if (!t || t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
+        pa_log_warn("Profile has no transport");
+        return -1;
+    }
+
+    u->transport = t;
+
+    if (u->profile == PROFILE_A2DP_SOURCE || u->profile == PROFILE_HFGW)
+        bt_transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
+    else if (bt_transport_acquire(u, false) < 0)
+        return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
+
+    bt_transport_config(u);
+
+    return 0;
+}
+
+/* Run from main thread */
+static int init_profile(struct userdata *u) {
+    int r = 0;
+    pa_assert(u);
+    pa_assert(u->profile != PROFILE_OFF);
+
+    if (setup_transport(u) < 0)
+        return -1;
+
+    pa_assert(u->transport);
+
+    if (u->profile == PROFILE_A2DP ||
+        u->profile == PROFILE_HSP ||
+        u->profile == PROFILE_HFGW)
+        if (add_sink(u) < 0)
+            r = -1;
+
+    if (u->profile == PROFILE_HSP ||
+        u->profile == PROFILE_A2DP_SOURCE ||
+        u->profile == PROFILE_HFGW)
+        if (add_source(u) < 0)
+            r = -1;
+
+    return r;
+}
+
+/* Run from main thread */
+static void stop_thread(struct userdata *u) {
+    char *k;
+
+    pa_assert(u);
+
+    if (u->sink && !USE_SCO_OVER_PCM(u))
+        pa_sink_unlink(u->sink);
+
+    if (u->source && !USE_SCO_OVER_PCM(u))
+        pa_source_unlink(u->source);
+
+    if (u->thread) {
+        pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+        pa_thread_free(u->thread);
+        u->thread = NULL;
+    }
+
+    if (u->rtpoll_item) {
+        pa_rtpoll_item_free(u->rtpoll_item);
+        u->rtpoll_item = NULL;
+    }
+
+    if (u->rtpoll) {
+        pa_thread_mq_done(&u->thread_mq);
+
+        pa_rtpoll_free(u->rtpoll);
+        u->rtpoll = NULL;
+    }
+
+    if (u->transport) {
+        bt_transport_release(u);
+        u->transport = NULL;
+    }
+
+    if (u->sink) {
+        if (u->profile == PROFILE_HSP) {
+            k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
+            pa_shared_remove(u->core, k);
+            pa_xfree(k);
+        }
+
+        pa_sink_unref(u->sink);
+        u->sink = NULL;
+    }
+
+    if (u->source) {
+        if (u->profile == PROFILE_HSP) {
+            k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
+            pa_shared_remove(u->core, k);
+            pa_xfree(k);
+        }
+
+        pa_source_unref(u->source);
+        u->source = NULL;
+    }
+
+    if (u->read_smoother) {
+        pa_smoother_free(u->read_smoother);
+        u->read_smoother = NULL;
+    }
+}
+
+/* Run from main thread */
+static int start_thread(struct userdata *u) {
+    pa_assert(u);
+    pa_assert(!u->thread);
+    pa_assert(!u->rtpoll);
+    pa_assert(!u->rtpoll_item);
+
+    u->rtpoll = pa_rtpoll_new();
+    pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll);
+
+    if (USE_SCO_OVER_PCM(u)) {
+        if (sco_over_pcm_state_update(u, false) < 0) {
+            char *k;
+
+            if (u->sink) {
+                k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
+                pa_shared_remove(u->core, k);
+                pa_xfree(k);
+                u->sink = NULL;
+            }
+            if (u->source) {
+                k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
+                pa_shared_remove(u->core, k);
+                pa_xfree(k);
+                u->source = NULL;
+            }
+            return -1;
+        }
+
+        pa_sink_ref(u->sink);
+        pa_source_ref(u->source);
+        /* FIXME: monitor stream_fd error */
+        return 0;
+    }
+
+    if (!(u->thread = pa_thread_new("bluetooth", thread_func, u))) {
+        pa_log_error("Failed to create IO thread");
+        return -1;
+    }
+
+    if (u->sink) {
+        pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+        pa_sink_set_rtpoll(u->sink, u->rtpoll);
+        pa_sink_put(u->sink);
+
+        if (u->sink->set_volume)
+            u->sink->set_volume(u->sink);
+    }
+
+    if (u->source) {
+        pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+        pa_source_set_rtpoll(u->source, u->rtpoll);
+        pa_source_put(u->source);
+
+        if (u->source->set_volume)
+            u->source->set_volume(u->source);
+    }
+
+    return 0;
+}
+
+static void save_sco_volume_callbacks(struct userdata *u) {
+    pa_assert(u);
+    pa_assert(USE_SCO_OVER_PCM(u));
+
+    u->hsp.sco_sink_set_volume = u->hsp.sco_sink->set_volume;
+    u->hsp.sco_source_set_volume = u->hsp.sco_source->set_volume;
+}
+
+static void restore_sco_volume_callbacks(struct userdata *u) {
+    pa_assert(u);
+    pa_assert(USE_SCO_OVER_PCM(u));
+
+    pa_sink_set_set_volume_callback(u->hsp.sco_sink, u->hsp.sco_sink_set_volume);
+    pa_source_set_set_volume_callback(u->hsp.sco_source, u->hsp.sco_source_set_volume);
+}
+
+/* Run from main thread */
+static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
+    struct userdata *u;
+    enum profile *d;
+
+    pa_assert(c);
+    pa_assert(new_profile);
+    pa_assert_se(u = c->userdata);
+
+    d = PA_CARD_PROFILE_DATA(new_profile);
+
+    if (*d != PROFILE_OFF) {
+        const pa_bluetooth_device *device = u->device;
+
+        if (!device->transports[*d] || device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
+            pa_log_warn("Profile not connected, refused to switch profile to %s", new_profile->name);
+            return -PA_ERR_IO;
+        }
+    }
+
+    stop_thread(u);
+
+    if (USE_SCO_OVER_PCM(u))
+        restore_sco_volume_callbacks(u);
+
+    u->profile = *d;
+    u->sample_spec = u->requested_sample_spec;
+
+    if (USE_SCO_OVER_PCM(u))
+        save_sco_volume_callbacks(u);
+
+    if (u->profile != PROFILE_OFF)
+        if (init_profile(u) < 0)
+            goto off;
+
+    if (u->sink || u->source)
+        if (start_thread(u) < 0)
+            goto off;
+
+    return 0;
+
+off:
+    stop_thread(u);
+
+    pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+
+    return -PA_ERR_IO;
+}
+
+/* Run from main thread */
+static void create_card_ports(struct userdata *u, pa_hashmap *ports) {
+    pa_device_port *port;
+    pa_device_port_new_data port_data;
+
+    const char *name_prefix = NULL;
+    const char *input_description = NULL;
+    const char *output_description = NULL;
+
+    pa_assert(u);
+    pa_assert(ports);
+    pa_assert(u->device);
+
+    switch (pa_bluetooth_get_form_factor(u->device->class)) {
+        case PA_BT_FORM_FACTOR_UNKNOWN:
+            break;
+
+        case PA_BT_FORM_FACTOR_HEADSET:
+            name_prefix = "headset";
+            input_description = output_description = _("Headset");
+            break;
+
+        case PA_BT_FORM_FACTOR_HANDSFREE:
+            name_prefix = "handsfree";
+            input_description = output_description = _("Handsfree");
+            break;
+
+        case PA_BT_FORM_FACTOR_MICROPHONE:
+            name_prefix = "microphone";
+            input_description = _("Microphone");
+            break;
+
+        case PA_BT_FORM_FACTOR_SPEAKER:
+            name_prefix = "speaker";
+            output_description = _("Speaker");
+            break;
+
+        case PA_BT_FORM_FACTOR_HEADPHONE:
+            name_prefix = "headphone";
+            output_description = _("Headphone");
+            break;
+
+        case PA_BT_FORM_FACTOR_PORTABLE:
+            name_prefix = "portable";
+            input_description = output_description = _("Portable");
+            break;
+
+        case PA_BT_FORM_FACTOR_CAR:
+            name_prefix = "car";
+            input_description = output_description = _("Car");
+            break;
+
+        case PA_BT_FORM_FACTOR_HIFI:
+            name_prefix = "hifi";
+            input_description = output_description = _("HiFi");
+            break;
+
+        case PA_BT_FORM_FACTOR_PHONE:
+            name_prefix = "phone";
+            input_description = output_description = _("Phone");
+            break;
+    }
+
+    if (!name_prefix)
+        name_prefix = "unknown";
+
+    if (!output_description)
+        output_description = _("Bluetooth Output");
+
+    if (!input_description)
+        input_description = _("Bluetooth Input");
+
+    u->output_port_name = pa_sprintf_malloc("%s-output", name_prefix);
+    u->input_port_name = pa_sprintf_malloc("%s-input", name_prefix);
+
+    pa_device_port_new_data_init(&port_data);
+    pa_device_port_new_data_set_name(&port_data, u->output_port_name);
+    pa_device_port_new_data_set_description(&port_data, output_description);
+    pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_OUTPUT);
+    pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_OUTPUT));
+    pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0));
+    pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0);
+    pa_device_port_new_data_done(&port_data);
+
+    pa_device_port_new_data_init(&port_data);
+    pa_device_port_new_data_set_name(&port_data, u->input_port_name);
+    pa_device_port_new_data_set_description(&port_data, input_description);
+    pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_INPUT);
+    pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_INPUT));
+    pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0));
+    pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0);
+    pa_device_port_new_data_done(&port_data);
+}
+
+/* Run from main thread */
+static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid, pa_hashmap *ports) {
+    pa_device_port *input_port, *output_port;
+    pa_card_profile *p = NULL;
+    enum profile *d;
+
+    pa_assert(u->input_port_name);
+    pa_assert(u->output_port_name);
+    pa_assert_se(input_port = pa_hashmap_get(ports, u->input_port_name));
+    pa_assert_se(output_port = pa_hashmap_get(ports, u->output_port_name));
+
+    if (pa_streq(uuid, A2DP_SINK_UUID)) {
+        p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile));
+        p->priority = 10;
+        p->n_sinks = 1;
+        p->n_sources = 0;
+        p->max_sink_channels = 2;
+        p->max_source_channels = 0;
+        pa_hashmap_put(output_port->profiles, p->name, p);
+
+        d = PA_CARD_PROFILE_DATA(p);
+        *d = PROFILE_A2DP;
+    } else if (pa_streq(uuid, A2DP_SOURCE_UUID)) {
+        p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile));
+        p->priority = 10;
+        p->n_sinks = 0;
+        p->n_sources = 1;
+        p->max_sink_channels = 0;
+        p->max_source_channels = 2;
+        pa_hashmap_put(input_port->profiles, p->name, p);
+
+        d = PA_CARD_PROFILE_DATA(p);
+        *d = PROFILE_A2DP_SOURCE;
+    } else if (pa_streq(uuid, HSP_HS_UUID) || pa_streq(uuid, HFP_HS_UUID)) {
+        p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile));
+        p->priority = 20;
+        p->n_sinks = 1;
+        p->n_sources = 1;
+        p->max_sink_channels = 1;
+        p->max_source_channels = 1;
+        pa_hashmap_put(input_port->profiles, p->name, p);
+        pa_hashmap_put(output_port->profiles, p->name, p);
+
+        d = PA_CARD_PROFILE_DATA(p);
+        *d = PROFILE_HSP;
+    } else if (pa_streq(uuid, HFP_AG_UUID)) {
+        p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(enum profile));
+        p->priority = 20;
+        p->n_sinks = 1;
+        p->n_sources = 1;
+        p->max_sink_channels = 1;
+        p->max_source_channels = 1;
+        pa_hashmap_put(input_port->profiles, p->name, p);
+        pa_hashmap_put(output_port->profiles, p->name, p);
+
+        d = PA_CARD_PROFILE_DATA(p);
+        *d = PROFILE_HFGW;
+    }
+
+    if (p) {
+        pa_bluetooth_transport *t;
+
+        if ((t = u->device->transports[*d]))
+            p->available = transport_state_to_availability(t->state);
+    }
+
+    return p;
+}
+
+/* Run from main thread */
+static int add_card(struct userdata *u) {
+    pa_card_new_data data;
+    bool b;
+    pa_card_profile *p;
+    enum profile *d;
+    pa_bt_form_factor_t ff;
+    char *n;
+    const char *default_profile;
+    const pa_bluetooth_device *device;
+    const pa_bluetooth_uuid *uuid;
+
+    pa_assert(u);
+    pa_assert(u->device);
+
+    device = u->device;
+
+    pa_card_new_data_init(&data);
+    data.driver = __FILE__;
+    data.module = u->module;
+
+    n = pa_bluetooth_cleanup_name(device->alias);
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, n);
+    pa_xfree(n);
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, device->address);
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "bluez");
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "sound");
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_BUS, "bluetooth");
+
+    if ((ff = pa_bluetooth_get_form_factor(device->class)) != PA_BT_FORM_FACTOR_UNKNOWN)
+        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, pa_bt_form_factor_to_string(ff));
+
+    pa_proplist_sets(data.proplist, "bluez.path", device->path);
+    pa_proplist_setf(data.proplist, "bluez.class", "0x%06x", (unsigned) device->class);
+    pa_proplist_sets(data.proplist, "bluez.alias", device->alias);
+    data.name = get_name("card", u->modargs, device->address, &b);
+    data.namereg_fail = b;
+
+    if (pa_modargs_get_proplist(u->modargs, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+        pa_log("Invalid properties");
+        pa_card_new_data_done(&data);
+        return -1;
+    }
+
+    create_card_ports(u, data.ports);
+
+    PA_LLIST_FOREACH(uuid, device->uuids) {
+        p = create_card_profile(u, uuid->uuid, data.ports);
+
+        if (!p)
+            continue;
+
+        if (pa_hashmap_get(data.profiles, p->name)) {
+            pa_card_profile_free(p);
+            continue;
+        }
+
+        pa_hashmap_put(data.profiles, p->name, p);
+    }
+
+    pa_assert(!pa_hashmap_isempty(data.profiles));
+
+    p = pa_card_profile_new("off", _("Off"), sizeof(enum profile));
+    p->available = PA_AVAILABLE_YES;
+    d = PA_CARD_PROFILE_DATA(p);
+    *d = PROFILE_OFF;
+    pa_hashmap_put(data.profiles, p->name, p);
+
+    if ((default_profile = pa_modargs_get_value(u->modargs, "profile", NULL))) {
+        if (pa_hashmap_get(data.profiles, default_profile))
+            pa_card_new_data_set_profile(&data, default_profile);
+        else
+            pa_log_warn("Profile '%s' not valid or not supported by device.", default_profile);
+    }
+
+    u->card = pa_card_new(u->core, &data);
+    pa_card_new_data_done(&data);
+
+    if (!u->card) {
+        pa_log("Failed to allocate card.");
+        return -1;
+    }
+
+    u->card->userdata = u;
+    u->card->set_profile = card_set_profile;
+
+    d = PA_CARD_PROFILE_DATA(u->card->active_profile);
+
+    if (*d != PROFILE_OFF && (!device->transports[*d] ||
+                              device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)) {
+        pa_log_warn("Default profile not connected, selecting off profile");
+        u->card->active_profile = pa_hashmap_get(u->card->profiles, "off");
+        u->card->save_profile = false;
+    }
+
+    d = PA_CARD_PROFILE_DATA(u->card->active_profile);
+    u->profile = *d;
+
+    if (USE_SCO_OVER_PCM(u))
+        save_sco_volume_callbacks(u);
+
+    return 0;
+}
+
+/* Run from main thread */
+static pa_bluetooth_device* find_device(struct userdata *u, const char *address, const char *path) {
+    pa_bluetooth_device *d = NULL;
+
+    pa_assert(u);
+
+    if (!address && !path) {
+        pa_log_error("Failed to get device address/path from module arguments.");
+        return NULL;
+    }
+
+    if (path) {
+        if (!(d = pa_bluetooth_discovery_get_by_path(u->discovery, path))) {
+            pa_log_error("%s is not a valid BlueZ audio device.", path);
+            return NULL;
+        }
+
+        if (address && !(pa_streq(d->address, address))) {
+            pa_log_error("Passed path %s address %s != %s don't match.", path, d->address, address);
+            return NULL;
+        }
+
+    } else {
+        if (!(d = pa_bluetooth_discovery_get_by_address(u->discovery, address))) {
+            pa_log_error("%s is not known.", address);
+            return NULL;
+        }
+    }
+
+    if (d) {
+        u->address = pa_xstrdup(d->address);
+        u->path = pa_xstrdup(d->path);
+    }
+
+    return d;
+}
+
+/* Run from main thread */
+static pa_hook_result_t uuid_added_cb(pa_bluetooth_discovery *y, const struct pa_bluetooth_hook_uuid_data *data,
+                                      struct userdata *u) {
+    pa_card_profile *p;
+
+    pa_assert(data);
+    pa_assert(data->device);
+    pa_assert(data->uuid);
+    pa_assert(u);
+
+    if (data->device != u->device)
+        return PA_HOOK_OK;
+
+    p = create_card_profile(u, data->uuid, u->card->ports);
+
+    if (!p)
+        return PA_HOOK_OK;
+
+    if (pa_hashmap_get(u->card->profiles, p->name)) {
+        pa_card_profile_free(p);
+        return PA_HOOK_OK;
+    }
+
+    pa_card_add_profile(u->card, p);
+
+    return PA_HOOK_OK;
+}
+
+/* Run from main thread */
+static pa_hook_result_t discovery_hook_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
+    pa_assert(u);
+    pa_assert(d);
+
+    if (d != u->device)
+        return PA_HOOK_OK;
+
+    if (d->dead)
+        pa_log_debug("Device %s removed: unloading module", d->path);
+    else if (!pa_bluetooth_device_any_audio_connected(d))
+        pa_log_debug("Unloading module, because device %s doesn't have any audio profiles connected anymore.", d->path);
+    else
+        return PA_HOOK_OK;
+
+    pa_module_unload(u->core, u->module, true);
+
+    return PA_HOOK_OK;
+}
+
+int pa__init(pa_module *m) {
+    pa_modargs *ma;
+    uint32_t channels;
+    struct userdata *u;
+    const char *address, *path;
+    pa_bluetooth_device *device;
+
+    pa_assert(m);
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log_error("Failed to parse module arguments");
+        goto fail;
+    }
+
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+    u->module = m;
+    u->core = m->core;
+    u->stream_fd = -1;
+    u->sample_spec = m->core->default_sample_spec;
+    u->modargs = ma;
+
+    if (pa_modargs_get_value(ma, "sco_sink", NULL) &&
+        !(u->hsp.sco_sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_sink", NULL), PA_NAMEREG_SINK))) {
+        pa_log("SCO sink not found");
+        goto fail;
+    }
+
+    if (pa_modargs_get_value(ma, "sco_source", NULL) &&
+        !(u->hsp.sco_source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_source", NULL), PA_NAMEREG_SOURCE))) {
+        pa_log("SCO source not found");
+        goto fail;
+    }
+
+    if (pa_modargs_get_value_u32(ma, "rate", &u->sample_spec.rate) < 0 ||
+        u->sample_spec.rate <= 0 || u->sample_spec.rate > PA_RATE_MAX) {
+        pa_log_error("Failed to get rate from module arguments");
+        goto fail;
+    }
+
+    u->auto_connect = true;
+    if (pa_modargs_get_value_boolean(ma, "auto_connect", &u->auto_connect)) {
+        pa_log("Failed to parse auto_connect= argument");
+        goto fail;
+    }
+
+    channels = u->sample_spec.channels;
+    if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 ||
+        channels <= 0 || channels > PA_CHANNELS_MAX) {
+        pa_log_error("Failed to get channels from module arguments");
+        goto fail;
+    }
+    u->sample_spec.channels = (uint8_t) channels;
+    u->requested_sample_spec = u->sample_spec;
+
+    address = pa_modargs_get_value(ma, "address", NULL);
+    path = pa_modargs_get_value(ma, "path", NULL);
+
+    if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))
+        goto fail;
+
+    if (!(device = find_device(u, address, path)))
+        goto fail;
+
+    u->device = device;
+
+    u->discovery_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) discovery_hook_cb, u);
+
+    u->uuid_added_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) uuid_added_cb, u);
+
+    u->sink_state_changed_slot =
+        pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED],
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_cb, u);
+
+    u->source_state_changed_slot =
+        pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED],
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_cb, u);
+
+    u->transport_state_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u);
+
+    u->transport_nrec_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_nrec_changed_cb, u);
+
+    u->transport_microphone_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
+
+    u->transport_speaker_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_speaker_gain_changed_cb, u);
+
+    /* Add the card structure. This will also initialize the default profile */
+    if (add_card(u) < 0)
+        goto fail;
+
+    if (!(u->msg = pa_msgobject_new(bluetooth_msg)))
+        goto fail;
+
+    u->msg->parent.process_msg = device_process_msg;
+    u->msg->card = u->card;
+
+    if (u->profile != PROFILE_OFF)
+        if (init_profile(u) < 0)
+            goto off;
+
+    if (u->sink || u->source)
+        if (start_thread(u) < 0)
+            goto off;
+
+    return 0;
+
+off:
+    stop_thread(u);
+
+    pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+
+    return 0;
+
+fail:
+
+    pa__done(m);
+
+    return -1;
+}
+
+int pa__get_n_used(pa_module *m) {
+    struct userdata *u;
+
+    pa_assert(m);
+    pa_assert_se(u = m->userdata);
+
+    return
+        (u->sink ? pa_sink_linked_by(u->sink) : 0) +
+        (u->source ? pa_source_linked_by(u->source) : 0);
+}
+
+void pa__done(pa_module *m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    stop_thread(u);
+
+    if (u->discovery_slot)
+        pa_hook_slot_free(u->discovery_slot);
+
+    if (u->uuid_added_slot)
+        pa_hook_slot_free(u->uuid_added_slot);
+
+    if (u->sink_state_changed_slot)
+        pa_hook_slot_free(u->sink_state_changed_slot);
+
+    if (u->source_state_changed_slot)
+        pa_hook_slot_free(u->source_state_changed_slot);
+
+    if (u->transport_state_changed_slot)
+        pa_hook_slot_free(u->transport_state_changed_slot);
+
+    if (u->transport_nrec_changed_slot)
+        pa_hook_slot_free(u->transport_nrec_changed_slot);
+
+    if (u->transport_microphone_changed_slot)
+        pa_hook_slot_free(u->transport_microphone_changed_slot);
+
+    if (u->transport_speaker_changed_slot)
+        pa_hook_slot_free(u->transport_speaker_changed_slot);
+
+    if (USE_SCO_OVER_PCM(u))
+        restore_sco_volume_callbacks(u);
+
+    if (u->msg)
+        pa_xfree(u->msg);
+
+    if (u->card)
+        pa_card_free(u->card);
+
+    if (u->a2dp.buffer)
+        pa_xfree(u->a2dp.buffer);
+
+    sbc_finish(&u->a2dp.sbc);
+
+    if (u->modargs)
+        pa_modargs_free(u->modargs);
+
+    pa_xfree(u->output_port_name);
+    pa_xfree(u->input_port_name);
+
+    pa_xfree(u->address);
+    pa_xfree(u->path);
+
+    if (u->discovery)
+        pa_bluetooth_discovery_unref(u->discovery);
+
+    pa_xfree(u);
+}
diff --git a/src/modules/bluetooth/module-bluez4-discover.c b/src/modules/bluetooth/module-bluez4-discover.c
new file mode 100644
index 0000000..e5b30f5
--- /dev/null
+++ b/src/modules/bluetooth/module-bluez4-discover.c
@@ -0,0 +1,194 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008-2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio 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 Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-shared.h>
+
+#include "module-bluez4-discover-symdef.h"
+#include "bluez4-util.h"
+
+PA_MODULE_AUTHOR("João Paulo Rechi Vita");
+PA_MODULE_DESCRIPTION("Detect available BlueZ 4 Bluetooth audio devices and load BlueZ 4 Bluetooth audio drivers");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_USAGE("sco_sink=<name of sink> "
+                "sco_source=<name of source> ");
+PA_MODULE_LOAD_ONCE(true);
+
+static const char* const valid_modargs[] = {
+    "sco_sink",
+    "sco_source",
+    "async", /* deprecated */
+    NULL
+};
+
+struct userdata {
+    pa_module *module;
+    pa_modargs *modargs;
+    pa_core *core;
+    pa_bluetooth_discovery *discovery;
+    pa_hook_slot *slot;
+    pa_hashmap *hashmap;
+};
+
+struct module_info {
+    char *path;
+    uint32_t module;
+};
+
+static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
+    struct module_info *mi;
+
+    pa_assert(u);
+    pa_assert(d);
+
+    mi = pa_hashmap_get(u->hashmap, d->path);
+
+    if (pa_bluetooth_device_any_audio_connected(d)) {
+
+        if (!mi) {
+            pa_module *m = NULL;
+            char *args;
+
+            /* Oh, awesome, a new device has shown up and been connected! */
+
+            args = pa_sprintf_malloc("address=\"%s\" path=\"%s\"", d->address, d->path);
+
+            if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) &&
+                pa_modargs_get_value(u->modargs, "sco_source", NULL)) {
+                char *tmp;
+
+                tmp = pa_sprintf_malloc("%s sco_sink=\"%s\" sco_source=\"%s\"", args,
+                                        pa_modargs_get_value(u->modargs, "sco_sink", NULL),
+                                        pa_modargs_get_value(u->modargs, "sco_source", NULL));
+                pa_xfree(args);
+                args = tmp;
+            }
+
+            pa_log_debug("Loading module-bluez4-device %s", args);
+            m = pa_module_load(u->module->core, "module-bluez4-device", args);
+            pa_xfree(args);
+
+            if (m) {
+                mi = pa_xnew(struct module_info, 1);
+                mi->module = m->index;
+                mi->path = pa_xstrdup(d->path);
+
+                pa_hashmap_put(u->hashmap, mi->path, mi);
+            } else
+                pa_log_debug("Failed to load module for device %s", d->path);
+        }
+
+    } else {
+
+        if (mi) {
+
+            /* Hmm, disconnection? Then the module unloads itself */
+
+            pa_log_debug("Unregistering module for %s", d->path);
+            pa_hashmap_remove(u->hashmap, mi->path);
+            pa_xfree(mi->path);
+            pa_xfree(mi);
+        }
+    }
+
+    return PA_HOOK_OK;
+}
+
+int pa__init(pa_module* m) {
+    struct userdata *u;
+    pa_modargs *ma = NULL;
+
+    pa_assert(m);
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments");
+        goto fail;
+    }
+
+    if (pa_modargs_get_value(ma, "async", NULL))
+        pa_log_warn("The 'async' argument is deprecated and does nothing.");
+
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+    u->module = m;
+    u->core = m->core;
+    u->modargs = ma;
+    ma = NULL;
+    u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    if (!(u->discovery = pa_bluetooth_discovery_get(u->core)))
+        goto fail;
+
+    u->slot = pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
+                              PA_HOOK_NORMAL, (pa_hook_cb_t) load_module_for_device, u);
+
+    return 0;
+
+fail:
+    pa__done(m);
+
+    if (ma)
+        pa_modargs_free(ma);
+
+    return -1;
+}
+
+void pa__done(pa_module* m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->slot)
+        pa_hook_slot_free(u->slot);
+
+    if (u->discovery)
+        pa_bluetooth_discovery_unref(u->discovery);
+
+    if (u->hashmap) {
+        struct module_info *mi;
+
+        while ((mi = pa_hashmap_steal_first(u->hashmap))) {
+            pa_xfree(mi->path);
+            pa_xfree(mi);
+        }
+
+        pa_hashmap_free(u->hashmap);
+    }
+
+    if (u->modargs)
+        pa_modargs_free(u->modargs);
+
+    pa_xfree(u);
+}

commit 0589bed30512acb2c7160e4285c44fe2bdb17fbf
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:51 2013 -0300

    bluetooth: Update modules description and copyright
    
    The current set of bluetooth modules only support up to BlueZ 4. Since
    the BlueZ API when through a big change with the release of BlueZ 5 the
    modules will be forked into a new set for BlueZ 5.
    
    This commit also fixes the spelling of Bluetooth (it's a trademark which
    should always be spelled with capital B) and the spelling of my name,
    and also update the copyright note dates throughout the Bluetooth
    modules.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index 11941ec..49831e6 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1,7 +1,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2008-2009 Joao Paulo Rechi Vita
+  Copyright 2008-2013 João Paulo Rechi Vita
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h
index 3361b0f..c69ba71 100644
--- a/src/modules/bluetooth/bluetooth-util.h
+++ b/src/modules/bluetooth/bluetooth-util.h
@@ -4,7 +4,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2008-2009 Joao Paulo Rechi Vita
+  Copyright 2008-2013 João Paulo Rechi Vita
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 6e21863..5da197b 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -1,8 +1,8 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2008-2009 Joao Paulo Rechi Vita
-  Copyright 2011-2012 BMW Car IT GmbH.
+  Copyright 2008-2013 João Paulo Rechi Vita
+  Copyright 2011-2013 BMW Car IT GmbH.
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
@@ -60,8 +60,8 @@
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
 
-PA_MODULE_AUTHOR("Joao Paulo Rechi Vita");
-PA_MODULE_DESCRIPTION("Bluetooth audio sink and source");
+PA_MODULE_AUTHOR("João Paulo Rechi Vita");
+PA_MODULE_DESCRIPTION("BlueZ 4 Bluetooth audio sink and source");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(false);
 PA_MODULE_USAGE(
diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c
index 4ebf298..d89890b 100644
--- a/src/modules/bluetooth/module-bluetooth-discover.c
+++ b/src/modules/bluetooth/module-bluetooth-discover.c
@@ -1,7 +1,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2008-2009 Joao Paulo Rechi Vita
+  Copyright 2008-2013 João Paulo Rechi Vita
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
@@ -37,8 +37,8 @@
 #include "module-bluetooth-discover-symdef.h"
 #include "bluetooth-util.h"
 
-PA_MODULE_AUTHOR("Joao Paulo Rechi Vita");
-PA_MODULE_DESCRIPTION("Detect available bluetooth audio devices and load bluetooth audio drivers");
+PA_MODULE_AUTHOR("João Paulo Rechi Vita");
+PA_MODULE_DESCRIPTION("Detect available BlueZ 4 Bluetooth audio devices and load BlueZ 4 Bluetooth audio drivers");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_USAGE("sco_sink=<name of sink> "
                 "sco_source=<name of source> ");

commit 09b6c36944fc0d5e1818319e71a31c5f64c5d3b3
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:50 2013 -0300

    Revert "bluetooth: Detect BlueZ 5"
    
    This reverts commit 2247b187393ef68879162faee8b422da9f5e556f.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index aee375a..11941ec 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -61,19 +61,12 @@
     " </interface>"                                                     \
     "</node>"
 
-typedef enum pa_bluez_version {
-    BLUEZ_VERSION_UNKNOWN,
-    BLUEZ_VERSION_4,
-    BLUEZ_VERSION_5,
-} pa_bluez_version_t;
-
 struct pa_bluetooth_discovery {
     PA_REFCNT_DECLARE;
 
     pa_core *core;
     pa_dbus_connection *connection;
     PA_LLIST_HEAD(pa_dbus_pending, pending);
-    pa_bluez_version_t version;
     bool adapters_listed;
     pa_hashmap *devices;
     pa_hashmap *transports;
@@ -883,46 +876,6 @@ static void list_adapters(pa_bluetooth_discovery *y) {
     send_and_add_to_pending(y, m, get_properties_reply, NULL);
 }
 
-static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
-    DBusMessage *r;
-    pa_dbus_pending *p;
-    pa_bluetooth_discovery *y;
-
-    pa_assert_se(p = userdata);
-    pa_assert_se(y = p->context_data);
-    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
-
-    if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
-        pa_log_info("D-Bus ObjectManager not detected so falling back to BlueZ version 4 API.");
-        y->version = BLUEZ_VERSION_4;
-        list_adapters(y);
-        goto finish;
-    }
-
-    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
-        pa_log("GetManagedObjects() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
-        goto finish;
-    }
-
-    pa_log_info("D-Bus ObjectManager detected so assuming BlueZ version 5.");
-    y->version = BLUEZ_VERSION_5;
-
-finish:
-    dbus_message_unref(r);
-
-    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
-    pa_dbus_pending_free(p);
-}
-
-static void init_bluez(pa_bluetooth_discovery *y) {
-    DBusMessage *m;
-    pa_assert(y);
-
-    pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.freedesktop.DBus.ObjectManager",
-                                                  "GetManagedObjects"));
-    send_and_add_to_pending(y, m, get_managed_objects_reply, NULL);
-}
-
 static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
     const char *key;
     DBusMessageIter variant_i;
@@ -1066,12 +1019,11 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
                 pa_log_debug("Bluetooth daemon disappeared.");
                 remove_all_devices(y);
                 y->adapters_listed = false;
-                y->version = BLUEZ_VERSION_UNKNOWN;
             }
 
             if (new_owner && *new_owner) {
                 pa_log_debug("Bluetooth daemon appeared.");
-                init_bluez(y);
+                list_adapters(y);
             }
         }
 
@@ -1754,7 +1706,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
     pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
     pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
 
-    init_bluez(y);
+    list_adapters(y);
 
     return y;
 

commit f3fadfa07f5f8b4def0f5d196fc8dad4a1ceadb4
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:49 2013 -0300

    Revert "bluetooth: Parse the tree returned by ObjectManager"
    
    This reverts commit c4bd51a34547ed0991ff3c7219c2d5de49669ca8.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index ded9afe..aee375a 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -362,6 +362,8 @@ static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, boo
 
     dbus_message_iter_recurse(i, &variant_i);
 
+/*     pa_log_debug("Parsing property org.bluez.Device.%s", key); */
+
     switch (dbus_message_iter_get_arg_type(&variant_i)) {
 
         case DBUS_TYPE_STRING: {
@@ -450,11 +452,6 @@ static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, boo
                     uuiddata.uuid = value;
                     pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata);
 
-                    if (d->discovery->version >= BLUEZ_VERSION_5) {
-                        dbus_message_iter_next(&ai);
-                        continue;
-                    }
-
                     /* Vudentz said the interfaces are here when the UUIDs are announced */
                     if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway",
@@ -866,25 +863,16 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
     send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
 }
 
-static void register_adapter_endpoints(pa_bluetooth_discovery *y, const char *path) {
-    register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
-    register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
-
-    /* For BlueZ 5, only A2DP is registered in the Media API */
-    if (y->version >= BLUEZ_VERSION_5)
-        return;
-
-    register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
-    register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
-}
-
 static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
     DBusMessage *m;
 
     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
     send_and_add_to_pending(y, m, get_properties_reply, NULL);
 
-    register_adapter_endpoints(y, path);
+    register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
+    register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
+    register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
+    register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
 }
 
 static void list_adapters(pa_bluetooth_discovery *y) {
@@ -895,94 +883,10 @@ static void list_adapters(pa_bluetooth_discovery *y) {
     send_and_add_to_pending(y, m, get_properties_reply, NULL);
 }
 
-static int parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
-    DBusMessageIter element_i;
-    int ret = 0;
-
-    dbus_message_iter_recurse(i, &element_i);
-
-    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
-        DBusMessageIter dict_i;
-
-        dbus_message_iter_recurse(&element_i, &dict_i);
-
-        if (parse_device_property(d, &dict_i, is_property_change))
-            ret = -1;
-
-        dbus_message_iter_next(&element_i);
-    }
-
-    if (!d->address || !d->alias || d->paired < 0 || d->trusted < 0) {
-        pa_log_error("Non-optional information missing for device %s", d->path);
-        d->device_info_valid = -1;
-        return -1;
-    }
-
-    d->device_info_valid = 1;
-    return ret;
-}
-
-static int parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
-    DBusMessageIter element_i;
-    const char *path;
-
-    pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
-    dbus_message_iter_get_basic(dict_i, &path);
-
-    pa_assert_se(dbus_message_iter_next(dict_i));
-    pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_ARRAY);
-
-    dbus_message_iter_recurse(dict_i, &element_i);
-
-    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
-        DBusMessageIter iface_i;
-        const char *interface;
-
-        dbus_message_iter_recurse(&element_i, &iface_i);
-
-        pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_STRING);
-        dbus_message_iter_get_basic(&iface_i, &interface);
-
-        pa_assert_se(dbus_message_iter_next(&iface_i));
-        pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY);
-
-        if (pa_streq(interface, "org.bluez.Adapter1")) {
-            pa_log_debug("Adapter %s found", path);
-            register_adapter_endpoints(y, path);
-        } else if (pa_streq(interface, "org.bluez.Device1")) {
-            pa_bluetooth_device *d;
-
-            if (pa_hashmap_get(y->devices, path)) {
-                pa_log("Found duplicated D-Bus path for device %s", path);
-                return -1;
-            }
-
-            pa_log_debug("Device %s found", path);
-
-            d = device_new(y, path);
-            pa_hashmap_put(y->devices, d->path, d);
-
-            /* FIXME: BlueZ 5 doesn't support the old Audio interface, and thus
-               it's not possible to know if any audio profile is about to be
-               connected. This can introduce regressions with modules such as
-               module-card-restore */
-            d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
-
-            if (parse_device_properties(d, &iface_i, false) < 0)
-                return -1;
-        }
-
-        dbus_message_iter_next(&element_i);
-    }
-
-    return 0;
-}
-
 static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
     DBusMessage *r;
     pa_dbus_pending *p;
     pa_bluetooth_discovery *y;
-    DBusMessageIter arg_i, element_i;
 
     pa_assert_se(p = userdata);
     pa_assert_se(y = p->context_data);
@@ -1003,23 +907,6 @@ static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata)
     pa_log_info("D-Bus ObjectManager detected so assuming BlueZ version 5.");
     y->version = BLUEZ_VERSION_5;
 
-    if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) {
-        pa_log("Invalid reply signature for GetManagedObjects().");
-        goto finish;
-    }
-
-    dbus_message_iter_recurse(&arg_i, &element_i);
-    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
-        DBusMessageIter dict_i;
-
-        dbus_message_iter_recurse(&element_i, &dict_i);
-
-        /* Ignore errors here and proceed with next object */
-        parse_interfaces_and_properties(y, &dict_i);
-
-        dbus_message_iter_next(&element_i);
-    }
-
 finish:
     dbus_message_unref(r);
 

commit 35ded8dfe5bfb0998146747ccf55a218be54f0d6
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:48 2013 -0300

    Revert "bluetooth: Fix error checking style"
    
    This reverts commit d9ed42c40f744b273c8d466e95dd4a06e41364b9.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index 978d6d4..ded9afe 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -906,7 +906,7 @@ static int parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i, b
 
         dbus_message_iter_recurse(&element_i, &dict_i);
 
-        if (parse_device_property(d, &dict_i, is_property_change) < 0)
+        if (parse_device_property(d, &dict_i, is_property_change))
             ret = -1;
 
         dbus_message_iter_next(&element_i);

commit ccace933f2cb1dacee0e053018e1e9b20190e2fd
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:47 2013 -0300

    Revert "bluetooth: BlueZ 5 interface rename to org.bluez.MediaEndpoint1"
    
    This reverts commit d22ea7ff7630bef83a3bf8c26cc56c196dec33a1.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index a8e841a..978d6d4 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -61,30 +61,6 @@
     " </interface>"                                                     \
     "</node>"
 
-#define MEDIA_ENDPOINT_1_INTROSPECT_XML                                 \
-    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
-    "<node>"                                                            \
-    " <interface name=\"org.bluez.MediaEndpoint1\">"                    \
-    "  <method name=\"SetConfiguration\">"                              \
-    "   <arg name=\"transport\" direction=\"in\" type=\"o\"/>"          \
-    "   <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>"     \
-    "  </method>"                                                       \
-    "  <method name=\"SelectConfiguration\">"                           \
-    "   <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>"      \
-    "   <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>"    \
-    "  </method>"                                                       \
-    "  <method name=\"ClearConfiguration\">"                            \
-    "  </method>"                                                       \
-    "  <method name=\"Release\">"                                       \
-    "  </method>"                                                       \
-    " </interface>"                                                     \
-    " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
-    "  <method name=\"Introspect\">"                                    \
-    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
-    "  </method>"                                                       \
-    " </interface>"                                                     \
-    "</node>"
-
 typedef enum pa_bluez_version {
     BLUEZ_VERSION_UNKNOWN,
     BLUEZ_VERSION_4,
@@ -1473,7 +1449,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     dbus_message_iter_get_basic(&args, &path);
 
     if (pa_hashmap_get(y->transports, path)) {
-        pa_log("Endpoint SetConfiguration: Transport %s is already configured.", path);
+        pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path);
         goto fail2;
     }
 
@@ -1567,10 +1543,11 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     return NULL;
 
 fail:
-    pa_log("Endpoint SetConfiguration: invalid arguments");
+    pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
 
 fail2:
-    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to set configuration"));
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
+                                            "Unable to set configuration"));
     return r;
 }
 
@@ -1584,7 +1561,7 @@ static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage
     dbus_error_init(&e);
 
     if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
-        pa_log("Endpoint ClearConfiguration: %s", e.message);
+        pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message);
         dbus_error_free(&e);
         goto fail;
     }
@@ -1609,7 +1586,8 @@ static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage
     return r;
 
 fail:
-    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to clear configuration"));
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
+                                            "Unable to clear configuration"));
     return r;
 }
 
@@ -1679,7 +1657,7 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage
     dbus_error_init(&e);
 
     if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
-        pa_log("Endpoint SelectConfiguration: %s", e.message);
+        pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message);
         dbus_error_free(&e);
         goto fail;
     }
@@ -1776,7 +1754,8 @@ done:
     return r;
 
 fail:
-    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to select configuration"));
+    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
+                                            "Unable to select configuration"));
     return r;
 }
 
@@ -1800,19 +1779,17 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
         !pa_streq(path, HFP_HS_ENDPOINT))
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
-    interface = y->version == BLUEZ_VERSION_4 ? "org.bluez.MediaEndpoint" : "org.bluez.MediaEndpoint1";
-
     if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
-        const char *xml = y->version == BLUEZ_VERSION_4 ? ENDPOINT_INTROSPECT_XML : MEDIA_ENDPOINT_1_INTROSPECT_XML;
+        const char *xml = ENDPOINT_INTROSPECT_XML;
 
         pa_assert_se(r = dbus_message_new_method_return(m));
         pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
 
-    } else if (dbus_message_is_method_call(m, interface, "SetConfiguration"))
+    } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration"))
         r = endpoint_set_configuration(c, m, userdata);
-    else if (dbus_message_is_method_call(m, interface, "SelectConfiguration"))
+    else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration"))
         r = endpoint_select_configuration(c, m, userdata);
-    else if (dbus_message_is_method_call(m, interface, "ClearConfiguration"))
+    else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration"))
         r = endpoint_clear_configuration(c, m, userdata);
     else
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

commit bf46379bb60e1d42b03b420bb15f7d31434fa839
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:46 2013 -0300

    Revert "bluetooth: BlueZ 5 interface rename to org.bluez.Media1"
    
    This reverts commit 61e8fd8854308cb2d41e6313f25ae7131c912ea8.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index b161a01..a8e841a 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -830,7 +830,8 @@ static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
     }
 
     if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
-        pa_log("RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+        pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r),
+               pa_dbus_get_error_message(r));
         goto finish;
     }
 
@@ -847,11 +848,10 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
     DBusMessage *m;
     DBusMessageIter i, d;
     uint8_t codec = 0;
-    const char *interface = y->version == BLUEZ_VERSION_4 ? "org.bluez.Media" : "org.bluez.Media1";
 
     pa_log_debug("Registering %s on adapter %s.", endpoint, path);
 
-    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, interface, "RegisterEndpoint"));
+    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint"));
 
     dbus_message_iter_init_append(m, &i);
 

commit 38cc2ecbe62b7b65ddbe50decae52ed53baebf66
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:45 2013 -0300

    Revert "bluetooth: BlueZ 5 interface rename to org.bluez.MediaTransport1"
    
    This reverts commit cfb96b2530690df113e1aceb5b82fc166f9d2cfd.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index d5ff12f..b161a01 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1302,7 +1302,6 @@ bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
 
 int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
     const char *accesstype = "rw";
-    const char *interface;
     DBusMessage *m, *r;
     DBusError err;
     int ret;
@@ -1312,8 +1311,6 @@ int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, siz
     pa_assert(t->device);
     pa_assert(t->device->discovery);
 
-    interface = t->device->discovery->version == BLUEZ_VERSION_4 ? "org.bluez.MediaTransport" : "org.bluez.MediaTransport1";
-
     if (optional) {
         /* FIXME: we are trying to acquire the transport only if the stream is
            playing, without actually initiating the stream request from our side
@@ -1330,7 +1327,7 @@ int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, siz
 
     dbus_error_init(&err);
 
-    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, interface, "Acquire"));
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Acquire"));
     pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
     r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
 
@@ -1341,7 +1338,7 @@ int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, siz
 
     if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o,
                                DBUS_TYPE_INVALID)) {
-        pa_log("Failed to parse the media transport Acquire() reply: %s", err.message);
+        pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message);
         ret = -1;
         dbus_error_free(&err);
         goto fail;
@@ -1360,7 +1357,6 @@ fail:
 
 void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
     const char *accesstype = "rw";
-    const char *interface;
     DBusMessage *m;
     DBusError err;
 
@@ -1368,11 +1364,9 @@ void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
     pa_assert(t->device);
     pa_assert(t->device->discovery);
 
-    interface = t->device->discovery->version == BLUEZ_VERSION_4 ? "org.bluez.MediaTransport" : "org.bluez.MediaTransport1";
-
     dbus_error_init(&err);
 
-    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, interface, "Release"));
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release"));
     pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
     dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
 

commit 4f294bcf56f19f60aec389d535ee4e2542d37383
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:44 2013 -0300

    Revert "bluetooth: Support ObjectManager interface add/remove"
    
    This reverts commit 114edb0696ce979ad10f5616067afda1b76ae4e2.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index ddbf6a2..d5ff12f 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1229,62 +1229,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
             goto fail;
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")) {
-        DBusMessageIter arg_i;
-
-        if (y->version != BLUEZ_VERSION_5)
-            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
-
-        if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) {
-            pa_log("Invalid signature found in InterfacesAdded");
-            goto fail;
-        }
-
-        if (parse_interfaces_and_properties(y, &arg_i) < 0)
-            goto fail;
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")) {
-        const char *path;
-        DBusMessageIter arg_i;
-        DBusMessageIter element_i;
-
-        if (y->version != BLUEZ_VERSION_5)
-            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
-
-        if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oas")) {
-            pa_log("Invalid signature found in InterfacesRemoved");
-            goto fail;
-        }
-
-        dbus_message_iter_get_basic(&arg_i, &path);
-
-        pa_assert_se(dbus_message_iter_next(&arg_i));
-        pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
-
-        dbus_message_iter_recurse(&arg_i, &element_i);
-
-        while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) {
-            const char *interface;
-
-            dbus_message_iter_get_basic(&element_i, &interface);
-
-            if (pa_streq(interface, "org.bluez.Device1")) {
-                pa_bluetooth_device *d;
-
-                if (!(d = pa_hashmap_remove(y->devices, path)))
-                    pa_log_warn("Unknown device removed %s", path);
-                else {
-                    pa_log_debug("Device %s removed", path);
-                    run_callback(d, true);
-                    device_free(d);
-                }
-            }
-
-            dbus_message_iter_next(&element_i);
-        }
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
     }
 
 fail:
@@ -1942,8 +1886,6 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
                 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
                 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
                 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
-                "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
-                "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
                 NULL) < 0) {
         pa_log("Failed to add D-Bus matches: %s", err.message);
         goto fail;
@@ -2017,8 +1959,6 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
             "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
             "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
             "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
-            "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
-            "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
             NULL);
 
         if (y->filter_added)

commit 2edc3581b52985b5e074e968727af9ef588b318a
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:43 2013 -0300

    Revert "bluetooth: Support Properties.PropertiesChanged signal"
    
    This reverts commit 235611a7d13ab251689f5b480183d7f39e043a2e.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index 9a759aa..ddbf6a2 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1285,35 +1285,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
         }
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", "PropertiesChanged")) {
-        DBusMessageIter arg_i;
-        const char *interface;
-
-        if (y->version != BLUEZ_VERSION_5)
-            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
-
-        if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) {
-            pa_log("Invalid signature found in PropertiesChanged");
-            goto fail;
-        }
-
-        dbus_message_iter_get_basic(&arg_i, &interface);
-
-        pa_assert_se(dbus_message_iter_next(&arg_i));
-        pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
-
-        if (pa_streq(interface, "org.bluez.Device1")) {
-            pa_bluetooth_device *d;
-
-            if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
-                pa_log_warn("Property change in unknown device %s", dbus_message_get_path(m));
-                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-            }
-
-            parse_device_properties(d, &arg_i, true);
-        }
-
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
     }
 
 fail:
@@ -1973,8 +1944,6 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
                 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
                 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
                 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
-                "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
-                ",arg0='org.bluez.Device1'",
                 NULL) < 0) {
         pa_log("Failed to add D-Bus matches: %s", err.message);
         goto fail;
@@ -2050,8 +2019,6 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
             "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
             "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
             "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
-            "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
-            ",arg0='org.bluez.Device1'",
             NULL);
 
         if (y->filter_added)

commit 133f59e86e90b89b36019bbe3a35662d8385890d
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:42 2013 -0300

    Revert "bluetooth: Parse media transport's properties"
    
    This reverts commit 2f79fb580ad583b2492b567d32630e7e03883855.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index 39a5a77..9a759aa 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1090,24 +1090,6 @@ static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *
     return 0;
 }
 
-static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter *i) {
-    DBusMessageIter element_i;
-
-    dbus_message_iter_recurse(i, &element_i);
-
-    while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
-        DBusMessageIter dict_i;
-
-        dbus_message_iter_recurse(&element_i, &dict_i);
-
-        transport_parse_property(t, &dict_i);
-
-        dbus_message_iter_next(&element_i);
-    }
-
-    return 0;
-}
-
 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
     DBusError err;
     pa_bluetooth_discovery *y;
@@ -1329,13 +1311,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
             }
 
             parse_device_properties(d, &arg_i, true);
-        } else if (pa_streq(interface, "org.bluez.MediaTransport1")) {
-            pa_bluetooth_transport *t;
-
-            if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
-                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
-            parse_transport_properties(t, &arg_i);
         }
 
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@@ -2000,8 +1975,6 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
                 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
                 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
                 ",arg0='org.bluez.Device1'",
-                "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
-                ",arg0='org.bluez.MediaTransport1'",
                 NULL) < 0) {
         pa_log("Failed to add D-Bus matches: %s", err.message);
         goto fail;
@@ -2079,8 +2052,6 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
             "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
             "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
             ",arg0='org.bluez.Device1'",
-            "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
-            ",arg0='org.bluez.MediaTransport1'",
             NULL);
 
         if (y->filter_added)

commit 93c3f5521540850c2c5199f2b7f4c18b81ddbda4
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:41 2013 -0300

    Revert "bluetooth: Support media transport's State property"
    
    This reverts commit 6fdf2b05b8867d4597c497243b469ecd0d8c2426.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index 4158411..39a5a77 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -126,20 +126,6 @@ static pa_bt_audio_state_t audio_state_from_string(const char* value) {
     return PA_BT_AUDIO_STATE_INVALID;
 }
 
-static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
-    pa_assert(value);
-    pa_assert(state);
-
-    if (pa_streq(value, "idle"))
-        *state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
-    else if (pa_streq(value, "pending") || pa_streq(value, "active")) /* We don't need such a separation */
-        *state = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
-    else
-        return -1;
-
-    return 0;
-}
-
 const char *pa_bt_profile_to_string(enum profile profile) {
     switch(profile) {
         case PROFILE_A2DP:
@@ -1099,29 +1085,6 @@ static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *
 
             break;
          }
-
-        case DBUS_TYPE_STRING: {
-
-            const char *value;
-            dbus_message_iter_get_basic(&variant_i, &value);
-
-            if (pa_streq(key, "State")) { /* Added in BlueZ 5.0 */
-                bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
-
-                if (transport_state_from_string(value, &t->state) < 0) {
-                    pa_log("Transport %s has an invalid state: '%s'", t->path, value);
-                    return -1;
-                }
-
-                pa_log_debug("dbus: transport %s set to state '%s'", t->path, value);
-                pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
-
-                if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
-                    run_callback(t->device, old_any_connected);
-            }
-
-            break;
-        }
     }
 
     return 0;
@@ -1600,10 +1563,7 @@ static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char
         memcpy(t->config, config, size);
     }
 
-    if (d->discovery->version == BLUEZ_VERSION_4)
-        t->state = audio_state_to_transport_state(d->profile_state[p]);
-    else
-        t->state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
+    t->state = audio_state_to_transport_state(d->profile_state[p]);
 
     return t;
 }

commit 44ca6512207e4181918ff6cab62e2d6c1d1e4b51
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:40 2013 -0300

    Revert "bluetooth: Update to new BlueZ 5 transport acquire/release API"
    
    This reverts commit 9615def4b96f0bab365ddc03f6c003f382a54752.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index 98517df..4158411 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1448,52 +1448,40 @@ bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
 }
 
 int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
+    const char *accesstype = "rw";
+    const char *interface;
     DBusMessage *m, *r;
     DBusError err;
     int ret;
     uint16_t i, o;
-    const char *method;
 
     pa_assert(t);
     pa_assert(t->device);
     pa_assert(t->device->discovery);
 
-    dbus_error_init(&err);
-
-    if (t->device->discovery->version == BLUEZ_VERSION_4) {
-        const char *accesstype = "rw";
-
-        if (optional) {
-            /* We are trying to acquire the transport only if the stream is
-               playing, without actually initiating the stream request from our side
-               (which is typically undesireable specially for hfgw use-cases.
-               However this approach is racy, since the stream could have been
-               suspended in the meantime, so we can't really guarantee that the
-               stream will not be requested with the API in BlueZ 4.x */
-            if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
-                pa_log_info("Failed optional acquire of unavailable transport %s", t->path);
-                return -1;
-            }
+    interface = t->device->discovery->version == BLUEZ_VERSION_4 ? "org.bluez.MediaTransport" : "org.bluez.MediaTransport1";
+
+    if (optional) {
+        /* FIXME: we are trying to acquire the transport only if the stream is
+           playing, without actually initiating the stream request from our side
+           (which is typically undesireable specially for hfgw use-cases.
+           However this approach is racy, since the stream could have been
+           suspended in the meantime, so we can't really guarantee that the
+           stream will not be requested until BlueZ's API supports this
+           atomically. */
+        if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
+            pa_log_info("Failed optional acquire of transport %s", t->path);
+            return -1;
         }
-
-        method = "Acquire";
-        pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", method));
-        pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
-    } else {
-        pa_assert(t->device->discovery->version == BLUEZ_VERSION_5);
-
-        method = optional ? "TryAcquire" : "Acquire";
-        pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport1", method));
     }
 
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, interface, "Acquire"));
+    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
     r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
 
     if (!r) {
-        if (optional && pa_streq(err.name, "org.bluez.Error.NotAvailable"))
-            pa_log_info("Failed optional acquire of unavailable transport %s", t->path);
-        else
-            pa_log("Transport %s() failed for transport %s (%s)", method, t->path, err.message);
-
         dbus_error_free(&err);
         return -1;
     }
@@ -1518,6 +1506,8 @@ fail:
 }
 
 void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
+    const char *accesstype = "rw";
+    const char *interface;
     DBusMessage *m;
     DBusError err;
 
@@ -1525,18 +1515,12 @@ void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
     pa_assert(t->device);
     pa_assert(t->device->discovery);
 
-    dbus_error_init(&err);
-
-    if (t->device->discovery->version == BLUEZ_VERSION_4) {
-        const char *accesstype = "rw";
+    interface = t->device->discovery->version == BLUEZ_VERSION_4 ? "org.bluez.MediaTransport" : "org.bluez.MediaTransport1";
 
-        pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release"));
-        pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
-    } else {
-        pa_assert(t->device->discovery->version == BLUEZ_VERSION_5);
-        pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport1", "Release"));
-    }
+    dbus_error_init(&err);
 
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, interface, "Release"));
+    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
     dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
 
     if (dbus_error_is_set(&err)) {
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index de29e52..6e21863 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -367,8 +367,14 @@ static int bt_transport_acquire(struct userdata *u, bool optional) {
     pa_log_debug("Acquiring transport %s", u->transport->path);
 
     u->stream_fd = pa_bluetooth_transport_acquire(u->transport, optional, &u->read_link_mtu, &u->write_link_mtu);
-    if (u->stream_fd < 0)
+    if (u->stream_fd < 0) {
+        if (!optional)
+            pa_log("Failed to acquire transport %s", u->transport->path);
+        else
+            pa_log_info("Failed optional acquire of transport %s", u->transport->path);
+
         return -1;
+    }
 
     u->transport_acquired = true;
     pa_log_info("Transport %s acquired: fd %d", u->transport->path, u->stream_fd);

commit 9882f2d4fd8bf65fb1c2d6d1aeb6a815fbdbeab6
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Tue Aug 13 01:53:39 2013 -0300

    Revert "bluetooth: Support transport auto-release"
    
    This reverts commit 0e4c16e120f95b122c7139deb046192927d1b6c7.
    
    This is part of the reversion of BlueZ 5 support so it can be added back
    in a separate set of modules. This makes the code easier to maintain and
    decrease PulseAudio's binary size.

diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index 892e5fa..98517df 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1534,12 +1534,6 @@ void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
         pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
     } else {
         pa_assert(t->device->discovery->version == BLUEZ_VERSION_5);
-
-        if (t->state <= PA_BLUETOOTH_TRANSPORT_STATE_IDLE) {
-            pa_log_info("Transport %s auto-released by BlueZ or already released", t->path);
-            return;
-        }
-
         pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport1", "Release"));
     }
 



More information about the pulseaudio-commits mailing list