From 6861de91294c8bed206e8338d1074ebc57683ae4 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche 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 #include #include +#include +#include #include #include @@ -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