diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index a48082dd0..653767cd7 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -54,6 +54,7 @@ struct transport_data { }; #define HSP_AG_PROFILE "/Profile/HSPAGProfile" +#define HFP_AG_PROFILE "/Profile/HFPAGProfile" #define HSP_HS_PROFILE "/Profile/HSPHSProfile" /* RFCOMM channel for HSP headset role @@ -508,6 +509,8 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, p = PA_BLUETOOTH_PROFILE_HSP_HS; } else if (pa_streq(handler, HSP_HS_PROFILE)) { p = PA_BLUETOOTH_PROFILE_HFP_AG; + } else if (pa_streq(handler, HFP_AG_PROFILE)) { + p = PA_BLUETOOTH_PROFILE_HFP_HF; } else { pa_log_error("Invalid handler"); goto fail; @@ -585,7 +588,8 @@ static DBusHandlerResult profile_handler(DBusConnection *c, DBusMessage *m, void pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); - if (!pa_streq(path, HSP_AG_PROFILE) && !pa_streq(path, HSP_HS_PROFILE)) + if (!pa_streq(path, HSP_AG_PROFILE) && !pa_streq(path, HSP_HS_PROFILE) + && !pa_streq(path, HFP_AG_PROFILE)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { @@ -630,6 +634,10 @@ static void profile_init(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile object_name = HSP_HS_PROFILE; uuid = PA_BLUETOOTH_UUID_HSP_HS; break; + case PA_BLUETOOTH_PROFILE_HFP_HF: + object_name = HFP_AG_PROFILE; + uuid = PA_BLUETOOTH_UUID_HFP_AG; + break; default: pa_assert_not_reached(); break; @@ -649,6 +657,9 @@ static void profile_done(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile case PA_BLUETOOTH_PROFILE_HFP_AG: dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_HS_PROFILE); break; + case PA_BLUETOOTH_PROFILE_HFP_HF: + dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HFP_AG_PROFILE); + break; default: pa_assert_not_reached(); break; @@ -691,6 +702,8 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d if (enable_hs_role) profile_init(backend, PA_BLUETOOTH_PROFILE_HFP_AG); profile_init(backend, PA_BLUETOOTH_PROFILE_HSP_HS); + if (pa_bluetooth_discovery_get_enable_native_hfp_hf(y)) + profile_init(backend, PA_BLUETOOTH_PROFILE_HFP_HF); return backend; } @@ -703,6 +716,8 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) { if (backend->enable_hs_role) profile_done(backend, PA_BLUETOOTH_PROFILE_HFP_AG); profile_done(backend, PA_BLUETOOTH_PROFILE_HSP_HS); + if (pa_bluetooth_discovery_get_enable_native_hfp_hf(backend->discovery)) + profile_done(backend, PA_BLUETOOTH_PROFILE_HFP_HF); pa_dbus_connection_unref(backend->connection); diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index c1ff7cfe3..b97787c46 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -114,6 +114,7 @@ struct pa_bluetooth_discovery { int headset_backend; pa_bluetooth_backend *ofono_backend, *native_backend; PA_LLIST_HEAD(pa_dbus_pending, pending); + bool enable_native_hfp_hf; }; static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, @@ -191,15 +192,29 @@ static const char *transport_state_to_string(pa_bluetooth_transport_state_t stat } static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) { + bool show_hfp, show_hsp, enable_native_hfp_hf; + + enable_native_hfp_hf = pa_bluetooth_discovery_get_enable_native_hfp_hf(device->discovery); + + if (enable_native_hfp_hf) { + show_hfp = pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF); + show_hsp = !show_hfp; + } else { + show_hfp = false; + show_hsp = true; + } + 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_HSP_HS: - return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS) - || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT) - || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF); + return show_hsp + && ( !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS) + || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT)); + case PA_BLUETOOTH_PROFILE_HFP_HF: + return show_hfp && !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF); case PA_BLUETOOTH_PROFILE_HFP_AG: return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG) || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG); @@ -731,6 +746,14 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_disc return NULL; } +bool pa_bluetooth_discovery_get_enable_native_hfp_hf(pa_bluetooth_discovery *y) +{ + pa_assert(y); + pa_assert(PA_REFCNT_VALUE(y) > 0); + + return y->enable_native_hfp_hf; +} + 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; @@ -1699,6 +1722,8 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) { return "a2dp_source"; case PA_BLUETOOTH_PROFILE_HSP_HS: return "headset_head_unit"; + case PA_BLUETOOTH_PROFILE_HFP_HF: + return "headset_handsfree"; case PA_BLUETOOTH_PROFILE_HFP_AG: return "headset_audio_gateway"; case PA_BLUETOOTH_PROFILE_OFF: @@ -2152,7 +2177,7 @@ static void object_manager_done(pa_bluetooth_discovery *y) { A2DP_OBJECT_MANAGER_PATH); } -pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) { +pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend, bool enable_native_hfp_hf) { pa_bluetooth_discovery *y; DBusError err; DBusConnection *conn; @@ -2165,6 +2190,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe PA_REFCNT_INIT(y); y->core = c; y->headset_backend = headset_backend; + y->enable_native_hfp_hf = enable_native_hfp_hf; y->adapters = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) adapter_free); y->devices = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index 861800500..eea3498f6 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -71,6 +71,7 @@ typedef enum profile { PA_BLUETOOTH_PROFILE_A2DP_SINK, PA_BLUETOOTH_PROFILE_A2DP_SOURCE, PA_BLUETOOTH_PROFILE_HSP_HS, + PA_BLUETOOTH_PROFILE_HFP_HF, PA_BLUETOOTH_PROFILE_HFP_AG, PA_BLUETOOTH_PROFILE_OFF } pa_bluetooth_profile_t; @@ -198,8 +199,9 @@ static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) { #define HEADSET_BACKEND_NATIVE 1 #define HEADSET_BACKEND_AUTO 2 -pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core, int headset_backend); +pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core, int headset_backend, bool default_profile_hfp); pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y); void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y); void pa_bluetooth_discovery_set_ofono_running(pa_bluetooth_discovery *y, bool is_running); +bool pa_bluetooth_discovery_get_enable_native_hfp_hf(pa_bluetooth_discovery *y); #endif diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c index ffaa14041..ff66070ad 100644 --- a/src/modules/bluetooth/module-bluetooth-policy.c +++ b/src/modules/bluetooth/module-bluetooth-policy.c @@ -156,7 +156,7 @@ static void card_set_profile(struct userdata *u, pa_card *card, bool revert_to_a if (!pa_streq(profile->name, "a2dp_sink")) continue; } else { - if (!pa_streq(profile->name, "headset_head_unit")) + if (!pa_streq(profile->name, "headset_head_unit") && !pa_streq(profile->name, "headset_handsfree")) continue; } @@ -191,7 +191,7 @@ static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) { return; /* Skip card if does not have active hsp profile */ - if (!pa_streq(card->active_profile->name, "headset_head_unit")) + if (!pa_streq(card->active_profile->name, "headset_head_unit") && !pa_streq(card->active_profile->name, "headset_handsfree")) return; /* Skip card if already has active a2dp profile */ @@ -203,7 +203,7 @@ static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) { return; /* Skip card if already has active hsp profile */ - if (pa_streq(card->active_profile->name, "headset_head_unit")) + if (pa_streq(card->active_profile->name, "headset_head_unit") || pa_streq(card->active_profile->name, "headset_handsfree")) return; } @@ -358,7 +358,9 @@ static pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_prof return PA_HOOK_OK; /* Do not automatically switch profiles for headsets, just in case */ - if (pa_streq(profile->name, "a2dp_sink") || pa_streq(profile->name, "headset_head_unit")) + if (pa_streq(profile->name, "a2dp_sink") || + pa_streq(profile->name, "headset_head_unit") || + pa_streq(profile->name, "headset_handsfree")) return PA_HOOK_OK; is_active_profile = card->active_profile == profile; diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index 4eff85e77..cc5a0bc13 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -262,6 +262,7 @@ static int sco_process_render(struct userdata *u) { pa_assert(u); pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || + u->profile == PA_BLUETOOTH_PROFILE_HFP_HF || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG); pa_assert(u->sink); @@ -328,6 +329,7 @@ static int sco_process_push(struct userdata *u) { pa_assert(u); pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || + u->profile == PA_BLUETOOTH_PROFILE_HFP_HF|| u->profile == PA_BLUETOOTH_PROFILE_HFP_AG); pa_assert(u->source); pa_assert(u->read_smoother); @@ -767,7 +769,9 @@ static void handle_sink_block_size_change(struct userdata *u) { /* Run from I/O thread */ static void transport_config_mtu(struct userdata *u) { - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF + || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { u->read_block_size = u->read_link_mtu; u->write_block_size = u->write_link_mtu; @@ -1009,7 +1013,8 @@ static int add_source(struct userdata *u) { if (u->a2dp_codec) pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->a2dp_codec->name); pa_source_new_data_set_sample_spec(&data, &u->decoder_sample_spec); - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS) + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); connect_ports(u, &data, PA_DIRECTION_INPUT); @@ -1021,6 +1026,7 @@ static int add_source(struct userdata *u) { data.suspend_cause = PA_SUSPEND_USER; break; case PA_BLUETOOTH_PROFILE_HSP_HS: + case PA_BLUETOOTH_PROFILE_HFP_HF: /* u->stream_fd contains the error returned by the last transport_acquire() * EAGAIN means we are waiting for a NewConnection signal */ if (u->stream_fd == -EAGAIN) @@ -1045,7 +1051,9 @@ static int add_source(struct userdata *u) { u->source->parent.process_msg = source_process_msg; u->source->set_state_in_io_thread = source_set_state_in_io_thread_cb; - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) { pa_source_set_set_volume_callback(u->source, source_set_volume_cb); u->source->n_volume_steps = 16; } @@ -1195,7 +1203,8 @@ static int add_sink(struct userdata *u) { if (u->a2dp_codec) pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->a2dp_codec->name); pa_sink_new_data_set_sample_spec(&data, &u->encoder_sample_spec); - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS) + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); connect_ports(u, &data, PA_DIRECTION_OUTPUT); @@ -1206,6 +1215,7 @@ static int add_sink(struct userdata *u) { data.suspend_cause = PA_SUSPEND_USER; break; case PA_BLUETOOTH_PROFILE_HSP_HS: + case PA_BLUETOOTH_PROFILE_HFP_HF: /* u->stream_fd contains the error returned by the last transport_acquire() * EAGAIN means we are waiting for a NewConnection signal */ if (u->stream_fd == -EAGAIN) @@ -1232,7 +1242,9 @@ static int add_sink(struct userdata *u) { u->sink->parent.process_msg = sink_process_msg; u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) { pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); u->sink->n_volume_steps = 16; } @@ -1241,7 +1253,9 @@ static int add_sink(struct userdata *u) { /* Run from main thread */ static int transport_config(struct userdata *u) { - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF + || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { u->encoder_sample_spec.format = PA_SAMPLE_S16LE; u->encoder_sample_spec.channels = 1; u->encoder_sample_spec.rate = 8000; @@ -1311,6 +1325,7 @@ static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, [PA_BLUETOOTH_PROFILE_HSP_HS] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, + [PA_BLUETOOTH_PROFILE_HFP_HF] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, [PA_BLUETOOTH_PROFILE_HFP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, [PA_BLUETOOTH_PROFILE_OFF] = 0 }; @@ -1930,7 +1945,20 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro break; case PA_BLUETOOTH_PROFILE_HSP_HS: - cp = pa_card_profile_new(name, _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t)); + cp = pa_card_profile_new(name, _("Headset Head Unit (HSP)"), sizeof(pa_bluetooth_profile_t)); + cp->priority = 30; + cp->n_sinks = 1; + cp->n_sources = 1; + cp->max_sink_channels = 1; + cp->max_source_channels = 1; + pa_hashmap_put(input_port->profiles, cp->name, cp); + pa_hashmap_put(output_port->profiles, cp->name, cp); + + p = PA_CARD_PROFILE_DATA(cp); + break; + + case PA_BLUETOOTH_PROFILE_HFP_HF: + cp = pa_card_profile_new(name, _("Headset Handsfree (HFP)"), sizeof(pa_bluetooth_profile_t)); cp->priority = 30; cp->n_sinks = 1; cp->n_sources = 1; @@ -2016,8 +2044,10 @@ static int uuid_to_profile(const char *uuid, pa_bluetooth_profile_t *_r) { *_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_bluetooth_uuid_is_hsp_hs(uuid) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) + else if (pa_bluetooth_uuid_is_hsp_hs(uuid)) *_r = PA_BLUETOOTH_PROFILE_HSP_HS; + else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) + *_r = PA_BLUETOOTH_PROFILE_HFP_HF; else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG)) *_r = PA_BLUETOOTH_PROFILE_HFP_AG; else @@ -2036,6 +2066,7 @@ static int add_card(struct userdata *u) { pa_bluetooth_profile_t *p; const char *uuid; void *state; + bool enable_native_hfp_hf, has_both; pa_assert(u); pa_assert(u->device); @@ -2066,9 +2097,22 @@ static int add_card(struct userdata *u) { create_card_ports(u, data.ports); + enable_native_hfp_hf = pa_bluetooth_discovery_get_enable_native_hfp_hf(u->discovery); + + has_both = enable_native_hfp_hf && pa_hashmap_get(d->uuids, PA_BLUETOOTH_UUID_HFP_HF) && pa_hashmap_get(d->uuids, PA_BLUETOOTH_UUID_HSP_HS); PA_HASHMAP_FOREACH(uuid, d->uuids, state) { pa_bluetooth_profile_t profile; + if (!enable_native_hfp_hf && pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) { + pa_log_info("device supports HFP but disabling profile as requested"); + continue; + } + + if (has_both && pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS)) { + pa_log_info("device support HSP and HFP, selecting HFP only"); + continue; + } + if (uuid_to_profile(uuid, &profile) < 0) continue; diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c index c1c761522..14583b6cd 100644 --- a/src/modules/bluetooth/module-bluez5-discover.c +++ b/src/modules/bluetooth/module-bluez5-discover.c @@ -110,6 +110,7 @@ int pa__init(pa_module *m) { int headset_backend; bool autodetect_mtu; uint32_t output_rate_refresh_interval_ms; + bool enable_native_hfp_hf = true; pa_assert(m); @@ -133,6 +134,9 @@ int pa__init(pa_module *m) { autodetect_mtu = false; if (pa_modargs_get_value_boolean(ma, "autodetect_mtu", &autodetect_mtu) < 0) { pa_log("Invalid boolean value for autodetect_mtu parameter"); + } + if (pa_modargs_get_value_boolean(ma, "enable_native_hfp_hf", &enable_native_hfp_hf) < 0) { + pa_log("enable_native_hfp_hf must be true or false"); goto fail; } @@ -149,7 +153,7 @@ int pa__init(pa_module *m) { u->output_rate_refresh_interval_ms = output_rate_refresh_interval_ms; 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, headset_backend))) + if (!(u->discovery = pa_bluetooth_discovery_get(u->core, headset_backend, enable_native_hfp_hf))) goto fail; u->device_connection_changed_slot =