f9c7ffa9b6
PulseAudio is used for handling all audio on postmarketOS. This also involves Bluetooth audio such as A2DP, HSP and HFP audio. In the case of HFP/HSP, the HF and AG can interact with each other through AT commands defined in the Bluetooth HFP 1.8 spec. This set of patches implements HFP support to allow Bluetooth devices to accept/reject/hangup calls, dial numbers, DTMF tone generation, query signal strength, roaming status, service status, AG battery level, call status, etc. More details in the upstream MR: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/693 Available in edge for testing this merge request with a broader user base. Not intended for backporting to stable branches. [ci:skip-build]: already built successfully in CI
365 lines
14 KiB
Diff
365 lines
14 KiB
Diff
From 6861de91294c8bed206e8338d1074ebc57683ae4 Mon Sep 17 00:00:00 2001
|
|
From: Dylan Van Assche <me@dylanvanassche.be>
|
|
Date: Sat, 16 Apr 2022 08:46:13 +0200
|
|
Subject: [PATCH 21/26] bluetooth: support +CIEV, RING, and +CLIP URCs
|
|
|
|
Report changes of the CIND indicators and calls to the HF with URCs:
|
|
|
|
- +CIEV: report any change to the CIND indicators such as signal
|
|
strength, roaming status, service status, etc.
|
|
- RING: play a ringtone on the HF side for incoming calls.
|
|
- +CLIP: enhanced call status reports the subscriber number to the HF
|
|
for incoming calls.
|
|
---
|
|
src/modules/bluetooth/backend-native.c | 258 +++++++++++++++++++++++++
|
|
src/modules/bluetooth/bluez5-util.h | 12 ++
|
|
2 files changed, 270 insertions(+)
|
|
|
|
diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
|
|
index faefed9bd..e12b38d71 100644
|
|
--- a/src/modules/bluetooth/backend-native.c
|
|
+++ b/src/modules/bluetooth/backend-native.c
|
|
@@ -29,6 +29,8 @@
|
|
#include <pulsecore/core-util.h>
|
|
#include <pulsecore/dbus-shared.h>
|
|
#include <pulsecore/log.h>
|
|
+#include <pulse/timeval.h>
|
|
+#include <pulse/rtclock.h>
|
|
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
@@ -42,6 +44,7 @@
|
|
#include "upower.h"
|
|
#include "modemmanager.h"
|
|
|
|
+#define RING_WAIT_TIME ((pa_usec_t) (3 * PA_USEC_PER_SEC))
|
|
#define MANDATORY_CALL_INDICATORS \
|
|
"(\"call\",(0-1))," \
|
|
"(\"callsetup\",(0-3))," \
|
|
@@ -49,10 +52,17 @@
|
|
|
|
struct pa_bluetooth_backend {
|
|
pa_core *core;
|
|
+ pa_time_event *timer_ring_event;
|
|
pa_dbus_connection *connection;
|
|
pa_bluetooth_discovery *discovery;
|
|
pa_hook_slot *adapter_uuids_changed_slot;
|
|
pa_hook_slot *host_battery_level_changed_slot;
|
|
+ pa_hook_slot *host_operation_succeed_slot;
|
|
+ pa_hook_slot *host_operation_failed_slot;
|
|
+ pa_hook_slot *host_signal_strength_changed_slot;
|
|
+ pa_hook_slot *host_has_service_changed_slot;
|
|
+ pa_hook_slot *host_is_roaming_changed_slot;
|
|
+ pa_hook_slot *host_calls_changed_slot;
|
|
pa_upower_backend *upower;
|
|
pa_modemmanager_backend *modemmanager;
|
|
bool enable_shared_profiles;
|
|
@@ -63,6 +73,8 @@ struct pa_bluetooth_backend {
|
|
uint32_t cind_enabled_indicators;
|
|
pa_bluetooth_cops_t cops_format;
|
|
bool clip_call_line_reporting_enabled;
|
|
+ unsigned int cind_call_indicator;
|
|
+ unsigned int cind_call_setup_indicator;
|
|
|
|
PA_LLIST_HEAD(pa_dbus_pending, pending);
|
|
};
|
|
@@ -1172,6 +1184,206 @@ static pa_hook_result_t host_battery_level_changed_cb(pa_bluetooth_discovery *y,
|
|
return PA_HOOK_OK;
|
|
}
|
|
|
|
+static pa_hook_result_t host_operation_failed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) {
|
|
+ int rfcomm_fd;
|
|
+
|
|
+ pa_assert(y);
|
|
+ pa_assert(m);
|
|
+ pa_assert(b);
|
|
+
|
|
+ /* Get RFCOMM channel if available */
|
|
+ rfcomm_fd = get_rfcomm_fd (y);
|
|
+ if (rfcomm_fd < 0)
|
|
+ return PA_HOOK_OK;
|
|
+
|
|
+ /* Notify HF about AG operation failure over RFCOMM */
|
|
+ rfcomm_write_error(b, rfcomm_fd, CMEE_AG_FAILURE);
|
|
+
|
|
+ return PA_HOOK_OK;
|
|
+}
|
|
+
|
|
+static pa_hook_result_t host_operation_succeed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) {
|
|
+ int rfcomm_fd;
|
|
+
|
|
+ pa_assert(y);
|
|
+ pa_assert(m);
|
|
+ pa_assert(b);
|
|
+
|
|
+ /* Get RFCOMM channel if available */
|
|
+ rfcomm_fd = get_rfcomm_fd (y);
|
|
+ if (rfcomm_fd < 0)
|
|
+ return PA_HOOK_OK;
|
|
+
|
|
+ /* Notify HF about AG operation succeed over RFCOMM */
|
|
+ rfcomm_write_response(rfcomm_fd, "OK");
|
|
+
|
|
+ return PA_HOOK_OK;
|
|
+}
|
|
+
|
|
+static pa_hook_result_t host_signal_strength_changed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) {
|
|
+ int rfcomm_fd;
|
|
+
|
|
+ pa_assert(y);
|
|
+ pa_assert(m);
|
|
+ pa_assert(b);
|
|
+
|
|
+ /* Get RFCOMM channel if available */
|
|
+ rfcomm_fd = get_rfcomm_fd (y);
|
|
+ if (rfcomm_fd < 0)
|
|
+ return PA_HOOK_OK;
|
|
+
|
|
+ /* Notify HF about AG signal strength change */
|
|
+ if (b->cmer_indicator_reporting_enabled && (b->cind_enabled_indicators & (1 << CIND_SIGNAL_STRENGTH_INDICATOR))) {
|
|
+ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_SIGNAL_STRENGTH_INDICATOR, pa_modemmanager_get_signal_strength(m));
|
|
+ /* Skip notification if indicator is disabled or event reporting is completely disabled */
|
|
+ } else
|
|
+ pa_log_debug("Signal strength change indicator disabled, skipping notification");
|
|
+
|
|
+ return PA_HOOK_OK;
|
|
+}
|
|
+
|
|
+static pa_hook_result_t host_has_service_changed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) {
|
|
+ int rfcomm_fd;
|
|
+
|
|
+ pa_assert(y);
|
|
+ pa_assert(m);
|
|
+ pa_assert(b);
|
|
+
|
|
+ /* Get RFCOMM channel if available */
|
|
+ rfcomm_fd = get_rfcomm_fd (y);
|
|
+ if (rfcomm_fd < 0)
|
|
+ return PA_HOOK_OK;
|
|
+
|
|
+ /* Notify HF about AG cellular service status change */
|
|
+ if (b->cmer_indicator_reporting_enabled && (b->cind_enabled_indicators & (1 << CIND_SERVICE_INDICATOR))) {
|
|
+ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_SERVICE_INDICATOR, pa_modemmanager_has_service(m));
|
|
+ /* Skip notification if indicator is disabled or event reporting is completely disabled */
|
|
+ } else
|
|
+ pa_log_debug("Cellular service status change indicator disabled, skipping notification");
|
|
+
|
|
+ return PA_HOOK_OK;
|
|
+}
|
|
+
|
|
+static pa_hook_result_t host_is_roaming_changed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) {
|
|
+ int rfcomm_fd;
|
|
+
|
|
+ pa_assert(y);
|
|
+ pa_assert(m);
|
|
+ pa_assert(b);
|
|
+
|
|
+ /* Get RFCOMM channel if available */
|
|
+ rfcomm_fd = get_rfcomm_fd (y);
|
|
+ if (rfcomm_fd < 0)
|
|
+ return PA_HOOK_OK;
|
|
+
|
|
+ /* Notify HF about AG roaming status change */
|
|
+ if (b->cmer_indicator_reporting_enabled && (b->cind_enabled_indicators & (1 << CIND_ROAMING_INDICATOR))) {
|
|
+ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_ROAMING_INDICATOR, pa_modemmanager_is_roaming(m));
|
|
+ /* Skip notification if indicator is disabled or event reporting is completely disabled */
|
|
+ } else
|
|
+ pa_log_debug("Roaming status change indicator disabled, skipping notification");
|
|
+
|
|
+ return PA_HOOK_OK;
|
|
+}
|
|
+
|
|
+static void timer_ring_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
|
|
+ int rfcomm_fd;
|
|
+ unsigned int type;
|
|
+ pa_bluetooth_discovery *y = userdata;
|
|
+ pa_hashmap *calls;
|
|
+ call_status_t *call;
|
|
+
|
|
+ /* Get RFCOMM channel if available */
|
|
+ rfcomm_fd = get_rfcomm_fd (y);
|
|
+ if (rfcomm_fd < 0)
|
|
+ return;
|
|
+
|
|
+ calls = pa_modemmanager_get_calls(y->native_backend->modemmanager);
|
|
+
|
|
+ /* Stop ringing if no calls are available */
|
|
+ if (pa_hashmap_isempty(calls))
|
|
+ return;
|
|
+
|
|
+ /* FIXME: support three-way calling */
|
|
+ call = pa_hashmap_first(calls);
|
|
+
|
|
+ /* Send RING indicator */
|
|
+ rfcomm_write_response(rfcomm_fd, "RING");
|
|
+
|
|
+ /* If enabled, send +CLIP indications to HF about the caller */
|
|
+ if (y->native_backend->clip_call_line_reporting_enabled && call->number) {
|
|
+ /* International numbers start with '+' */
|
|
+ if (strncmp("+", call->number, 1) == 0)
|
|
+ type = CLIP_INTERNATIONAL_NUMBER;
|
|
+ else
|
|
+ type = CLIP_NATIONAL_NUMBER;
|
|
+ rfcomm_write_response(rfcomm_fd, "+CLIP: \"%s\",%d", call->number, type);
|
|
+ }
|
|
+
|
|
+ if (y->native_backend->timer_ring_event)
|
|
+ pa_core_rttime_restart(y->native_backend->core, y->native_backend->timer_ring_event, pa_rtclock_now() + RING_WAIT_TIME);
|
|
+}
|
|
+
|
|
+static pa_hook_result_t host_calls_changed_cb(pa_bluetooth_discovery *y, const pa_modemmanager_backend *m, pa_bluetooth_backend *b) {
|
|
+ int rfcomm_fd;
|
|
+ unsigned int call_indicator;
|
|
+ unsigned int call_setup_indicator;
|
|
+ pa_hashmap *calls;
|
|
+ call_status_t *call;
|
|
+
|
|
+ pa_assert(y);
|
|
+ pa_assert(m);
|
|
+ pa_assert(b);
|
|
+
|
|
+ /* Get RFCOMM channel if available */
|
|
+ rfcomm_fd = get_rfcomm_fd (y);
|
|
+ if (rfcomm_fd < 0)
|
|
+ return PA_HOOK_OK;
|
|
+
|
|
+ calls = pa_modemmanager_get_calls(m);
|
|
+ call = pa_hashmap_isempty(calls)? NULL: pa_hashmap_first(calls);
|
|
+
|
|
+ /* Notify HF about call indicator state, if changed */
|
|
+ if (call && call->status == PA_MODEMMANAGER_CALL_STATE_ACTIVE)
|
|
+ call_indicator = CIND_CALL_AT_LEAST_ONE;
|
|
+ else
|
|
+ call_indicator = CIND_CALL_NONE;
|
|
+
|
|
+ if (b->cind_call_indicator != call_indicator) {
|
|
+ b->cind_call_indicator = call_indicator;
|
|
+ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_CALL_INDICATOR, b->cind_call_indicator);
|
|
+ }
|
|
+
|
|
+ /* Notify HF about call setup indicator state, if changed */
|
|
+ if (call && call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING)
|
|
+ call_setup_indicator = CIND_CALL_SETUP_INCOMING;
|
|
+ else if (call && !call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_DIALING)
|
|
+ call_setup_indicator = CIND_CALL_SETUP_OUTGOING_DIALING;
|
|
+ else if (call && !call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING)
|
|
+ call_setup_indicator = CIND_CALL_SETUP_OUTGOING_ALERTING;
|
|
+ else
|
|
+ call_setup_indicator = CIND_CALL_SETUP_NONE;
|
|
+
|
|
+ if (b->cind_call_setup_indicator != call_setup_indicator) {
|
|
+ b->cind_call_setup_indicator = call_setup_indicator;
|
|
+ rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_CALL_SETUP_INDICATOR, b->cind_call_setup_indicator);
|
|
+ }
|
|
+
|
|
+ /* Start/stop ringing */
|
|
+ if (call && call->is_incoming && call->status == PA_MODEMMANAGER_CALL_STATE_RINGING) {
|
|
+ if (!b->timer_ring_event) {
|
|
+ pa_log_debug("RING indicator started");
|
|
+ b->timer_ring_event = pa_core_rttime_new(b->core, pa_rtclock_now(), timer_ring_cb, y);
|
|
+ }
|
|
+ } else if (b->timer_ring_event) {
|
|
+ pa_log_debug("RING indicator stopped");
|
|
+ b->core->mainloop->time_free(b->timer_ring_event);
|
|
+ b->timer_ring_event = NULL;
|
|
+ }
|
|
+
|
|
+ return PA_HOOK_OK;
|
|
+}
|
|
+
|
|
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_bluetooth_discovery *discovery = t->device->discovery;
|
|
@@ -1658,6 +1870,30 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d
|
|
pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), PA_HOOK_NORMAL,
|
|
(pa_hook_cb_t) host_battery_level_changed_cb, backend);
|
|
|
|
+ backend->host_operation_failed_slot =
|
|
+ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), PA_HOOK_NORMAL,
|
|
+ (pa_hook_cb_t) host_operation_failed_cb, backend);
|
|
+
|
|
+ backend->host_operation_succeed_slot =
|
|
+ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), PA_HOOK_NORMAL,
|
|
+ (pa_hook_cb_t) host_operation_succeed_cb, backend);
|
|
+
|
|
+ backend->host_signal_strength_changed_slot =
|
|
+ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_SIGNAL_STRENGTH_CHANGED), PA_HOOK_NORMAL,
|
|
+ (pa_hook_cb_t) host_signal_strength_changed_cb, backend);
|
|
+
|
|
+ backend->host_has_service_changed_slot =
|
|
+ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_HAS_SERVICE_CHANGED), PA_HOOK_NORMAL,
|
|
+ (pa_hook_cb_t) host_has_service_changed_cb, backend);
|
|
+
|
|
+ backend->host_is_roaming_changed_slot =
|
|
+ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_IS_ROAMING_CHANGED), PA_HOOK_NORMAL,
|
|
+ (pa_hook_cb_t) host_is_roaming_changed_cb, backend);
|
|
+
|
|
+ backend->host_calls_changed_slot =
|
|
+ pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), PA_HOOK_NORMAL,
|
|
+ (pa_hook_cb_t) host_calls_changed_cb, backend);
|
|
+
|
|
if (!backend->enable_hsp_hs && !backend->enable_hfp_hf)
|
|
pa_log_warn("Both HSP HS and HFP HF bluetooth profiles disabled in native backend. Native backend will not register for headset connections.");
|
|
|
|
@@ -1684,6 +1920,10 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d
|
|
/* CLIP is disabled by default */
|
|
backend->clip_call_line_reporting_enabled = false;
|
|
|
|
+ /* TODO: Initiate CIND call & callsetup indicators at startup */
|
|
+ backend->cind_call_indicator = CIND_CALL_NONE;
|
|
+ backend->cind_call_setup_indicator = CIND_CALL_SETUP_NONE;
|
|
+
|
|
return backend;
|
|
}
|
|
|
|
@@ -1698,6 +1938,24 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) {
|
|
if (backend->host_battery_level_changed_slot)
|
|
pa_hook_slot_free(backend->host_battery_level_changed_slot);
|
|
|
|
+ if (backend->host_operation_failed_slot)
|
|
+ pa_hook_slot_free(backend->host_operation_failed_slot);
|
|
+
|
|
+ if (backend->host_operation_succeed_slot)
|
|
+ pa_hook_slot_free(backend->host_operation_succeed_slot);
|
|
+
|
|
+ if (backend->host_signal_strength_changed_slot)
|
|
+ pa_hook_slot_free(backend->host_signal_strength_changed_slot);
|
|
+
|
|
+ if (backend->host_has_service_changed_slot)
|
|
+ pa_hook_slot_free(backend->host_has_service_changed_slot);
|
|
+
|
|
+ if (backend->host_is_roaming_changed_slot)
|
|
+ pa_hook_slot_free(backend->host_is_roaming_changed_slot);
|
|
+
|
|
+ if (backend->host_calls_changed_slot)
|
|
+ pa_hook_slot_free(backend->host_calls_changed_slot);
|
|
+
|
|
if (backend->enable_shared_profiles)
|
|
native_backend_apply_profile_registration_change(backend, false);
|
|
|
|
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
|
|
index fb88f00e2..35b3153b8 100644
|
|
--- a/src/modules/bluetooth/bluez5-util.h
|
|
+++ b/src/modules/bluetooth/bluez5-util.h
|
|
@@ -242,6 +242,18 @@ typedef enum pa_bluetooth_clcc {
|
|
CLCC_INCOMING = 4
|
|
} pa_bluetooth_clcc_t;
|
|
|
|
+typedef enum pa_bluetooth_cind_call {
|
|
+ CIND_CALL_NONE = 0,
|
|
+ CIND_CALL_AT_LEAST_ONE = 1,
|
|
+} pa_bluetooth_cind_call_t;
|
|
+
|
|
+typedef enum pa_bluetooth_cind_call_setup {
|
|
+ CIND_CALL_SETUP_NONE = 0,
|
|
+ CIND_CALL_SETUP_INCOMING = 1,
|
|
+ CIND_CALL_SETUP_OUTGOING_DIALING = 2,
|
|
+ CIND_CALL_SETUP_OUTGOING_ALERTING = 3
|
|
+} pa_bluetooth_cind_call_setup_t;
|
|
+
|
|
#ifdef HAVE_BLUEZ_5_OFONO_HEADSET
|
|
pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y);
|
|
void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b);
|
|
--
|
|
2.35.1
|
|
|