From 5b40ed62b4f121d60726e6548cf5e95b605c816b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 16 Sep 2022 15:02:25 +0200 Subject: [PATCH] bluez5: backend-native: Link with ModemManager Voice object The Voice object lists the Call objects, which provides status of each call. +CIND call indicator is set if at least one of the call is active. +CIND callsetup indicator is set if one of the call is in ringing in or out or dialing state. --- spa/plugins/bluez5/backend-native.c | 66 +++++++- spa/plugins/bluez5/modemmanager.c | 232 ++++++++++++++++++++++++++++ spa/plugins/bluez5/modemmanager.h | 9 ++ 3 files changed, 304 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 9d29e8b00..8da0c98d3 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -84,6 +84,8 @@ struct modem { unsigned int signal_strength; bool network_is_roaming; char *operator_name; + bool active_call; + unsigned int call_setup; }; struct impl { @@ -111,6 +113,7 @@ struct impl { struct modem modem; void *modemmanager; + struct spa_source *ring_timer; }; struct transport_data { @@ -816,9 +819,9 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) rfcomm_send_reply(rfcomm, "+CIND:%s", CIND_INDICATORS); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CIND?")) { - rfcomm_send_reply(rfcomm, "+CIND: %d,%d,0,0,%d,%d", backend->modem.network_has_service, - rfcomm->cind_call_active, - backend->modem.signal_strength, backend->modem.network_is_roaming); + rfcomm_send_reply(rfcomm, "+CIND: %d,%d,%d,0,%d,%d", backend->modem.network_has_service, + backend->modem.active_call, backend->modem.call_setup, backend->modem.signal_strength, + backend->modem.network_is_roaming); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CMER")) { int mode, keyp, disp, ind; @@ -2333,6 +2336,58 @@ static void send_ciev_for_each_rfcomm(struct impl *backend, int indicator, int v } } +static void ring_timer_event(void *data, uint64_t expirations) +{ + struct impl *backend = data; + struct timespec ts; + const uint64_t timeout = 1 * SPA_NSEC_PER_SEC; + struct rfcomm *rfcomm; + + ts.tv_sec = timeout / SPA_NSEC_PER_SEC; + ts.tv_nsec = timeout % SPA_NSEC_PER_SEC; + spa_loop_utils_update_timer(backend->loop_utils, backend->ring_timer, &ts, NULL, false); + + spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { + if (rfcomm->slc_configured) + rfcomm_send_reply(rfcomm, "RING"); + } +} + +static void set_call_active(bool active, void *user_data) +{ + struct impl *backend = user_data; + + if (backend->modem.active_call != active) { + backend->modem.active_call = active; + send_ciev_for_each_rfcomm(backend, CIND_CALL, active); + } +} + +static void set_call_setup(enum call_setup value, void *user_data) +{ + struct impl *backend = user_data; + enum call_setup old = backend->modem.call_setup; + + if (backend->modem.call_setup != value) { + backend->modem.call_setup = value; + send_ciev_for_each_rfcomm(backend, CIND_CALLSETUP, value); + } + + if (value == CIND_CALLSETUP_INCOMING) { + if (backend->ring_timer == NULL) + backend->ring_timer = spa_loop_utils_add_timer(backend->loop_utils, ring_timer_event, backend); + + if (backend->ring_timer == NULL) { + spa_log_warn(backend->log, "Failed to create ring timer"); + return; + } + + ring_timer_event(backend, 0); + } else if (old == CIND_CALLSETUP_INCOMING) { + spa_loop_utils_update_timer(backend->loop_utils, backend->ring_timer, NULL, NULL, false); + } +} + static void set_modem_operator_name(const char *name, void *user_data) { struct impl *backend = user_data; @@ -2389,6 +2444,9 @@ static int backend_native_free(void *data) backend->modemmanager = NULL; } + if (backend->ring_timer) + spa_loop_utils_destroy_source(backend->loop_utils, backend->ring_timer); + #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE dbus_connection_unregister_object_path(backend->conn, PROFILE_HSP_AG); dbus_connection_unregister_object_path(backend->conn, PROFILE_HSP_HS); @@ -2443,6 +2501,8 @@ static const struct mm_ops mm_ops = { .set_modem_signal_strength = set_modem_signal_strength, .set_modem_operator_name = set_modem_operator_name, .set_modem_roaming = set_modem_roaming, + .set_call_active = set_call_active, + .set_call_setup = set_call_setup, }; struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, diff --git a/spa/plugins/bluez5/modemmanager.c b/spa/plugins/bluez5/modemmanager.c index b231d35f7..f6717672b 100644 --- a/spa/plugins/bluez5/modemmanager.c +++ b/spa/plugins/bluez5/modemmanager.c @@ -31,6 +31,18 @@ #define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager" +struct call { + struct spa_list link; + struct impl *this; + DBusPendingCall *pending; + + char *path; + char *number; + bool call_indicator; + MMCallDirection direction; + MMCallState state; +}; + struct modem { char *path; bool network_has_service; @@ -50,6 +62,7 @@ struct impl { void *user_data; struct modem modem; + struct spa_list call_list; }; static bool mm_dbus_connection_send_with_reply(struct impl *this, DBusMessage *m, DBusPendingCall **pending_return, @@ -77,6 +90,132 @@ static bool mm_dbus_connection_send_with_reply(struct impl *this, DBusMessage *m return true; } +static void mm_call_state_changed(struct impl *this) +{ + struct call *call; + bool call_indicator = false; + enum call_setup call_setup_indicator = CIND_CALLSETUP_NONE; + + spa_list_for_each(call, &this->call_list, link) { + call_indicator |= (call->state == MM_CALL_STATE_ACTIVE); + + if (call->state == MM_CALL_STATE_RINGING_IN && call_setup_indicator < CIND_CALLSETUP_INCOMING) + call_setup_indicator = CIND_CALLSETUP_INCOMING; + else if (call->state == MM_CALL_STATE_DIALING && call_setup_indicator < CIND_CALLSETUP_DIALING) + call_setup_indicator = CIND_CALLSETUP_DIALING; + else if (call->state == MM_CALL_STATE_RINGING_OUT && call_setup_indicator < CIND_CALLSETUP_ALERTING) + call_setup_indicator = CIND_CALLSETUP_ALERTING; + } + + if (this->ops->set_call_active) + this->ops->set_call_active(call_indicator, this->user_data); + + if (this->ops->set_call_setup) + this->ops->set_call_setup(call_setup_indicator, this->user_data); +} + +static void mm_get_call_properties_reply(DBusPendingCall *pending, void *user_data) +{ + struct call *call = user_data; + struct impl *this = call->this; + DBusMessage *r; + DBusMessageIter arg_i, element_i; + MMCallDirection direction; + MMCallState state; + + spa_assert(call->pending == pending); + dbus_pending_call_unref(pending); + call->pending = NULL; + + r = dbus_pending_call_steal_reply(pending); + if (r == NULL) + return; + + if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { + spa_log_warn(this->log, "ModemManager D-Bus Call not available"); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(this->log, "GetAll() failed: %s", dbus_message_get_error_name(r)); + goto finish; + } + + if (!dbus_message_iter_init(r, &arg_i) || !spa_streq(dbus_message_get_signature(r), "a{sv}")) { + spa_log_error(this->log, "Invalid arguments in GetAll() reply"); + goto finish; + } + + spa_log_debug(this->log, "Call path: %s", call->path); + + dbus_message_iter_recurse(&arg_i, &element_i); + while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) { + DBusMessageIter i, value_i; + const char *key; + + dbus_message_iter_recurse(&element_i, &i); + + dbus_message_iter_get_basic(&i, &key); + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &value_i); + + if (spa_streq(key, MM_CALL_PROPERTY_DIRECTION)) { + dbus_message_iter_get_basic(&value_i, &direction); + spa_log_debug(this->log, "Call direction: %u", direction); + call->direction = direction; + } else if (spa_streq(key, MM_CALL_PROPERTY_NUMBER)) { + char *number; + + dbus_message_iter_get_basic(&value_i, &number); + spa_log_debug(this->log, "Call number: %s", number); + if (call->number) + free(call->number); + call->number = strdup(number); + } else if (spa_streq(key, MM_CALL_PROPERTY_STATE)) { + dbus_message_iter_get_basic(&value_i, &state); + spa_log_debug(this->log, "Call state: %u", state); + call->state = state; + mm_call_state_changed(this); + } + + dbus_message_iter_next(&element_i); + } + +finish: + dbus_message_unref(r); +} + +static DBusHandlerResult mm_parse_voice_properties(struct impl *this, DBusMessageIter *props_i) +{ + while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { + DBusMessageIter i, value_i, element_i; + const char *key; + + dbus_message_iter_recurse(props_i, &i); + + dbus_message_iter_get_basic(&i, &key); + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &value_i); + + if (spa_streq(key, MM_MODEM_VOICE_PROPERTY_CALLS)) { + spa_log_debug(this->log, "Voice properties"); + dbus_message_iter_recurse(&value_i, &element_i); + + while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_OBJECT_PATH) { + const char *call_object; + + dbus_message_iter_get_basic(&element_i, &call_object); + spa_log_debug(this->log, " Call: %s", call_object); + + dbus_message_iter_next(&element_i); + } + } + + dbus_message_iter_next(props_i); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + static DBusHandlerResult mm_parse_modem3gpp_properties(struct impl *this, DBusMessageIter *props_i) { while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { @@ -221,6 +360,9 @@ static DBusHandlerResult mm_parse_interfaces(struct impl *this, DBusMessageIter } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { spa_log_debug(this->log, "Found Modem3GPP interface %s, path %s", interface, path); mm_parse_modem3gpp_properties(this, &props_i); + } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_VOICE)) { + spa_log_debug(this->log, "Found Voice interface %s, path %s", interface, path); + mm_parse_voice_properties(this, &props_i); } dbus_message_iter_next(&element_i); @@ -267,6 +409,33 @@ finish: dbus_message_unref(r); } +static void call_free(struct call *call) { + spa_list_remove(&call->link); + + if (call->pending != NULL) { + dbus_pending_call_cancel(call->pending); + dbus_pending_call_unref(call->pending); + } + + if (call->number) + free(call->number); + if (call->path) + free(call->path); + free(call); +} + +static void mm_clean_voice(struct impl *this) +{ + struct call *call; + + spa_list_consume(call, &this->call_list, link) + call_free(call); + if (this->ops->set_call_setup) + this->ops->set_call_setup(CIND_CALLSETUP_NONE, this->user_data); + if (this->ops->set_call_active) + this->ops->set_call_active(false, this->user_data); +} + static void mm_clean_modem3gpp(struct impl *this) { if (this->ops->set_modem_operator_name) @@ -312,6 +481,7 @@ static DBusHandlerResult mm_filter_cb(DBusConnection *bus, DBusMessage *m, void if (spa_streq(name, MM_DBUS_SERVICE)) { if (old_owner && *old_owner) { spa_log_debug(this->log, "ModemManager daemon disappeared (%s)", old_owner); + mm_clean_voice(this); mm_clean_modem3gpp(this); mm_clean_modem(this); } @@ -355,6 +525,9 @@ static DBusHandlerResult mm_filter_cb(DBusConnection *bus, DBusMessage *m, void } else if (spa_streq(iface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { spa_log_debug(this->log, "Modem3GPP interface %s removed, path %s", iface, path); mm_clean_modem3gpp(this); + } else if (spa_streq(iface, MM_DBUS_INTERFACE_MODEM_VOICE)) { + spa_log_debug(this->log, "Voice interface %s removed, path %s", iface, path); + mm_clean_voice(this); } dbus_message_iter_next(&element_i); @@ -383,7 +556,58 @@ static DBusHandlerResult mm_filter_cb(DBusConnection *bus, DBusMessage *m, void } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { spa_log_debug(this->log, "Properties changed on %s", path); mm_parse_modem3gpp_properties(this, &props_i); + } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_VOICE)) { + spa_log_debug(this->log, "Properties changed on %s", path); + mm_parse_voice_properties(this, &props_i); } + } else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLADDED)) { + DBusMessageIter iface_i; + const char *path; + struct call *call_object; + const char *mm_call_interface = MM_DBUS_INTERFACE_CALL; + + if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "o")) { + spa_log_error(this->log, "Invalid signature found in %s", MM_MODEM_VOICE_SIGNAL_CALLADDED); + goto finish; + } + + dbus_message_iter_get_basic(&iface_i, &path); + spa_log_debug(this->log, "New call: %s", path); + + call_object = calloc(1, sizeof(struct call)); + if (call_object == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + call_object->this = this; + call_object->path = strdup(path); + spa_list_append(&this->call_list, &call_object->link); + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, path, DBUS_INTERFACE_PROPERTIES, "GetAll"); + if (m == NULL) + goto finish; + dbus_message_append_args(m, DBUS_TYPE_STRING, &mm_call_interface, DBUS_TYPE_INVALID); + if (!mm_dbus_connection_send_with_reply(this, m, &call_object->pending, mm_get_call_properties_reply, call_object)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + goto finish; + } + } else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLDELETED)) { + const char *path; + DBusMessageIter iface_i; + struct call *call, *call_tmp; + + if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "o")) { + spa_log_error(this->log, "Invalid signature found in %s", MM_MODEM_VOICE_SIGNAL_CALLDELETED); + goto finish; + } + + dbus_message_iter_get_basic(&iface_i, &path); + spa_log_debug(this->log, "Call ended: %s", path); + + spa_list_for_each_safe(call, call_tmp, &this->call_list, link) { + if (spa_streq(call->path, path)) + call_free(call); + } + mm_call_state_changed(this); } finish: @@ -416,6 +640,12 @@ static int add_filters(struct impl *this) dbus_bus_add_match(this->conn, "type='signal',sender='" MM_DBUS_SERVICE "'," "interface='" DBUS_INTERFACE_PROPERTIES "',member='" DBUS_SIGNAL_PROPERTIES_CHANGED "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" MM_DBUS_SERVICE "'," + "interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLADDED "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" MM_DBUS_SERVICE "'," + "interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLDELETED "'", &err); this->filters_added = true; @@ -484,6 +714,7 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct mm_op this->conn = dbus_connection; this->ops = ops; this->user_data = user_data; + spa_list_init(&this->call_list); if (add_filters(this) < 0) { goto fail; @@ -520,6 +751,7 @@ void mm_unregister(void *data) dbus_pending_call_unref(this->pending); } + mm_clean_voice(this); mm_clean_modem3gpp(this); mm_clean_modem(this); diff --git a/spa/plugins/bluez5/modemmanager.h b/spa/plugins/bluez5/modemmanager.h index 550573ecf..558852a06 100644 --- a/spa/plugins/bluez5/modemmanager.h +++ b/spa/plugins/bluez5/modemmanager.h @@ -27,11 +27,20 @@ #include "defs.h" +enum call_setup { + CIND_CALLSETUP_NONE = 0, + CIND_CALLSETUP_INCOMING, + CIND_CALLSETUP_DIALING, + CIND_CALLSETUP_ALERTING +}; + struct mm_ops { void (*set_modem_service)(bool available, void *user_data); void (*set_modem_signal_strength)(unsigned int strength, void *user_data); void (*set_modem_operator_name)(const char *name, void *user_data); void (*set_modem_roaming)(bool is_roaming, void *user_data); + void (*set_call_active)(bool active, void *user_data); + void (*set_call_setup)(enum call_setup value, void *user_data); }; #ifdef HAVE_BLUEZ_5_BACKEND_NATIVE_MM