* [PATCH BlueZ v2 2/3] audio/hfp-hf: Add simple call support
2025-12-19 14:29 [PATCH BlueZ v2 1/3] audio/hfp-hf: Add Operator name support Frédéric Danis
@ 2025-12-19 14:29 ` Frédéric Danis
2025-12-19 14:29 ` [PATCH BlueZ v2 3/3] audio/hfp-hf: Add in-band ringtone flag support Frédéric Danis
2025-12-19 15:27 ` [BlueZ,v2,1/3] audio/hfp-hf: Add Operator name support bluez.test.bot
2 siblings, 0 replies; 4+ messages in thread
From: Frédéric Danis @ 2025-12-19 14:29 UTC (permalink / raw)
To: linux-bluetooth
This allows to dial, hang-up, answer or reject a call.
---
v1->v2:
- Replace GSList by struct queue
- Set remote number property if available on call interface creation
profiles/audio/hfp-hf.c | 186 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 186 insertions(+)
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 384480c9e..a3263c018 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -36,11 +36,13 @@
#include "src/btd.h"
#include "src/dbus-common.h"
#include "src/device.h"
+#include "src/error.h"
#include "src/log.h"
#include "src/plugin.h"
#include "src/profile.h"
#include "src/service.h"
#include "src/shared/hfp.h"
+#include "src/shared/queue.h"
#include "telephony.h"
@@ -52,6 +54,7 @@ struct hfp_device {
uint16_t version;
GIOChannel *io;
struct hfp_hf *hf;
+ struct queue *calls;
};
static void hfp_hf_debug(const char *str, void *user_data)
@@ -59,6 +62,33 @@ static void hfp_hf_debug(const char *str, void *user_data)
DBG_IDX(0xffff, "%s", str);
}
+static enum call_state hfp_call_status_to_call_state(
+ enum hfp_call_status status)
+{
+ switch (status) {
+ case CALL_STATUS_ACTIVE: return CALL_STATE_ACTIVE; break;
+ case CALL_STATUS_HELD: return CALL_STATE_HELD; break;
+ case CALL_STATUS_DIALING: return CALL_STATE_DIALING; break;
+ case CALL_STATUS_ALERTING: return CALL_STATE_ALERTING; break;
+ case CALL_STATUS_INCOMING: return CALL_STATE_INCOMING; break;
+ case CALL_STATUS_WAITING: return CALL_STATE_WAITING; break;
+ case CALL_STATUS_RESPONSE_AND_HOLD:
+ return CALL_STATE_RESPONSE_AND_HOLD; break;
+ default:
+ DBG("Unknown hfp_call_status: %u", status);
+ }
+
+ return CALL_STATE_DISCONNECTED;
+}
+
+static bool call_id_cmp(const void *data, const void *match_data)
+{
+ struct call *call = (struct call *) data;
+ uint16_t id = GPOINTER_TO_UINT(match_data);
+
+ return call->idx == id;
+}
+
static void device_destroy(struct hfp_device *dev)
{
DBG("%s", telephony_get_path(dev->telephony));
@@ -115,6 +145,42 @@ static void hfp_hf_update_operator(const char *operator_name, void *user_data)
telephony_set_operator_name(dev->telephony, operator_name);
}
+static void hfp_hf_call_added(uint id, enum hfp_call_status status,
+ void *user_data)
+{
+ struct hfp_device *dev = user_data;
+ struct call *call;
+ const char *number;
+
+ call = telephony_new_call(dev->telephony, id,
+ hfp_call_status_to_call_state(status),
+ NULL);
+ if ((number = hfp_hf_call_get_number(dev->hf, id)) != NULL)
+ call->line_id = g_strdup(number);
+ if (telephony_call_register_interface(call)) {
+ telephony_free_call(call);
+ return;
+ }
+
+ queue_push_tail(dev->calls, call);
+}
+
+static void hfp_hf_call_removed(uint id, void *user_data)
+{
+ struct hfp_device *dev = user_data;
+ struct call *call;
+
+ call = queue_find(dev->calls, call_id_cmp, GUINT_TO_POINTER(id));
+ if (!call) {
+ DBG("Unknown call id: %u", id);
+ return;
+ }
+
+ telephony_call_set_state(call, CALL_STATE_DISCONNECTED);
+ queue_remove(dev->calls, call);
+ telephony_call_unregister_interface(call);
+}
+
static void hfp_hf_session_ready_cb(enum hfp_result res, enum hfp_error cme_err,
void *user_data)
{
@@ -129,16 +195,56 @@ static void hfp_hf_session_ready_cb(enum hfp_result res, enum hfp_error cme_err,
telephony_set_state(dev->telephony, CONNECTED);
}
+static void hfp_hf_call_status_updated(uint id, enum hfp_call_status status,
+ void *user_data)
+{
+ struct hfp_device *dev = user_data;
+ struct call *call;
+
+ call = queue_find(dev->calls, call_id_cmp, GUINT_TO_POINTER(id));
+ if (!call) {
+ DBG("Unknown call id: %u", id);
+ return;
+ }
+
+ telephony_call_set_state(call, hfp_call_status_to_call_state(status));
+}
+
+static void hfp_hf_call_line_id_updated(uint id, const char *number,
+ unsigned int type,
+ void *user_data)
+{
+ struct hfp_device *dev = user_data;
+ struct call *call;
+
+ call = queue_find(dev->calls, call_id_cmp, GUINT_TO_POINTER(id));
+ if (!call) {
+ DBG("Unknown call id: %u", id);
+ return;
+ }
+
+ telephony_call_set_line_id(call, number);
+}
+
static struct hfp_hf_callbacks hf_session_callbacks = {
.session_ready = hfp_hf_session_ready_cb,
.update_indicator = hfp_hf_update_indicator,
.update_operator = hfp_hf_update_operator,
+ .call_added = hfp_hf_call_added,
+ .call_removed = hfp_hf_call_removed,
+ .call_status_updated = hfp_hf_call_status_updated,
+ .call_line_id_updated = hfp_hf_call_line_id_updated,
};
static void hfp_disconnect_watch(void *user_data)
{
+ struct hfp_device *dev = user_data;
+
DBG("");
+ queue_destroy(dev->calls, NULL);
+ dev->calls = NULL;
+
device_destroy(user_data);
}
@@ -168,6 +274,8 @@ static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
dev, NULL);
hfp_hf_session_register(dev->hf, &hf_session_callbacks, dev);
+ dev->calls = queue_new();
+
if (!hfp_hf_session(dev->hf)) {
error("Could not start SLC creation");
hfp_hf_disconnect(dev->hf);
@@ -184,7 +292,85 @@ failed:
device_destroy(dev);
}
+static void cmd_complete(enum hfp_result res, enum hfp_error cme_err,
+ void *user_data)
+{
+ DBusMessage *msg = user_data;
+
+ if (res != HFP_RESULT_OK) {
+ DBusMessage *reply;
+ const char *name = dbus_message_get_member(msg);
+
+ error("Command %s error: %d", name, res);
+ reply = g_dbus_create_error(msg, ERROR_INTERFACE
+ ".Failed",
+ "Command %s failed: %d", name, res);
+ g_dbus_send_message(btd_get_dbus_connection(), reply);
+ dbus_message_unref(msg);
+ return;
+ }
+
+ g_dbus_send_reply(btd_get_dbus_connection(), msg, DBUS_TYPE_INVALID);
+ dbus_message_unref(msg);
+}
+
+static DBusMessage *dial(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data)
+{
+ struct hfp_device *dev = profile_data;
+ const char *number;
+ bool ret;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID)) {
+ return btd_error_invalid_args(msg);
+ }
+
+ if (strncmp(number, URI_PREFIX, strlen(URI_PREFIX)) != 0)
+ return btd_error_invalid_args(msg);
+
+ ret = hfp_hf_dial(dev->hf, number + strlen(URI_PREFIX), cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg, "Dial command failed");
+
+ return NULL;
+}
+
+static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg,
+ void *call_data)
+{
+ struct call *call = call_data;
+ struct hfp_device *dev = telephony_get_profile_data(call->device);
+ bool ret;
+
+ ret = hfp_hf_call_answer(dev->hf, call->idx, cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg, "Answer call command failed");
+
+ return NULL;
+}
+
+static DBusMessage *call_hangup(DBusConnection *conn, DBusMessage *msg,
+ void *call_data)
+{
+ struct call *call = call_data;
+ struct hfp_device *dev = telephony_get_profile_data(call->device);
+ bool ret;
+
+ ret = hfp_hf_call_hangup(dev->hf, call->idx, cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg, "Hangup call command failed");
+
+ return NULL;
+}
+
struct telephony_callbacks hfp_callbacks = {
+ .dial = dial,
+ .call_answer = call_answer,
+ .call_hangup = call_hangup,
};
static int hfp_connect(struct btd_service *service)
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread