Commit eecfb743 authored by pali's avatar pali
Browse files

bluetooth: Add support for automatic connection of disconnected profiles

Sometimes only bluetooth profile is connected (A2DP, HSP, HFP) for particular device. In this case pulseaudio refused to use disconnected profile. This change implements connecting of disconnected profiles and change implementation of A2DP codec switching from callback to hook method via a new PA_BLUETOOTH_HOOK_PROFILE_CONNECTION_CHANGED hook.

Some bluetooth device do not allow connection of both HSP and HFP profiles at the same time. This change also handles this situation and try to first disconnect other profile if requested profile cannot be connected.

Now when all bluetooth code is asynchronous (problematic non-asynchronous was dropped), this automatic connection of disconnected profiles is also non-blocking and asynchronous.
parent a96e0637
......@@ -1778,11 +1778,9 @@ struct change_a2dp_profile_data {
const char **codec_endpoints;
size_t codec_endpoints_i;
size_t codec_endpoints_count;
void (*cb)(bool, void *);
void *userdata;
};
static bool change_a2dp_profile_next(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, const char **codec_endpoints, size_t codec_endpoints_i, size_t codec_endpoints_count, bool from_cb, void (*cb)(bool, void *), void *userdata);
static bool change_a2dp_profile_next(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, const char **codec_endpoints, size_t codec_endpoints_i, size_t codec_endpoints_count);
static void change_a2dp_profile_reply(DBusPendingCall *pending, void *userdata) {
DBusMessage *r;
......@@ -1807,12 +1805,11 @@ static void change_a2dp_profile_reply(DBusPendingCall *pending, void *userdata)
pa_xfree(data->codec_endpoints);
} else if (dbus_message_get_type(r) != DBUS_MESSAGE_TYPE_ERROR) {
pa_log_info("Changing a2dp profile for %s to %s via endpoint %s succeeded", data->device_path, pa_bluetooth_profile_to_string(data->profile), data->codec_endpoints[data->codec_endpoints_i-1]);
data->device->change_a2dp_profile_in_progress = false;
data->cb(true, data->userdata);
pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_PROFILE_CONNECTION_CHANGED], &(struct pa_bluetooth_device_and_profile){ device, data->profile });
pa_xfree(data->codec_endpoints);
} else {
pa_log_warn("Changing a2dp profile for %s to %s via endpoint %s failed: %s: %s", data->device_path, pa_bluetooth_profile_to_string(data->profile), data->codec_endpoints[data->codec_endpoints_i-1], dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
change_a2dp_profile_next(device, data->profile, data->codec_endpoints, data->codec_endpoints_i, data->codec_endpoints_count, true, data->cb, data->userdata);
change_a2dp_profile_next(device, data->profile, data->codec_endpoints, data->codec_endpoints_i, data->codec_endpoints_count);
}
dbus_message_unref(r);
......@@ -1822,7 +1819,7 @@ static void change_a2dp_profile_reply(DBusPendingCall *pending, void *userdata)
pa_xfree(data);
}
static bool change_a2dp_profile_next(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, const char **codec_endpoints, size_t codec_endpoints_i, size_t codec_endpoints_count, bool from_cb, void (*cb)(bool, void *), void *userdata) {
static bool change_a2dp_profile_next(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, const char **codec_endpoints, size_t codec_endpoints_i, size_t codec_endpoints_count) {
const pa_a2dp_codec *a2dp_codec;
bool is_a2dp_sink;
const char *endpoint;
......@@ -1843,10 +1840,7 @@ static bool change_a2dp_profile_next(pa_bluetooth_device *device, pa_bluetooth_p
next:
if (codec_endpoints_i >= codec_endpoints_count) {
pa_log_error("Changing a2dp profile for %s to %s failed: No endpoint accepted connection", device->path, pa_bluetooth_profile_to_string(profile));
if (from_cb) {
device->change_a2dp_profile_in_progress = false;
cb(false, userdata);
}
pa_hook_fire(&device->discovery->hooks[PA_BLUETOOTH_HOOK_PROFILE_CONNECTION_CHANGED], &(struct pa_bluetooth_device_and_profile){ device, profile });
pa_xfree(codec_endpoints);
return false;
}
......@@ -1869,8 +1863,6 @@ next:
pa_dbus_append_basic_array_variant_dict_entry(&dict, "Capabilities", DBUS_TYPE_BYTE, &config, config_size);
dbus_message_iter_close_container(&iter, &dict);
device->change_a2dp_profile_in_progress = true;
pa_log_debug("Changing a2dp profile for %s to %s via endpoint %s with codec %s using local endpoint %s", device->path, pa_bluetooth_profile_to_string(profile), endpoint, a2dp_codec->name, pa_endpoint);
data = pa_xnew0(struct change_a2dp_profile_data, 1);
......@@ -1880,23 +1872,16 @@ next:
data->codec_endpoints = codec_endpoints;
data->codec_endpoints_i = codec_endpoints_i;
data->codec_endpoints_count = codec_endpoints_count;
data->cb = cb;
data->userdata = userdata;
send_and_add_to_pending(device->discovery, m, change_a2dp_profile_reply, data);
return true;
}
bool pa_bluetooth_device_change_a2dp_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, void (*cb)(bool, void *), void *userdata) {
bool pa_bluetooth_device_change_a2dp_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
const pa_a2dp_codec *a2dp_codec;
bool is_a2dp_sink;
size_t codec_endpoints_count;
const char **codec_endpoints;
if (device->change_a2dp_profile_in_progress) {
pa_log_error("Changing a2dp profile for %s to %s failed: Operation already in progress", device->path, pa_bluetooth_profile_to_string(profile));
return false;
}
a2dp_codec = pa_bluetooth_profile_to_a2dp_codec(profile);
is_a2dp_sink = pa_bluetooth_profile_is_a2dp_sink(profile);
......@@ -1908,7 +1893,141 @@ bool pa_bluetooth_device_change_a2dp_profile(pa_bluetooth_device *device, pa_blu
codec_endpoints = pa_xnew0(const char *, codec_endpoints_count);
pa_assert_se(pa_bluetooth_device_find_a2dp_endpoints_for_codec(device, a2dp_codec, is_a2dp_sink, codec_endpoints, codec_endpoints_count) == codec_endpoints_count);
return change_a2dp_profile_next(device, profile, codec_endpoints, 0, codec_endpoints_count, false, cb, userdata);
return change_a2dp_profile_next(device, profile, codec_endpoints, 0, codec_endpoints_count);
}
static const char *pa_bluetooth_profile_to_uuid(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
if (profile == PA_BLUETOOTH_PROFILE_HSP_AUDIO_GATEWAY)
return PA_BLUETOOTH_UUID_HSP_AG;
else if (profile == PA_BLUETOOTH_PROFILE_HFP_AUDIO_GATEWAY)
return PA_BLUETOOTH_UUID_HFP_AG;
else if (profile == PA_BLUETOOTH_PROFILE_HSP_HEAD_UNIT)
return pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT) ? PA_BLUETOOTH_UUID_HSP_HS_ALT : PA_BLUETOOTH_UUID_HSP_HS;
else if (profile == PA_BLUETOOTH_PROFILE_HFP_HEAD_UNIT)
return PA_BLUETOOTH_UUID_HFP_HF;
else if (pa_bluetooth_profile_is_a2dp_sink(profile))
return PA_BLUETOOTH_UUID_A2DP_SINK;
else if (pa_bluetooth_profile_is_a2dp_source(profile))
return PA_BLUETOOTH_UUID_A2DP_SOURCE;
else
pa_assert_not_reached();
}
struct connect_profile_data {
char *device_path;
const char *profile_uuid;
pa_bluetooth_profile_t profile;
};
static void pa_bluetooth_device_connect_profile_reply(DBusPendingCall *pending, void *userdata) {
DBusMessage *r;
pa_dbus_pending *p;
pa_bluetooth_discovery *y;
pa_bluetooth_device *device;
struct connect_profile_data *data;
pa_assert(pending);
pa_assert_se(p = userdata);
pa_assert_se(y = p->context_data);
pa_assert_se(data = p->call_data);
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
device = pa_hashmap_get(y->devices, data->device_path);
if (!device)
pa_log_warn("Connecting device %s to profile %s (%s) failed: Device is not connected anymore", data->device_path, pa_bluetooth_profile_to_string(data->profile), data->profile_uuid);
else if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR)
pa_log_warn("Connecting device %s to profile %s (%s) failed: %s: %s", data->device_path, pa_bluetooth_profile_to_string(data->profile), data->profile_uuid, dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
else
pa_log_info("Connecting device %s to profile %s (%s) succeeded", data->device_path, pa_bluetooth_profile_to_string(data->profile), data->profile_uuid);
if (device)
pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_PROFILE_CONNECTION_CHANGED], &(struct pa_bluetooth_device_and_profile){ device, data->profile });
dbus_message_unref(r);
PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
pa_dbus_pending_free(p);
pa_xfree(data->device_path);
pa_xfree(data);
}
void pa_bluetooth_device_connect_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
DBusMessage *m;
const char *profile_uuid;
struct connect_profile_data *data;
profile_uuid = pa_bluetooth_profile_to_uuid(device, profile);
pa_log_info("Connecting device %s to profile %s (%s)", device->path, pa_bluetooth_profile_to_string(profile), profile_uuid);
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, device->path, BLUEZ_DEVICE_INTERFACE, "ConnectProfile"));
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &profile_uuid, DBUS_TYPE_INVALID));
data = pa_xnew0(struct connect_profile_data, 1);
data->device_path = pa_xstrdup(device->path);
data->profile_uuid = profile_uuid;
data->profile = profile;
send_and_add_to_pending(device->discovery, m, pa_bluetooth_device_connect_profile_reply, data);
}
struct connect_and_disconnect_profile_data {
char *device_path;
const char *disconnect_uuid;
pa_bluetooth_profile_t disconnect_profile;
pa_bluetooth_profile_t connect_profile;
};
static void pa_bluetooth_device_disconnect_profile_reply(DBusPendingCall *pending, void *userdata) {
DBusMessage *r;
pa_dbus_pending *p;
pa_bluetooth_discovery *y;
pa_bluetooth_device *device;
struct connect_and_disconnect_profile_data *data;
pa_assert(pending);
pa_assert_se(p = userdata);
pa_assert_se(y = p->context_data);
pa_assert_se(data = p->call_data);
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
device = pa_hashmap_get(y->devices, data->device_path);
if (!device)
pa_log_warn("Disconnecting device %s from profile %s (%s) failed: Device is not connected anymore", data->device_path, pa_bluetooth_profile_to_string(data->disconnect_profile), data->disconnect_uuid);
else if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR)
pa_log_warn("Disconnecting device %s from profile %s (%s) failed: %s: %s", data->device_path, pa_bluetooth_profile_to_string(data->disconnect_profile), data->disconnect_uuid, dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
else {
pa_log_info("Disconnecting device %s from profile %s (%s) succeeded", data->device_path, pa_bluetooth_profile_to_string(data->disconnect_profile), data->disconnect_uuid);
pa_bluetooth_device_connect_profile(device, data->connect_profile);
}
dbus_message_unref(r);
PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
pa_dbus_pending_free(p);
pa_xfree(data->device_path);
pa_xfree(data);
}
void pa_bluetooth_device_disconnect_and_connect_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t disconnect_profile, pa_bluetooth_profile_t connect_profile) {
DBusMessage *m;
const char *disconnect_uuid;
struct connect_and_disconnect_profile_data *data;
disconnect_uuid = pa_bluetooth_profile_to_uuid(device, disconnect_profile);
pa_log_info("Disconnecting device %s from profile %s (%s)", device->path, pa_bluetooth_profile_to_string(disconnect_profile), disconnect_uuid);
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, device->path, BLUEZ_DEVICE_INTERFACE, "DisconnectProfile"));
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &disconnect_uuid, DBUS_TYPE_INVALID));
data = pa_xnew0(struct connect_and_disconnect_profile_data, 1);
data->device_path = pa_xstrdup(device->path);
data->disconnect_uuid = disconnect_uuid;
data->disconnect_profile = disconnect_profile;
data->connect_profile = connect_profile;
send_and_add_to_pending(device->discovery, m, pa_bluetooth_device_disconnect_profile_reply, data);
}
unsigned pa_bluetooth_profile_count(void) {
......
......@@ -47,6 +47,7 @@ typedef struct pa_bluetooth_backend pa_bluetooth_backend;
typedef enum pa_bluetooth_hook {
PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_PROFILE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device_and_profile */
PA_BLUETOOTH_HOOK_DEVICE_UNLINK, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
......@@ -65,6 +66,11 @@ typedef enum pa_bluetooth_hook {
#define PA_BLUETOOTH_PROFILE_A2DP_START_INDEX 5
typedef unsigned pa_bluetooth_profile_t;
struct pa_bluetooth_device_and_profile {
pa_bluetooth_device *device;
pa_bluetooth_profile_t profile;
};
typedef enum pa_bluetooth_transport_state {
PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED,
PA_BLUETOOTH_TRANSPORT_STATE_IDLE,
......@@ -112,7 +118,7 @@ struct pa_bluetooth_device {
bool tried_to_link_with_adapter;
bool valid;
bool autodetect_mtu;
bool change_a2dp_profile_in_progress;
pa_bluetooth_profile_t new_profile_in_progress;
/* Device information */
char *path;
......@@ -163,7 +169,10 @@ void pa_bluetooth_transport_put(pa_bluetooth_transport *t);
void pa_bluetooth_transport_free(pa_bluetooth_transport *t);
size_t pa_bluetooth_device_find_a2dp_endpoints_for_codec(const pa_bluetooth_device *device, const pa_a2dp_codec *a2dp_codec, bool is_a2dp_sink, const char **endpoints, size_t endpoints_max_count);
bool pa_bluetooth_device_change_a2dp_profile(pa_bluetooth_device *d, pa_bluetooth_profile_t profile, void (*cb)(bool, void *), void *userdata);
bool pa_bluetooth_device_change_a2dp_profile(pa_bluetooth_device *d, pa_bluetooth_profile_t profile);
void pa_bluetooth_device_connect_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile);
void pa_bluetooth_device_disconnect_and_connect_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t disconnect_profile, pa_bluetooth_profile_t connect_profile);
bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d);
bool pa_bluetooth_device_a2dp_sink_transport_connected(const pa_bluetooth_device *d);
......
......@@ -95,6 +95,7 @@ struct userdata {
pa_core *core;
pa_hook_slot *device_connection_changed_slot;
pa_hook_slot *profile_connection_changed_slot;
pa_hook_slot *transport_state_changed_slot;
pa_hook_slot *transport_speaker_gain_changed_slot;
pa_hook_slot *transport_microphone_gain_changed_slot;
......@@ -1282,31 +1283,6 @@ static int transport_config(struct userdata *u) {
}
}
static int init_profile(struct userdata *u);
static int start_thread(struct userdata *u);
static void stop_thread(struct userdata *u);
static void change_a2dp_profile_cb(bool success, void *userdata) {
struct userdata *u = (struct userdata *) userdata;
if (!success)
goto off;
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;
off:
stop_thread(u);
pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
}
/* Run from main thread */
static int setup_transport(struct userdata *u) {
pa_bluetooth_transport *t;
......@@ -1319,15 +1295,9 @@ 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 (!pa_bluetooth_profile_is_a2dp(u->profile) || !u->support_a2dp_codec_switch) {
pa_log_warn("Profile %s has no transport", pa_bluetooth_profile_to_string(u->profile));
return -1;
}
if (!pa_bluetooth_device_change_a2dp_profile(u->device, u->profile, change_a2dp_profile_cb, u))
return -1;
/* Changing A2DP endpoint is now in progress and callback will be called after operation finish */
return -EINPROGRESS;
}
u->transport = t;
......@@ -1369,9 +1339,7 @@ static int init_profile(struct userdata *u) {
pa_assert(u->profile != PA_BLUETOOTH_PROFILE_OFF);
r = setup_transport(u);
if (r == -EINPROGRESS)
return 0;
else if (r < 0)
if (r < 0)
return -1;
pa_assert(u->transport);
......@@ -2006,7 +1974,10 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro
else
cp->available = PA_AVAILABLE_NO;
if (cp->available == PA_AVAILABLE_NO && u->support_a2dp_codec_switch && pa_bluetooth_profile_is_a2dp(profile))
if (cp->available == PA_AVAILABLE_NO && u->support_a2dp_codec_switch &&
(u->device->new_profile_in_progress ||
(pa_bluetooth_profile_is_a2dp_sink(profile) && pa_bluetooth_device_a2dp_sink_transport_connected(u->device)) ||
(pa_bluetooth_profile_is_a2dp_source(profile) && pa_bluetooth_device_a2dp_source_transport_connected(u->device))))
cp->available = PA_AVAILABLE_UNKNOWN;
return cp;
......@@ -2024,10 +1995,27 @@ static int set_profile_cb(pa_card *c, pa_card_profile *new_profile) {
p = PA_CARD_PROFILE_DATA(new_profile);
if (*p != PA_BLUETOOTH_PROFILE_OFF) {
const pa_bluetooth_device *d = u->device;
pa_bluetooth_device *d = u->device;
d->new_profile_in_progress = 0;
if (!d->transports[*p] || d->transports[*p]->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
if (pa_bluetooth_profile_is_a2dp(*p) && u->support_a2dp_codec_switch) {
if ((pa_bluetooth_profile_is_a2dp_sink(*p) && pa_bluetooth_device_a2dp_sink_transport_connected(d)) ||
(pa_bluetooth_profile_is_a2dp_source(*p) && pa_bluetooth_device_a2dp_source_transport_connected(d))) {
pa_log_info("Profile with different A2DP codec is in use, trying to asynchronously change it");
if (!pa_bluetooth_device_change_a2dp_profile(d, *p))
return -PA_ERR_IO;
d->new_profile_in_progress = *p;
/* profile changing is in progress now, return error from callback as new profile is not active yet */
return -PA_ERR_IO;
}
}
if ((!d->transports[*p] || d->transports[*p]->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) && (!pa_bluetooth_profile_is_a2dp(*p) || !u->support_a2dp_codec_switch)) {
pa_log_warn("Refused to switch profile to %s: Not connected", new_profile->name);
pa_log_info("Profile %s is not connected yet, trying to asynchronously connect it", new_profile->name);
pa_bluetooth_device_connect_profile(d, *p);
d->new_profile_in_progress = *p;
/* profile changing is in progress now, return error from callback as new profile is not active yet */
return -PA_ERR_IO;
}
}
......@@ -2065,7 +2053,7 @@ static void add_card_profile(pa_bluetooth_profile_t profile, pa_card_new_data *d
}
static void choose_initial_profile(struct userdata *u) {
struct pa_bluetooth_transport *transport;
pa_bluetooth_transport *transport;
pa_card_profile *iter_profile;
pa_card_profile *profile;
void *state;
......@@ -2265,8 +2253,32 @@ static void handle_transport_state_change(struct userdata *u, struct pa_bluetoot
oldavail = cp->available;
newavail = transport_state_to_availability(t->state);
if (newavail == PA_AVAILABLE_NO && u->support_a2dp_codec_switch && pa_bluetooth_profile_is_a2dp(t->profile))
if (u->support_a2dp_codec_switch && pa_bluetooth_profile_is_a2dp(t->profile)) {
pa_card_profile *iter_cp;
void *state;
if (newavail == PA_AVAILABLE_NO &&
(u->device->new_profile_in_progress ||
(pa_bluetooth_profile_is_a2dp_sink(t->profile) && pa_bluetooth_device_a2dp_sink_transport_connected(u->device)) ||
(pa_bluetooth_profile_is_a2dp_source(t->profile) && pa_bluetooth_device_a2dp_source_transport_connected(u->device)))) {
newavail = PA_AVAILABLE_UNKNOWN;
}
/* Change availability for other profiles (except current) in same A2DP category (sink / source) */
PA_HASHMAP_FOREACH(iter_cp, u->card->profiles, state) {
if (cp == iter_cp)
continue;
if (!pa_startswith(iter_cp->name, "a2dp_"))
continue;
if (pa_bluetooth_profile_is_a2dp_sink(t->profile) && !pa_startswith(iter_cp->name, "a2dp_sink"))
continue;
if (pa_bluetooth_profile_is_a2dp_source(t->profile) && !pa_startswith(iter_cp->name, "a2dp_source"))
continue;
/* Do not set PA_AVAILABLE_YES for other profiles */
pa_card_profile_set_available(iter_cp, (newavail == PA_AVAILABLE_YES) ? PA_AVAILABLE_UNKNOWN : newavail);
}
}
pa_card_profile_set_available(cp, newavail);
......@@ -2338,7 +2350,7 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
pa_assert(d);
pa_assert(u);
if (d != u->device || pa_bluetooth_device_any_transport_connected(d) || d->change_a2dp_profile_in_progress)
if (d != u->device || pa_bluetooth_device_any_transport_connected(d) || d->new_profile_in_progress)
return PA_HOOK_OK;
pa_log_debug("Unloading module for device %s", d->path);
......@@ -2347,6 +2359,60 @@ 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 profile_connection_changed_cb(pa_bluetooth_discovery *y, const struct pa_bluetooth_device_and_profile *device_and_profile, struct userdata *u) {
const pa_bluetooth_device *d = device_and_profile->device;
pa_bluetooth_profile_t p = device_and_profile->profile;
pa_bluetooth_transport *t;
pa_card_profile *cp;
pa_assert(d);
pa_assert(p);
pa_assert(u);
pa_log_debug("profile_connection_changed_cb d=%p p=%d u->device=%p u->device->new_profile_in_progress=%d", d, p, u->device, u->device->new_profile_in_progress);
if (d != u->device || !u->device->new_profile_in_progress)
return PA_HOOK_OK;
pa_assert(p != PA_BLUETOOTH_PROFILE_OFF);
if (p == u->device->new_profile_in_progress ||
(u->support_a2dp_codec_switch &&
((pa_bluetooth_profile_is_a2dp_sink(p) && pa_bluetooth_profile_is_a2dp_sink(u->device->new_profile_in_progress)) ||
(pa_bluetooth_profile_is_a2dp_source(p) && pa_bluetooth_profile_is_a2dp_source(u->device->new_profile_in_progress))))) {
/* Asynchronous operation for profile change finished */
u->device->new_profile_in_progress = 0;
t = u->device->transports[p];
if ((t && t->state > PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) ||
(u->support_a2dp_codec_switch &&
((pa_bluetooth_profile_is_a2dp_sink(p) && pa_bluetooth_device_a2dp_sink_transport_connected(u->device)) ||
(pa_bluetooth_profile_is_a2dp_source(p) && pa_bluetooth_device_a2dp_source_transport_connected(u->device))))) {
/* Activate newly connected profile */
pa_assert_se(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(p)));
pa_card_set_profile(u->card, cp, true);
} else {
/* Some bluetooth headsets do not allow connecting both HSP and HFP profile at the same time
* Try to first disconnect one profile and then connect second profile */
u->device->new_profile_in_progress = p;
if (p == PA_BLUETOOTH_PROFILE_HSP_HEAD_UNIT && u->device->transports[PA_BLUETOOTH_PROFILE_HFP_HEAD_UNIT] && u->device->transports[PA_BLUETOOTH_PROFILE_HFP_HEAD_UNIT]->state >= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
pa_bluetooth_device_disconnect_and_connect_profile(u->device, PA_BLUETOOTH_PROFILE_HFP_HEAD_UNIT, PA_BLUETOOTH_PROFILE_HSP_HEAD_UNIT);
else if (p == PA_BLUETOOTH_PROFILE_HFP_HEAD_UNIT && u->device->transports[PA_BLUETOOTH_PROFILE_HSP_HEAD_UNIT] && u->device->transports[PA_BLUETOOTH_PROFILE_HSP_HEAD_UNIT]->state >= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
pa_bluetooth_device_disconnect_and_connect_profile(u->device, PA_BLUETOOTH_PROFILE_HSP_HEAD_UNIT, PA_BLUETOOTH_PROFILE_HFP_HEAD_UNIT);
else if (p == PA_BLUETOOTH_PROFILE_HSP_AUDIO_GATEWAY && u->device->transports[PA_BLUETOOTH_PROFILE_HFP_AUDIO_GATEWAY] && u->device->transports[PA_BLUETOOTH_PROFILE_HFP_AUDIO_GATEWAY]->state >= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
pa_bluetooth_device_disconnect_and_connect_profile(u->device, PA_BLUETOOTH_PROFILE_HFP_AUDIO_GATEWAY, PA_BLUETOOTH_PROFILE_HSP_AUDIO_GATEWAY);
else if (p == PA_BLUETOOTH_PROFILE_HFP_AUDIO_GATEWAY && u->device->transports[PA_BLUETOOTH_PROFILE_HSP_AUDIO_GATEWAY] && u->device->transports[PA_BLUETOOTH_PROFILE_HSP_AUDIO_GATEWAY]->state >= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
pa_bluetooth_device_disconnect_and_connect_profile(u->device, PA_BLUETOOTH_PROFILE_HSP_AUDIO_GATEWAY, PA_BLUETOOTH_PROFILE_HFP_AUDIO_GATEWAY);
else
u->device->new_profile_in_progress = 0;
}
}
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);
......@@ -2497,6 +2563,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->profile_connection_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_PROFILE_CONNECTION_CHANGED),
PA_HOOK_NORMAL, (pa_hook_cb_t) profile_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);
......@@ -2559,6 +2629,9 @@ void pa__done(pa_module *m) {
if (u->device_connection_changed_slot)
pa_hook_slot_free(u->device_connection_changed_slot);
if (u->profile_connection_changed_slot)
pa_hook_slot_free(u->profile_connection_changed_slot);
if (u->transport_state_changed_slot)
pa_hook_slot_free(u->transport_state_changed_slot);
......
......@@ -63,7 +63,7 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
module_loaded = pa_hashmap_get(u->loaded_device_paths, d->path) ? true : false;
/* When changing A2DP profile there is no transport connected, ensure that no module is unloaded */
if (module_loaded && !pa_bluetooth_device_any_transport_connected(d) && !d->change_a2dp_profile_in_progress) {
if (module_loaded && !pa_bluetooth_device_any_transport_connected(d) && !d->new_profile_in_progress) {
/* disconnection, the module unloads itself */
pa_log_debug("Unregistering module for %s", d->path);
pa_hashmap_remove(u->loaded_device_paths, d->path);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment