From: "Frédéric Danis" <frederic.danis@collabora.com>
To: linux-bluetooth@vger.kernel.org
Subject: [RFC BlueZ v2 27/27] audio/hfp-hf: Enable enhanced call status if supported by remote AG
Date: Fri, 27 Jun 2025 16:51:36 +0200 [thread overview]
Message-ID: <20250627145136.421853-28-frederic.danis@collabora.com> (raw)
In-Reply-To: <20250627145136.421853-1-frederic.danis@collabora.com>
On reception of OK result from AT+CLCC command, and based on the calls
listed by +CLCC results, this updates, removes or creates calls.
---
profiles/audio/hfp-hf.c | 197 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 191 insertions(+), 6 deletions(-)
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 5542bce96..5f03556dd 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -147,6 +147,15 @@ struct indicator {
ciev_func_t cb;
};
+struct clcc_entry {
+ uint8_t idx;
+ uint8_t direction;
+ uint8_t status;
+ uint8_t mode;
+ uint8_t multiparty;
+ char *number;
+};
+
struct hfp_device {
struct telephony *telephony;
uint16_t version;
@@ -161,6 +170,8 @@ struct hfp_device {
enum call_setup call_setup;
enum call_held call_held;
GSList *calls;
+ bool clcc_in_progress;
+ GSList *clcc_entries;
};
struct hfp_server {
@@ -334,6 +345,92 @@ static uint8_t next_index(struct hfp_device *dev)
return 0;
}
+static void clcc_update_call(gpointer data, gpointer user_data)
+{
+ struct call *call = data;
+ struct hfp_device *dev = user_data;
+ GSList *l;
+
+ for (l = dev->clcc_entries; l; l = l->next) {
+ struct clcc_entry *entry = l->data;
+
+ if (call->idx == entry->idx) {
+ telephony_call_set_state(call, entry->status);
+ telephony_call_set_multiparty(call,
+ entry->multiparty);
+ telephony_call_set_line_id(call, entry->number);
+ dev->clcc_entries = g_slist_remove(dev->clcc_entries,
+ entry);
+ g_free(entry->number);
+ g_free(entry);
+ return;
+ }
+ }
+
+ telephony_call_set_state(call, CALL_STATE_DISCONNECTED);
+ dev->calls = g_slist_remove(dev->calls, call);
+ telephony_call_unregister_interface(call);
+}
+
+static void clcc_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_device *dev = user_data;
+ GSList *l;
+
+ DBG("");
+
+ dev->clcc_in_progress = false;
+
+ if (result != HFP_RESULT_OK) {
+ error("hf-client: CLCC error: %d", result);
+ return;
+ }
+
+ g_slist_foreach(dev->calls, clcc_update_call, dev);
+
+ /* Remaining calls should be added */
+ for (l = dev->clcc_entries; l; l = dev->clcc_entries) {
+ struct clcc_entry *entry = l->data;
+ struct call *call;
+
+ call = telephony_new_call(dev->telephony, entry->idx,
+ entry->status,
+ NULL);
+ call->multiparty = entry->multiparty;
+ if (entry->number)
+ call->line_id = g_strdup(entry->number);
+
+ if (telephony_call_register_interface(call))
+ telephony_free_call(call);
+ else
+ dev->calls = g_slist_append(dev->calls, call);
+
+ dev->clcc_entries = g_slist_remove(dev->clcc_entries, entry);
+ g_free(entry->number);
+ g_free(entry);
+ }
+}
+
+static bool request_calls_update(struct hfp_device *dev)
+{
+ if (!(dev->hfp_hf_features & HFP_HF_FEAT_ENHANCED_CALL_STATUS) ||
+ !(dev->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) ||
+ (telephony_get_state(dev->telephony) != CONNECTED))
+ return false;
+
+ if (dev->clcc_in_progress)
+ return true;
+
+ if (!hfp_hf_send_command(dev->hf, clcc_resp, dev, "AT+CLCC")) {
+ info("hf-client: Could not send AT+CLCC");
+ return false;
+ }
+
+ dev->clcc_in_progress = true;
+ return true;
+}
+
static void ccwa_cb(struct hfp_context *context, void *user_data)
{
struct hfp_device *dev = user_data;
@@ -359,6 +456,9 @@ static void ccwa_cb(struct hfp_context *context, void *user_data)
}
}
+ if (request_calls_update(dev))
+ return;
+
if (!found) {
struct call *call;
uint8_t idx = next_index(dev);
@@ -400,6 +500,58 @@ static void ciev_cb(struct hfp_context *context, void *user_data)
}
}
+static void clcc_cb(struct hfp_context *context, void *user_data)
+{
+ struct hfp_device *dev = user_data;
+ struct clcc_entry *entry;
+ unsigned int val;
+ char number[MAX_NUMBER_LEN];
+
+ DBG("");
+
+ entry = g_new0(struct clcc_entry, 1);
+
+ if (!hfp_context_get_number(context, &val)) {
+ error("hf-client: Could not get index");
+ goto failed;
+ }
+ entry->idx = val;
+
+ if (!hfp_context_get_number(context, &val) || val > 1) {
+ error("hf-client: Could not get direction");
+ goto failed;
+ }
+ entry->direction = val;
+
+ if (!hfp_context_get_number(context, &val) ||
+ val > CALL_STATE_DISCONNECTED) {
+ error("hf-client: Could not get callstate");
+ goto failed;
+ }
+ entry->status = val;
+
+ if (!hfp_context_get_number(context, &val)) {
+ error("hf-client: Could not get mode");
+ goto failed;
+ }
+ entry->mode = val;
+
+ if (!hfp_context_get_number(context, &val)) {
+ error("hf-client: Could not get multiparty");
+ goto failed;
+ }
+ entry->multiparty = val;
+
+ if (hfp_context_get_string(context, number, MAX_NUMBER_LEN))
+ entry->number = g_strdup(number);
+
+ dev->clcc_entries = g_slist_append(dev->clcc_entries, entry);
+ return;
+
+failed:
+ g_free(entry);
+}
+
static void clip_cb(struct hfp_context *context, void *user_data)
{
struct hfp_device *dev = user_data;
@@ -450,6 +602,23 @@ static void cops_cb(struct hfp_context *context, void *user_data)
telephony_set_operator_name(dev->telephony, name);
}
+static void cmee_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_device *dev = user_data;
+
+ DBG("");
+
+ if (result != HFP_RESULT_OK) {
+ error("hf-client: CMEE error: %d", result);
+ return;
+ }
+
+ if (dev->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ request_calls_update(dev);
+ }
+}
+
static void ccwa_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
@@ -463,9 +632,10 @@ static void ccwa_resp(enum hfp_result result, enum hfp_error cme_err,
}
if (dev->features & HFP_AG_FEAT_EXTENDED_RES_CODE) {
- if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
- "AT+CMEE=1"))
+ if (!hfp_hf_send_command(dev->hf, cmee_resp, dev, "AT+CMEE=1"))
info("hf-client: Could not send AT+CMEE=1");
+ } else if (dev->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ request_calls_update(dev);
}
}
@@ -485,9 +655,10 @@ static void nrec_resp(enum hfp_result result, enum hfp_error cme_err,
if (!hfp_hf_send_command(dev->hf, ccwa_resp, dev, "AT+CCWA=1"))
info("hf-client: Could not send AT+CCWA=1");
} else if (dev->features & HFP_AG_FEAT_EXTENDED_RES_CODE) {
- if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
- "AT+CMEE=1"))
+ if (!hfp_hf_send_command(dev->hf, cmee_resp, dev, "AT+CMEE=1"))
info("hf-client: Could not send AT+CMEE=1");
+ } else if (dev->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ request_calls_update(dev);
}
}
@@ -512,9 +683,13 @@ static void clip_resp(enum hfp_result result, enum hfp_error cme_err,
if (!hfp_hf_send_command(dev->hf, ccwa_resp, dev, "AT+CCWA=1"))
info("hf-client: Could not send AT+CCWA=1");
} else if (dev->features & HFP_AG_FEAT_EXTENDED_RES_CODE) {
- if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
- "AT+CMEE=1"))
+ if (!hfp_hf_send_command(dev->hf, cmee_resp, dev, "AT+CMEE=1"))
info("hf-client: Could not send AT+CMEE=1");
+ } else if (dev->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ request_calls_update(dev);
+ }
+}
+
static void cops_status_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
@@ -570,6 +745,7 @@ static void slc_completed(struct hfp_device *dev)
hfp_hf_register(dev->hf, ccwa_cb, "+CCWA", dev, NULL);
hfp_hf_register(dev->hf, ciev_cb, "+CIEV", dev, NULL);
+ hfp_hf_register(dev->hf, clcc_cb, "+CLCC", dev, NULL);
hfp_hf_register(dev->hf, clip_cb, "+CLIP", dev, NULL);
hfp_hf_register(dev->hf, cops_cb, "+COPS", dev, NULL);
@@ -793,6 +969,9 @@ static void ciev_call_cb(uint8_t val, void *user_data)
return;
}
+ if (request_calls_update(dev))
+ return;
+
if (dev->call == val)
return;
@@ -854,6 +1033,9 @@ static void ciev_callsetup_cb(uint8_t val, void *user_data)
return;
}
+ if (request_calls_update(dev))
+ return;
+
if (dev->call_setup == val)
return;
@@ -935,6 +1117,9 @@ static void ciev_callheld_cb(uint8_t val, void *user_data)
return;
}
+ if (request_calls_update(dev))
+ return;
+
dev->call_held = val;
if (dev->call_held == CIND_CALLHELD_NONE) {
--
2.43.0
next prev parent reply other threads:[~2025-06-27 14:51 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 01/27] doc: Add new telephony related profiles interfaces Frédéric Danis
2025-06-27 16:15 ` New Telephony interface for HSP, HFP and CCP bluez.test.bot
2025-07-02 17:38 ` [RFC BlueZ v2 01/27] doc: Add new telephony related profiles interfaces Luiz Augusto von Dentz
2025-06-27 14:51 ` [RFC BlueZ v2 02/27] audio/telephony: Add shared interfaces implementation Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 03/27] audio/telephony: Add skeleton for HFP profile Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 04/27] audio/hfp-hf: Add HFP SLC connection and event support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 05/27] audio/hfp-hf: Add dial support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 06/27] audio/hfp-hf: Add hangup all calls support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 07/27] audio/hfp-hf: Add answer a specific call support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 08/27] client/telephony: Add new submenu Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 09/27] audio/hfp-hf: Remove call interface during profile disconnection Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 10/27] audio/hfp-hf: Create existing call during SLC phase Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 11/27] audio/telephony: Add hangup_active and hangup_held functions Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 12/27] audio/hfp-hf: Add hangup_active and hangup_held support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 13/27] client/telephony: " Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 14/27] audio/hfp-hf: Add SendTones support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 15/27] client/telephony: " Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 16/27] doc: Make telephony docs more generic Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 17/27] client/telephony: Remove IncomingLine Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 18/27] audio/telephony: " Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 19/27] audio/hfp-hf: Add HFP HF server and SDP record Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 20/27] audio/hfp-hf: Add operator name support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 21/27] audio/telephony: Add call line identication property support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 22/27] audio/hfp-hf: Add call line idenfication support Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 23/27] audio/hfp-hf: Disable NREC during connection setup Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 24/27] audio/hfp-hf: Enable waiting call if supported by remote AG Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 25/27] audio/hfp-hf: Enable extended error " Frédéric Danis
2025-06-27 14:51 ` [RFC BlueZ v2 26/27] audio/telephony: Add call multiparty property support Frédéric Danis
2025-06-27 14:51 ` Frédéric Danis [this message]
2025-07-02 17:50 ` [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Luiz Augusto von Dentz
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=20250627145136.421853-28-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.