366 lines
14 KiB
Diff
366 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
|
||
|
|