pulseaudio/src/modules/bluetooth/backend-native.c
James Bottomley 66ed99a13d bluetooth: separate HSP and HFP
When all headsets supported both HSP and HFP, life was good and we
only needed to implement HSP in the native backend.  Unfortunately
some headsets have started supporting HFP only.  Unfortuantely, we
can't simply switch to HFP only because that might break older HSP
only headsets meaning we need to support both HSP and HFP separately.

This patch separates them from a joint profile to being two separate
ones.  The older one retains the headset_head_unit name, meaning any
saved parameters will still select this (keeping us backward
compatible).  It also introduces a new headset_handsfree.

For headsets that support both HSP and HFP, the two profiles will
become separately visible and selectable.  This will only matter once
we start adding features to HFP that HSP can't support (like wideband
audio).

Signed-off-by: <James.Bottomley@HansenPartnership.com>

---
v6:

- merge profile switching fixes patch from Rodrigo Araujo

v5:

- rename option to enable_native_hfp_hf
- don't call profile_done for HFP_HF unless it was initialised

v3:

- Update for PA 11.0

v2:

- fold in review feedback
- add global disable option for not registering HFP

v3:

- change parameter to enable_profile_hfp
- update device_supports_profile to be aware of hfp/hsp exclusivity
- change parameter to enable_profile_hfp_hf

bluetooth: separate HSP and HFP (to me merged with this patch)

Hi.

First, just to say that your patches are going great. Finally I can use
the microphone of my HFP only headset (a version of a Bluedio T2+).

So far, I've only encontered one problem: the auto_switch option of
module_bluetooth_policy stops working. Dug through the code and I think
you missed a few spots were you have to hangle the new headset_handsfree
profile in module_bluetooth_policy.c

Applying the following after applying your v5 patches fixed the issue
for me, now when I start making a VOIP call the profile switches to
headset_handsfree and the mic works automatically, and when the call
finishes it reverts back to a2dp.

Thanks and best regards.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/491>
2021-02-23 06:54:39 +00:00

725 lines
24 KiB
C

/***
This file is part of PulseAudio.
Copyright 2014 Wim Taymans <wim.taymans at gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/shared.h>
#include <pulsecore/core-error.h>
#include <pulsecore/core-util.h>
#include <pulsecore/dbus-shared.h>
#include <pulsecore/log.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/sco.h>
#include "bluez5-util.h"
struct pa_bluetooth_backend {
pa_core *core;
pa_dbus_connection *connection;
pa_bluetooth_discovery *discovery;
bool enable_hs_role;
PA_LLIST_HEAD(pa_dbus_pending, pending);
};
struct transport_data {
int rfcomm_fd;
pa_io_event *rfcomm_io;
int sco_fd;
pa_io_event *sco_io;
pa_mainloop_api *mainloop;
};
#define HSP_AG_PROFILE "/Profile/HSPAGProfile"
#define HFP_AG_PROFILE "/Profile/HFPAGProfile"
#define HSP_HS_PROFILE "/Profile/HSPHSProfile"
/* RFCOMM channel for HSP headset role
* The choice seems to be a bit arbitrary -- it looks like at least channels 2, 4 and 5 also work*/
#define HSP_HS_DEFAULT_CHANNEL 3
#define PROFILE_INTROSPECT_XML \
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
"<node>" \
" <interface name=\"" BLUEZ_PROFILE_INTERFACE "\">" \
" <method name=\"Release\">" \
" </method>" \
" <method name=\"RequestDisconnection\">" \
" <arg name=\"device\" direction=\"in\" type=\"o\"/>" \
" </method>" \
" <method name=\"NewConnection\">" \
" <arg name=\"device\" direction=\"in\" type=\"o\"/>" \
" <arg name=\"fd\" direction=\"in\" type=\"h\"/>" \
" <arg name=\"opts\" direction=\"in\" type=\"a{sv}\"/>" \
" </method>" \
" </interface>" \
" <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">" \
" <method name=\"Introspect\">" \
" <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
" </method>" \
" </interface>" \
"</node>"
static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
DBusPendingCallNotifyFunction func, void *call_data) {
pa_dbus_pending *p;
DBusPendingCall *call;
pa_assert(backend);
pa_assert(m);
pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1));
p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data);
PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p);
dbus_pending_call_set_notify(call, func, p, NULL);
return p;
}
static int sco_do_connect(pa_bluetooth_transport *t) {
pa_bluetooth_device *d = t->device;
struct sockaddr_sco addr;
socklen_t len;
int err, i;
int sock;
bdaddr_t src;
bdaddr_t dst;
const char *src_addr, *dst_addr;
src_addr = d->adapter->address;
dst_addr = d->address;
/* don't use ba2str to avoid -lbluetooth */
for (i = 5; i >= 0; i--, src_addr += 3)
src.b[i] = strtol(src_addr, NULL, 16);
for (i = 5; i >= 0; i--, dst_addr += 3)
dst.b[i] = strtol(dst_addr, NULL, 16);
sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
if (sock < 0) {
pa_log_error("socket(SEQPACKET, SCO) %s", pa_cstrerror(errno));
return -1;
}
len = sizeof(addr);
memset(&addr, 0, len);
addr.sco_family = AF_BLUETOOTH;
bacpy(&addr.sco_bdaddr, &src);
if (bind(sock, (struct sockaddr *) &addr, len) < 0) {
pa_log_error("bind(): %s", pa_cstrerror(errno));
goto fail_close;
}
memset(&addr, 0, len);
addr.sco_family = AF_BLUETOOTH;
bacpy(&addr.sco_bdaddr, &dst);
pa_log_info("doing connect");
err = connect(sock, (struct sockaddr *) &addr, len);
if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) {
pa_log_error("connect(): %s", pa_cstrerror(errno));
goto fail_close;
}
return sock;
fail_close:
close(sock);
return -1;
}
static int sco_do_accept(pa_bluetooth_transport *t) {
struct transport_data *trd = t->userdata;
struct sockaddr_sco addr;
socklen_t optlen;
int sock;
memset(&addr, 0, sizeof(addr));
optlen = sizeof(addr);
pa_log_info ("doing accept");
sock = accept(trd->sco_fd, (struct sockaddr *) &addr, &optlen);
if (sock < 0) {
if (errno != EAGAIN)
pa_log_error("accept(): %s", pa_cstrerror(errno));
goto fail;
}
return sock;
fail:
return -1;
}
static int sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
int sock;
socklen_t len;
if (optional)
sock = sco_do_accept(t);
else
sock = sco_do_connect(t);
if (sock < 0)
goto fail;
if (imtu) *imtu = 48;
if (omtu) *omtu = 48;
if (t->device->autodetect_mtu) {
struct sco_options sco_opt;
len = sizeof(sco_opt);
memset(&sco_opt, 0, len);
if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0)
pa_log_warn("getsockopt(SCO_OPTIONS) failed, loading defaults");
else {
pa_log_debug("autodetected imtu = omtu = %u", sco_opt.mtu);
if (imtu) *imtu = sco_opt.mtu;
if (omtu) *omtu = sco_opt.mtu;
}
}
return sock;
fail:
return -1;
}
static void sco_release_cb(pa_bluetooth_transport *t) {
pa_log_info("Transport %s released", t->path);
/* device will close the SCO socket for us */
}
static void sco_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
pa_bluetooth_transport *t = userdata;
pa_assert(io);
pa_assert(t);
if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
pa_log_error("error listening SCO connection: %s", pa_cstrerror(errno));
goto fail;
}
if (t->state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
pa_log_info("SCO incoming connection: changing state to PLAYING");
pa_bluetooth_transport_set_state (t, PA_BLUETOOTH_TRANSPORT_STATE_PLAYING);
}
fail:
return;
}
static int sco_listen(pa_bluetooth_transport *t) {
struct transport_data *trd = t->userdata;
struct sockaddr_sco addr;
int sock, i;
bdaddr_t src;
const char *src_addr;
sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, BTPROTO_SCO);
if (sock < 0) {
pa_log_error("socket(SEQPACKET, SCO) %s", pa_cstrerror(errno));
return -1;
}
src_addr = t->device->adapter->address;
/* don't use ba2str to avoid -lbluetooth */
for (i = 5; i >= 0; i--, src_addr += 3)
src.b[i] = strtol(src_addr, NULL, 16);
/* Bind to local address */
memset(&addr, 0, sizeof(addr));
addr.sco_family = AF_BLUETOOTH;
bacpy(&addr.sco_bdaddr, &src);
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
pa_log_error("bind(): %s", pa_cstrerror(errno));
goto fail_close;
}
pa_log_info ("doing listen");
if (listen(sock, 1) < 0) {
pa_log_error("listen(): %s", pa_cstrerror(errno));
goto fail_close;
}
trd->sco_fd = sock;
trd->sco_io = trd->mainloop->io_new(trd->mainloop, sock, PA_IO_EVENT_INPUT,
sco_io_callback, t);
return sock;
fail_close:
close(sock);
return -1;
}
static void register_profile_reply(DBusPendingCall *pending, void *userdata) {
DBusMessage *r;
pa_dbus_pending *p;
pa_bluetooth_backend *b;
char *profile;
pa_assert(pending);
pa_assert_se(p = userdata);
pa_assert_se(b = p->context_data);
pa_assert_se(profile = p->call_data);
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
pa_log_info("Couldn't register profile %s because it is disabled in BlueZ", profile);
goto finish;
}
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
pa_log_error(BLUEZ_PROFILE_MANAGER_INTERFACE ".RegisterProfile() failed: %s: %s", dbus_message_get_error_name(r),
pa_dbus_get_error_message(r));
goto finish;
}
finish:
dbus_message_unref(r);
PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
pa_dbus_pending_free(p);
pa_xfree(profile);
}
static void register_profile(pa_bluetooth_backend *b, const char *profile, const char *uuid) {
DBusMessage *m;
DBusMessageIter i, d;
dbus_bool_t autoconnect;
dbus_uint16_t version, chan;
pa_log_debug("Registering Profile %s %s", profile, uuid);
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, "/org/bluez", BLUEZ_PROFILE_MANAGER_INTERFACE, "RegisterProfile"));
dbus_message_iter_init_append(m, &i);
pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &profile));
pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &uuid));
dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&d);
if (pa_bluetooth_uuid_is_hsp_hs(uuid)) {
/* In the headset role, the connection will only be initiated from the remote side */
autoconnect = 0;
pa_dbus_append_basic_variant_dict_entry(&d, "AutoConnect", DBUS_TYPE_BOOLEAN, &autoconnect);
chan = HSP_HS_DEFAULT_CHANNEL;
pa_dbus_append_basic_variant_dict_entry(&d, "Channel", DBUS_TYPE_UINT16, &chan);
/* HSP version 1.2 */
version = 0x0102;
pa_dbus_append_basic_variant_dict_entry(&d, "Version", DBUS_TYPE_UINT16, &version);
}
dbus_message_iter_close_container(&i, &d);
send_and_add_to_pending(b, m, register_profile_reply, pa_xstrdup(profile));
}
static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
pa_bluetooth_transport *t = userdata;
pa_assert(io);
pa_assert(t);
if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
pa_log_info("Lost RFCOMM connection.");
goto fail;
}
if (events & PA_IO_EVENT_INPUT) {
char buf[512];
ssize_t len;
int gain, dummy;
bool do_reply = false;
len = pa_read(fd, buf, 511, NULL);
if (len < 0) {
pa_log_error("RFCOMM read error: %s", pa_cstrerror(errno));
goto fail;
}
buf[len] = 0;
pa_log_debug("RFCOMM << %s", buf);
/* There are only four HSP AT commands:
* AT+VGS=value: value between 0 and 15, sent by the HS to AG to set the speaker gain.
* +VGS=value is sent by AG to HS as a response to an AT+VGS command or when the gain
* is changed on the AG side.
* AT+VGM=value: value between 0 and 15, sent by the HS to AG to set the microphone gain.
* +VGM=value is sent by AG to HS as a response to an AT+VGM command or when the gain
* is changed on the AG side.
* AT+CKPD=200: Sent by HS when headset button is pressed.
* RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because
* it does not expect a reply. */
if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM=%d\r\n", &gain) == 1) {
t->speaker_gain = gain;
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), t);
do_reply = true;
} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS=%d\r\n", &gain) == 1) {
t->microphone_gain = gain;
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t);
do_reply = true;
} else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) {
do_reply = true;
} else {
do_reply = false;
}
if (do_reply) {
pa_log_debug("RFCOMM >> OK");
len = write(fd, "\r\nOK\r\n", 6);
/* we ignore any errors, it's not critical and real errors should
* be caught with the HANGUP and ERROR events handled above */
if (len < 0)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
}
return;
fail:
pa_bluetooth_transport_unlink(t);
pa_bluetooth_transport_free(t);
}
static void transport_destroy(pa_bluetooth_transport *t) {
struct transport_data *trd = t->userdata;
if (trd->sco_io) {
trd->mainloop->io_free(trd->sco_io);
shutdown(trd->sco_fd, SHUT_RDWR);
close (trd->sco_fd);
}
trd->mainloop->io_free(trd->rfcomm_io);
shutdown(trd->rfcomm_fd, SHUT_RDWR);
close (trd->rfcomm_fd);
pa_xfree(trd);
}
static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) {
struct transport_data *trd = t->userdata;
char buf[512];
ssize_t len, written;
if (t->speaker_gain == gain)
return;
t->speaker_gain = gain;
/* If we are in the AG role, we send a command to the head set to change
* the speaker gain. In the HS role, source and sink are swapped, so
* in this case we notify the AG that the microphone gain has changed */
if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS) {
len = sprintf(buf, "\r\n+VGS=%d\r\n", gain);
pa_log_debug("RFCOMM >> +VGS=%d", gain);
} else {
len = sprintf(buf, "\r\nAT+VGM=%d\r\n", gain);
pa_log_debug("RFCOMM >> AT+VGM=%d", gain);
}
written = write(trd->rfcomm_fd, buf, len);
if (written != len)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) {
struct transport_data *trd = t->userdata;
char buf[512];
ssize_t len, written;
if (t->microphone_gain == gain)
return;
t->microphone_gain = gain;
/* If we are in the AG role, we send a command to the head set to change
* the microphone gain. In the HS role, source and sink are swapped, so
* in this case we notify the AG that the speaker gain has changed */
if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS) {
len = sprintf(buf, "\r\n+VGM=%d\r\n", gain);
pa_log_debug("RFCOMM >> +VGM=%d", gain);
} else {
len = sprintf(buf, "\r\nAT+VGS=%d\r\n", gain);
pa_log_debug("RFCOMM >> AT+VGS=%d", gain);
}
written = write (trd->rfcomm_fd, buf, len);
if (written != len)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) {
pa_bluetooth_backend *b = userdata;
pa_bluetooth_device *d;
pa_bluetooth_transport *t;
pa_bluetooth_profile_t p;
DBusMessage *r;
int fd;
const char *sender, *path, PA_UNUSED *handler;
DBusMessageIter arg_i;
char *pathfd;
struct transport_data *trd;
if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oha{sv}")) {
pa_log_error("Invalid signature found in NewConnection");
goto fail;
}
handler = dbus_message_get_path(m);
if (pa_streq(handler, HSP_AG_PROFILE)) {
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;
}
pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_OBJECT_PATH);
dbus_message_iter_get_basic(&arg_i, &path);
d = pa_bluetooth_discovery_get_device_by_path(b->discovery, path);
if (d == NULL) {
pa_log_error("Device doesnt exist for %s", path);
goto fail;
}
pa_assert_se(dbus_message_iter_next(&arg_i));
pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_UNIX_FD);
dbus_message_iter_get_basic(&arg_i, &fd);
pa_log_debug("dbus: NewConnection path=%s, fd=%d, profile %s", path, fd,
pa_bluetooth_profile_to_string(p));
sender = dbus_message_get_sender(m);
pathfd = pa_sprintf_malloc ("%s/fd%d", path, fd);
t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, 0);
pa_xfree(pathfd);
t->acquire = sco_acquire_cb;
t->release = sco_release_cb;
t->destroy = transport_destroy;
t->set_speaker_gain = set_speaker_gain;
t->set_microphone_gain = set_microphone_gain;
trd = pa_xnew0(struct transport_data, 1);
trd->rfcomm_fd = fd;
trd->mainloop = b->core->mainloop;
trd->rfcomm_io = trd->mainloop->io_new(b->core->mainloop, fd, PA_IO_EVENT_INPUT,
rfcomm_io_callback, t);
t->userdata = trd;
sco_listen(t);
pa_bluetooth_transport_put(t);
pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
pa_assert_se(r = dbus_message_new_method_return(m));
return r;
fail:
pa_assert_se(r = dbus_message_new_error(m, BLUEZ_ERROR_INVALID_ARGUMENTS, "Unable to handle new connection"));
return r;
}
static DBusMessage *profile_request_disconnection(DBusConnection *conn, DBusMessage *m, void *userdata) {
DBusMessage *r;
pa_assert_se(r = dbus_message_new_method_return(m));
return r;
}
static DBusHandlerResult profile_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
pa_bluetooth_backend *b = userdata;
DBusMessage *r = NULL;
const char *path, *interface, *member;
pa_assert(b);
path = dbus_message_get_path(m);
interface = dbus_message_get_interface(m);
member = dbus_message_get_member(m);
pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
if (!pa_streq(path, 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")) {
const char *xml = PROFILE_INTROSPECT_XML;
pa_assert_se(r = dbus_message_new_method_return(m));
pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
} else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "Release")) {
pa_log_debug("Release not handled");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
} else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "RequestDisconnection")) {
r = profile_request_disconnection(c, m, userdata);
} else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "NewConnection"))
r = profile_new_connection(c, m, userdata);
else
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (r) {
pa_assert_se(dbus_connection_send(pa_dbus_connection_get(b->connection), r, NULL));
dbus_message_unref(r);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
static void profile_init(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile) {
static const DBusObjectPathVTable vtable_profile = {
.message_function = profile_handler,
};
const char *object_name;
const char *uuid;
pa_assert(b);
switch (profile) {
case PA_BLUETOOTH_PROFILE_HSP_HS:
object_name = HSP_AG_PROFILE;
uuid = PA_BLUETOOTH_UUID_HSP_AG;
break;
case PA_BLUETOOTH_PROFILE_HFP_AG:
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;
}
pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(b->connection), object_name, &vtable_profile, b));
register_profile(b, object_name, uuid);
}
static void profile_done(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile) {
pa_assert(b);
switch (profile) {
case PA_BLUETOOTH_PROFILE_HSP_HS:
dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_AG_PROFILE);
break;
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;
}
}
void pa_bluetooth_native_backend_enable_hs_role(pa_bluetooth_backend *native_backend, bool enable_hs_role) {
if (enable_hs_role == native_backend->enable_hs_role)
return;
if (enable_hs_role)
profile_init(native_backend, PA_BLUETOOTH_PROFILE_HFP_AG);
else
profile_done(native_backend, PA_BLUETOOTH_PROFILE_HFP_AG);
native_backend->enable_hs_role = enable_hs_role;
}
pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_hs_role) {
pa_bluetooth_backend *backend;
DBusError err;
pa_log_debug("Bluetooth Headset Backend API support using the native backend");
backend = pa_xnew0(pa_bluetooth_backend, 1);
backend->core = c;
dbus_error_init(&err);
if (!(backend->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) {
pa_log("Failed to get D-Bus connection: %s", err.message);
dbus_error_free(&err);
pa_xfree(backend);
return NULL;
}
backend->discovery = y;
backend->enable_hs_role = enable_hs_role;
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;
}
void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) {
pa_assert(backend);
pa_dbus_free_pending_list(&backend->pending);
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);
pa_xfree(backend);
}