All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Frédéric Danis" <frederic.danis@collabora.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v2 2/3] audio/hfp-hf: Add simple call support
Date: Fri, 19 Dec 2025 15:29:01 +0100	[thread overview]
Message-ID: <20251219142902.559714-2-frederic.danis@collabora.com> (raw)
In-Reply-To: <20251219142902.559714-1-frederic.danis@collabora.com>

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


  reply	other threads:[~2025-12-19 14:29 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251219142902.559714-2-frederic.danis@collabora.com \
    --to=frederic.danis@collabora.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.