[pulseaudio-commits] 8 commits - src/modules

Tanu Kaskinen tanuk at kemper.freedesktop.org
Wed Aug 10 18:37:39 UTC 2016


 src/modules/bluetooth/backend-native.c       |    2 
 src/modules/bluetooth/bluez4-util.c          |  123 ++++++++-------------
 src/modules/bluetooth/bluez4-util.h          |   34 +----
 src/modules/bluetooth/bluez5-util.c          |  125 ++++++++++++++++++++-
 src/modules/bluetooth/bluez5-util.h          |    4 
 src/modules/bluetooth/module-bluez4-device.c |  158 +++++++++++++++------------
 src/modules/bluetooth/module-bluez5-device.c |   72 ++++++++----
 src/modules/module-card-restore.c            |   32 -----
 8 files changed, 319 insertions(+), 231 deletions(-)

New commits:
commit 32c2a6d64aaa1d14b06c1af27a616e58d04e8e79
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sun Jul 31 02:44:55 2016 +0300

    bluetooth: don't create the HSP/HFP profile twice
    
    create_card_profile() used to get called separately for HSP and HFP,
    so if a headset supports both profiles, a profile named
    "headset_head_unit" would get created twice. The second instance would
    get immediately freed, so that wasn't a particularly serious problem.
    However, I think it makes more sense to create the profile only once.
    This patch makes things so that before a profile is created, we check
    what name that profile would have, and if a profile with that name
    already exists, we don't create the profile.
    
    A couple of Yocto releases (jethro and krogoth) have non-upstream
    patches that suffer from this double creation. The patches add
    associations between profiles and ports, and those associations use
    the profile name as the key. When the second profile gets freed, the
    associations between the profile and its ports get removed, and since
    the profile name is used as the key, this erroneously affects the
    first profile too. Crashing ensues.
    
    BugLink: https://bugzilla.yoctoproject.org/show_bug.cgi?id=10018

diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
index 0c561d1..0de8ecb 100644
--- a/src/modules/bluetooth/module-bluez4-device.c
+++ b/src/modules/bluetooth/module-bluez4-device.c
@@ -2162,18 +2162,23 @@ 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) {
+static pa_card_profile *create_card_profile(struct userdata *u, pa_bluez4_profile_t profile, pa_hashmap *ports) {
     pa_device_port *input_port, *output_port;
+    const char *name;
     pa_card_profile *p = NULL;
-    pa_bluez4_profile_t *d;
+    pa_bluez4_profile_t *d = NULL;
+    pa_bluez4_transport *t;
 
     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_BLUEZ4_UUID_A2DP_SINK)) {
-        p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(pa_bluez4_profile_t));
+    name = pa_bluez4_profile_to_string(profile);
+
+    switch (profile) {
+    case PA_BLUEZ4_PROFILE_A2DP_SINK:
+        p = pa_card_profile_new(name, _("High Fidelity Playback (A2DP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 10;
         p->n_sinks = 1;
         p->n_sources = 0;
@@ -2182,9 +2187,10 @@ 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 = PA_BLUEZ4_PROFILE_A2DP_SINK;
-    } else if (pa_streq(uuid, PA_BLUEZ4_UUID_A2DP_SOURCE)) {
-        p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(pa_bluez4_profile_t));
+        break;
+
+    case PA_BLUEZ4_PROFILE_A2DP_SOURCE:
+        p = pa_card_profile_new(name, _("High Fidelity Capture (A2DP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 10;
         p->n_sinks = 0;
         p->n_sources = 1;
@@ -2193,9 +2199,10 @@ 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 = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
-    } else if (pa_streq(uuid, PA_BLUEZ4_UUID_HSP_HS) || pa_streq(uuid, PA_BLUEZ4_UUID_HFP_HF)) {
-        p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(pa_bluez4_profile_t));
+        break;
+
+    case PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT:
+        p = pa_card_profile_new(name, _("Telephony Duplex (HSP/HFP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 20;
         p->n_sinks = 1;
         p->n_sources = 1;
@@ -2205,9 +2212,10 @@ 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 = PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT;
-    } else if (pa_streq(uuid, PA_BLUEZ4_UUID_HFP_AG)) {
-        p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(pa_bluez4_profile_t));
+        break;
+
+    case PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY:
+        p = pa_card_profile_new(name, _("Handsfree Gateway"), sizeof(pa_bluez4_profile_t));
         p->priority = 20;
         p->n_sinks = 1;
         p->n_sources = 1;
@@ -2217,19 +2225,35 @@ 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 = PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY;
+        break;
+
+    case PA_BLUEZ4_PROFILE_OFF:
+        pa_assert_not_reached();
     }
 
-    if (p) {
-        pa_bluez4_transport *t;
+    *d = profile;
 
-        if ((t = u->device->transports[*d]))
-            p->available = transport_state_to_availability(t->state);
-    }
+    if ((t = u->device->transports[*d]))
+        p->available = transport_state_to_availability(t->state);
 
     return p;
 }
 
+static int uuid_to_profile(const char *uuid, pa_bluez4_profile_t *_r) {
+    if (pa_streq(uuid, PA_BLUEZ4_UUID_A2DP_SINK))
+        *_r = PA_BLUEZ4_PROFILE_A2DP_SINK;
+    else if (pa_streq(uuid, PA_BLUEZ4_UUID_A2DP_SOURCE))
+        *_r = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
+    else if (pa_streq(uuid, PA_BLUEZ4_UUID_HSP_HS) || pa_streq(uuid, PA_BLUEZ4_UUID_HFP_HF))
+        *_r = PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT;
+    else if (pa_streq(uuid, PA_BLUEZ4_UUID_HSP_AG) || pa_streq(uuid, PA_BLUEZ4_UUID_HFP_AG))
+        *_r = PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY;
+    else
+        return -PA_ERR_INVALID;
+
+    return 0;
+}
+
 /* Run from main thread */
 static int add_card(struct userdata *u) {
     pa_card_new_data data;
@@ -2278,16 +2302,15 @@ static int add_card(struct userdata *u) {
     create_card_ports(u, data.ports);
 
     PA_HASHMAP_FOREACH(uuid, device->uuids, state) {
-        p = create_card_profile(u, uuid, data.ports);
+        pa_bluez4_profile_t profile;
 
-        if (!p)
+        if (uuid_to_profile(uuid, &profile) < 0)
             continue;
 
-        if (pa_hashmap_get(data.profiles, p->name)) {
-            pa_card_profile_free(p);
+        if (pa_hashmap_get(data.profiles, pa_bluez4_profile_to_string(profile)))
             continue;
-        }
 
+        p = create_card_profile(u, profile, data.ports);
         pa_hashmap_put(data.profiles, p->name, p);
     }
 
@@ -2387,6 +2410,7 @@ static pa_bluez4_device* find_device(struct userdata *u, const char *address, co
 /* Run from main thread */
 static pa_hook_result_t uuid_added_cb(pa_bluez4_discovery *y, const struct pa_bluez4_hook_uuid_data *data,
                                       struct userdata *u) {
+    pa_bluez4_profile_t profile;
     pa_card_profile *p;
 
     pa_assert(data);
@@ -2397,16 +2421,13 @@ static pa_hook_result_t uuid_added_cb(pa_bluez4_discovery *y, const struct pa_bl
     if (data->device != u->device)
         return PA_HOOK_OK;
 
-    p = create_card_profile(u, data->uuid, u->card->ports);
-
-    if (!p)
+    if (uuid_to_profile(data->uuid, &profile) < 0)
         return PA_HOOK_OK;
 
-    if (pa_hashmap_get(u->card->profiles, p->name)) {
-        pa_card_profile_free(p);
+    if (pa_hashmap_get(u->card->profiles, pa_bluez4_profile_to_string(profile)))
         return PA_HOOK_OK;
-    }
 
+    p = create_card_profile(u, profile, u->card->ports);
     pa_card_add_profile(u->card, p);
 
     return PA_HOOK_OK;
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 498d0e1..e610095 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -1772,8 +1772,9 @@ 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) {
+static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_profile_t profile, pa_hashmap *ports) {
     pa_device_port *input_port, *output_port;
+    const char *name;
     pa_card_profile *cp = NULL;
     pa_bluetooth_profile_t *p;
 
@@ -1782,8 +1783,11 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
     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)) {
-        cp = pa_card_profile_new("a2dp_sink", _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
+    name = pa_bluetooth_profile_to_string(profile);
+
+    switch (profile) {
+    case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+        cp = pa_card_profile_new(name, _("High Fidelity Playback (A2DP Sink)"), sizeof(pa_bluetooth_profile_t));
         cp->priority = 10;
         cp->n_sinks = 1;
         cp->n_sources = 0;
@@ -1792,9 +1796,10 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
         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));
+        break;
+
+    case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+        cp = pa_card_profile_new(name, _("High Fidelity Capture (A2DP Source)"), sizeof(pa_bluetooth_profile_t));
         cp->priority = 10;
         cp->n_sinks = 0;
         cp->n_sources = 1;
@@ -1803,9 +1808,10 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
         pa_hashmap_put(input_port->profiles, cp->name, cp);
 
         p = PA_CARD_PROFILE_DATA(cp);
-        *p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
-    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) {
-        cp = pa_card_profile_new("headset_head_unit", _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
+        break;
+
+    case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
+        cp = pa_card_profile_new(name, _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
         cp->priority = 20;
         cp->n_sinks = 1;
         cp->n_sources = 1;
@@ -1815,9 +1821,10 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
         pa_hashmap_put(output_port->profiles, cp->name, cp);
 
         p = PA_CARD_PROFILE_DATA(cp);
-        *p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
-    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG)) {
-        cp = pa_card_profile_new("headset_audio_gateway", _("Headset Audio Gateway (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
+        break;
+
+    case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
+        cp = pa_card_profile_new(name, _("Headset Audio Gateway (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
         cp->priority = 20;
         cp->n_sinks = 1;
         cp->n_sources = 1;
@@ -1827,16 +1834,19 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
         pa_hashmap_put(output_port->profiles, cp->name, cp);
 
         p = PA_CARD_PROFILE_DATA(cp);
-        *p = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;
-    }
+        break;
 
-    if (cp) {
-        if (u->device->transports[*p])
-            cp->available = transport_state_to_availability(u->device->transports[*p]->state);
-        else
-            cp->available = PA_AVAILABLE_NO;
+    case PA_BLUETOOTH_PROFILE_OFF:
+        pa_assert_not_reached();
     }
 
+    *p = profile;
+
+    if (u->device->transports[*p])
+        cp->available = transport_state_to_availability(u->device->transports[*p]->state);
+    else
+        cp->available = PA_AVAILABLE_NO;
+
     return cp;
 }
 
@@ -1882,6 +1892,21 @@ off:
     return -PA_ERR_IO;
 }
 
+static int uuid_to_profile(const char *uuid, pa_bluetooth_profile_t *_r) {
+    if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
+        *_r = PA_BLUETOOTH_PROFILE_A2DP_SINK;
+    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
+        *_r = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
+    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
+        *_r = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
+    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG))
+        *_r = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;
+    else
+        return -PA_ERR_INVALID;
+
+    return 0;
+}
+
 /* Run from main thread */
 static int add_card(struct userdata *u) {
     const pa_bluetooth_device *d;
@@ -1923,16 +1948,15 @@ 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);
+        pa_bluetooth_profile_t profile;
 
-        if (!cp)
+        if (uuid_to_profile(uuid, &profile) < 0)
             continue;
 
-        if (pa_hashmap_get(data.profiles, cp->name)) {
-            pa_card_profile_free(cp);
+        if (pa_hashmap_get(data.profiles, pa_bluetooth_profile_to_string(profile)))
             continue;
-        }
 
+        cp = create_card_profile(u, profile, data.ports);
         pa_hashmap_put(data.profiles, cp->name, cp);
     }
 

commit 83ac6c5ae52d8463f479b84a0829597f3b470caf
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sun Aug 7 18:45:54 2016 +0300

    bluetooth: refactor BlueZ 4 transport state setting
    
    Add transport_set_state() that encapsulates changing the variable,
    logging and firing the change hook.
    
    I also made a cosmetic change to the corresponding BlueZ 5 log
    message so that both messages have the format that I like.

diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
index baf3aea..90b1563 100644
--- a/src/modules/bluetooth/bluez4-util.c
+++ b/src/modules/bluetooth/bluez4-util.c
@@ -77,6 +77,7 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluez4_discovery *y, DBusMess
                                                 void *call_data);
 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 void transport_set_state(pa_bluez4_transport *transport, pa_bluez4_transport_state_t state);
 
 static pa_bluez4_audio_state_t audio_state_from_string(const char* value) {
     pa_assert(value);
@@ -198,8 +199,7 @@ static void device_free(pa_bluez4_device *d) {
 
         d->transports[i] = NULL;
         pa_hashmap_remove(d->discovery->transports, t->path);
-        t->state = PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED;
-        pa_hook_fire(&d->discovery->hooks[PA_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED], t);
+        transport_set_state(t, PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED);
         transport_free(t);
     }
 
@@ -515,7 +515,6 @@ static int parse_audio_property(pa_bluez4_device *d, const char *interface, DBus
 
             if (pa_streq(key, "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);
 
@@ -534,16 +533,7 @@ static int parse_audio_property(pa_bluez4_device *d, const char *interface, DBus
                 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_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_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED], transport);
-                }
+                transport_set_state(transport, audio_state_to_transport_state(state));
             }
 
             break;
@@ -1248,6 +1238,18 @@ static pa_bluez4_transport *transport_new(pa_bluez4_device *d, const char *owner
     return t;
 }
 
+static void transport_set_state(pa_bluez4_transport *transport, pa_bluez4_transport_state_t state) {
+    if (transport->state == state)
+        return;
+
+    pa_log_debug("Transport %s state: %s -> %s",
+                 transport->path, transport_state_to_string(transport->state), transport_state_to_string(state));
+
+    transport->state = state;
+
+    pa_hook_fire(&transport->device->discovery->hooks[PA_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED], transport);
+}
+
 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
     pa_bluez4_discovery *y = userdata;
     pa_bluez4_device *d;
@@ -1392,8 +1394,7 @@ static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage
         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_BLUEZ4_TRANSPORT_STATE_DISCONNECTED;
-        pa_hook_fire(&y->hooks[PA_BLUEZ4_HOOK_TRANSPORT_STATE_CHANGED], t);
+        transport_set_state(t, PA_BLUEZ4_TRANSPORT_STATE_DISCONNECTED);
 
         if (old_any_connected != pa_bluez4_device_any_audio_connected(t->device))
             run_callback(t->device, false);
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 93eebde..7d63f35 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -267,7 +267,7 @@ void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_tr
 
     old_any_connected = pa_bluetooth_device_any_transport_connected(t->device);
 
-    pa_log_debug("Transport %s state changed from %s to %s",
+    pa_log_debug("Transport %s state: %s -> %s",
                  t->path, transport_state_to_string(t->state), transport_state_to_string(state));
 
     t->state = state;

commit 15e3d828dd4e811f558ec98f4d6fb4df7df84e30
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sun Aug 7 18:45:53 2016 +0300

    bluetooth: unify BlueZ 4 and BlueZ 5 profile constant names
    
    This should make it slightly easier to copy code between BlueZ 4 and
    BlueZ 5.

diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
index a30e438..baf3aea 100644
--- a/src/modules/bluetooth/bluez4-util.c
+++ b/src/modules/bluetooth/bluez4-util.c
@@ -95,13 +95,13 @@ static pa_bluez4_audio_state_t audio_state_from_string(const char* value) {
 
 const char *pa_bluez4_profile_to_string(pa_bluez4_profile_t profile) {
     switch(profile) {
-        case PA_BLUEZ4_PROFILE_A2DP:
+        case PA_BLUEZ4_PROFILE_A2DP_SINK:
             return "a2dp";
         case PA_BLUEZ4_PROFILE_A2DP_SOURCE:
             return "a2dp_source";
-        case PA_BLUEZ4_PROFILE_HSP:
+        case PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT:
             return "hsp";
-        case PA_BLUEZ4_PROFILE_HFGW:
+        case PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY:
             return "hfgw";
         case PA_BLUEZ4_PROFILE_OFF:
             pa_assert_not_reached();
@@ -115,16 +115,16 @@ static int profile_from_interface(const char *interface, pa_bluez4_profile_t *p)
     pa_assert(p);
 
     if (pa_streq(interface, "org.bluez.AudioSink")) {
-        *p = PA_BLUEZ4_PROFILE_A2DP;
+        *p = PA_BLUEZ4_PROFILE_A2DP_SINK;
         return 0;
     } else if (pa_streq(interface, "org.bluez.AudioSource")) {
         *p = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
         return 0;
     } else if (pa_streq(interface, "org.bluez.Headset")) {
-        *p = PA_BLUEZ4_PROFILE_HSP;
+        *p = PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT;
         return 0;
     } else if (pa_streq(interface, "org.bluez.HandsfreeGateway")) {
-        *p = PA_BLUEZ4_PROFILE_HFGW;
+        *p = PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY;
         return 0;
     }
 
@@ -1197,7 +1197,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 == PA_BLUEZ4_PROFILE_HSP);
+    pa_assert(t->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT);
 
     set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
                  "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
@@ -1207,7 +1207,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 == PA_BLUEZ4_PROFILE_HSP);
+    pa_assert(t->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT);
 
     set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
                  "SpeakerGain", DBUS_TYPE_UINT16, &gain);
@@ -1327,11 +1327,11 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
         goto fail;
 
     if (dbus_message_has_path(m, ENDPOINT_PATH_HFP_AG))
-        p = PA_BLUEZ4_PROFILE_HSP;
+        p = PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT;
     else if (dbus_message_has_path(m, ENDPOINT_PATH_HFP_HS))
-        p = PA_BLUEZ4_PROFILE_HFGW;
+        p = PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY;
     else if (dbus_message_has_path(m, ENDPOINT_PATH_A2DP_SOURCE))
-        p = PA_BLUEZ4_PROFILE_A2DP;
+        p = PA_BLUEZ4_PROFILE_A2DP_SINK;
     else
         p = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
 
diff --git a/src/modules/bluetooth/bluez4-util.h b/src/modules/bluetooth/bluez4-util.h
index abbbfab..ed588f1 100644
--- a/src/modules/bluetooth/bluez4-util.h
+++ b/src/modules/bluetooth/bluez4-util.h
@@ -43,10 +43,10 @@ typedef struct pa_bluez4_transport pa_bluez4_transport;
 struct userdata;
 
 typedef enum pa_bluez4_profile {
-    PA_BLUEZ4_PROFILE_A2DP,
+    PA_BLUEZ4_PROFILE_A2DP_SINK,
     PA_BLUEZ4_PROFILE_A2DP_SOURCE,
-    PA_BLUEZ4_PROFILE_HSP,
-    PA_BLUEZ4_PROFILE_HFGW,
+    PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT,
+    PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY,
     PA_BLUEZ4_PROFILE_OFF
 } pa_bluez4_profile_t;
 
diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
index bae4e45..0c561d1 100644
--- a/src/modules/bluetooth/module-bluez4-device.c
+++ b/src/modules/bluetooth/module-bluez4-device.c
@@ -198,7 +198,7 @@ enum {
 
 #define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC)
 
-#define USE_SCO_OVER_PCM(u) (u->profile == PA_BLUEZ4_PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source))
+#define USE_SCO_OVER_PCM(u) (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT && (u->hsp.sco_sink && u->hsp.sco_source))
 
 static int init_profile(struct userdata *u);
 
@@ -241,7 +241,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 == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW) {
+    if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY) {
         u->read_block_size = u->read_link_mtu;
         u->write_block_size = u->write_link_mtu;
     } else {
@@ -260,7 +260,7 @@ 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 == PA_BLUEZ4_PROFILE_A2DP ?
+                                                (u->profile == PA_BLUEZ4_PROFILE_A2DP_SINK ?
                                                  FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
                                                 pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
     }
@@ -291,7 +291,7 @@ static void setup_stream(struct userdata *u) {
 
     pa_log_debug("Stream properly set up, we're ready to roll!");
 
-    if (u->profile == PA_BLUEZ4_PROFILE_A2DP)
+    if (u->profile == PA_BLUEZ4_PROFILE_A2DP_SINK)
         a2dp_set_bitpool(u, u->a2dp.max_bitpool);
 
     u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
@@ -550,7 +550,7 @@ static int hsp_process_render(struct userdata *u) {
     int ret = 0;
 
     pa_assert(u);
-    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY);
     pa_assert(u->sink);
 
     /* First, render some data */
@@ -615,7 +615,7 @@ static int hsp_process_push(struct userdata *u) {
     pa_memchunk memchunk;
 
     pa_assert(u);
-    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY);
     pa_assert(u->source);
     pa_assert(u->read_smoother);
 
@@ -733,7 +733,7 @@ static int a2dp_process_render(struct userdata *u) {
     int ret = 0;
 
     pa_assert(u);
-    pa_assert(u->profile == PA_BLUEZ4_PROFILE_A2DP);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_A2DP_SINK);
     pa_assert(u->sink);
 
     /* First, render some data */
@@ -1028,7 +1028,7 @@ static void thread_func(void *userdata) {
             if (pollfd && (pollfd->revents & POLLIN)) {
                 int n_read;
 
-                if (u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW)
+                if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY)
                     n_read = hsp_process_push(u);
                 else
                     n_read = a2dp_process_push(u);
@@ -1086,7 +1086,7 @@ static void thread_func(void *userdata) {
                                 pa_memblock_unref(tmp.memblock);
                                 u->write_index += skip_bytes;
 
-                                if (u->profile == PA_BLUEZ4_PROFILE_A2DP)
+                                if (u->profile == PA_BLUEZ4_PROFILE_A2DP_SINK)
                                     a2dp_reduce_bitpool(u);
                             }
                         }
@@ -1102,7 +1102,7 @@ static void thread_func(void *userdata) {
                     if (u->write_index <= 0)
                         u->started_at = pa_rtclock_now();
 
-                    if (u->profile == PA_BLUEZ4_PROFILE_A2DP) {
+                    if (u->profile == PA_BLUEZ4_PROFILE_A2DP_SINK) {
                         if ((n_written = a2dp_process_render(u)) < 0)
                             goto io_fail;
                     } else {
@@ -1202,10 +1202,10 @@ static pa_available_t transport_state_to_availability(pa_bluez4_transport_state_
 
 static pa_direction_t get_profile_direction(pa_bluez4_profile_t p) {
     static const pa_direction_t profile_direction[] = {
-        [PA_BLUEZ4_PROFILE_A2DP] = PA_DIRECTION_OUTPUT,
+        [PA_BLUEZ4_PROFILE_A2DP_SINK] = 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_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
         [PA_BLUEZ4_PROFILE_OFF] = 0
     };
 
@@ -1327,7 +1327,7 @@ static void sink_set_volume_cb(pa_sink *s) {
 
     pa_assert(u);
     pa_assert(u->sink == s);
-    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HSP);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT);
     pa_assert(u->transport);
 
     gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM);
@@ -1354,7 +1354,7 @@ static void source_set_volume_cb(pa_source *s) {
 
     pa_assert(u);
     pa_assert(u->source == s);
-    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HSP);
+    pa_assert(u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT);
     pa_assert(u->transport);
 
     gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM);
@@ -1562,7 +1562,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 == PA_BLUEZ4_PROFILE_HSP)
+        if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT)
             pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
         data.card = u->card;
         data.name = get_name("sink", u->modargs, u->address, pa_bluez4_profile_to_string(u->profile), &b);
@@ -1577,11 +1577,11 @@ static int add_sink(struct userdata *u) {
 
         if (!u->transport_acquired)
             switch (u->profile) {
-                case PA_BLUEZ4_PROFILE_A2DP:
-                case PA_BLUEZ4_PROFILE_HSP:
+                case PA_BLUEZ4_PROFILE_A2DP_SINK:
+                case PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT:
                     pa_assert_not_reached(); /* Profile switch should have failed */
                     break;
-                case PA_BLUEZ4_PROFILE_HFGW:
+                case PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY:
                     data.suspend_cause = PA_SUSPEND_USER;
                     break;
                 case PA_BLUEZ4_PROFILE_A2DP_SOURCE:
@@ -1602,7 +1602,7 @@ static int add_sink(struct userdata *u) {
         u->sink->set_port = sink_set_port_cb;
     }
 
-    if (u->profile == PA_BLUEZ4_PROFILE_HSP) {
+    if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
 
@@ -1632,7 +1632,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 == PA_BLUEZ4_PROFILE_HSP)
+        if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT)
             pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
 
         data.card = u->card;
@@ -1649,14 +1649,14 @@ static int add_source(struct userdata *u) {
 
         if (!u->transport_acquired)
             switch (u->profile) {
-                case PA_BLUEZ4_PROFILE_HSP:
+                case PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT:
                     pa_assert_not_reached(); /* Profile switch should have failed */
                     break;
                 case PA_BLUEZ4_PROFILE_A2DP_SOURCE:
-                case PA_BLUEZ4_PROFILE_HFGW:
+                case PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY:
                     data.suspend_cause = PA_SUSPEND_USER;
                     break;
-                case PA_BLUEZ4_PROFILE_A2DP:
+                case PA_BLUEZ4_PROFILE_A2DP_SINK:
                 case PA_BLUEZ4_PROFILE_OFF:
                     pa_assert_not_reached();
             }
@@ -1674,12 +1674,12 @@ static int add_source(struct userdata *u) {
         u->source->set_port = source_set_port_cb;
     }
 
-    if ((u->profile == PA_BLUEZ4_PROFILE_HSP) || (u->profile == PA_BLUEZ4_PROFILE_HFGW)) {
+    if ((u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT) || (u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY)) {
         pa_bluez4_transport *t = u->transport;
         pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
     }
 
-    if (u->profile == PA_BLUEZ4_PROFILE_HSP) {
+    if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT) {
         pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
         u->source->n_volume_steps = 16;
 
@@ -1794,7 +1794,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 == PA_BLUEZ4_PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool;
+    a2dp->sbc.bitpool = u->profile == PA_BLUEZ4_PROFILE_A2DP_SINK ? a2dp->max_bitpool : a2dp->min_bitpool;
     a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
     a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
 
@@ -1803,7 +1803,7 @@ static void bt_transport_config_a2dp(struct userdata *u) {
 }
 
 static void bt_transport_config(struct userdata *u) {
-    if (u->profile == PA_BLUEZ4_PROFILE_HSP || u->profile == PA_BLUEZ4_PROFILE_HFGW) {
+    if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY) {
         u->sample_spec.format = PA_SAMPLE_S16LE;
         u->sample_spec.channels = 1;
         u->sample_spec.rate = 8000;
@@ -1842,7 +1842,7 @@ static int setup_transport(struct userdata *u) {
 
     u->transport = t;
 
-    if (u->profile == PA_BLUEZ4_PROFILE_A2DP_SOURCE || u->profile == PA_BLUEZ4_PROFILE_HFGW)
+    if (u->profile == PA_BLUEZ4_PROFILE_A2DP_SOURCE || u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY)
         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 */
@@ -1863,15 +1863,15 @@ static int init_profile(struct userdata *u) {
 
     pa_assert(u->transport);
 
-    if (u->profile == PA_BLUEZ4_PROFILE_A2DP ||
-        u->profile == PA_BLUEZ4_PROFILE_HSP ||
-        u->profile == PA_BLUEZ4_PROFILE_HFGW)
+    if (u->profile == PA_BLUEZ4_PROFILE_A2DP_SINK ||
+        u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT ||
+        u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY)
         if (add_sink(u) < 0)
             r = -1;
 
-    if (u->profile == PA_BLUEZ4_PROFILE_HSP ||
+    if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT ||
         u->profile == PA_BLUEZ4_PROFILE_A2DP_SOURCE ||
-        u->profile == PA_BLUEZ4_PROFILE_HFGW)
+        u->profile == PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY)
         if (add_source(u) < 0)
             r = -1;
 
@@ -1914,7 +1914,7 @@ static void stop_thread(struct userdata *u) {
     }
 
     if (u->sink) {
-        if (u->profile == PA_BLUEZ4_PROFILE_HSP) {
+        if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT) {
             k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
             pa_shared_remove(u->core, k);
             pa_xfree(k);
@@ -1925,7 +1925,7 @@ static void stop_thread(struct userdata *u) {
     }
 
     if (u->source) {
-        if (u->profile == PA_BLUEZ4_PROFILE_HSP) {
+        if (u->profile == PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT) {
             k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
             pa_shared_remove(u->core, k);
             pa_xfree(k);
@@ -2182,7 +2182,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 = PA_BLUEZ4_PROFILE_A2DP;
+        *d = PA_BLUEZ4_PROFILE_A2DP_SINK;
     } else if (pa_streq(uuid, PA_BLUEZ4_UUID_A2DP_SOURCE)) {
         p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 10;
@@ -2205,7 +2205,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 = PA_BLUEZ4_PROFILE_HSP;
+        *d = PA_BLUEZ4_PROFILE_HEADSET_HEAD_UNIT;
     } else if (pa_streq(uuid, PA_BLUEZ4_UUID_HFP_AG)) {
         p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(pa_bluez4_profile_t));
         p->priority = 20;
@@ -2217,7 +2217,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 = PA_BLUEZ4_PROFILE_HFGW;
+        *d = PA_BLUEZ4_PROFILE_HEADSET_AUDIO_GATEWAY;
     }
 
     if (p) {

commit 52a9ee618f7f250fd3c5be3f9a000f1aa5f77db6
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sun Aug 7 18:45:52 2016 +0300

    bluetooth: unify BlueZ 4 and BlueZ 5 UUID handling
    
    A hashmap is more convenient than a linked list for storing the UUIDs,
    so change the BlueZ 4 code accordingly.
    
    Rename the BlueZ 4 UUID constants to match the BlueZ 5 naming.
    
    The only changes to the BlueZ 5 code are the addition of one comment
    and making another comment a bit clearer.

diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
index 97b5ab0..a30e438 100644
--- a/src/modules/bluetooth/bluez4-util.c
+++ b/src/modules/bluetooth/bluez4-util.c
@@ -146,23 +146,6 @@ static pa_bluez4_transport_state_t audio_state_to_transport_state(pa_bluez4_audi
     pa_assert_not_reached();
 }
 
-static pa_bluez4_uuid *uuid_new(const char *uuid) {
-    pa_bluez4_uuid *u;
-
-    u = pa_xnew(pa_bluez4_uuid, 1);
-    u->uuid = pa_xstrdup(uuid);
-    PA_LLIST_INIT(pa_bluez4_uuid, u);
-
-    return u;
-}
-
-static void uuid_free(pa_bluez4_uuid *u) {
-    pa_assert(u);
-
-    pa_xfree(u->uuid);
-    pa_xfree(u);
-}
-
 static pa_bluez4_device* device_new(pa_bluez4_discovery *discovery, const char *path) {
     pa_bluez4_device *d;
     unsigned i;
@@ -181,7 +164,7 @@ static pa_bluez4_device* device_new(pa_bluez4_discovery *discovery, const char *
     d->path = pa_xstrdup(path);
     d->paired = -1;
     d->alias = NULL;
-    PA_LLIST_HEAD_INIT(pa_bluez4_uuid, d->uuids);
+    d->uuids = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
     d->address = NULL;
     d->class = -1;
     d->trusted = -1;
@@ -204,7 +187,6 @@ static void transport_free(pa_bluez4_transport *t) {
 }
 
 static void device_free(pa_bluez4_device *d) {
-    pa_bluez4_uuid *u;
     pa_bluez4_transport *t;
     unsigned i;
 
@@ -221,10 +203,8 @@ static void device_free(pa_bluez4_device *d) {
         transport_free(t);
     }
 
-    while ((u = d->uuids)) {
-        PA_LLIST_REMOVE(pa_bluez4_uuid, d->uuids, u);
-        uuid_free(u);
-    }
+    if (d->uuids)
+        pa_hashmap_free(d->uuids);
 
     pa_xfree(d->name);
     pa_xfree(d->path);
@@ -416,7 +396,6 @@ static int parse_device_property(pa_bluez4_device *d, DBusMessageIter *i, bool i
         }
 
         case DBUS_TYPE_ARRAY: {
-
             DBusMessageIter ai;
             dbus_message_iter_recurse(&variant_i, &ai);
 
@@ -424,42 +403,46 @@ static int parse_device_property(pa_bluez4_device *d, DBusMessageIter *i, bool i
                 DBusMessage *m;
                 bool has_audio = false;
 
+                /* bluetoothd never removes UUIDs from a device object so we
+                 * don't need to check for disappeared UUIDs here. */
                 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
-                    pa_bluez4_uuid *node;
                     const char *value;
+                    char *uuid;
                     struct pa_bluez4_hook_uuid_data uuiddata;
 
                     dbus_message_iter_get_basic(&ai, &value);
 
-                    if (pa_bluez4_uuid_has(d->uuids, value)) {
+                    if (pa_hashmap_get(d->uuids, value)) {
                         dbus_message_iter_next(&ai);
                         continue;
                     }
 
-                    node = uuid_new(value);
-                    PA_LLIST_PREPEND(pa_bluez4_uuid, d->uuids, node);
+                    uuid = pa_xstrdup(value);
+                    pa_hashmap_put(d->uuids, uuid, uuid);
+
+                    pa_log_debug("%s: %s", key, value);
 
                     uuiddata.device = d;
                     uuiddata.uuid = value;
                     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) {
+                    if (pa_streq(PA_BLUEZ4_UUID_HSP_AG, value) || pa_streq(PA_BLUEZ4_UUID_HFP_AG, value)) {
                         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) {
+                    } else if (pa_streq(PA_BLUEZ4_UUID_HSP_HS, value) || pa_streq(PA_BLUEZ4_UUID_HFP_HF, value)) {
                         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) {
+                    } else if (pa_streq(PA_BLUEZ4_UUID_A2DP_SINK, value)) {
                         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) {
+                    } else if (pa_streq(PA_BLUEZ4_UUID_A2DP_SOURCE, value)) {
                         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);
@@ -829,7 +812,7 @@ static void register_endpoint(pa_bluez4_discovery *y, const char *path, const ch
 
     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)) {
+    if (pa_streq(uuid, PA_BLUEZ4_UUID_HFP_AG) || pa_streq(uuid, PA_BLUEZ4_UUID_HFP_HF)) {
         uint8_t capability = 0;
         pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1);
     } else {
@@ -860,10 +843,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, 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);
+    register_endpoint(y, path, ENDPOINT_PATH_HFP_AG, PA_BLUEZ4_UUID_HFP_AG);
+    register_endpoint(y, path, ENDPOINT_PATH_HFP_HS, PA_BLUEZ4_UUID_HFP_HF);
+    register_endpoint(y, path, ENDPOINT_PATH_A2DP_SOURCE, PA_BLUEZ4_UUID_A2DP_SOURCE);
+    register_endpoint(y, path, ENDPOINT_PATH_A2DP_SINK, PA_BLUEZ4_UUID_A2DP_SINK);
 }
 
 static void list_adapters(pa_bluez4_discovery *y) {
@@ -1888,16 +1871,3 @@ char *pa_bluez4_cleanup_name(const char *name) {
 
     return t;
 }
-
-bool pa_bluez4_uuid_has(pa_bluez4_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
index 7c76aac..abbbfab 100644
--- a/src/modules/bluetooth/bluez4-util.h
+++ b/src/modules/bluetooth/bluez4-util.h
@@ -27,34 +27,21 @@
 
 #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"
-
-#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 PA_BLUEZ4_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
+#define PA_BLUEZ4_UUID_A2DP_SINK   "0000110b-0000-1000-8000-00805f9b34fb"
+#define PA_BLUEZ4_UUID_HSP_HS      "00001108-0000-1000-8000-00805f9b34fb"
+#define PA_BLUEZ4_UUID_HSP_AG      "00001112-0000-1000-8000-00805f9b34fb"
+#define PA_BLUEZ4_UUID_HFP_HF      "0000111e-0000-1000-8000-00805f9b34fb"
+#define PA_BLUEZ4_UUID_HFP_AG      "0000111f-0000-1000-8000-00805f9b34fb"
 
 #define HSP_MAX_GAIN 15
 
-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_bluez4_uuid {
-    char *uuid;
-    PA_LLIST_FIELDS(pa_bluez4_uuid);
-};
-
 typedef enum pa_bluez4_profile {
     PA_BLUEZ4_PROFILE_A2DP,
     PA_BLUEZ4_PROFILE_A2DP_SOURCE,
@@ -123,9 +110,9 @@ struct pa_bluez4_device {
     pa_bluez4_transport *transports[PA_BLUEZ4_PROFILE_COUNT];
     int paired;
     char *alias;
-    PA_LLIST_HEAD(pa_bluez4_uuid, uuids);
     char *address;
     int class;
+    pa_hashmap *uuids; /* char* -> char* (hashmap-as-a-set) */
     int trusted;
 
     /* Audio state */
@@ -170,7 +157,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(pa_bluez4_profile_t profile);
 
 #endif
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 890b8ec..93eebde 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -732,8 +732,8 @@ static void parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i) {
             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. */
+                /* bluetoothd never removes UUIDs from a device object so we
+                 * don't need to check for disappeared UUIDs here. */
                 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
                     const char *value;
                     char *uuid;
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 2aa6ea6..7f124e2 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -102,7 +102,7 @@ struct pa_bluetooth_device {
     char *alias;
     char *address;
     uint32_t class_of_device;
-    pa_hashmap *uuids;
+    pa_hashmap *uuids; /* char* -> char* (hashmap-as-a-set) */
 
     pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
 
diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
index 13fb7ab..bae4e45 100644
--- a/src/modules/bluetooth/module-bluez4-device.c
+++ b/src/modules/bluetooth/module-bluez4-device.c
@@ -2172,7 +2172,7 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
     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)) {
+    if (pa_streq(uuid, PA_BLUEZ4_UUID_A2DP_SINK)) {
         p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 10;
         p->n_sinks = 1;
@@ -2183,7 +2183,7 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
 
         d = PA_CARD_PROFILE_DATA(p);
         *d = PA_BLUEZ4_PROFILE_A2DP;
-    } else if (pa_streq(uuid, A2DP_SOURCE_UUID)) {
+    } else if (pa_streq(uuid, PA_BLUEZ4_UUID_A2DP_SOURCE)) {
         p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 10;
         p->n_sinks = 0;
@@ -2194,7 +2194,7 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
 
         d = PA_CARD_PROFILE_DATA(p);
         *d = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
-    } else if (pa_streq(uuid, HSP_HS_UUID) || pa_streq(uuid, HFP_HS_UUID)) {
+    } else if (pa_streq(uuid, PA_BLUEZ4_UUID_HSP_HS) || pa_streq(uuid, PA_BLUEZ4_UUID_HFP_HF)) {
         p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(pa_bluez4_profile_t));
         p->priority = 20;
         p->n_sinks = 1;
@@ -2206,7 +2206,7 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
 
         d = PA_CARD_PROFILE_DATA(p);
         *d = PA_BLUEZ4_PROFILE_HSP;
-    } else if (pa_streq(uuid, HFP_AG_UUID)) {
+    } else if (pa_streq(uuid, PA_BLUEZ4_UUID_HFP_AG)) {
         p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(pa_bluez4_profile_t));
         p->priority = 20;
         p->n_sinks = 1;
@@ -2240,7 +2240,8 @@ static int add_card(struct userdata *u) {
     char *n;
     const char *profile_str;
     const pa_bluez4_device *device;
-    const pa_bluez4_uuid *uuid;
+    const char *uuid;
+    void *state;
 
     pa_assert(u);
     pa_assert(u->device);
@@ -2276,8 +2277,8 @@ static int add_card(struct userdata *u) {
 
     create_card_ports(u, data.ports);
 
-    PA_LLIST_FOREACH(uuid, device->uuids) {
-        p = create_card_profile(u, uuid->uuid, data.ports);
+    PA_HASHMAP_FOREACH(uuid, device->uuids, state) {
+        p = create_card_profile(u, uuid, data.ports);
 
         if (!p)
             continue;

commit 570288ccc9e2ce50c270125020353c0899a29b55
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sun Aug 7 18:45:51 2016 +0300

    bluetooth: update device's valid flag after parsing properties
    
    The properties_received flag affects whether the device should be
    considered valid, so let's update the valid flag after setting the
    properties_received flag.
    
    There's a call to device_update_valid() anyway later when setting
    the device adapters, so this change isn't strictly necessary, but
    this makes it more obvious that the code is correct (and less
    fragile).

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index e4cf044..890b8ec 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -773,6 +773,7 @@ static void parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i)
 
     if (!d->properties_received) {
         d->properties_received = true;
+        device_update_valid(d);
 
         if (!d->address || !d->adapter_path || !d->alias)
             pa_log_error("Non-optional information missing for device %s", d->path);

commit eec4d29247386a33d8c2b94a16de8f9ccf63c563
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sun Aug 7 18:45:50 2016 +0300

    bluetooth: remove a redundant assignment
    
    pa_bluetooth_transport_put() assigns the transport to the device's
    transports array, so the caller doesn't have to do that.

diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index 8d9d95c..222e33b 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -351,7 +351,7 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
 
     p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
     pathfd = pa_sprintf_malloc ("%s/fd%d", path, fd);
-    d->transports[p] = t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, 0);
+    t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, 0);
     pa_xfree(pathfd);
 
     t->acquire = bluez5_sco_acquire_cb;

commit c538bc7aa42e8853cd155d53aeed1221eed3c1fb
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sun Aug 7 18:45:49 2016 +0300

    card-restore: don't switch profiles when availability changes
    
    module-card-restore should only restore the initial state of new
    cards, but profile_available_changed_callback() changed the profile
    whenever the saved profile became available. That caused interference
    with module-bluetooth-policy, which also sets card profiles based on
    the availability changes.
    
    The original reason for having this code was to work around the
    problem that bluetooth cards used to be created with only one profile
    available, and other profiles would become available soon after the
    card creation. Now the bluetooth card creation is delayed until all
    profiles are available, so this bad workaround can be removed.
    
    Discussion:
    https://lists.freedesktop.org/archives/pulseaudio-discuss/2016-August/026575.html

diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c
index 718a0dd..3c0307b 100644
--- a/src/modules/module-card-restore.c
+++ b/src/modules/module-card-restore.c
@@ -479,36 +479,6 @@ static pa_hook_result_t card_profile_added_callback(pa_core *c, pa_card_profile
     return PA_HOOK_OK;
 }
 
-static pa_hook_result_t profile_available_changed_callback(void *hook_data, void *call_data, void *userdata) {
-    pa_card_profile *profile = call_data;
-    pa_card *card;
-    struct userdata *u = userdata;
-    struct entry *entry;
-
-    pa_assert(profile);
-    pa_assert(u);
-
-    card = profile->card;
-
-    if (profile->available == PA_AVAILABLE_NO)
-        return PA_HOOK_OK;
-
-    entry = entry_read(u, card->name);
-    if (!entry)
-        return PA_HOOK_OK;
-
-    if (!pa_streq(profile->name, entry->profile)) {
-        entry_free(entry);
-        return PA_HOOK_OK;
-    }
-
-    pa_log_info("Card %s profile %s became available, activating.", card->name, profile->name);
-    pa_card_set_profile(profile->card, profile, true);
-
-    entry_free(entry);
-    return PA_HOOK_OK;
-}
-
 static pa_hook_result_t port_offset_change_callback(pa_core *c, pa_device_port *port, struct userdata *u) {
     struct entry *entry;
     pa_card *card;
@@ -654,8 +624,6 @@ int pa__init(pa_module*m) {
     pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_preferred_port_changed_callback, u);
     pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_changed_callback, u);
     pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_added_callback, u);
-    pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED],
-                                                             PA_HOOK_NORMAL, profile_available_changed_callback, u);
     pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) port_offset_change_callback, u);
 
     if (!(fname = pa_state_path("card-database", true)))

commit d9b885e0f1d3eafd38b7ff8ff09c31c040d85b47
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sun Aug 7 18:45:48 2016 +0300

    bluetooth: wait for all profiles to connect before creating card
    
    The CONNECTION_CHANGED hook is used to notify the discovery module
    about new and removed devices. When a bluetooth device connects, the
    hook used to be called immediately when the first profile connected.
    That meant that only one profile was marked as available during the
    card creation, other profiles would get marked as available later.
    
    That makes it hard for module-card-restore to restore the saved
    profile, if the saved profile becomes available with some delay.
    module-card-restore has a workaround for this problem, but that turned
    out to interfere with module-bluetooth-policy, so the workaround will
    be removed in the next patch.
    
    The BlueZ 4 code doesn't need changes, because we use the
    org.bluez.Audio interface to get a notification when all profiles are
    connected.

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 03c76bf..e4cf044 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -21,6 +21,8 @@
 #include <config.h>
 #endif
 
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
 #include <pulse/xmalloc.h>
 
 #include <pulsecore/core.h>
@@ -35,6 +37,8 @@
 
 #include "bluez5-util.h"
 
+#define WAIT_FOR_PROFILES_TIMEOUT_USEC (3 * PA_USEC_PER_SEC)
+
 #define BLUEZ_SERVICE "org.bluez"
 #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1"
 #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1"
@@ -164,6 +168,95 @@ static const char *transport_state_to_string(pa_bluetooth_transport_state_t stat
     return "invalid";
 }
 
+static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
+    switch (profile) {
+        case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK);
+        case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
+        case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
+            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
+                || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF);
+        case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
+            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG)
+                || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG);
+        case PA_BLUETOOTH_PROFILE_OFF:
+            pa_assert_not_reached();
+    }
+
+    pa_assert_not_reached();
+}
+
+static bool device_is_profile_connected(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
+    if (device->transports[profile] && device->transports[profile]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+        return true;
+    else
+        return false;
+}
+
+static unsigned device_count_disconnected_profiles(pa_bluetooth_device *device) {
+    pa_bluetooth_profile_t profile;
+    unsigned count = 0;
+
+    for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) {
+        if (!device_supports_profile(device, profile))
+            continue;
+
+        if (!device_is_profile_connected(device, profile))
+            count++;
+    }
+
+    return count;
+}
+
+static void device_stop_waiting_for_profiles(pa_bluetooth_device *device) {
+    if (!device->wait_for_profiles_timer)
+        return;
+
+    device->discovery->core->mainloop->time_free(device->wait_for_profiles_timer);
+    device->wait_for_profiles_timer = NULL;
+}
+
+static void wait_for_profiles_cb(pa_mainloop_api *api, pa_time_event* event, const struct timeval *tv, void *userdata) {
+    pa_bluetooth_device *device = userdata;
+    pa_strbuf *buf;
+    pa_bluetooth_profile_t profile;
+    bool first = true;
+    char *profiles_str;
+
+    device_stop_waiting_for_profiles(device);
+
+    buf = pa_strbuf_new();
+
+    for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) {
+        if (device_is_profile_connected(device, profile))
+            continue;
+
+        if (!device_supports_profile(device, profile))
+            continue;
+
+        if (first)
+            first = false;
+        else
+            pa_strbuf_puts(buf, ", ");
+
+        pa_strbuf_puts(buf, pa_bluetooth_profile_to_string(profile));
+    }
+
+    profiles_str = pa_strbuf_to_string_free(buf);
+    pa_log_debug("Timeout expired, and device %s still has disconnected profiles: %s",
+                 device->path, profiles_str);
+    pa_xfree(profiles_str);
+    pa_hook_fire(&device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], device);
+}
+
+static void device_start_waiting_for_profiles(pa_bluetooth_device *device) {
+    pa_assert(!device->wait_for_profiles_timer);
+    device->wait_for_profiles_timer = pa_core_rttime_new(device->discovery->core,
+                                                         pa_rtclock_now() + WAIT_FOR_PROFILES_TIMEOUT_USEC,
+                                                         wait_for_profiles_cb, device);
+}
+
 void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state) {
     bool old_any_connected;
 
@@ -181,8 +274,27 @@ void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_tr
 
     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);
+    if (old_any_connected != pa_bluetooth_device_any_transport_connected(t->device)) {
+        unsigned n_disconnected_profiles;
+
+        /* If there are profiles that are expected to get connected soon (based
+         * on the UUID list), we wait for a bit before announcing the new
+         * device, so that all profiles have time to get connected before the
+         * card object is created. If we didn't wait, the card would always
+         * have only one profile marked as available in the initial state,
+         * which would prevent module-card-restore from restoring the initial
+         * profile properly. */
+
+        n_disconnected_profiles = device_count_disconnected_profiles(t->device);
+
+        if (n_disconnected_profiles == 0)
+            device_stop_waiting_for_profiles(t->device);
+
+        if (!old_any_connected && n_disconnected_profiles > 0)
+            device_start_waiting_for_profiles(t->device);
+        else
+            pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
+    }
 }
 
 void pa_bluetooth_transport_put(pa_bluetooth_transport *t) {
@@ -416,6 +528,8 @@ static void device_free(pa_bluetooth_device *d) {
 
     pa_assert(d);
 
+    device_stop_waiting_for_profiles(d);
+
     for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
         pa_bluetooth_transport *t;
 
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index d66e8a3..2aa6ea6 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -105,6 +105,8 @@ struct pa_bluetooth_device {
     pa_hashmap *uuids;
 
     pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
+
+    pa_time_event *wait_for_profiles_timer;
 };
 
 struct pa_bluetooth_adapter {



More information about the pulseaudio-commits mailing list