pmaports/temp/pulseaudio/0021-bluetooth-support-CIEV-RING-and-CLIP-URCs.patch

366 lines
14 KiB
Diff
Raw Normal View History

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