From 4a89a13bda2c68bf58b4517cc381702b099d2afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 16 Sep 2022 15:17:04 +0200 Subject: [PATCH] bluez5: backend-native: Support of ATA and AT+CHUP Allow to answer, reject or terminate a call. Answering or rejecting a call can only be done on an incoming call. Terminating a call can only be done on active, dialing or alerting call. --- spa/plugins/bluez5/backend-native.c | 48 +++++++++ spa/plugins/bluez5/modemmanager.c | 161 ++++++++++++++++++++++++++++ spa/plugins/bluez5/modemmanager.h | 29 +++++ 3 files changed, 238 insertions(+) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 6ad075ac0..dc6a86d59 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -799,6 +799,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) } /* send reply to HF with the features supported by Audio Gateway (=computer) */ + ag_features |= mm_supported_features(); ag_features |= SPA_BT_HFP_AG_FEATURE_HF_INDICATORS; rfcomm_send_reply(rfcomm, "+BRSF: %u", ag_features); rfcomm_send_reply(rfcomm, "OK"); @@ -1024,6 +1025,40 @@ next_indicator: } else if (spa_strstartswith(buf, "AT+APLSIRI?")) { // This command is sent when we activate Apple extensions rfcomm_send_reply(rfcomm, "OK"); + } else if (!mm_is_available(backend->modemmanager)) { + spa_log_warn(backend->log, "RFCOMM receive command but modem not available: %s", buf); + rfcomm_send_error(rfcomm, CMEE_NO_CONNECTION_TO_PHONE); + return true; + + /* ***** + * Following commands requires a Service Level Connection + * and acces to a modem + * ***** */ + + } else if (!backend->modem.network_has_service) { + spa_log_warn(backend->log, "RFCOMM receive command but network not available: %s", buf); + rfcomm_send_error(rfcomm, CMEE_NO_NETWORK_SERVICE); + return true; + + /* ***** + * Following commands requires a Service Level Connection, + * acces to a modem and to the network + * ***** */ + + } else if (spa_strstartswith(buf, "ATA")) { + enum cmee_error error; + + if (!mm_answer_call(backend->modemmanager, rfcomm, &error)) { + rfcomm_send_error(rfcomm, error); + return true; + } + } else if (spa_strstartswith(buf, "AT+CHUP")) { + enum cmee_error error; + + if (!mm_hangup_call(backend->modemmanager, rfcomm, &error)) { + rfcomm_send_error(rfcomm, error); + return true; + } } else { return false; } @@ -2450,6 +2485,18 @@ static void set_modem_signal_strength(unsigned int strength, void *user_data) } } +static void send_cmd_result(bool success, enum cmee_error error, void *user_data) +{ + struct rfcomm *rfcomm = user_data; + + if (success) { + rfcomm_send_reply(rfcomm, "OK"); + return; + } + + rfcomm_send_error(rfcomm, error); +} + static int backend_native_free(void *data) { struct impl *backend = data; @@ -2516,6 +2563,7 @@ static const struct spa_bt_backend_implementation backend_impl = { }; static const struct mm_ops mm_ops = { + .send_cmd_result = send_cmd_result, .set_modem_service = set_modem_service, .set_modem_signal_strength = set_modem_signal_strength, .set_modem_operator_name = set_modem_operator_name, diff --git a/spa/plugins/bluez5/modemmanager.c b/spa/plugins/bluez5/modemmanager.c index 3f0030bcb..66fd98438 100644 --- a/spa/plugins/bluez5/modemmanager.c +++ b/spa/plugins/bluez5/modemmanager.c @@ -65,6 +65,12 @@ struct impl { struct spa_list call_list; }; +struct dbus_cmd_data { + struct impl *this; + struct call *call; + void *user_data; +}; + static bool mm_dbus_connection_send_with_reply(struct impl *this, DBusMessage *m, DBusPendingCall **pending_return, DBusPendingCallNotifyFunction function, void *user_data) { @@ -738,6 +744,161 @@ finish: return success; } +bool mm_is_available(void *modemmanager) +{ + struct impl *this = modemmanager; + + if (this == NULL) + return false; + + return this->modem.path != NULL; +} + +unsigned int mm_supported_features() +{ + return SPA_BT_HFP_AG_FEATURE_REJECT_CALL; +} + +static void mm_get_call_simple_reply(DBusPendingCall *pending, void *data) +{ + struct dbus_cmd_data *dbus_cmd_data = data; + struct impl *this = dbus_cmd_data->this; + struct call *call = dbus_cmd_data->call; + void *user_data = dbus_cmd_data->user_data; + DBusMessage *r; + + free(data); + + 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 method not available"); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(this->log, "ModemManager method failed: %s", dbus_message_get_error_name(r)); + goto finish; + } + + this->ops->send_cmd_result(true, 0, user_data); + return; + +finish: + this->ops->send_cmd_result(false, CMEE_AG_FAILURE, user_data); +} + +bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error) +{ + struct impl *this = modemmanager; + struct call *call_object, *call_tmp; + struct dbus_cmd_data *data; + DBusMessage *m; + + call_object = NULL; + spa_list_for_each(call_tmp, &this->call_list, link) { + if (call_tmp->state == MM_CALL_STATE_RINGING_IN) { + call_object = call_tmp; + break; + } + } + if (!call_object) { + spa_log_debug(this->log, "No ringing in call"); + if (error) + *error = CMEE_OPERATION_NOT_ALLOWED; + return false; + } + + data = malloc(sizeof(struct dbus_cmd_data)); + if (!data) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + data->this = this; + data->call = call_object; + data->user_data = user_data; + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object->path, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_ACCEPT); + if (m == NULL) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + if (!mm_dbus_connection_send_with_reply(this, m, &call_object->pending, mm_get_call_simple_reply, data)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + + return true; +} + +bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error) +{ + struct impl *this = modemmanager; + struct call *call_object, *call_tmp; + struct dbus_cmd_data *data; + DBusMessage *m; + + call_object = NULL; + spa_list_for_each(call_tmp, &this->call_list, link) { + if (call_tmp->state == MM_CALL_STATE_ACTIVE) { + call_object = call_tmp; + break; + } + } + if (!call_object) { + spa_list_for_each(call_tmp, &this->call_list, link) { + if (call_tmp->state == MM_CALL_STATE_RINGING_OUT || + call_tmp->state == MM_CALL_STATE_RINGING_IN || + call_tmp->state == MM_CALL_STATE_DIALING) { + call_object = call_tmp; + break; + } + } + } + if (!call_object) { + spa_log_debug(this->log, "No call to reject or hang up"); + if (error) + *error = CMEE_OPERATION_NOT_ALLOWED; + return false; + } + + data = malloc(sizeof(struct dbus_cmd_data)); + if (!data) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + data->this = this; + data->call = call_object; + data->user_data = user_data; + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object->path, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_HANGUP); + if (m == NULL) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + if (!mm_dbus_connection_send_with_reply(this, m, &call_object->pending, mm_get_call_simple_reply, data)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + + return true; +} + void *mm_register(struct spa_log *log, void *dbus_connection, const struct mm_ops *ops, void *user_data) { struct impl *this; diff --git a/spa/plugins/bluez5/modemmanager.h b/spa/plugins/bluez5/modemmanager.h index 401e584ba..3631c345f 100644 --- a/spa/plugins/bluez5/modemmanager.h +++ b/spa/plugins/bluez5/modemmanager.h @@ -45,6 +45,7 @@ enum call_setup { }; struct mm_ops { + void (*send_cmd_result)(bool success, enum cmee_error error, void *user_data); 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); @@ -56,6 +57,10 @@ struct mm_ops { #ifdef HAVE_BLUEZ_5_BACKEND_NATIVE_MM void *mm_register(struct spa_log *log, void *dbus_connection, const struct mm_ops *ops, void *user_data); void mm_unregister(void *data); +bool mm_is_available(void *modemmanager); +unsigned int mm_supported_features(); +bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error); +bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error); #else void *mm_register(struct spa_log *log, void *dbus_connection, const struct mm_ops *ops, void *user_data) { @@ -65,6 +70,30 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct mm_op void mm_unregister(void *data) { } + +bool mm_is_available(void *modemmanager) +{ + return false; +} + +unsigned int mm_supported_features() +{ + return 0; +} + +bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error) +{ + if (error) + *error = CMEE_OPERATION_NOT_SUPPORTED; + return false; +} + +bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error) +{ + if (error) + *error = CMEE_OPERATION_NOT_SUPPORTED; + return false; +} #endif #endif