public inbox for linux-bluetooth@vger.kernel.org
 help / color / mirror / Atom feed
* RE: New Telephony interface for HSP, HFP and CCP
  2025-05-28  8:59 [RFC BlueZ 01/10] doc: Add new telephony related profiles interfaces Frédéric Danis
@ 2025-05-28 10:33 ` bluez.test.bot
  0 siblings, 0 replies; 32+ messages in thread
From: bluez.test.bot @ 2025-05-28 10:33 UTC (permalink / raw)
  To: linux-bluetooth, frederic.danis

[-- Attachment #1: Type: text/plain, Size: 1532 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=966926

---Test result---

Test Summary:
CheckPatch                    PENDING   0.26 seconds
GitLint                       PENDING   0.30 seconds
BuildEll                      PASS      20.18 seconds
BluezMake                     PASS      2624.40 seconds
MakeCheck                     PASS      20.41 seconds
MakeDistcheck                 FAIL      8.76 seconds
CheckValgrind                 PASS      275.83 seconds
CheckSmatch                   PASS      302.61 seconds
bluezmakeextell               PASS      128.49 seconds
IncrementalBuild              PENDING   0.26 seconds
ScanBuild                     PASS      897.49 seconds

Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:

##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:

##############################
Test: MakeDistcheck - FAIL
Desc: Run Bluez Make Distcheck
Output:

make[2]: *** No rule to make target 'doc/mgmt-api.txt', needed by 'distdir-am'.  Stop.
make[1]: *** [Makefile:12236: distdir] Error 2
make: *** [Makefile:12312: dist] Error 2
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:



---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP
@ 2025-06-27 14:51 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
                   ` (27 more replies)
  0 siblings, 28 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

This will introduce a new Telephony interface wich is intended to be
shared by the profiles able to control telephony calls.

The idea is to split the call control interface from the audio streaming,
as it is done for AVRCP and A2DP.
As for A2DP, the audio part will be delegated to the audio daemon (like
PipeWire) by the creation of new endpoints for CVSD and mSBC, LC3 endpoint
already exists.

The interface is mostly based on the one done for PipeWire's native
backend.

This will simplify the qualification of the telephony related profiles as
the qualification will no more depend on external projects, and calls can
be controlled from bluetoothctl.

A first implementation allows to dial or hangup a call using HFP.

v1->v2:
  - Rename org.bluez.TelephonyCall1 to org.bluez.Call1
  - Remove reference to profiles in org.bluez.TelephonyAg1 object path
  - Add profile UUID property to org.bluez.TelephonyAg1
  - Add OperatorName property to org.bluez.TelephonyAg1
  - Rename telephony_set_call_state() to telephony_call_set_state()
  - Use first available index of call for new call
  - Fix DBus message memory leak in hfp_dial_cb()
  - Display UUID and OperatorName in bluetoothctl telephony.show command
  - Add hangup-active and hangup-held support
  - Add SendTones support
  - Remove HFP specific comments in documentation
  - Add HFP HF server and related SDP record
  - Add OperatorName support to HFP HF
  - Add call line identification property support to HFP HF
  - Disable NREC during HFP HF connection phase
  - Enable Waiting call event to HFP HF
  - Enable Extended error support in HFP HF
  - Add telephony_call_set_multiparty() to telephony API
  - Enable Enhanced call status support in HFP HF, and use it to update
    calls status if available on both side

Frédéric Danis (27):
  doc: Add new telephony related profiles interfaces
  audio/telephony: Add shared interfaces implementation
  audio/telephony: Add skeleton for HFP profile
  audio/hfp-hf: Add HFP SLC connection and event support
  audio/hfp-hf: Add dial support
  audio/hfp-hf: Add hangup all calls support
  audio/hfp-hf: Add answer a specific call support
  client/telephony: Add new submenu
  audio/hfp-hf: Remove call interface during profile disconnection
  audio/hfp-hf: Create existing call during SLC phase
  audio/telephony: Add hangup_active and hangup_held functions
  audio/hfp-hf: Add hangup_active and hangup_held support
  client/telephony: Add hangup_active and hangup_held support
  audio/hfp-hf: Add SendTones support
  client/telephony: Add SendTones support
  doc: Make telephony docs more generic
  client/telephony: Remove IncomingLine
  audio/telephony: Remove IncomingLine
  audio/hfp-hf: Add HFP HF server and SDP record
  audio/hfp-hf: Add operator name support
  audio/telephony: Add call line identication property support
  audio/hfp-hf: Add call line idenfication support
  audio/hfp-hf: Disable NREC during connection setup
  audio/hfp-hf: Enable waiting call if supported by remote AG
  audio/hfp-hf: Enable extended error if supported by remote AG
  audio/telephony: Add call multiparty property support
  audio/hfp-hf: Enable enhanced call status if supported by remote AG

 Makefile.am                       |    4 +
 Makefile.plugins                  |    5 +
 Makefile.tools                    |    9 +-
 client/bluetoothctl-telephony.rst |   95 ++
 client/main.c                     |    3 +
 client/telephony.c                |  676 ++++++++++
 client/telephony.h                |   12 +
 configure.ac                      |    7 +
 doc/org.bluez.Call.rst            |  107 ++
 doc/org.bluez.TelephonyAg.rst     |  206 +++
 profiles/audio/hfp-hf.c           | 2092 +++++++++++++++++++++++++++++
 profiles/audio/telephony.c        |  808 +++++++++++
 profiles/audio/telephony.h        |  118 ++
 13 files changed, 4139 insertions(+), 3 deletions(-)
 create mode 100644 client/bluetoothctl-telephony.rst
 create mode 100644 client/telephony.c
 create mode 100644 client/telephony.h
 create mode 100644 doc/org.bluez.Call.rst
 create mode 100644 doc/org.bluez.TelephonyAg.rst
 create mode 100644 profiles/audio/hfp-hf.c
 create mode 100644 profiles/audio/telephony.c
 create mode 100644 profiles/audio/telephony.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 01/27] doc: Add new telephony related profiles interfaces
  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 ` 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
                   ` (26 subsequent siblings)
  27 siblings, 2 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

These are interfaces are meant to be generic to the telephony related
"headset" profiles like HSP HS, HFP HF, and CCP.
---
v1->v2:
  - Rename org.bluez.TelephonyCall1 to org.bluez.Call1
  - Remove reference to profiles in org.bluez.TelephonyAg1 object path
  - Add profile UUID property to org.bluez.TelephonyAg1
  - Add OperatorName property to org.bluez.TelephonyAg1

 Makefile.am                   |   4 +
 doc/org.bluez.Call.rst        | 136 ++++++++++++++++++++++
 doc/org.bluez.TelephonyAg.rst | 207 ++++++++++++++++++++++++++++++++++
 3 files changed, 347 insertions(+)
 create mode 100644 doc/org.bluez.Call.rst
 create mode 100644 doc/org.bluez.TelephonyAg.rst

diff --git a/Makefile.am b/Makefile.am
index 02ad23cf2..12714ecf8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -388,6 +388,7 @@ man_MANS += doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \
 		doc/org.bluez.obex.Message.5 \
 		doc/org.bluez.obex.AgentManager.5 doc/org.bluez.obex.Agent.5 \
 		doc/org.bluez.obex.Image.5
+man_MANS += doc/org.bluez.TelephonyAg.5 doc/org.bluez.Call.5
 endif
 manual_pages += src/bluetoothd.8
 manual_pages += doc/hci.7 doc/mgmt.7 doc/l2cap.7 doc/rfcomm.7 doc/sco.7
@@ -422,6 +423,7 @@ manual_pages += doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \
 		doc/org.bluez.obex.Message.5 \
 		doc/org.bluez.obex.AgentManager.5 doc/org.bluez.obex.Agent.5 \
 		doc/org.bluez.obex.Image.5
+manual_pages += doc/org.bluez.TelephonyAg.5 doc/org.bluez.Call.5
 
 EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
 			src/main.conf profiles/network/network.conf \
@@ -505,6 +507,8 @@ EXTRA_DIST += doc/org.bluez.obex.Client.rst doc/org.bluez.obex.Session.rst \
 		doc/org.bluez.obex.AgentManager.rst doc/org.bluez.obex.Agent.rst \
 		doc/org.bluez.obex.Image.rst
 
+EXTRA_DIST += doc/org.bluez.TelephonyAg.rst doc/org.bluez.Call.rst
+
 EXTRA_DIST += doc/pics-opp.txt doc/pixit-opp.txt \
 		doc/pts-opp.txt
 
diff --git a/doc/org.bluez.Call.rst b/doc/org.bluez.Call.rst
new file mode 100644
index 000000000..3fcd6f6ea
--- /dev/null
+++ b/doc/org.bluez.Call.rst
@@ -0,0 +1,136 @@
+===============
+org.bluez.Call1
+===============
+
+--------------------------------------------
+BlueZ D-Bus Telephony Call API documentation
+--------------------------------------------
+
+:Version: BlueZ
+:Date: May 2025
+:Manual section: 5
+:Manual group: Linux System Administration
+
+Interface
+=========
+
+:Service:	org.bluez
+:Interface:	org.bluez.Call1 [experimental]
+:Object path:	[variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony_ag#/call#
+
+Methods
+-------
+
+void Answer()
+`````````````
+
+Answers an incoming call. Only valid if the state of the call is "incoming".
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void Hangup()
+`````````````
+
+Hangs up the call.
+
+For an incoming call, the call is hung up using ATH or equivalent. For a
+waiting call, the remote party is notified by using the User Determined User
+Busy (UDUB) condition. This is generally implemented using CHLD=0.
+
+Please note that the GSM specification does not allow the release of a held
+call when a waiting call exists. This is because 27.007 allows CHLD=1X to
+operate only on active calls. Hence a held call cannot be hung up without
+affecting the state of the incoming call (e.g. using other CHLD alternatives).
+Most manufacturers provide vendor extensions that do allow the state of the
+held call to be modified using CHLD=1X or equivalent. It should be noted that
+Bluetooth HFP specifies the classic 27.007 behavior and does not allow CHLD=1X
+to modify the state of held calls.
+
+Based on the discussion above, it should also be noted that releasing a
+particular party of a held multiparty call might not be possible on some
+implementations. It is recommended for the applications to structure their UI
+accordingly.
+
+NOTE: Releasing active calls does not produce side-effects. That is the state
+of held or waiting calls is not affected. As an exception, in the case where a
+single active call and a waiting call are present, releasing the active call
+will result in the waiting call transitioning to the 'incoming' state.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+Properties
+----------
+
+string LineIdentification [readonly]
+````````````````````````````````````
+
+Contains the Line Identification information returned by the network, if
+present. For incoming calls this is effectively the CLIP. For outgoing calls
+this attribute will hold the dialed number, or the COLP if received by the
+audio gateway.
+
+Please note that COLP may be different from the dialed number. A special
+"withheld" value means the remote party refused to provide caller ID and the
+"override category" option was not provisioned for the current subscriber.
+
+string IncomingLine [readonly, optional]
+````````````````````````````````````````
+
+Contains the Called Line Identification information returned by the network.
+This is only available for incoming calls and indicates the local subscriber
+number which was dialed by the remote party. This is useful for subscribers
+which have a multiple line service with their network provider and would like
+to know what line the call is coming in on.
+
+string Name [readonly]
+``````````````````````
+
+Contains the Name Identification information returned by the network, if
+present.
+
+boolean Multiparty [readonly]
+`````````````````````````````
+
+Contains the indication if the call is part of a multiparty call or not.
+
+Notifications if a call becomes part or leaves a multiparty call are sent.
+
+string State [readonly]
+```````````````````````
+
+Contains the state of the current call.
+
+Possible values:
+
+:"active":
+
+	The call is active
+
+:"held":
+
+	The call is on hold
+
+:"dialing":
+
+	The call is being dialed
+
+:"alerting":
+
+	The remote party is being alerted
+
+:"incoming":
+
+	Incoming call in progress
+
+:"waiting":
+
+	Call is waiting
+
+:"disconnected":
+
+	No further use of this object is allowed, it will be
+	destroyed shortly
diff --git a/doc/org.bluez.TelephonyAg.rst b/doc/org.bluez.TelephonyAg.rst
new file mode 100644
index 000000000..ddb5eec0f
--- /dev/null
+++ b/doc/org.bluez.TelephonyAg.rst
@@ -0,0 +1,207 @@
+======================
+org.bluez.TelephonyAg1
+======================
+
+-----------------------------------------------------
+BlueZ D-Bus Telephony Audio Gateway API documentation
+-----------------------------------------------------
+
+:Version: BlueZ
+:Date: May 2025
+:Manual section: 5
+:Manual group: Linux System Administration
+
+Interface
+=========
+
+:Service:	org.bluez
+:Interface:	org.bluez.TelephonyAg1 [experimental]
+:Object path:	[variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony_ag#
+
+Methods
+-------
+
+object Dial(string number)
+``````````````````````````
+
+Call number, if number is void try to call last dialed number.
+Initiates a new outgoing call. Returns the object path to the newly created
+call.
+
+The number must be a string containing the following characters:
+`[0-9+*#,ABCD]{1,80}` The character set can contain numbers, `+`, `*`, `#`,
+`,` and the letters `A` to `D`. Besides this sanity checking no further number
+validation is performed. It is assumed that the gateway and/or the network
+will perform further validation.
+
+If number is an empty string, it will try to call last dialed number.
+
+NOTE: If an active call (single or multiparty) exists, then it is
+automatically put on hold if the dial procedure is successful.
+
+Possible Errors:
+
+:org.bluez.Error.InvalidState:
+:org.bluez.Error.InvalidArguments:
+:org.bluez.Error.NotSupported:
+:org.bluez.Error.Failed:
+
+void SwapCalls()
+````````````````
+
+Swaps Active and Held calls. The effect of this is that all calls (0 or more
+including calls in a multi-party conversation) that were Active are now Held,
+and all calls (0 or more) that were Held are now Active.
+
+GSM specification does not allow calls to be swapped in the case where Held,
+Active and Waiting calls exist. Some modems implement this anyway, thus it is
+manufacturer specific whether this method will succeed in the case of Held,
+Active and Waiting calls.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void ReleaseAndAnswer()
+```````````````````````
+
+Releases currently active call (0 or more) and answers the currently waiting
+call. Please note that if the current call is a multiparty call, then all
+parties in the multi-party call will be released.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void ReleaseAndSwap()
+`````````````````````
+
+Releases currently active call (0 or more) and activates any currently held
+calls. Please note that if the current call is a multiparty call, then all
+parties in the multi-party call will be released.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void HoldAndAnswer()
+````````````````````
+
+Puts the current call (including multi-party calls) on hold and answers the
+currently waiting call. Calling this function when a user already has a both
+Active and Held calls is invalid, since in GSM a user can have only a single
+Held call at a time.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void HangupAll()
+````````````````
+
+Releases all calls except waiting calls. This includes multiparty calls.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void HangupActive()
+```````````````````
+
+Releases active calls. This includes multiparty active calls.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void HangupHeld()
+`````````````````
+
+Releases held calls except waiting calls. This includes multiparty held calls.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+array{object} CreateMultiparty()
+````````````````````````````````
+
+Joins active and held calls together into a multi-party call. If one of the
+calls is already a multi-party call, then the other call is added to the
+multiparty conversation. Returns the new list of calls participating in the
+multiparty call.
+
+There can only be one subscriber controlled multi-party call according to the
+GSM specification.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void SendTones(string tones)
+````````````````````````````
+
+Sends the DTMF tones to the network. The tones have a fixed duration.
+Tones can be one of: '0' - '9', '*', '#', 'A', 'B', 'C', 'D'. The last four
+are typically not used in normal circumstances.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.InvalidArgs
+:org.bluez.Error.Failed
+
+Properties
+----------
+
+string UUID [readonly]
+``````````````````````
+
+UUID of the profile which the Telephony Audio Gateway is for.
+
+string State [readonly]
+```````````````````````
+
+Contains the state of the current connection.
+
+Possible values:
+
+:"connecting":
+
+	RFComm connection in progress
+
+:"slc_connecting":
+
+	Service Level Connection in progress
+
+:"connected":
+
+	RFComm and Service Level Connection are connected
+
+:"disconnecting":
+
+	No further use of this object is allowed, it will be destroyed shortly
+
+boolean Service [readonly]
+``````````````````````````
+
+Network service availability.
+
+byte Signal [readonly]
+``````````````````````
+
+Network level signal from 0 to 5.
+
+boolean Roaming [readonly]
+``````````````````````````
+
+Network roaming usage.
+
+byte BattChg [readonly]
+```````````````````````
+
+Battery level from 0 to 5.
+
+string OperatorName [readonly, optional]
+````````````````````````````````````````
+
+Operator name
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 02/27] audio/telephony: Add shared interfaces implementation
  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 14:51 ` 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
                   ` (25 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
v1->v2:
  - Rename org.bluez.TelephonyCall1 to org.bluez.Call1
  - Remove reference to profiles in org.bluez.TelephonyAg1 object path
  - Add profile UUID property to org.bluez.TelephonyAg1
  - Add OperatorName property to org.bluez.TelephonyAg1
  - Rename telephony_set_call_state() to telephony_call_set_state()

 profiles/audio/telephony.c | 777 +++++++++++++++++++++++++++++++++++++
 profiles/audio/telephony.h | 113 ++++++
 2 files changed, 890 insertions(+)
 create mode 100644 profiles/audio/telephony.c
 create mode 100644 profiles/audio/telephony.h

diff --git a/profiles/audio/telephony.c b/profiles/audio/telephony.c
new file mode 100644
index 000000000..83a7ff40c
--- /dev/null
+++ b/profiles/audio/telephony.c
@@ -0,0 +1,777 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <stdint.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "bluetooth/sdp.h"
+#include "bluetooth/sdp_lib.h"
+#include "lib/uuid.h"
+
+#include "gdbus/gdbus.h"
+
+#include "btio/btio.h"
+#include "src/adapter.h"
+#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 "telephony.h"
+
+#define TELEPHONY_AG_INTERFACE "org.bluez.TelephonyAg1"
+#define TELEPHONY_CALL_INTERFACE "org.bluez.Call1"
+
+struct telephony {
+	struct btd_service		*service;
+	struct btd_device		*device;
+	char				*path;
+	bdaddr_t			src;
+	bdaddr_t			dst;
+	void				*profile_data;
+	struct telephony_callbacks	*cbs;
+	enum connection_state		state;
+	bool				network_service;
+	uint8_t				signal;
+	bool				roaming;
+	uint8_t				battchg;
+	char				*operator_name;
+};
+
+static const char *state_to_string(enum connection_state state)
+{
+	switch (state) {
+	case CONNECTING:
+		return "connecting";
+	case SLC_CONNECTING:
+		return "slc_connecting";
+	case CONNECTED:
+		return "connected";
+	case DISCONNECTING:
+		return "disconnecting";
+	}
+
+	return NULL;
+}
+
+static const char *call_state_to_string(enum call_state state)
+{
+	switch (state) {
+	case CALL_STATE_ACTIVE:
+		return "active";
+	case CALL_STATE_HELD:
+		return "held";
+	case CALL_STATE_DIALING:
+		return "dialing";
+	case CALL_STATE_ALERTING:
+		return "alerting";
+	case CALL_STATE_INCOMING:
+		return "incoming";
+	case CALL_STATE_WAITING:
+		return "waiting";
+	case CALL_STATE_DISCONNECTED:
+		return "disconnected";
+	}
+
+	return NULL;
+}
+
+struct telephony *telephony_new(struct btd_service *service,
+				void *profile_data,
+				struct telephony_callbacks *cbs)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	const char *path = device_get_path(device);
+	struct btd_adapter *adapter = device_get_adapter(device);
+	struct telephony *ag;
+	static int id;
+
+	ag = g_new0(struct telephony, 1);
+	bacpy(&ag->src, btd_adapter_get_address(adapter));
+	bacpy(&ag->dst, device_get_address(device));
+	ag->service = btd_service_ref(service);
+	ag->device = btd_device_ref(device);
+	ag->path = g_strdup_printf("%s/telephony_ag%u", path, id++);
+	ag->profile_data = profile_data;
+	ag->cbs = cbs;
+
+	return ag;
+}
+
+void telephony_free(struct telephony *telephony)
+{
+	btd_service_unref(telephony->service);
+	btd_device_unref(telephony->device);
+	g_free(telephony->operator_name);
+	g_free(telephony->path);
+	g_free(telephony);
+}
+
+static DBusMessage *dial(DBusConnection *conn, DBusMessage *msg,
+					void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->dial)
+		return telephony->cbs->dial(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static DBusMessage *swap_calls(DBusConnection *conn, DBusMessage *msg,
+					void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->swap_calls)
+		return telephony->cbs->swap_calls(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static DBusMessage *release_and_answer(DBusConnection *conn, DBusMessage *msg,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->release_and_answer)
+		return telephony->cbs->release_and_answer(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static DBusMessage *release_and_swap(DBusConnection *conn, DBusMessage *msg,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->release_and_swap)
+		return telephony->cbs->release_and_swap(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static DBusMessage *hold_and_answer(DBusConnection *conn, DBusMessage *msg,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->hold_and_answer)
+		return telephony->cbs->hold_and_answer(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static DBusMessage *hangup_all(DBusConnection *conn, DBusMessage *msg,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->hangup_all)
+		return telephony->cbs->hangup_all(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static DBusMessage *create_multiparty(DBusConnection *conn, DBusMessage *msg,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->create_multiparty)
+		return telephony->cbs->create_multiparty(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static DBusMessage *send_tones(DBusConnection *conn, DBusMessage *msg,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->send_tones)
+		return telephony->cbs->send_tones(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static gboolean property_get_uuid(const GDBusPropertyTable *property,
+					DBusMessageIter *iter,
+					void *user_data)
+{
+	struct telephony *telephony = user_data;
+	struct btd_profile *p = btd_service_get_profile(telephony->service);
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &p->remote_uuid);
+
+	return TRUE;
+}
+
+static gboolean property_get_state(const GDBusPropertyTable *property,
+					DBusMessageIter *iter,
+					void *user_data)
+{
+	struct telephony *telephony = user_data;
+	const char *string;
+
+	string = state_to_string(telephony->state);
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
+
+	return TRUE;
+}
+
+static gboolean property_get_service(const GDBusPropertyTable *property,
+					DBusMessageIter *iter,
+					void *user_data)
+{
+	struct telephony *telephony = user_data;
+	dbus_bool_t value;
+
+	value = telephony->network_service;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+	return TRUE;
+}
+
+static gboolean property_get_signal(const GDBusPropertyTable *property,
+					DBusMessageIter *iter,
+					void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE,
+					&telephony->signal);
+
+	return TRUE;
+}
+
+static gboolean property_get_roaming(const GDBusPropertyTable *property,
+					DBusMessageIter *iter,
+					void *user_data)
+{
+	struct telephony *telephony = user_data;
+	dbus_bool_t value;
+
+	value = telephony->roaming;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+	return TRUE;
+}
+
+static gboolean property_get_battchg(const GDBusPropertyTable *property,
+					DBusMessageIter *iter,
+					void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE,
+					&telephony->battchg);
+
+	return TRUE;
+}
+
+static gboolean property_operator_name_exists(
+	const GDBusPropertyTable *property,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	return telephony->operator_name != NULL;
+}
+
+static gboolean property_get_operator_name(const GDBusPropertyTable *property,
+	DBusMessageIter *iter, void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->operator_name == NULL)
+		return FALSE;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
+					&telephony->operator_name);
+
+	return TRUE;
+}
+
+static const GDBusMethodTable telephony_methods[] = {
+	{ GDBUS_ASYNC_METHOD("Dial", GDBUS_ARGS({"number", "s"}), NULL,
+						dial) },
+	{ GDBUS_ASYNC_METHOD("SwapCalls", NULL, NULL, swap_calls) },
+	{ GDBUS_ASYNC_METHOD("ReleaseAndAnswer", NULL, NULL,
+						release_and_answer) },
+	{ GDBUS_ASYNC_METHOD("ReleaseAndSwap", NULL, NULL,
+						release_and_swap) },
+	{ GDBUS_ASYNC_METHOD("HoldAndAnswer", NULL, NULL,
+						hold_and_answer) },
+	{ GDBUS_ASYNC_METHOD("HangupAll", NULL, NULL, hangup_all) },
+	{ GDBUS_ASYNC_METHOD("CreateMultiparty", NULL,
+						GDBUS_ARGS({ "calls", "ao" }),
+						create_multiparty) },
+	{ GDBUS_ASYNC_METHOD("SendTones", GDBUS_ARGS({"number", "s"}), NULL,
+						send_tones) },
+	{ }
+};
+
+static const GDBusPropertyTable telephony_properties[] = {
+	{ "UUID", "s", property_get_uuid },
+	{ "State", "s", property_get_state },
+	{ "Service", "b", property_get_service },
+	{ "Signal", "y", property_get_signal },
+	{ "Roaming", "b", property_get_roaming },
+	{ "BattChg", "y", property_get_battchg },
+	{ "OperatorName", "s", property_get_operator_name, NULL,
+			property_operator_name_exists },
+	{ }
+};
+
+static void path_unregister(void *data)
+{
+	struct telephony *telephony = data;
+
+	DBG("Unregistered interface %s on path %s",  TELEPHONY_AG_INTERFACE,
+						telephony->path);
+}
+
+int telephony_register_interface(struct telephony *telephony)
+{
+	if (telephony->cbs == NULL)
+		return -EINVAL;
+
+	if (!g_dbus_register_interface(btd_get_dbus_connection(),
+			telephony->path,
+			TELEPHONY_AG_INTERFACE,
+			telephony_methods, NULL,
+			telephony_properties, telephony,
+			path_unregister)) {
+		return -EINVAL;
+	}
+
+	DBG("Registered interface %s on path %s", TELEPHONY_AG_INTERFACE,
+						telephony->path);
+
+	return 0;
+}
+
+void telephony_unregister_interface(struct telephony *telephony)
+{
+	g_dbus_unregister_interface(btd_get_dbus_connection(), telephony->path,
+					TELEPHONY_AG_INTERFACE);
+}
+
+struct btd_service *telephony_get_service(struct telephony *telephony)
+{
+	return telephony->service;
+}
+
+struct btd_device *telephony_get_device(struct telephony *telephony)
+{
+	return telephony->device;
+}
+
+const char *telephony_get_path(struct telephony *telephony)
+{
+	return telephony->path;
+}
+
+bdaddr_t telephony_get_src(struct telephony *telephony)
+{
+	return telephony->src;
+}
+
+bdaddr_t telephony_get_dst(struct telephony *telephony)
+{
+	return telephony->dst;
+}
+
+void *telephony_get_profile_data(struct telephony *telephony)
+{
+	return telephony->profile_data;
+}
+
+void telephony_set_state(struct telephony *telephony,
+				enum connection_state state)
+{
+	char address[18];
+
+	if (telephony->state == state)
+		return;
+
+	ba2str(&telephony->dst, address);
+	DBG("device %s state %s -> %s", address,
+				state_to_string(telephony->state),
+				state_to_string(state));
+
+	telephony->state = state;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+					telephony->path, TELEPHONY_AG_INTERFACE,
+					"State");
+}
+
+enum connection_state telephony_get_state(struct telephony *telephony)
+{
+	return telephony->state;
+}
+
+void telephony_set_network_service(struct telephony *telephony, bool service)
+{
+	char address[18];
+
+	if (telephony->network_service == service)
+		return;
+
+	ba2str(&telephony->dst, address);
+	DBG("device %s network service %u -> %u", address,
+					telephony->network_service,
+					service);
+
+	telephony->network_service = service;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+			telephony->path, TELEPHONY_AG_INTERFACE,
+			"Service");
+}
+
+bool telephony_get_network_service(struct telephony *telephony)
+{
+	return telephony->network_service;
+}
+
+void telephony_set_signal(struct telephony *telephony, uint8_t signal)
+{
+	char address[18];
+
+	if (telephony->signal == signal)
+		return;
+
+	ba2str(&telephony->dst, address);
+	DBG("device %s signal %u -> %u", address, telephony->signal, signal);
+
+	telephony->signal = signal;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+			telephony->path, TELEPHONY_AG_INTERFACE,
+			"Signal");
+}
+
+uint8_t telephony_get_signal(struct telephony *telephony)
+{
+	return telephony->signal;
+}
+
+void telephony_set_roaming(struct telephony *telephony, bool roaming)
+{
+	char address[18];
+
+	if (telephony->roaming == roaming)
+		return;
+
+	ba2str(&telephony->dst, address);
+	DBG("device %s roaming %u -> %u", address,
+					telephony->roaming,
+					roaming);
+
+	telephony->roaming = roaming;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+			telephony->path, TELEPHONY_AG_INTERFACE,
+			"Roaming");
+}
+
+bool telephony_get_roaming(struct telephony *telephony)
+{
+	return telephony->roaming;
+}
+
+void telephony_set_battchg(struct telephony *telephony, uint8_t battchg)
+{
+	char address[18];
+
+	if (telephony->battchg == battchg)
+		return;
+
+	ba2str(&telephony->dst, address);
+	DBG("device %s battchg %u -> %u", address, telephony->battchg, battchg);
+
+	telephony->battchg = battchg;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+			telephony->path, TELEPHONY_AG_INTERFACE,
+			"BattChg");
+}
+
+uint8_t telephony_get_battchg(struct telephony *telephony)
+{
+	return telephony->battchg;
+}
+
+void telephony_set_operator_name(struct telephony *telephony,
+					const char *name)
+{
+	char address[18];
+
+	if (telephony->operator_name &&
+			g_str_equal(telephony->operator_name, name))
+		return;
+
+	ba2str(&telephony->dst, address);
+	DBG("device %s operator name %s -> %s", address,
+			telephony->operator_name, name);
+
+	if (telephony->operator_name)
+		g_free(telephony->operator_name);
+	telephony->operator_name = g_strdup(name);
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+			telephony->path, TELEPHONY_AG_INTERFACE,
+			"OperatorName");
+}
+
+const char *telephony_get_operator_name(struct telephony *telephony)
+{
+	return telephony->operator_name;
+}
+
+struct call *telephony_new_call(struct telephony *telephony,
+				uint8_t idx,
+				enum call_state state,
+				void *user_data)
+{
+	struct call *call;
+
+	call = g_new0(struct call, 1);
+	call->device = telephony;
+	call->state = state;
+	call->idx = idx;
+	call->path = g_strdup_printf("%s/call%u", telephony->path, call->idx);
+
+	return call;
+}
+
+void telephony_free_call(struct call *call)
+{
+	if (call->pending_msg)
+		dbus_message_unref(call->pending_msg);
+
+	g_free(call->name);
+	g_free(call->incoming_line);
+	g_free(call->line_id);
+	g_free(call->path);
+	g_free(call);
+}
+
+static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg,
+	void *call_data)
+{
+	struct call *call = call_data;
+	struct telephony *telephony = call->device;
+
+	return telephony->cbs->call_answer(conn, msg, call_data);
+}
+
+static DBusMessage *call_hangup(DBusConnection *conn, DBusMessage *msg,
+	void *call_data)
+{
+	struct call *call = call_data;
+	struct telephony *telephony = call->device;
+
+	return telephony->cbs->call_hangup(conn, msg, call_data);
+}
+
+static gboolean call_line_id_exists(const GDBusPropertyTable *property,
+	void *data)
+{
+	struct call *call = data;
+
+	return call->line_id != NULL;
+}
+
+static gboolean call_property_get_line_id(
+	const GDBusPropertyTable *property,
+	DBusMessageIter *iter, void *data)
+{
+	struct call *call = data;
+
+	if (call->line_id == NULL)
+		return FALSE;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &call->line_id);
+
+	return TRUE;
+}
+
+static gboolean call_incoming_line_exists(const GDBusPropertyTable *property,
+	void *data)
+{
+	struct call *call = data;
+
+	return call->incoming_line != NULL;
+}
+
+static gboolean call_property_get_incoming_line(
+	const GDBusPropertyTable *property,
+	DBusMessageIter *iter, void *data)
+{
+	struct call *call = data;
+
+	if (call->incoming_line == NULL)
+		return FALSE;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
+		&call->incoming_line);
+
+	return TRUE;
+}
+
+static gboolean call_name_exists(const GDBusPropertyTable *property,
+	void *data)
+{
+	struct call *call = data;
+
+	return call->name != NULL;
+}
+
+static gboolean call_property_get_name(const GDBusPropertyTable *property,
+	DBusMessageIter *iter, void *data)
+{
+	struct call *call = data;
+
+	if (call->name == NULL)
+		return FALSE;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &call->name);
+
+	return TRUE;
+}
+
+static gboolean call_property_get_multiparty(
+	const GDBusPropertyTable *property,
+	DBusMessageIter *iter, void *data)
+{
+	struct call *call = data;
+	dbus_bool_t value;
+
+	value = call->multiparty;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+	return TRUE;
+}
+
+static gboolean call_property_get_state(const GDBusPropertyTable *property,
+	DBusMessageIter *iter, void *data)
+{
+	struct call *call = data;
+	const char *string;
+
+	string = call_state_to_string(call->state);
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
+
+	return TRUE;
+}
+
+static const GDBusMethodTable telephony_call_methods[] = {
+	{ GDBUS_ASYNC_METHOD("Answer", NULL, NULL, call_answer) },
+	{ GDBUS_ASYNC_METHOD("Hangup", NULL, NULL, call_hangup) },
+	{ }
+};
+
+static const GDBusPropertyTable telephony_call_properties[] = {
+	{ "LineIdentification", "s", call_property_get_line_id, NULL,
+			call_line_id_exists },
+	{ "IncomingLine", "s", call_property_get_incoming_line, NULL,
+			call_incoming_line_exists },
+	{ "Name", "s", call_property_get_name, NULL, call_name_exists },
+	{ "Multiparty", "b", call_property_get_multiparty },
+	{ "State", "s", call_property_get_state },
+	{ }
+};
+
+static void call_path_unregister(void *user_data)
+{
+	struct call *call = user_data;
+
+	DBG("Unregistered interface %s on path %s",  TELEPHONY_CALL_INTERFACE,
+			call->path);
+
+	telephony_free_call(call);
+}
+
+int telephony_call_register_interface(struct call *call)
+{
+	if (call->device->cbs == NULL)
+		return -EINVAL;
+
+	if (!g_dbus_register_interface(btd_get_dbus_connection(),
+			call->path,
+			TELEPHONY_CALL_INTERFACE,
+			telephony_call_methods, NULL,
+			telephony_call_properties, call,
+			call_path_unregister)) {
+		return -EINVAL;
+	}
+
+	DBG("Registered interface %s on path %s", TELEPHONY_CALL_INTERFACE,
+						call->path);
+
+	return 0;
+}
+
+void telephony_call_unregister_interface(struct call *call)
+{
+	g_dbus_unregister_interface(btd_get_dbus_connection(),
+					call->path,
+					TELEPHONY_CALL_INTERFACE);
+}
+
+void telephony_call_set_state(struct call *call, enum call_state state)
+{
+	if (call->state == state)
+		return;
+
+	DBG("%s state %s -> %s", call->path, call_state_to_string(call->state),
+					call_state_to_string(state));
+
+	call->state = state;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+			call->path, TELEPHONY_CALL_INTERFACE,
+			"State");
+}
diff --git a/profiles/audio/telephony.h b/profiles/audio/telephony.h
new file mode 100644
index 000000000..aaf41888d
--- /dev/null
+++ b/profiles/audio/telephony.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+enum connection_state {
+	CONNECTING = 0,
+	SLC_CONNECTING,
+	CONNECTED,
+	DISCONNECTING
+};
+
+enum call_state {
+	CALL_STATE_ACTIVE = 0,
+	CALL_STATE_HELD,
+	CALL_STATE_DIALING,
+	CALL_STATE_ALERTING,
+	CALL_STATE_INCOMING,
+	CALL_STATE_WAITING,
+	CALL_STATE_DISCONNECTED,
+};
+
+struct telephony;
+
+struct telephony_callbacks {
+	DBusMessage *(*dial)(DBusConnection *conn, DBusMessage *msg,
+					void *profile_data);
+	DBusMessage *(*swap_calls)(DBusConnection *conn, DBusMessage *msg,
+					void *profile_data);
+	DBusMessage *(*release_and_answer)(DBusConnection *conn,
+					DBusMessage *msg,
+					void *profile_data);
+	DBusMessage *(*release_and_swap)(DBusConnection *conn,
+					DBusMessage *msg,
+					void *profile_data);
+	DBusMessage *(*hold_and_answer)(DBusConnection *conn,
+					DBusMessage *msg,
+					void *profile_data);
+	DBusMessage *(*hangup_all)(DBusConnection *conn, DBusMessage *msg,
+					void *profile_data);
+	DBusMessage *(*create_multiparty)(DBusConnection *conn,
+					DBusMessage *msg,
+					void *profile_data);
+	DBusMessage *(*send_tones)(DBusConnection *conn, DBusMessage *msg,
+					void *profile_data);
+
+	DBusMessage *(*call_answer)(DBusConnection *conn, DBusMessage *msg,
+					void *call_data);
+	DBusMessage *(*call_hangup)(DBusConnection *conn, DBusMessage *msg,
+					void *call_data);
+	const char *(*call_get_line_id)(void *call_data);
+	const char *(*call_get_incoming_line)(void *call_data);
+	const char *(*call_get_name)(void *call_data);
+	bool (*call_get_multiparty)(void *call_data);
+	enum call_state (*call_get_state)(void *call_data);
+};
+
+struct call {
+	struct telephony	*device;
+	char			*path;
+	uint8_t			idx;
+
+	char			*line_id;
+	char			*incoming_line;
+	char			*name;
+	bool			multiparty;
+	enum call_state		state;
+
+	DBusMessage		*pending_msg;
+};
+
+struct telephony *telephony_new(struct btd_service *service,
+				void *profile_data,
+				struct telephony_callbacks *cbs);
+void telephony_free(struct telephony *telephony);
+int telephony_register_interface(struct telephony *telephony);
+void telephony_unregister_interface(struct telephony *telephony);
+
+struct btd_service *telephony_get_service(struct telephony *telephony);
+struct btd_device *telephony_get_device(struct telephony *telephony);
+const char *telephony_get_path(struct telephony *telephony);
+bdaddr_t telephony_get_src(struct telephony *telephony);
+bdaddr_t telephony_get_dst(struct telephony *telephony);
+void *telephony_get_profile_data(struct telephony *telephony);
+void telephony_set_state(struct telephony *telephony,
+				enum connection_state state);
+enum connection_state telephony_get_state(struct telephony *telephony);
+void telephony_set_network_service(struct telephony *telephony, bool service);
+bool telephony_get_network_service(struct telephony *telephony);
+void telephony_set_signal(struct telephony *telephony, uint8_t signal);
+uint8_t telephony_get_signal(struct telephony *telephony);
+void telephony_set_roaming(struct telephony *telephony, bool roaming);
+bool telephony_get_roaming(struct telephony *telephony);
+void telephony_set_battchg(struct telephony *telephony, uint8_t battchg);
+uint8_t telephony_get_battchg(struct telephony *telephony);
+void telephony_set_operator_name(struct telephony *telephony,
+				const char *name);
+const char *telephony_get_operator_name(struct telephony *telephony);
+
+struct call *telephony_new_call(struct telephony *telephony,
+	uint8_t idx,
+	enum call_state state,
+	void *user_data);
+void telephony_free_call(struct call *call);
+int telephony_call_register_interface(struct call *call);
+void telephony_call_unregister_interface(struct call *call);
+
+void telephony_call_set_state(struct call *call, enum call_state state);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 03/27] audio/telephony: Add skeleton for HFP profile
  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 14:51 ` [RFC BlueZ v2 02/27] audio/telephony: Add shared interfaces implementation Frédéric Danis
@ 2025-06-27 14:51 ` 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
                   ` (24 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 Makefile.plugins        |   5 +
 configure.ac            |   7 ++
 profiles/audio/hfp-hf.c | 216 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 228 insertions(+)
 create mode 100644 profiles/audio/hfp-hf.c

diff --git a/Makefile.plugins b/Makefile.plugins
index bae4363d0..66e8033e4 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -150,3 +150,8 @@ if ASHA
 builtin_modules += asha
 builtin_sources += profiles/audio/asha.h profiles/audio/asha.c
 endif
+
+if HFP
+builtin_modules += hfp
+builtin_sources += profiles/audio/telephony.c profiles/audio/hfp-hf.c
+endif
diff --git a/configure.ac b/configure.ac
index 07aeb46ca..247e1c928 100644
--- a/configure.ac
+++ b/configure.ac
@@ -238,6 +238,13 @@ if test "${enable_asha}" != "no"; then
 	AC_DEFINE(HAVE_ASHA, 1, [Define to 1 if you have ASHA support.])
 fi
 
+AC_ARG_ENABLE(hfp, AS_HELP_STRING([--disable-hfp],
+		[disable HFP support]), [enable_hfp=${enableval}])
+AM_CONDITIONAL(HFP, test "${enable_hfp}" != "no")
+if test "${enable_hfp}" != "no"; then
+	AC_DEFINE(HAVE_HFP, 1, [Define to 1 if you have HFP support.])
+fi
+
 AC_ARG_ENABLE(tools, AS_HELP_STRING([--disable-tools],
 		[disable Bluetooth tools]), [enable_tools=${enableval}])
 AM_CONDITIONAL(TOOLS, test "${enable_tools}" != "no")
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
new file mode 100644
index 000000000..f049ee5f7
--- /dev/null
+++ b/profiles/audio/hfp-hf.c
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2014  Intel Corporation. All rights reserved.
+ *  Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <stdint.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "bluetooth/sdp.h"
+#include "bluetooth/sdp_lib.h"
+#include "lib/uuid.h"
+
+#include "gdbus/gdbus.h"
+
+#include "btio/btio.h"
+#include "src/adapter.h"
+#include "src/btd.h"
+#include "src/dbus-common.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/plugin.h"
+#include "src/profile.h"
+#include "src/service.h"
+
+#include "telephony.h"
+
+struct hfp_device {
+	struct telephony	*telephony;
+	uint16_t		version;
+	GIOChannel		*io;
+};
+
+static void device_destroy(struct hfp_device *dev)
+{
+	DBG("%s", telephony_get_path(dev->telephony));
+
+	if (dev->io) {
+		g_io_channel_unref(dev->io);
+		dev->io = NULL;
+	}
+
+	telephony_unregister_interface(dev->telephony);
+}
+
+static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+	struct hfp_device *dev = user_data;
+	struct btd_service *service = telephony_get_service(dev->telephony);
+
+	DBG("");
+
+	if (err) {
+		error("%s", err->message);
+		goto failed;
+	}
+
+	g_io_channel_set_close_on_unref(chan, FALSE);
+
+	btd_service_connecting_complete(service, 0);
+
+	return;
+
+failed:
+	g_io_channel_shutdown(chan, TRUE, NULL);
+	device_destroy(dev);
+}
+
+struct telephony_callbacks hfp_callbacks = {
+};
+
+static int hfp_connect(struct btd_service *service)
+{
+	struct hfp_device *dev;
+	struct btd_profile *p;
+	const sdp_record_t *rec;
+	sdp_list_t *list, *protos;
+	sdp_profile_desc_t *desc;
+	int channel;
+	bdaddr_t src, dst;
+	GError *err = NULL;
+
+	DBG("");
+
+	dev = btd_service_get_user_data(service);
+
+	p = btd_service_get_profile(service);
+	rec = btd_device_get_record(telephony_get_device(dev->telephony),
+					p->remote_uuid);
+	if (!rec)
+		return -EIO;
+
+	if (sdp_get_profile_descs(rec, &list) == 0) {
+		desc = list->data;
+		dev->version = desc->version;
+	}
+	sdp_list_free(list, free);
+
+	if (sdp_get_access_protos(rec, &protos) < 0) {
+		error("unable to get access protocols from record");
+		return -EIO;
+	}
+
+	channel = sdp_get_proto_port(protos, RFCOMM_UUID);
+	sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+	sdp_list_free(protos, NULL);
+	if (channel <= 0) {
+		error("unable to get RFCOMM channel from record");
+		return -EIO;
+	}
+
+	src = telephony_get_src(dev->telephony);
+	dst = telephony_get_dst(dev->telephony);
+	dev->io = bt_io_connect(connect_cb, dev,
+		NULL, &err,
+		BT_IO_OPT_SOURCE_BDADDR, &src,
+		BT_IO_OPT_DEST_BDADDR, &dst,
+		BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+		BT_IO_OPT_CHANNEL, channel,
+		BT_IO_OPT_INVALID);
+	if (dev->io == NULL) {
+		error("unable to start connection");
+		return -EIO;
+	}
+
+	return telephony_register_interface(dev->telephony);
+}
+
+static int hfp_disconnect(struct btd_service *service)
+{
+	DBG("");
+
+	btd_service_disconnecting_complete(service, 0);
+
+	return 0;
+}
+
+static int hfp_probe(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	const char *path = device_get_path(device);
+	struct hfp_device *dev;
+
+	DBG("%s", path);
+
+	dev = g_new0(struct hfp_device, 1);
+	if (!dev)
+		return -EINVAL;
+
+	dev->telephony = telephony_new(service, dev, &hfp_callbacks);
+	btd_service_set_user_data(service, dev);
+
+	return 0;
+}
+
+static void hfp_remove(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	const char *path = device_get_path(device);
+	struct hfp_device *dev;
+
+	DBG("%s", path);
+
+	dev = btd_service_get_user_data(service);
+
+	telephony_free(dev->telephony);
+	g_free(dev);
+}
+
+static struct btd_profile hfp_hf_profile = {
+	.name		= "hfp",
+	.priority	= BTD_PROFILE_PRIORITY_MEDIUM,
+
+	.remote_uuid	= HFP_AG_UUID,
+	.device_probe	= hfp_probe,
+	.device_remove	= hfp_remove,
+
+	.auto_connect	= true,
+	.connect	= hfp_connect,
+	.disconnect	= hfp_disconnect,
+
+	.experimental	= true,
+};
+
+static int hfp_init(void)
+{
+	btd_profile_register(&hfp_hf_profile);
+
+	return 0;
+}
+
+static void hfp_exit(void)
+{
+	btd_profile_unregister(&hfp_hf_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(hfp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+		hfp_init, hfp_exit)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 04/27] audio/hfp-hf: Add HFP SLC connection and event support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (2 preceding siblings ...)
  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 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 05/27] audio/hfp-hf: Add dial support Frédéric Danis
                   ` (23 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
v1->v2:
  - Rename telephony_set_call_state() calls to telephony_call_set_state()
  - Use first available index of call for new call

 profiles/audio/hfp-hf.c | 934 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 934 insertions(+)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index f049ee5f7..a993229b9 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -36,23 +36,121 @@
 #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 "telephony.h"
 
+#define CALL_IND_NO_CALL_IN_PROGRESS	0x00
+#define CALL_IND_CALL_IN_PROGRESS	0x01
+
+#define CHLD_FEAT_REL		0x00000001
+#define CHLD_FEAT_REL_ACC	0x00000002
+#define CHLD_FEAT_REL_X		0x00000004
+#define CHLD_FEAT_HOLD_ACC	0x00000008
+#define CHLD_FEAT_PRIV_X	0x00000010
+#define CHLD_FEAT_MERGE		0x00000020
+#define CHLD_FEAT_MERGE_DETACH	0x00000040
+
+#define HFP_HF_FEAT_ECNR				0x00000001
+#define HFP_HF_FEAT_3WAY				0x00000002
+#define HFP_HF_FEAT_CLIP				0x00000004
+#define HFP_HF_FEAT_VOICE_RECOGNITION			0x00000008
+#define HFP_HF_FEAT_REMOTE_VOLUME_CONTROL		0x00000010
+#define HFP_HF_FEAT_ENHANCED_CALL_STATUS		0x00000020
+#define HFP_HF_FEAT_ENHANCED_CALL_CONTROL		0x00000040
+#define HFP_HF_FEAT_CODEC_NEGOTIATION			0x00000080
+#define HFP_HF_FEAT_HF_INDICATORS			0x00000100
+#define HFP_HF_FEAT_ESCO_S4_T2				0x00000200
+#define HFP_HF_FEAT_ENHANCED_VOICE_RECOGNITION_STATUS	0x00000400
+#define HFP_HF_FEAT_VOICE_RECOGNITION_TEXT		0x00000800
+
+#define HFP_AG_FEAT_3WAY				0x00000001
+#define HFP_AG_FEAT_ECNR				0x00000002
+#define HFP_AG_FEAT_VOICE_RECOGNITION			0x00000004
+#define HFP_AG_FEAT_IN_BAND_RING_TONE			0x00000008
+#define HFP_AG_FEAT_ATTACH_VOICE_TAG			0x00000010
+#define HFP_AG_FEAT_REJECT_CALL				0x00000020
+#define HFP_AG_FEAT_ENHANCED_CALL_STATUS		0x00000040
+#define HFP_AG_FEAT_ENHANCED_CALL_CONTROL		0x00000080
+#define HFP_AG_FEAT_EXTENDED_RES_CODE			0x00000100
+#define HFP_AG_FEAT_CODEC_NEGOTIATION			0x00000200
+#define HFP_AG_FEAT_HF_INDICATORS			0x00000400
+#define HFP_AG_FEAT_ESCO_S4_T2				0x00000800
+#define HFP_AG_FEAT_ENHANCED_VOICE_RECOGNITION_STATUS	0x00001000
+#define HFP_AG_FEAT_VOICE_RECOGNITION_TEXT		0x00001000
+
+#define HFP_HF_FEATURES		(HFP_HF_FEAT_ECNR | HFP_HF_FEAT_3WAY |\
+				HFP_HF_FEAT_CLIP |\
+				HFP_HF_FEAT_REMOTE_VOLUME_CONTROL |\
+				HFP_HF_FEAT_ENHANCED_CALL_STATUS |\
+				HFP_HF_FEAT_ESCO_S4_T2)
+
+enum hfp_indicator {
+	HFP_INDICATOR_SERVICE = 0,
+	HFP_INDICATOR_CALL,
+	HFP_INDICATOR_CALLSETUP,
+	HFP_INDICATOR_CALLHELD,
+	HFP_INDICATOR_SIGNAL,
+	HFP_INDICATOR_ROAM,
+	HFP_INDICATOR_BATTCHG,
+	HFP_INDICATOR_LAST
+};
+
+enum call_setup {
+	CIND_CALLSETUP_NONE = 0,
+	CIND_CALLSETUP_INCOMING,
+	CIND_CALLSETUP_DIALING,
+	CIND_CALLSETUP_ALERTING
+};
+
+enum call_held {
+	CIND_CALLHELD_NONE = 0,
+	CIND_CALLHELD_HOLD_AND_ACTIVE,
+	CIND_CALLHELD_HOLD
+};
+
+typedef void (*ciev_func_t)(uint8_t val, void *user_data);
+
+struct indicator {
+	uint8_t index;
+	uint32_t min;
+	uint32_t max;
+	uint32_t val;
+	ciev_func_t cb;
+};
+
 struct hfp_device {
 	struct telephony	*telephony;
 	uint16_t		version;
 	GIOChannel		*io;
+	enum connection_state	state;
+	uint32_t		hfp_hf_features;
+	uint32_t		features;
+	struct hfp_hf		*hf;
+	struct indicator	ag_ind[HFP_INDICATOR_LAST];
+	uint32_t		chld_features;
+	bool			call;
+	enum call_setup		call_setup;
+	enum call_held		call_held;
+	GSList			*calls;
 };
 
 static void device_destroy(struct hfp_device *dev)
 {
 	DBG("%s", telephony_get_path(dev->telephony));
 
+	telephony_set_state(dev->telephony, DISCONNECTING);
+
+	if (dev->hf) {
+		hfp_hf_unref(dev->hf);
+		dev->hf = NULL;
+	}
+
 	if (dev->io) {
 		g_io_channel_unref(dev->io);
 		dev->io = NULL;
@@ -61,6 +159,817 @@ static void device_destroy(struct hfp_device *dev)
 	telephony_unregister_interface(dev->telephony);
 }
 
+static void slc_error(struct hfp_device *dev)
+{
+	error("Could not create SLC - dropping connection");
+	hfp_hf_disconnect(dev->hf);
+}
+
+static void set_chld_feat(struct hfp_device *dev, char *feat)
+{
+	DBG(" %s", feat);
+
+	if (strcmp(feat, "0") == 0)
+		dev->chld_features |= CHLD_FEAT_REL;
+	else if (strcmp(feat, "1") == 0)
+		dev->chld_features |= CHLD_FEAT_REL_ACC;
+	else if (strcmp(feat, "1x") == 0)
+		dev->chld_features |= CHLD_FEAT_REL_X;
+	else if (strcmp(feat, "2") == 0)
+		dev->chld_features |= CHLD_FEAT_HOLD_ACC;
+	else if (strcmp(feat, "2x") == 0)
+		dev->chld_features |= CHLD_FEAT_PRIV_X;
+	else if (strcmp(feat, "3") == 0)
+		dev->chld_features |= CHLD_FEAT_MERGE;
+	else if (strcmp(feat, "4") == 0)
+		dev->chld_features |= CHLD_FEAT_MERGE_DETACH;
+}
+
+static const char *cme_error_to_string(uint8_t cme_error)
+{
+	switch (cme_error) {
+	case 0: return "AG failure";
+	case 1: return "no connection to phone";
+	case 3: return "operation not allowed";
+	case 4: return "operation not supported";
+	case 5: return "PH-SIM PIN required";
+	case 10: return "SIM not inserted";
+	case 11: return "SIM PIN required";
+	case 12: return "SIM PUK required";
+	case 13: return "SIM failure";
+	case 14: return "SIM busy";
+	case 16: return "incorrect password";
+	case 17: return "SIM PIN2 required";
+	case 18: return "SIM PUK2 required";
+	case 20: return "memory full";
+	case 21: return "invalid index";
+	case 23: return "memory failure";
+	case 24: return "text string too long";
+	case 25: return "invalid characters in text string";
+	case 26: return "dial string too long";
+	case 27: return "invalid characters in dial string";
+	case 30: return "no network service";
+	case 31: return "network Timeout";
+	case 32: return "network not allowed - Emergency calls only";
+	default: return "Unknown CME error";
+	}
+}
+
+static void cmd_complete_cb(enum hfp_result result, enum hfp_error cme_err,
+	void *user_data)
+{
+	DBusMessage *msg = user_data;
+	DBusMessage *reply = NULL;
+
+	DBG("%u", result);
+
+	if (msg == NULL)
+		return;
+
+	switch (result) {
+	case HFP_RESULT_OK:
+		reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+		break;
+	case HFP_RESULT_NO_CARRIER:
+		reply = btd_error_failed(msg, "no-carrier");
+		break;
+	case HFP_RESULT_ERROR:
+		reply = btd_error_failed(msg, "unknown");
+		break;
+	case HFP_RESULT_BUSY:
+		reply = btd_error_busy(msg);
+		break;
+	case HFP_RESULT_NO_ANSWER:
+		reply = btd_error_failed(msg, "no-answer");
+		break;
+	case HFP_RESULT_DELAYED:
+		reply = btd_error_failed(msg, "delayed");
+		break;
+	case HFP_RESULT_REJECTED:
+		reply = btd_error_failed(msg, "rejected");
+		break;
+	case HFP_RESULT_CME_ERROR:
+		reply = btd_error_failed(msg, cme_error_to_string(cme_err));
+		break;
+	case HFP_RESULT_CONNECT:
+	case HFP_RESULT_RING:
+	case HFP_RESULT_NO_DIALTONE:
+	default:
+		reply = btd_error_failed(msg, "unknown");
+		error("hf-client: Unknown error code %d", result);
+		break;
+	}
+
+	if (reply) {
+		g_dbus_send_message(btd_get_dbus_connection(), reply);
+		dbus_message_unref(msg);
+	}
+}
+
+static uint8_t next_index(struct hfp_device *dev)
+{
+	uint8_t i;
+
+	for (i = 1; i != 0; i++) {
+		GSList *l;
+		bool found = false;
+
+		for (l = dev->calls; l; l = l->next) {
+			struct call *call = l->data;
+
+			if (call->idx == i) {
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+			return i;
+	}
+
+	error("hf-client: No free call index found");
+	return 0;
+}
+
+static void ciev_cb(struct hfp_context *context, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+	unsigned int index, val;
+	int i;
+
+	DBG("");
+
+	if (!hfp_context_get_number(context, &index))
+		return;
+
+	if (!hfp_context_get_number(context, &val))
+		return;
+
+	for (i = 0; i < HFP_INDICATOR_LAST; i++) {
+		if (dev->ag_ind[i].index != index)
+			continue;
+
+		if (dev->ag_ind[i].cb) {
+			dev->ag_ind[i].val = val;
+			dev->ag_ind[i].cb(val, dev);
+			return;
+		}
+	}
+}
+
+static void slc_completed(struct hfp_device *dev)
+{
+	int i;
+	struct indicator *ag_ind;
+
+	DBG("");
+
+	ag_ind = dev->ag_ind;
+
+	telephony_set_state(dev->telephony, CONNECTED);
+
+	/* Notify Android with indicators */
+	for (i = 0; i < HFP_INDICATOR_LAST; i++) {
+		if (!ag_ind[i].cb)
+			continue;
+
+		ag_ind[i].cb(ag_ind[i].val, dev);
+	}
+
+	/* TODO: register unsolicited results handlers */
+
+	hfp_hf_register(dev->hf, ciev_cb, "+CIEV", dev, NULL);
+
+	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+COPS=3,0"))
+		info("hf-client: Could not send AT+COPS=3,0");
+}
+
+static void slc_chld_resp(enum hfp_result result, enum hfp_error cme_err,
+							void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	hfp_hf_unregister(dev->hf, "+CHLD");
+
+	if (result != HFP_RESULT_OK) {
+		error("hf-client: CHLD error: %d", result);
+		slc_error(dev);
+		return;
+	}
+
+	slc_completed(dev);
+}
+
+static void slc_chld_cb(struct hfp_context *context, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+	char feat[3];
+
+	if (!hfp_context_open_container(context))
+		goto failed;
+
+	while (hfp_context_get_unquoted_string(context, feat, sizeof(feat)))
+		set_chld_feat(dev, feat);
+
+	if (!hfp_context_close_container(context))
+		goto failed;
+
+	return;
+
+failed:
+	error("hf-client: Error on CHLD response");
+	slc_error(dev);
+}
+
+static void slc_cmer_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: CMER error: %d", result);
+		goto failed;
+	}
+
+	/* Continue with SLC creation */
+	if (!(dev->features & HFP_AG_FEAT_3WAY)) {
+		slc_completed(dev);
+		return;
+	}
+
+	if (!hfp_hf_register(dev->hf, slc_chld_cb, "+CHLD", dev, NULL)) {
+		error("hf-client: Could not register +CHLD");
+		goto failed;
+	}
+
+	if (!hfp_hf_send_command(dev->hf, slc_chld_resp, dev, "AT+CHLD=?")) {
+		error("hf-client: Could not send AT+CHLD");
+		goto failed;
+	}
+
+	return;
+
+failed:
+	slc_error(dev);
+}
+
+static void slc_cind_status_resp(enum hfp_result result,
+	enum hfp_error cme_err,
+	void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	hfp_hf_unregister(dev->hf, "+CIND");
+
+	if (result != HFP_RESULT_OK) {
+		error("hf-client: CIND error: %d", result);
+		goto failed;
+	}
+
+	/* Continue with SLC creation */
+	if (!hfp_hf_send_command(dev->hf, slc_cmer_resp, dev,
+		"AT+CMER=3,0,0,1")) {
+		error("hf-client: Counld not send AT+CMER");
+		goto failed;
+	}
+
+	return;
+
+failed:
+	slc_error(dev);
+}
+
+static void set_indicator_value(uint8_t index, unsigned int val,
+	struct indicator *ag_ind, struct hfp_device *dev)
+{
+	int i;
+
+	for (i = 0; i < HFP_INDICATOR_LAST; i++) {
+		if (index != ag_ind[i].index)
+			continue;
+
+		ag_ind[i].val = val;
+		ag_ind[i].cb(val, dev);
+		return;
+	}
+}
+
+static void slc_cind_status_cb(struct hfp_context *context,
+	void *user_data)
+{
+	struct hfp_device *dev = user_data;
+	uint8_t index = 1;
+
+	DBG("");
+
+	while (hfp_context_has_next(context)) {
+		uint32_t val;
+
+		if (!hfp_context_get_number(context, &val)) {
+			error("hf-client: Error on CIND status response");
+			return;
+		}
+
+		set_indicator_value(index++, val, dev->ag_ind, dev);
+	}
+}
+
+static void slc_cind_resp(enum hfp_result result, enum hfp_error cme_err,
+	void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	hfp_hf_unregister(dev->hf, "+CIND");
+
+	if (result != HFP_RESULT_OK) {
+		error("hf-client: CIND error: %d", result);
+		goto failed;
+	}
+
+	/* Continue with SLC creation */
+	if (!hfp_hf_register(dev->hf, slc_cind_status_cb, "+CIND", dev,
+			NULL)) {
+		error("hf-client: Counld not register +CIND");
+		goto failed;
+	}
+
+	if (!hfp_hf_send_command(dev->hf, slc_cind_status_resp, dev,
+			"AT+CIND?")) {
+		error("hf-client: Counld not send AT+CIND?");
+		goto failed;
+	}
+
+	return;
+
+failed:
+	slc_error(dev);
+}
+
+static void ciev_service_cb(uint8_t val, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	if (val > 1) {
+		error("hf-client: Incorrect state %u:", val);
+		return;
+	}
+
+	telephony_set_network_service(dev->telephony, val);
+}
+
+static void activate_calls(gpointer data, gpointer user_data)
+{
+	struct call *call = data;
+
+	if (call->state == CALL_STATE_DIALING ||
+			call->state == CALL_STATE_ALERTING ||
+			call->state == CALL_STATE_INCOMING)
+		telephony_call_set_state(call, CALL_STATE_ACTIVE);
+}
+
+static void deactivate_active_calls(gpointer data, gpointer user_data)
+{
+	struct call *call = data;
+	struct hfp_device *dev = user_data;
+
+	if (call->state == CALL_STATE_ACTIVE) {
+		telephony_call_set_state(call, CALL_STATE_DISCONNECTED);
+		dev->calls = g_slist_remove(dev->calls, call);
+		telephony_call_unregister_interface(call);
+	}
+}
+
+static void ciev_call_cb(uint8_t val, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	if (val > CALL_IND_CALL_IN_PROGRESS) {
+		error("hf-client: Incorrect call state %u:", val);
+		return;
+	}
+
+	if (dev->call == val)
+		return;
+
+	dev->call = !!val;
+
+	if (dev->call == TRUE)
+		g_slist_foreach(dev->calls, activate_calls, dev);
+	else
+		g_slist_foreach(dev->calls, deactivate_active_calls, dev);
+}
+
+static void callsetup_deactivate(gpointer data, gpointer user_data)
+{
+	struct call *call = data;
+	struct hfp_device *dev = user_data;
+
+	if (call->state == CALL_STATE_DIALING ||
+			call->state == CALL_STATE_ALERTING ||
+			call->state == CALL_STATE_INCOMING ||
+			call->state == CALL_STATE_WAITING) {
+		telephony_call_set_state(call, CALL_STATE_DISCONNECTED);
+		dev->calls = g_slist_remove(dev->calls, call);
+		telephony_call_unregister_interface(call);
+	}
+}
+
+static void callsetup_alerting(gpointer data, gpointer user_data)
+{
+	struct call *call = data;
+
+	if (call->state == CALL_STATE_DIALING)
+		telephony_call_set_state(call, CALL_STATE_ALERTING);
+}
+
+static void ciev_callsetup_cb(uint8_t val, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	if (val > CIND_CALLSETUP_ALERTING) {
+		error("hf-client: Incorrect call setup state %u:", val);
+		return;
+	}
+
+	if (dev->call_setup == val)
+		return;
+
+	dev->call_setup = val;
+
+	if (dev->call_setup == CIND_CALLSETUP_NONE) {
+		g_slist_foreach(dev->calls, callsetup_deactivate, dev);
+	} else if (dev->call_setup == CIND_CALLSETUP_INCOMING) {
+		bool found = FALSE;
+		GSList *l;
+
+		for (l = dev->calls; l; l = l->next) {
+			struct call *call = l->data;
+
+			if (call->state == CALL_STATE_INCOMING ||
+				call->state == CALL_STATE_WAITING) {
+				DBG("incoming call already in progress (%d)",
+								 call->state);
+				found = TRUE;
+				break;
+			}
+		}
+
+		if (!found) {
+			struct call *call;
+			uint8_t idx = next_index(dev);
+
+			call = telephony_new_call(dev->telephony, idx,
+							CALL_STATE_INCOMING,
+							NULL);
+			if (telephony_call_register_interface(call)) {
+				telephony_free_call(call);
+				return;
+			}
+			dev->calls = g_slist_append(dev->calls, call);
+		}
+	} else if (dev->call_setup == CIND_CALLSETUP_DIALING) {
+		bool found = FALSE;
+		GSList *l;
+
+		for (l = dev->calls; l; l = l->next) {
+			struct call *call = l->data;
+
+			if (call->state == CALL_STATE_DIALING ||
+				call->state == CALL_STATE_ALERTING) {
+				DBG("dialing call already in progress (%d)",
+								call->state);
+				found = TRUE;
+				break;
+			}
+		}
+
+		if (!found) {
+			struct call *call;
+			uint8_t idx = next_index(dev);
+
+			call = telephony_new_call(dev->telephony, idx,
+							CALL_STATE_DIALING,
+							NULL);
+			if (telephony_call_register_interface(call)) {
+				telephony_free_call(call);
+				return;
+			}
+			dev->calls = g_slist_append(dev->calls, call);
+		}
+	} else if (dev->call_setup == CIND_CALLSETUP_ALERTING) {
+		g_slist_foreach(dev->calls, callsetup_alerting, dev);
+	}
+}
+
+static void ciev_callheld_cb(uint8_t val, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	if (val > CIND_CALLHELD_HOLD) {
+		error("hf-client: Incorrect call held state %u:", val);
+		return;
+	}
+
+	dev->call_held = val;
+
+	if (dev->call_held == CIND_CALLHELD_NONE) {
+		GSList *l;
+		bool found_waiting = FALSE;
+
+		for (l = dev->calls; l; l = l->next) {
+			struct call *call = l->data;
+
+			if (call->state != CALL_STATE_WAITING)
+				continue;
+
+			telephony_call_set_state(call,
+					CALL_STATE_DISCONNECTED);
+			found_waiting = TRUE;
+			dev->calls = g_slist_remove(dev->calls, call);
+			telephony_call_unregister_interface(call);
+		}
+
+		if (!found_waiting) {
+			for (l = dev->calls; l; l = l->next) {
+				struct call *call = l->data;
+
+				if (call->state != CALL_STATE_HELD)
+					continue;
+
+				telephony_call_set_state(call,
+						CALL_STATE_DISCONNECTED);
+				dev->calls = g_slist_remove(dev->calls, call);
+				telephony_call_unregister_interface(call);
+			}
+		}
+	} else if (dev->call_held == CIND_CALLHELD_HOLD_AND_ACTIVE) {
+		GSList *l;
+
+		for (l = dev->calls; l; l = l->next) {
+			struct call *call = l->data;
+
+			if (call->state == CALL_STATE_ACTIVE)
+				telephony_call_set_state(call,
+							CALL_STATE_HELD);
+			else if (call->state == CALL_STATE_HELD)
+				telephony_call_set_state(call,
+							CALL_STATE_ACTIVE);
+		}
+	} else if (dev->call_held == CIND_CALLHELD_HOLD) {
+		GSList *l;
+
+		for (l = dev->calls; l; l = l->next) {
+			struct call *call = l->data;
+
+			if (call->state == CALL_STATE_ACTIVE ||
+					call->state == CALL_STATE_WAITING)
+				telephony_call_set_state(call, CALL_STATE_HELD);
+		}
+	}
+}
+
+static void ciev_signal_cb(uint8_t val, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	if (val > 5) {
+		error("hf-client: Incorrect signal value %u:", val);
+		return;
+	}
+
+	telephony_set_signal(dev->telephony, val);
+}
+
+static void ciev_roam_cb(uint8_t val, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	if (val > 1) {
+		error("hf-client: Incorrect roaming state %u:", val);
+		return;
+	}
+
+	telephony_set_roaming(dev->telephony, val);
+}
+
+static void ciev_battchg_cb(uint8_t val, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	if (val > 5) {
+		error("hf-client: Incorrect battery charge value %u:", val);
+		return;
+	}
+
+	telephony_set_battchg(dev->telephony, val);
+}
+
+static void set_indicator_parameters(uint8_t index, const char *indicator,
+	unsigned int min,
+	unsigned int max,
+	struct indicator *ag_ind)
+{
+	DBG("%s, %i", indicator, index);
+
+	/* TODO: Verify min/max values ? */
+
+	if (strcmp("service", indicator) == 0) {
+		ag_ind[HFP_INDICATOR_SERVICE].index = index;
+		ag_ind[HFP_INDICATOR_SERVICE].min = min;
+		ag_ind[HFP_INDICATOR_SERVICE].max = max;
+		ag_ind[HFP_INDICATOR_SERVICE].cb = ciev_service_cb;
+		return;
+	}
+
+	if (strcmp("call", indicator) == 0) {
+		ag_ind[HFP_INDICATOR_CALL].index = index;
+		ag_ind[HFP_INDICATOR_CALL].min = min;
+		ag_ind[HFP_INDICATOR_CALL].max = max;
+		ag_ind[HFP_INDICATOR_CALL].cb = ciev_call_cb;
+		return;
+	}
+
+	if (strcmp("callsetup", indicator) == 0) {
+		ag_ind[HFP_INDICATOR_CALLSETUP].index = index;
+		ag_ind[HFP_INDICATOR_CALLSETUP].min = min;
+		ag_ind[HFP_INDICATOR_CALLSETUP].max = max;
+		ag_ind[HFP_INDICATOR_CALLSETUP].cb = ciev_callsetup_cb;
+		return;
+	}
+
+	if (strcmp("callheld", indicator) == 0) {
+		ag_ind[HFP_INDICATOR_CALLHELD].index = index;
+		ag_ind[HFP_INDICATOR_CALLHELD].min = min;
+		ag_ind[HFP_INDICATOR_CALLHELD].max = max;
+		ag_ind[HFP_INDICATOR_CALLHELD].cb = ciev_callheld_cb;
+		return;
+	}
+
+	if (strcmp("signal", indicator) == 0) {
+		ag_ind[HFP_INDICATOR_SIGNAL].index = index;
+		ag_ind[HFP_INDICATOR_SIGNAL].min = min;
+		ag_ind[HFP_INDICATOR_SIGNAL].max = max;
+		ag_ind[HFP_INDICATOR_SIGNAL].cb = ciev_signal_cb;
+		return;
+	}
+
+	if (strcmp("roam", indicator) == 0) {
+		ag_ind[HFP_INDICATOR_ROAM].index = index;
+		ag_ind[HFP_INDICATOR_ROAM].min = min;
+		ag_ind[HFP_INDICATOR_ROAM].max = max;
+		ag_ind[HFP_INDICATOR_ROAM].cb = ciev_roam_cb;
+		return;
+	}
+
+	if (strcmp("battchg", indicator) == 0) {
+		ag_ind[HFP_INDICATOR_BATTCHG].index = index;
+		ag_ind[HFP_INDICATOR_BATTCHG].min = min;
+		ag_ind[HFP_INDICATOR_BATTCHG].max = max;
+		ag_ind[HFP_INDICATOR_BATTCHG].cb = ciev_battchg_cb;
+		return;
+	}
+
+	error("hf-client: Unknown indicator: %s", indicator);
+}
+
+static void slc_cind_cb(struct hfp_context *context, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+	int index = 1;
+
+	DBG("");
+
+	while (hfp_context_has_next(context)) {
+		char name[255];
+		unsigned int min, max;
+
+		/* e.g ("callsetup",(0-3)) */
+		if (!hfp_context_open_container(context))
+			break;
+
+		if (!hfp_context_get_string(context, name, sizeof(name))) {
+			error("hf-client: Could not get string");
+			goto failed;
+		}
+
+		if (!hfp_context_open_container(context)) {
+			error("hf-client: Could not open container");
+			goto failed;
+		}
+
+		if (!hfp_context_get_range(context, &min, &max)) {
+			if (!hfp_context_get_number(context, &min)) {
+				error("hf-client: Could not get number");
+				goto failed;
+			}
+
+			if (!hfp_context_get_number(context, &max)) {
+				error("hf-client: Could not get number");
+				goto failed;
+			}
+		}
+
+		if (!hfp_context_close_container(context)) {
+			error("hf-client: Could not close container");
+			goto failed;
+		}
+
+		if (!hfp_context_close_container(context)) {
+			error("hf-client: Could not close container");
+			goto failed;
+		}
+
+		set_indicator_parameters(index, name, min, max, dev->ag_ind);
+		index++;
+	}
+
+	return;
+
+failed:
+	error("hf-client: Error on CIND response");
+	slc_error(dev);
+}
+
+static void slc_brsf_cb(struct hfp_context *context, void *user_data)
+{
+	unsigned int feat;
+	struct hfp_device *dev = user_data;
+
+	DBG("");
+
+	if (hfp_context_get_number(context, &feat))
+		dev->features = feat;
+}
+
+static void slc_brsf_resp(enum hfp_result result, enum hfp_error cme_err,
+	void *user_data)
+{
+	struct hfp_device *dev = user_data;
+
+	hfp_hf_unregister(dev->hf, "+BRSF");
+
+	if (result != HFP_RESULT_OK) {
+		error("BRSF error: %d", result);
+		goto failed;
+	}
+
+	/* Continue with SLC creation */
+	if (!hfp_hf_register(dev->hf, slc_cind_cb, "+CIND", dev, NULL)) {
+		error("hf-client: Could not register for +CIND");
+		goto failed;
+	}
+
+	if (!hfp_hf_send_command(dev->hf, slc_cind_resp, dev, "AT+CIND=?")) {
+		error("hf-client: Could not send AT+CIND command");
+		goto failed;
+	}
+
+	return;
+
+failed:
+	slc_error(dev);
+}
+
+static bool create_slc(struct hfp_device *dev)
+{
+	DBG("");
+
+	if (!hfp_hf_register(dev->hf, slc_brsf_cb, "+BRSF", dev, NULL))
+		return false;
+
+	return hfp_hf_send_command(dev->hf, slc_brsf_resp, dev, "AT+BRSF=%u",
+							dev->hfp_hf_features);
+}
+
+static void hfp_disconnect_watch(void *user_data)
+{
+	DBG("");
+
+	device_destroy(user_data);
+}
+
 static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
 {
 	struct hfp_device *dev = user_data;
@@ -73,8 +982,25 @@ static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
 		goto failed;
 	}
 
+	dev->hf = hfp_hf_new(g_io_channel_unix_get_fd(chan));
+	if (!dev->hf) {
+		error("Could not create hfp io");
+		goto failed;
+	}
+
 	g_io_channel_set_close_on_unref(chan, FALSE);
 
+	hfp_hf_set_close_on_unref(dev->hf, true);
+	hfp_hf_set_disconnect_handler(dev->hf, hfp_disconnect_watch,
+					dev, NULL);
+
+	if (!create_slc(dev)) {
+		error("Could not start SLC creation");
+		hfp_hf_disconnect(dev->hf);
+		goto failed;
+	}
+
+	telephony_set_state(dev->telephony, SLC_CONNECTING);
 	btd_service_connecting_complete(service, 0);
 
 	return;
@@ -146,8 +1072,15 @@ static int hfp_connect(struct btd_service *service)
 
 static int hfp_disconnect(struct btd_service *service)
 {
+	struct hfp_device *dev;
+
 	DBG("");
 
+	dev = btd_service_get_user_data(service);
+
+	if (dev->hf)
+		hfp_hf_disconnect(dev->hf);
+
 	btd_service_disconnecting_complete(service, 0);
 
 	return 0;
@@ -166,6 +1099,7 @@ static int hfp_probe(struct btd_service *service)
 		return -EINVAL;
 
 	dev->telephony = telephony_new(service, dev, &hfp_callbacks);
+	dev->hfp_hf_features = HFP_HF_FEATURES;
 	btd_service_set_user_data(service, dev);
 
 	return 0;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 05/27] audio/hfp-hf: Add dial support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (3 preceding siblings ...)
  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 ` 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
                   ` (22 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 v1->v2:
  - Use first available index of call for new call
  - Fix DBus message memory leak

profiles/audio/hfp-hf.c | 75 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 75 insertions(+)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index a993229b9..ffe4ab708 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -1010,7 +1010,82 @@ failed:
 	device_destroy(dev);
 }
 
+static void hfp_dial_cb(enum hfp_result result, enum hfp_error cme_err,
+							void *user_data)
+{
+	struct call *call = user_data;
+	DBusMessage *msg = call->pending_msg;
+	DBusMessage *reply;
+	struct hfp_device *dev = telephony_get_profile_data(call->device);
+
+	DBG("");
+
+	call->pending_msg = NULL;
+
+	if (result != HFP_RESULT_OK) {
+		error("Dialing error: %d", result);
+		reply = g_dbus_create_error(msg, ERROR_INTERFACE
+					".Failed",
+					"Dial command failed: %d", result);
+		g_dbus_send_message(btd_get_dbus_connection(), reply);
+		dbus_message_unref(msg);
+		telephony_free_call(call);
+		return;
+	}
+
+	if (telephony_call_register_interface(call)) {
+		telephony_free_call(call);
+		return;
+	}
+
+	dev->calls = g_slist_append(dev->calls, call);
+
+	g_dbus_send_reply(btd_get_dbus_connection(), msg, DBUS_TYPE_INVALID);
+	dbus_message_unref(msg);
+}
+
+static DBusMessage *hfp_dial(DBusConnection *conn, DBusMessage *msg,
+				void *profile_data)
+{
+	struct hfp_device *dev = profile_data;
+	const char *number;
+	struct call *call;
+	uint8_t idx = next_index(dev);
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+					DBUS_TYPE_INVALID)) {
+		return btd_error_invalid_args(msg);
+	}
+
+	call = telephony_new_call(dev->telephony, idx, CALL_STATE_DIALING,
+					NULL);
+	call->pending_msg = dbus_message_ref(msg);
+
+	if (number != NULL && number[0] != '\0') {
+		DBG("Dialing %s", number);
+
+		call->line_id = g_strdup(number);
+
+		if (!hfp_hf_send_command(dev->hf, hfp_dial_cb, call,
+							"ATD%s;", number))
+			goto failed;
+	} else {
+		DBG("Redialing");
+
+		if (!hfp_hf_send_command(dev->hf, hfp_dial_cb, call,
+							"AT+BLDN"))
+			goto failed;
+	}
+
+	return NULL;
+
+failed:
+	telephony_free_call(call);
+	return btd_error_failed(msg, "Dial command failed");
+}
+
 struct telephony_callbacks hfp_callbacks = {
+	.dial = hfp_dial,
 };
 
 static int hfp_connect(struct btd_service *service)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 06/27] audio/hfp-hf: Add hangup all calls support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (4 preceding siblings ...)
  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 ` 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
                   ` (21 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/hfp-hf.c | 57 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index ffe4ab708..6dbd6b3b0 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -1084,8 +1084,65 @@ failed:
 	return btd_error_failed(msg, "Dial command failed");
 }
 
+static DBusMessage *hfp_hangup_all(DBusConnection *conn, DBusMessage *msg,
+				void *profile_data)
+{
+	struct hfp_device *dev = profile_data;
+	bool found_active = FALSE;
+	bool found_held = FALSE;
+	GSList *l;
+
+	DBG("");
+
+	for (l = dev->calls; l; l = l->next) {
+		struct call *call = l->data;
+
+		switch (call->state) {
+		case CALL_STATE_ACTIVE:
+		case CALL_STATE_DIALING:
+		case CALL_STATE_ALERTING:
+		case CALL_STATE_INCOMING:
+			found_active = TRUE;
+			break;
+		case CALL_STATE_HELD:
+		case CALL_STATE_WAITING:
+			found_held = TRUE;
+			break;
+		case CALL_STATE_DISCONNECTED:
+			break;
+		}
+	}
+
+	if (!found_active && !found_held)
+		return btd_error_failed(msg, "No call to hang up");
+
+	if (found_held) {
+		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb,
+				found_active ? NULL : dbus_message_ref(msg),
+				"AT+CHLD=0")) {
+			warn("Failed to hangup held calls");
+			goto failed;
+		}
+	}
+
+	if (found_active) {
+		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb,
+				dbus_message_ref(msg),
+				"AT+CHUP")) {
+			warn("Failed to hangup active calls");
+			goto failed;
+		}
+	}
+
+	return NULL;
+
+failed:
+	return btd_error_failed(msg, "Hang up all command failed");
+}
+
 struct telephony_callbacks hfp_callbacks = {
 	.dial = hfp_dial,
+	.hangup_all = hfp_hangup_all,
 };
 
 static int hfp_connect(struct btd_service *service)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 07/27] audio/hfp-hf: Add answer a specific call support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (5 preceding siblings ...)
  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 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 08/27] client/telephony: Add new submenu Frédéric Danis
                   ` (20 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/hfp-hf.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 6dbd6b3b0..e7bfe4f59 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -1140,9 +1140,31 @@ failed:
 	return btd_error_failed(msg, "Hang up all command failed");
 }
 
+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);
+
+	DBG("");
+
+	if (call->state != CALL_STATE_INCOMING)
+		return btd_error_failed(msg, "Invalid state call");
+
+	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb,
+			dbus_message_ref(msg), "ATA"))
+		goto failed;
+
+	return NULL;
+
+failed:
+	return btd_error_failed(msg, "Answer command failed");
+}
+
 struct telephony_callbacks hfp_callbacks = {
 	.dial = hfp_dial,
 	.hangup_all = hfp_hangup_all,
+	.call_answer = call_answer,
 };
 
 static int hfp_connect(struct btd_service *service)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 08/27] client/telephony: Add new submenu
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (6 preceding siblings ...)
  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 ` 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
                   ` (19 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
v1->v2:
  - Rename org.bluez.TelephonyCall1 to org.bluez.Call1
  - Display UUID and OperatorName in telephony.show command

 Makefile.tools                    |   9 +-
 client/bluetoothctl-telephony.rst |  95 ++++++
 client/main.c                     |   3 +
 client/telephony.c                | 526 ++++++++++++++++++++++++++++++
 client/telephony.h                |  12 +
 5 files changed, 642 insertions(+), 3 deletions(-)
 create mode 100644 client/bluetoothctl-telephony.rst
 create mode 100644 client/telephony.c
 create mode 100644 client/telephony.h

diff --git a/Makefile.tools b/Makefile.tools
index e60c31b1d..0f4eeb063 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -15,7 +15,8 @@ client_bluetoothctl_SOURCES = client/main.c \
 					client/player.h client/player.c \
 					client/mgmt.h client/mgmt.c \
 					client/assistant.h client/assistant.c \
-					client/hci.h client/hci.c
+					client/hci.h client/hci.c \
+					client/telephony.h client/telephony.c
 client_bluetoothctl_LDADD = lib/libbluetooth-internal.la \
 			gdbus/libgdbus-internal.la src/libshared-glib.la \
 			$(GLIB_LIBS) $(DBUS_LIBS) -lreadline
@@ -351,7 +352,8 @@ man_MANS += tools/rctest.1 tools/l2ping.1 tools/btattach.1 tools/isotest.1 \
 		client/bluetoothctl-advertise.1 client/bluetoothctl-endpoint.1 \
 		client/bluetoothctl-gatt.1 client/bluetoothctl-player.1 \
 		client/bluetoothctl-scan.1 client/bluetoothctl-transport.1 \
-		client/bluetoothctl-assistant.1 client/bluetoothctl-hci.1
+		client/bluetoothctl-assistant.1 client/bluetoothctl-hci.1 \
+		client/bluetoothctl-telephony.1
 
 endif
 
@@ -484,7 +486,8 @@ manual_pages += tools/hciattach.1 tools/hciconfig.1 \
 			client/bluetoothctl-scan.1 \
 			client/bluetoothctl-transport.1 \
 			client/bluetoothctl-assistant.1 \
-			client/bluetoothctl-hci.1
+			client/bluetoothctl-hci.1 \
+			client/bluetoothctl-telephony.1
 
 if HID2HCI
 udevdir = $(UDEV_DIR)
diff --git a/client/bluetoothctl-telephony.rst b/client/bluetoothctl-telephony.rst
new file mode 100644
index 000000000..a6870e781
--- /dev/null
+++ b/client/bluetoothctl-telephony.rst
@@ -0,0 +1,95 @@
+======================
+bluetoothctl-telephony
+======================
+
+-----------------
+Telephony Submenu
+-----------------
+
+:Version: BlueZ
+:Copyright: Free use of this software is granted under the terms of the GNU
+            Lesser General Public Licenses (LGPL).
+:Date: May 2025
+:Manual section: 1
+:Manual group: Linux System Administration
+
+SYNOPSIS
+========
+
+**bluetoothctl** [--options] [telephony.commands]
+
+Telephony Commands
+==================
+
+list
+----
+
+List available audio gateways.
+
+:Usage: **> list**
+
+show
+----
+
+Show audio gateway information.
+
+:Usage: **> show [audiogw]**
+
+select
+------
+
+Select default audio gateway.
+
+:Usage: **> select <audiogw>**
+
+dial
+----
+
+Dial number.
+
+:Usage: **> dial <number> [audiogw]**
+
+hangup-all
+----------
+
+Hangup all calls.
+
+:Usage: **> hangup-all**
+
+list-calls
+----------
+
+List available calls.
+
+:Usage: **> list-calls**
+
+show-call
+---------
+
+Show call information.
+
+:Usage: **> show-call <call>**
+
+answer
+------
+
+Answer call.
+
+:Usage: **> answer <call>**
+
+hangup
+------
+
+Hangup call.
+
+:Usage: **> hangup <call>**
+
+RESOURCES
+=========
+
+http://www.bluez.org
+
+REPORTING BUGS
+==============
+
+linux-bluetooth@vger.kernel.org
diff --git a/client/main.c b/client/main.c
index d99a5158e..5bb21da12 100644
--- a/client/main.c
+++ b/client/main.c
@@ -38,6 +38,7 @@
 #include "mgmt.h"
 #include "assistant.h"
 #include "hci.h"
+#include "telephony.h"
 
 /* String display constants */
 #define COLORED_NEW	COLOR_GREEN "NEW" COLOR_OFF
@@ -3468,6 +3469,7 @@ int main(int argc, char *argv[])
 	mgmt_add_submenu();
 	assistant_add_submenu();
 	hci_add_submenu();
+	telephony_add_submenu();
 	bt_shell_set_prompt(PROMPT_OFF, NULL);
 
 	bt_shell_handle_non_interactive_help();
@@ -3508,6 +3510,7 @@ int main(int argc, char *argv[])
 	mgmt_remove_submenu();
 	assistant_remove_submenu();
 	hci_remove_submenu();
+	telephony_remove_submenu();
 
 	g_dbus_client_unref(client);
 
diff --git a/client/telephony.c b/client/telephony.c
new file mode 100644
index 000000000..45d46df79
--- /dev/null
+++ b/client/telephony.c
@@ -0,0 +1,526 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+
+#include <glib.h>
+
+#include "gdbus/gdbus.h"
+
+#include "src/shared/shell.h"
+#include "print.h"
+#include "telephony.h"
+
+/* String display constants */
+#define COLORED_NEW	COLOR_GREEN "NEW" COLOR_OFF
+#define COLORED_CHG	COLOR_YELLOW "CHG" COLOR_OFF
+#define COLORED_DEL	COLOR_RED "DEL" COLOR_OFF
+
+#define BLUEZ_TELEPHONY_AG_INTERFACE "org.bluez.TelephonyAg1"
+#define BLUEZ_TELEPHONY_CALL_INTERFACE "org.bluez.Call1"
+
+static DBusConnection *dbus_conn;
+static GDBusProxy *default_ag;
+static GList *ags;
+static GList *calls;
+
+static GDBusClient *client;
+
+static bool check_default_ag(void)
+{
+	if (!default_ag) {
+		bt_shell_printf("No default audio gateway available\n");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static char *generic_generator(const char *text, int state, GList *source)
+{
+	static int index;
+
+	if (!source)
+		return NULL;
+
+	if (!state)
+		index = 0;
+
+	return g_dbus_proxy_path_lookup(source, &index, text);
+}
+
+static char *ag_generator(const char *text, int state)
+{
+	return generic_generator(text, state, ags);
+}
+
+static char *call_generator(const char *text, int state)
+{
+	return generic_generator(text, state, calls);
+}
+
+static char *proxy_description(GDBusProxy *proxy, const char *title,
+						const char *description)
+{
+	const char *path;
+
+	path = g_dbus_proxy_get_path(proxy);
+
+	return g_strdup_printf("%s%s%s%s %s ",
+					description ? "[" : "",
+					description ? : "",
+					description ? "] " : "",
+					title, path);
+}
+
+static void print_ag(void *data, void *user_data)
+{
+	GDBusProxy *proxy = data;
+	const char *description = user_data;
+	char *str;
+
+	str = proxy_description(proxy, "Telephony", description);
+
+	bt_shell_printf("%s%s\n", str,
+			default_ag == proxy ? "[default]" : "");
+
+	g_free(str);
+}
+
+static void print_call(void *data, void *user_data)
+{
+	GDBusProxy *proxy = data;
+	const char *description = user_data;
+	const char *path, *line_id;
+	DBusMessageIter iter;
+
+	path = g_dbus_proxy_get_path(proxy);
+
+	if (g_dbus_proxy_get_property(proxy, "LineIdentification", &iter))
+		dbus_message_iter_get_basic(&iter, &line_id);
+	else
+		line_id = "<unknown>";
+
+	bt_shell_printf("%s%s%sCall %s %s\n", description ? "[" : "",
+					description ? : "",
+					description ? "] " : "",
+					path, line_id);
+}
+
+static void cmd_list(int argc, char *arg[])
+{
+	g_list_foreach(ags, print_ag, NULL);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_show(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 2) {
+		if (check_default_ag() == FALSE)
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+		proxy = default_ag;
+	} else {
+		proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+						BLUEZ_TELEPHONY_AG_INTERFACE);
+		if (!proxy) {
+			bt_shell_printf("Audio gateway %s not available\n",
+								argv[1]);
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+		}
+	}
+
+	bt_shell_printf("Audio gateway %s\n", g_dbus_proxy_get_path(proxy));
+
+	print_property(proxy, "UUID");
+	print_property(proxy, "State");
+	print_property(proxy, "Service");
+	print_property(proxy, "Signal");
+	print_property(proxy, "Roaming");
+	print_property(proxy, "BattChg");
+	print_property(proxy, "OperatorName");
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_select(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+						BLUEZ_TELEPHONY_AG_INTERFACE);
+	if (proxy == NULL) {
+		bt_shell_printf("Audio gateway %s not available\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	if (default_ag == proxy)
+		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+
+	default_ag = proxy;
+	print_ag(proxy, NULL);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void dial_reply(DBusMessage *message, void *user_data)
+{
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message) == TRUE) {
+		bt_shell_printf("Failed to answer: %s\n", error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Dial successful\n");
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void dial_setup(DBusMessageIter *iter, void *user_data)
+{
+	const char *number = user_data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &number);
+}
+
+static void cmd_dial(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 3) {
+		if (check_default_ag() == FALSE)
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+		proxy = default_ag;
+	} else {
+		proxy = g_dbus_proxy_lookup(ags, NULL, argv[2],
+						BLUEZ_TELEPHONY_AG_INTERFACE);
+		if (!proxy) {
+			bt_shell_printf("Audio gateway %s not available\n",
+							argv[1]);
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+		}
+	}
+
+	if (g_dbus_proxy_method_call(proxy, "Dial", dial_setup,
+				dial_reply, argv[1], NULL) == FALSE) {
+		bt_shell_printf("Failed to dial\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Attempting to dial\n");
+}
+
+static void hangupall_reply(DBusMessage *message, void *user_data)
+{
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message) == TRUE) {
+		bt_shell_printf("Failed to answer: %s\n", error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Hangup all successful\n");
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_hangupall(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 2) {
+		if (check_default_ag() == FALSE)
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+		proxy = default_ag;
+	} else {
+		proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+						BLUEZ_TELEPHONY_AG_INTERFACE);
+		if (!proxy) {
+			bt_shell_printf("Audio gateway %s not available\n",
+							argv[1]);
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+		}
+	}
+
+	if (g_dbus_proxy_method_call(proxy, "HangupAll", NULL,
+				hangupall_reply, NULL, NULL) == FALSE) {
+		bt_shell_printf("Failed to hangup all calls\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Attempting to hangup all calls\n");
+}
+
+static void cmd_list_calls(int argc, char *arg[])
+{
+	g_list_foreach(calls, print_call, NULL);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_show_call(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 2)
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+	proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+				BLUEZ_TELEPHONY_CALL_INTERFACE);
+	if (!proxy) {
+		bt_shell_printf("Call %s not available\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Call %s\n", g_dbus_proxy_get_path(proxy));
+
+	print_property(proxy, "LineIdentification");
+	print_property(proxy, "IncomingLine");
+	print_property(proxy, "Name");
+	print_property(proxy, "Multiparty");
+	print_property(proxy, "State");
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void answer_reply(DBusMessage *message, void *user_data)
+{
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message) == TRUE) {
+		bt_shell_printf("Failed to answer: %s\n", error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Answer successful\n");
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_answer_call(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 2)
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+	proxy = g_dbus_proxy_lookup(calls, NULL, argv[1],
+				BLUEZ_TELEPHONY_CALL_INTERFACE);
+	if (!proxy) {
+		bt_shell_printf("Call %s not available\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	if (g_dbus_proxy_method_call(proxy, "Answer", NULL,
+				answer_reply, NULL, NULL) == FALSE) {
+		bt_shell_printf("Failed to answer call\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Attempting to answer\n");
+}
+
+static void hangup_reply(DBusMessage *message, void *user_data)
+{
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message) == TRUE) {
+		bt_shell_printf("Failed to answer: %s\n", error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Hangup successful\n");
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_hangup_call(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 2)
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+	proxy = g_dbus_proxy_lookup(calls, NULL, argv[1],
+			BLUEZ_TELEPHONY_CALL_INTERFACE);
+	if (!proxy) {
+		bt_shell_printf("Call %s not available\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	if (g_dbus_proxy_method_call(proxy, "Hangup", NULL,
+				hangup_reply, NULL, NULL) == FALSE) {
+		bt_shell_printf("Failed to hangup call\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Attempting to hangup\n");
+}
+
+static void ag_added(GDBusProxy *proxy)
+{
+	ags = g_list_append(ags, proxy);
+
+	if (default_ag == NULL)
+		default_ag = proxy;
+
+	print_ag(proxy, COLORED_NEW);
+}
+
+static void call_added(GDBusProxy *proxy)
+{
+	calls = g_list_append(calls, proxy);
+
+	print_call(proxy, COLORED_NEW);
+}
+
+static void proxy_added(GDBusProxy *proxy, void *user_data)
+{
+	const char *interface;
+
+	interface = g_dbus_proxy_get_interface(proxy);
+
+	if (!strcmp(interface, BLUEZ_TELEPHONY_AG_INTERFACE))
+		ag_added(proxy);
+	else if (!strcmp(interface, BLUEZ_TELEPHONY_CALL_INTERFACE))
+		call_added(proxy);
+}
+
+static void ag_removed(GDBusProxy *proxy)
+{
+	print_ag(proxy, COLORED_DEL);
+
+	if (default_ag == proxy)
+		default_ag = NULL;
+
+	ags = g_list_remove(ags, proxy);
+}
+
+static void call_removed(GDBusProxy *proxy)
+{
+	calls = g_list_remove(calls, proxy);
+
+	print_call(proxy, COLORED_DEL);
+}
+
+static void proxy_removed(GDBusProxy *proxy, void *user_data)
+{
+	const char *interface;
+
+	interface = g_dbus_proxy_get_interface(proxy);
+
+	if (!strcmp(interface, BLUEZ_TELEPHONY_AG_INTERFACE))
+		ag_removed(proxy);
+	else if (!strcmp(interface, BLUEZ_TELEPHONY_CALL_INTERFACE))
+		call_removed(proxy);
+}
+
+static void ag_property_changed(GDBusProxy *proxy, const char *name,
+						DBusMessageIter *iter)
+{
+	char *str;
+
+	str = proxy_description(proxy, "audiogw", COLORED_CHG);
+	print_iter(str, name, iter);
+	g_free(str);
+}
+
+static void call_property_changed(GDBusProxy *proxy, const char *name,
+						DBusMessageIter *iter)
+{
+	char *str;
+
+	str = proxy_description(proxy, "call", COLORED_CHG);
+	print_iter(str, name, iter);
+	g_free(str);
+}
+
+static void property_changed(GDBusProxy *proxy, const char *name,
+					DBusMessageIter *iter, void *user_data)
+{
+	const char *interface;
+
+	interface = g_dbus_proxy_get_interface(proxy);
+
+	if (!strcmp(interface, BLUEZ_TELEPHONY_AG_INTERFACE))
+		ag_property_changed(proxy, name, iter);
+	else if (!strcmp(interface, BLUEZ_TELEPHONY_CALL_INTERFACE))
+		call_property_changed(proxy, name, iter);
+}
+
+static void telephony_menu_pre_run(const struct bt_shell_menu *menu)
+{
+	dbus_conn = bt_shell_get_env("DBUS_CONNECTION");
+	if (!dbus_conn || client)
+		return;
+
+	client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez");
+
+	g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
+							property_changed, NULL);
+}
+
+static const struct bt_shell_menu telephony_menu = {
+	.name = "telephony",
+	.desc = "Telephony Submenu",
+	.pre_run = telephony_menu_pre_run,
+	.entries = {
+	{ "list",         NULL, cmd_list, "List available audio gateway" },
+	{ "show",         "[audiogw]", cmd_show, "Audio gateway information",
+						ag_generator},
+	{ "select",       "<audiogw>", cmd_select,
+						"Select default audio gateway",
+						ag_generator},
+	{ "dial",         "<number> [audiogw]", cmd_dial, "Dial number",
+						ag_generator},
+	{ "hangup-all",   "[audiogw]", cmd_hangupall, "Hangup all calls",
+						ag_generator},
+	{ "list-calls",   NULL, cmd_list_calls, "List calls" },
+	{ "show-call",    "<call>", cmd_show_call, "Show call information",
+						call_generator},
+	{ "answer",       "<call>", cmd_answer_call, "Answer call",
+						call_generator},
+	{ "hangup",       "<call>", cmd_hangup_call, "Hangup call",
+						call_generator},
+	{} },
+};
+
+void telephony_add_submenu(void)
+{
+	bt_shell_add_submenu(&telephony_menu);
+}
+
+void telephony_remove_submenu(void)
+{
+	g_dbus_client_unref(client);
+}
diff --git a/client/telephony.h b/client/telephony.h
new file mode 100644
index 000000000..a869da240
--- /dev/null
+++ b/client/telephony.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+void telephony_add_submenu(void);
+void telephony_remove_submenu(void);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 09/27] audio/hfp-hf: Remove call interface during profile disconnection
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (7 preceding siblings ...)
  2025-06-27 14:51 ` [RFC BlueZ v2 08/27] client/telephony: Add new submenu Frédéric Danis
@ 2025-06-27 14:51 ` 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
                   ` (18 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/hfp-hf.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index e7bfe4f59..0fae3cd6f 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -1224,6 +1224,15 @@ static int hfp_connect(struct btd_service *service)
 	return telephony_register_interface(dev->telephony);
 }
 
+static void remove_calls(gpointer data, gpointer user_data)
+{
+	struct call *call = data;
+	struct hfp_device *dev = user_data;
+
+	dev->calls = g_slist_remove(dev->calls, call);
+	telephony_call_unregister_interface(call);
+}
+
 static int hfp_disconnect(struct btd_service *service)
 {
 	struct hfp_device *dev;
@@ -1232,6 +1241,8 @@ static int hfp_disconnect(struct btd_service *service)
 
 	dev = btd_service_get_user_data(service);
 
+	g_slist_foreach(dev->calls, remove_calls, dev);
+
 	if (dev->hf)
 		hfp_hf_disconnect(dev->hf);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 10/27] audio/hfp-hf: Create existing call during SLC phase
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (8 preceding siblings ...)
  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 ` 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
                   ` (17 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
v1->v2:
  - Use first available index of call for new call

 profiles/audio/hfp-hf.c | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 0fae3cd6f..ad759b8c3 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -565,10 +565,26 @@ static void ciev_call_cb(uint8_t val, void *user_data)
 
 	dev->call = !!val;
 
-	if (dev->call == TRUE)
-		g_slist_foreach(dev->calls, activate_calls, dev);
-	else
+	if (dev->call == TRUE) {
+		if (dev->calls == NULL) {
+			/* Create already active call during SLC */
+			struct call *call;
+			uint8_t idx = next_index(dev);
+
+			call = telephony_new_call(dev->telephony, idx,
+							CALL_STATE_ACTIVE,
+							NULL);
+			if (telephony_call_register_interface(call)) {
+				telephony_free_call(call);
+				return;
+			}
+			dev->calls = g_slist_append(dev->calls, call);
+		} else {
+			g_slist_foreach(dev->calls, activate_calls, dev);
+		}
+	} else {
 		g_slist_foreach(dev->calls, deactivate_active_calls, dev);
+	}
 }
 
 static void callsetup_deactivate(gpointer data, gpointer user_data)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 11/27] audio/telephony: Add hangup_active and hangup_held functions
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (9 preceding siblings ...)
  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 ` 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
                   ` (16 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

From: Frédéric Danis <frederic.danis.oss@gmail.com>

---
 profiles/audio/telephony.c | 26 ++++++++++++++++++++++++++
 profiles/audio/telephony.h |  4 ++++
 2 files changed, 30 insertions(+)

diff --git a/profiles/audio/telephony.c b/profiles/audio/telephony.c
index 83a7ff40c..2a4c19a58 100644
--- a/profiles/audio/telephony.c
+++ b/profiles/audio/telephony.c
@@ -204,6 +204,30 @@ static DBusMessage *hangup_all(DBusConnection *conn, DBusMessage *msg,
 	return btd_error_not_supported(msg);
 }
 
+static DBusMessage *hangup_active(DBusConnection *conn, DBusMessage *msg,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->hangup_active)
+		return telephony->cbs->hangup_active(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
+static DBusMessage *hangup_held(DBusConnection *conn, DBusMessage *msg,
+	void *user_data)
+{
+	struct telephony *telephony = user_data;
+
+	if (telephony->cbs && telephony->cbs->hangup_held)
+		return telephony->cbs->hangup_held(conn, msg,
+					telephony->profile_data);
+
+	return btd_error_not_supported(msg);
+}
+
 static DBusMessage *create_multiparty(DBusConnection *conn, DBusMessage *msg,
 	void *user_data)
 {
@@ -340,6 +364,8 @@ static const GDBusMethodTable telephony_methods[] = {
 	{ GDBUS_ASYNC_METHOD("HoldAndAnswer", NULL, NULL,
 						hold_and_answer) },
 	{ GDBUS_ASYNC_METHOD("HangupAll", NULL, NULL, hangup_all) },
+	{ GDBUS_ASYNC_METHOD("HangupActive", NULL, NULL, hangup_active) },
+	{ GDBUS_ASYNC_METHOD("HangupHeld", NULL, NULL, hangup_held) },
 	{ GDBUS_ASYNC_METHOD("CreateMultiparty", NULL,
 						GDBUS_ARGS({ "calls", "ao" }),
 						create_multiparty) },
diff --git a/profiles/audio/telephony.h b/profiles/audio/telephony.h
index aaf41888d..b02111264 100644
--- a/profiles/audio/telephony.h
+++ b/profiles/audio/telephony.h
@@ -43,6 +43,10 @@ struct telephony_callbacks {
 					void *profile_data);
 	DBusMessage *(*hangup_all)(DBusConnection *conn, DBusMessage *msg,
 					void *profile_data);
+	DBusMessage *(*hangup_active)(DBusConnection *conn, DBusMessage *msg,
+					void *profile_data);
+	DBusMessage *(*hangup_held)(DBusConnection *conn, DBusMessage *msg,
+					void *profile_data);
 	DBusMessage *(*create_multiparty)(DBusConnection *conn,
 					DBusMessage *msg,
 					void *profile_data);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 12/27] audio/hfp-hf: Add hangup_active and hangup_held support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (10 preceding siblings ...)
  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 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 13/27] client/telephony: " Frédéric Danis
                   ` (15 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

From: Frédéric Danis <frederic.danis.oss@gmail.com>

---
 profiles/audio/hfp-hf.c | 87 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index ad759b8c3..a2a1ae39f 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -1156,6 +1156,91 @@ failed:
 	return btd_error_failed(msg, "Hang up all command failed");
 }
 
+static DBusMessage *hfp_hangup_active(DBusConnection *conn, DBusMessage *msg,
+				void *profile_data)
+{
+	struct hfp_device *dev = profile_data;
+	bool found_active = FALSE;
+	GSList *l;
+
+	DBG("");
+
+	for (l = dev->calls; l; l = l->next) {
+		struct call *call = l->data;
+
+		switch (call->state) {
+		case CALL_STATE_ACTIVE:
+		case CALL_STATE_DIALING:
+		case CALL_STATE_ALERTING:
+		case CALL_STATE_INCOMING:
+			found_active = TRUE;
+			break;
+		case CALL_STATE_HELD:
+		case CALL_STATE_WAITING:
+		case CALL_STATE_DISCONNECTED:
+			break;
+		}
+	}
+
+	if (!found_active)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".InvalidState",
+					"No active call to hang up");
+
+	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb,
+			dbus_message_ref(msg),
+			"AT+CHUP")) {
+		warn("Failed to hangup active calls");
+		return btd_error_failed(msg, "Hang up active command failed");
+	}
+
+	return NULL;
+}
+
+static DBusMessage *hfp_hangup_held(DBusConnection *conn, DBusMessage *msg,
+				void *profile_data)
+{
+	struct hfp_device *dev = profile_data;
+	bool found_held = FALSE;
+	GSList *l;
+
+	DBG("");
+
+	if (!(dev->chld_features & CHLD_FEAT_REL))
+		return btd_error_not_supported(msg);
+
+	for (l = dev->calls; l; l = l->next) {
+		struct call *call = l->data;
+
+		switch (call->state) {
+		case CALL_STATE_HELD:
+		case CALL_STATE_WAITING:
+			found_held = TRUE;
+			break;
+		case CALL_STATE_ACTIVE:
+		case CALL_STATE_DIALING:
+		case CALL_STATE_ALERTING:
+		case CALL_STATE_INCOMING:
+		case CALL_STATE_DISCONNECTED:
+			break;
+		}
+	}
+
+	if (!found_held)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".InvalidState",
+					"No held call to hang up");
+
+	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb,
+			dbus_message_ref(msg),
+			"AT+CHLD=0")) {
+		warn("Failed to hangup held calls");
+		return btd_error_failed(msg, "Hang up held command failed");
+	}
+
+	return NULL;
+}
+
 static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg,
 	void *call_data)
 {
@@ -1180,6 +1265,8 @@ failed:
 struct telephony_callbacks hfp_callbacks = {
 	.dial = hfp_dial,
 	.hangup_all = hfp_hangup_all,
+	.hangup_active = hfp_hangup_active,
+	.hangup_held = hfp_hangup_held,
 	.call_answer = call_answer,
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 13/27] client/telephony: Add hangup_active and hangup_held support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (11 preceding siblings ...)
  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 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 14/27] audio/hfp-hf: Add SendTones support Frédéric Danis
                   ` (14 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

From: Frédéric Danis <frederic.danis.oss@gmail.com>

---
 client/telephony.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/client/telephony.c b/client/telephony.c
index 45d46df79..eb633748f 100644
--- a/client/telephony.c
+++ b/client/telephony.c
@@ -275,6 +275,98 @@ static void cmd_hangupall(int argc, char *argv[])
 	bt_shell_printf("Attempting to hangup all calls\n");
 }
 
+static void hangupactive_reply(DBusMessage *message, void *user_data)
+{
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message) == TRUE) {
+		bt_shell_printf("Failed to hangup active calls: %s\n",
+							error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Hangup active successful\n");
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_hangupactive(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 2) {
+		if (check_default_ag() == FALSE)
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+		proxy = default_ag;
+	} else {
+		proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+						BLUEZ_TELEPHONY_AG_INTERFACE);
+		if (!proxy) {
+			bt_shell_printf("Audio gateway %s not available\n",
+							argv[1]);
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+		}
+	}
+
+	if (g_dbus_proxy_method_call(proxy, "HangupActive", NULL,
+				hangupactive_reply, NULL, NULL) == FALSE) {
+		bt_shell_printf("Failed to hangup active calls\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Attempting to hangup active calls\n");
+}
+
+static void hangupheld_reply(DBusMessage *message, void *user_data)
+{
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message) == TRUE) {
+		bt_shell_printf("Failed to hangup held calls: %s\n",
+							error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Hangup held successful\n");
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_hangupheld(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 2) {
+		if (check_default_ag() == FALSE)
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+		proxy = default_ag;
+	} else {
+		proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+						BLUEZ_TELEPHONY_AG_INTERFACE);
+		if (!proxy) {
+			bt_shell_printf("Audio gateway %s not available\n",
+							argv[1]);
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+		}
+	}
+
+	if (g_dbus_proxy_method_call(proxy, "HangupHeld", NULL,
+				hangupheld_reply, NULL, NULL) == FALSE) {
+		bt_shell_printf("Failed to hangup held calls\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Attempting to hangup held calls\n");
+}
+
 static void cmd_list_calls(int argc, char *arg[])
 {
 	g_list_foreach(calls, print_call, NULL);
@@ -505,6 +597,11 @@ static const struct bt_shell_menu telephony_menu = {
 						ag_generator},
 	{ "hangup-all",   "[audiogw]", cmd_hangupall, "Hangup all calls",
 						ag_generator},
+	{ "hangup-active", "[audiogw]", cmd_hangupactive,
+						"Hangup active calls",
+						ag_generator},
+	{ "hangup-held",  "[audiogw]", cmd_hangupheld, "Hangup held calls",
+						ag_generator},
 	{ "list-calls",   NULL, cmd_list_calls, "List calls" },
 	{ "show-call",    "<call>", cmd_show_call, "Show call information",
 						call_generator},
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 14/27] audio/hfp-hf: Add SendTones support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (12 preceding siblings ...)
  2025-06-27 14:51 ` [RFC BlueZ v2 13/27] client/telephony: " Frédéric Danis
@ 2025-06-27 14:51 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 15/27] client/telephony: " Frédéric Danis
                   ` (13 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

From: Frédéric Danis <frederic.danis.oss@gmail.com>

---
 profiles/audio/hfp-hf.c | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index a2a1ae39f..9a659a281 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -1241,6 +1241,45 @@ static DBusMessage *hfp_hangup_held(DBusConnection *conn, DBusMessage *msg,
 	return NULL;
 }
 
+static DBusMessage *hfp_send_tones(DBusConnection *conn, DBusMessage *msg,
+				void *profile_data)
+{
+	struct hfp_device *dev = profile_data;
+	const char *tones;
+	bool found_active = FALSE;
+	GSList *l;
+
+	DBG("");
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &tones,
+					DBUS_TYPE_INVALID)) {
+		return btd_error_invalid_args(msg);
+	}
+
+	for (l = dev->calls; l; l = l->next) {
+		struct call *call = l->data;
+
+		if (call->state == CALL_STATE_ACTIVE) {
+			found_active = TRUE;
+			break;
+		}
+	}
+
+	if (!found_active)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".InvalidState",
+					"No active call to send tones");
+
+	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb,
+			dbus_message_ref(msg),
+			"AT+VTS=%s", tones)) {
+		warn("Failed to send tones: %s", tones);
+		return btd_error_failed(msg, "Failed to send tones");
+	}
+
+	return NULL;
+}
+
 static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg,
 	void *call_data)
 {
@@ -1267,6 +1306,7 @@ struct telephony_callbacks hfp_callbacks = {
 	.hangup_all = hfp_hangup_all,
 	.hangup_active = hfp_hangup_active,
 	.hangup_held = hfp_hangup_held,
+	.send_tones = hfp_send_tones,
 	.call_answer = call_answer,
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 15/27] client/telephony: Add SendTones support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (13 preceding siblings ...)
  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 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 16/27] doc: Make telephony docs more generic Frédéric Danis
                   ` (12 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

From: Frédéric Danis <frederic.danis.oss@gmail.com>

---
 client/telephony.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/client/telephony.c b/client/telephony.c
index eb633748f..5af88c008 100644
--- a/client/telephony.c
+++ b/client/telephony.c
@@ -367,6 +367,58 @@ static void cmd_hangupheld(int argc, char *argv[])
 	bt_shell_printf("Attempting to hangup held calls\n");
 }
 
+static void send_tones_reply(DBusMessage *message, void *user_data)
+{
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message) == TRUE) {
+		bt_shell_printf("Failed to send tones: %s\n", error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Send tones successful\n");
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void send_tones_setup(DBusMessageIter *iter, void *user_data)
+{
+	const char *tones = user_data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &tones);
+}
+
+static void cmd_send_tones(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (argc < 3) {
+		if (check_default_ag() == FALSE)
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+		proxy = default_ag;
+	} else {
+		proxy = g_dbus_proxy_lookup(ags, NULL, argv[2],
+						BLUEZ_TELEPHONY_AG_INTERFACE);
+		if (!proxy) {
+			bt_shell_printf("Audio gateway %s not available\n",
+							argv[2]);
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+		}
+	}
+
+	if (g_dbus_proxy_method_call(proxy, "SendTones", send_tones_setup,
+				send_tones_reply, argv[1], NULL) == FALSE) {
+		bt_shell_printf("Failed to send tones\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Attempting to send tones\n");
+}
+
 static void cmd_list_calls(int argc, char *arg[])
 {
 	g_list_foreach(calls, print_call, NULL);
@@ -602,6 +654,8 @@ static const struct bt_shell_menu telephony_menu = {
 						ag_generator},
 	{ "hangup-held",  "[audiogw]", cmd_hangupheld, "Hangup held calls",
 						ag_generator},
+	{ "send-tones",   "<tones> [audiogw]", cmd_send_tones, "Send tones",
+						ag_generator},
 	{ "list-calls",   NULL, cmd_list_calls, "List calls" },
 	{ "show-call",    "<call>", cmd_show_call, "Show call information",
 						call_generator},
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 16/27] doc: Make telephony docs more generic
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (14 preceding siblings ...)
  2025-06-27 14:51 ` [RFC BlueZ v2 15/27] client/telephony: " Frédéric Danis
@ 2025-06-27 14:51 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 17/27] client/telephony: Remove IncomingLine Frédéric Danis
                   ` (11 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

Remove HFP specific parts or explicitly point it.
---
 doc/org.bluez.Call.rst        | 37 ++++-------------------------------
 doc/org.bluez.TelephonyAg.rst |  3 +--
 2 files changed, 5 insertions(+), 35 deletions(-)

diff --git a/doc/org.bluez.Call.rst b/doc/org.bluez.Call.rst
index 3fcd6f6ea..f4ff37c05 100644
--- a/doc/org.bluez.Call.rst
+++ b/doc/org.bluez.Call.rst
@@ -35,23 +35,9 @@ void Hangup()
 
 Hangs up the call.
 
-For an incoming call, the call is hung up using ATH or equivalent. For a
-waiting call, the remote party is notified by using the User Determined User
-Busy (UDUB) condition. This is generally implemented using CHLD=0.
-
-Please note that the GSM specification does not allow the release of a held
-call when a waiting call exists. This is because 27.007 allows CHLD=1X to
-operate only on active calls. Hence a held call cannot be hung up without
-affecting the state of the incoming call (e.g. using other CHLD alternatives).
-Most manufacturers provide vendor extensions that do allow the state of the
-held call to be modified using CHLD=1X or equivalent. It should be noted that
-Bluetooth HFP specifies the classic 27.007 behavior and does not allow CHLD=1X
-to modify the state of held calls.
-
-Based on the discussion above, it should also be noted that releasing a
-particular party of a held multiparty call might not be possible on some
-implementations. It is recommended for the applications to structure their UI
-accordingly.
+For an incoming call, the call is hung up.
+For a waiting call, the remote party is notified. For HFP by using the User
+Determined User Busy (UDUB) condition.
 
 NOTE: Releasing active calls does not produce side-effects. That is the state
 of held or waiting calls is not affected. As an exception, in the case where a
@@ -69,22 +55,7 @@ string LineIdentification [readonly]
 ````````````````````````````````````
 
 Contains the Line Identification information returned by the network, if
-present. For incoming calls this is effectively the CLIP. For outgoing calls
-this attribute will hold the dialed number, or the COLP if received by the
-audio gateway.
-
-Please note that COLP may be different from the dialed number. A special
-"withheld" value means the remote party refused to provide caller ID and the
-"override category" option was not provisioned for the current subscriber.
-
-string IncomingLine [readonly, optional]
-````````````````````````````````````````
-
-Contains the Called Line Identification information returned by the network.
-This is only available for incoming calls and indicates the local subscriber
-number which was dialed by the remote party. This is useful for subscribers
-which have a multiple line service with their network provider and would like
-to know what line the call is coming in on.
+present.
 
 string Name [readonly]
 ``````````````````````
diff --git a/doc/org.bluez.TelephonyAg.rst b/doc/org.bluez.TelephonyAg.rst
index ddb5eec0f..ad217a6dd 100644
--- a/doc/org.bluez.TelephonyAg.rst
+++ b/doc/org.bluez.TelephonyAg.rst
@@ -24,11 +24,10 @@ Methods
 object Dial(string number)
 ``````````````````````````
 
-Call number, if number is void try to call last dialed number.
 Initiates a new outgoing call. Returns the object path to the newly created
 call.
 
-The number must be a string containing the following characters:
+For HFP, the number must be a string containing the following characters:
 `[0-9+*#,ABCD]{1,80}` The character set can contain numbers, `+`, `*`, `#`,
 `,` and the letters `A` to `D`. Besides this sanity checking no further number
 validation is performed. It is assumed that the gateway and/or the network
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 17/27] client/telephony: Remove IncomingLine
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (15 preceding siblings ...)
  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 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 18/27] audio/telephony: " Frédéric Danis
                   ` (10 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

This property has been removed from the documentation.
---
 client/telephony.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/client/telephony.c b/client/telephony.c
index 5af88c008..4678ffac9 100644
--- a/client/telephony.c
+++ b/client/telephony.c
@@ -443,7 +443,6 @@ static void cmd_show_call(int argc, char *argv[])
 	bt_shell_printf("Call %s\n", g_dbus_proxy_get_path(proxy));
 
 	print_property(proxy, "LineIdentification");
-	print_property(proxy, "IncomingLine");
 	print_property(proxy, "Name");
 	print_property(proxy, "Multiparty");
 	print_property(proxy, "State");
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 18/27] audio/telephony: Remove IncomingLine
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (16 preceding siblings ...)
  2025-06-27 14:51 ` [RFC BlueZ v2 17/27] client/telephony: Remove IncomingLine Frédéric Danis
@ 2025-06-27 14:51 ` 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
                   ` (9 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

This property has been removed from the documentation.
---
 profiles/audio/telephony.c | 26 --------------------------
 profiles/audio/telephony.h |  1 -
 2 files changed, 27 deletions(-)

diff --git a/profiles/audio/telephony.c b/profiles/audio/telephony.c
index 2a4c19a58..5e88240e6 100644
--- a/profiles/audio/telephony.c
+++ b/profiles/audio/telephony.c
@@ -616,7 +616,6 @@ void telephony_free_call(struct call *call)
 		dbus_message_unref(call->pending_msg);
 
 	g_free(call->name);
-	g_free(call->incoming_line);
 	g_free(call->line_id);
 	g_free(call->path);
 	g_free(call);
@@ -662,29 +661,6 @@ static gboolean call_property_get_line_id(
 	return TRUE;
 }
 
-static gboolean call_incoming_line_exists(const GDBusPropertyTable *property,
-	void *data)
-{
-	struct call *call = data;
-
-	return call->incoming_line != NULL;
-}
-
-static gboolean call_property_get_incoming_line(
-	const GDBusPropertyTable *property,
-	DBusMessageIter *iter, void *data)
-{
-	struct call *call = data;
-
-	if (call->incoming_line == NULL)
-		return FALSE;
-
-	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
-		&call->incoming_line);
-
-	return TRUE;
-}
-
 static gboolean call_name_exists(const GDBusPropertyTable *property,
 	void *data)
 {
@@ -742,8 +718,6 @@ static const GDBusMethodTable telephony_call_methods[] = {
 static const GDBusPropertyTable telephony_call_properties[] = {
 	{ "LineIdentification", "s", call_property_get_line_id, NULL,
 			call_line_id_exists },
-	{ "IncomingLine", "s", call_property_get_incoming_line, NULL,
-			call_incoming_line_exists },
 	{ "Name", "s", call_property_get_name, NULL, call_name_exists },
 	{ "Multiparty", "b", call_property_get_multiparty },
 	{ "State", "s", call_property_get_state },
diff --git a/profiles/audio/telephony.h b/profiles/audio/telephony.h
index b02111264..3f7580e80 100644
--- a/profiles/audio/telephony.h
+++ b/profiles/audio/telephony.h
@@ -70,7 +70,6 @@ struct call {
 	uint8_t			idx;
 
 	char			*line_id;
-	char			*incoming_line;
 	char			*name;
 	bool			multiparty;
 	enum call_state		state;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 19/27] audio/hfp-hf: Add HFP HF server and SDP record
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (17 preceding siblings ...)
  2025-06-27 14:51 ` [RFC BlueZ v2 18/27] audio/telephony: " Frédéric Danis
@ 2025-06-27 14:51 ` 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
                   ` (8 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/hfp-hf.c | 253 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 253 insertions(+)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 9a659a281..b49d40a43 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -45,6 +45,9 @@
 
 #include "telephony.h"
 
+#define HFP_HF_VERSION		0x0109
+#define HFP_HF_DEFAULT_CHANNEL	7
+
 #define CALL_IND_NO_CALL_IN_PROGRESS	0x00
 #define CALL_IND_CALL_IN_PROGRESS	0x01
 
@@ -56,6 +59,16 @@
 #define CHLD_FEAT_MERGE		0x00000020
 #define CHLD_FEAT_MERGE_DETACH	0x00000040
 
+#define HFP_HF_SDP_ECNR					0x0001
+#define HFP_HF_SDP_3WAY					0x0002
+#define HFP_HF_SDP_CLIP					0x0004
+#define HFP_HF_SDP_VOICE_RECOGNITION			0x0008
+#define HFP_HF_SDP_REMOTE_VOLUME_CONTROL		0x0010
+#define HFP_HF_SDP_WIDE_BAND_SPEECH			0x0020
+#define HFP_HF_SDP_ENHANCED_VOICE_RECOGNITION_STATUS	0x0040
+#define HFP_HF_SDP_VOICE_RECOGNITION_TEXT		0x0080
+#define HFP_HF_SDP_SUPER_WIDE_BAND_SPEECH		0x0100
+
 #define HFP_HF_FEAT_ECNR				0x00000001
 #define HFP_HF_FEAT_3WAY				0x00000002
 #define HFP_HF_FEAT_CLIP				0x00000004
@@ -84,6 +97,10 @@
 #define HFP_AG_FEAT_ENHANCED_VOICE_RECOGNITION_STATUS	0x00001000
 #define HFP_AG_FEAT_VOICE_RECOGNITION_TEXT		0x00001000
 
+#define HFP_HF_SDP_FEATURES	(HFP_HF_SDP_ECNR | HFP_HF_SDP_3WAY |\
+				HFP_HF_SDP_CLIP |\
+				HFP_HF_SDP_REMOTE_VOLUME_CONTROL)
+
 #define HFP_HF_FEATURES		(HFP_HF_FEAT_ECNR | HFP_HF_FEAT_3WAY |\
 				HFP_HF_FEAT_CLIP |\
 				HFP_HF_FEAT_REMOTE_VOLUME_CONTROL |\
@@ -140,6 +157,26 @@ struct hfp_device {
 	GSList			*calls;
 };
 
+struct hfp_server {
+	struct btd_adapter	*adapter;
+	GIOChannel		*io;
+	uint32_t		record_id;
+};
+
+static GSList *servers;
+
+static struct hfp_server *find_server(GSList *list, struct btd_adapter *a)
+{
+	for (; list; list = list->next) {
+		struct hfp_server *server = list->data;
+
+		if (server->adapter == a)
+			return server;
+	}
+
+	return NULL;
+}
+
 static void device_destroy(struct hfp_device *dev)
 {
 	DBG("%s", telephony_get_path(dev->telephony));
@@ -1427,6 +1464,219 @@ static void hfp_remove(struct btd_service *service)
 	g_free(dev);
 }
 
+static sdp_record_t *hfp_record(void)
+{
+	sdp_record_t *record;
+	uuid_t root_uuid, hfphf, genericaudio, l2cap, rfcomm;
+	sdp_list_t *root, *svclass_id, *aproto, *proto[2], *apseq, *pfseq;
+	sdp_data_t *channel, *features;
+	uint8_t hf_channel = HFP_HF_DEFAULT_CHANNEL;
+	sdp_profile_desc_t profile;
+	uint16_t feat = HFP_HF_SDP_FEATURES;
+
+	record = sdp_record_alloc();
+	if (!record) {
+		error("Unable to allocate new service record");
+		return NULL;
+	}
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(NULL, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	/* Service Class ID List */
+	sdp_uuid16_create(&hfphf, HANDSFREE_SVCLASS_ID);
+	svclass_id = sdp_list_append(NULL, &hfphf);
+	sdp_uuid16_create(&genericaudio, GENERIC_AUDIO_SVCLASS_ID);
+	svclass_id = sdp_list_append(svclass_id, &genericaudio);
+	sdp_set_service_classes(record, svclass_id);
+
+	/* Protocol Descriptor List */
+	sdp_uuid16_create(&l2cap, L2CAP_UUID);
+	proto[0] = sdp_list_append(NULL, &l2cap);
+	apseq = sdp_list_append(NULL, proto[0]);
+
+	sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
+	proto[1] = sdp_list_append(NULL, &rfcomm);
+	channel = sdp_data_alloc(SDP_UINT8, &hf_channel);
+	proto[1] = sdp_list_append(proto[1], channel);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(NULL, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	/* Bluetooth Profile Descriptor List */
+	sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
+	profile.version = HFP_HF_VERSION;
+	pfseq = sdp_list_append(NULL, &profile);
+	sdp_set_profile_descs(record, pfseq);
+
+	sdp_set_info_attr(record, "Hands-Free unit", NULL, NULL);
+
+	features = sdp_data_alloc(SDP_UINT16, &feat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	free(channel);
+	sdp_list_free(proto[0], NULL);
+	sdp_list_free(proto[1], NULL);
+	sdp_list_free(pfseq, NULL);
+	sdp_list_free(aproto, NULL);
+	sdp_list_free(apseq, NULL);
+	sdp_list_free(svclass_id, NULL);
+	sdp_list_free(root, NULL);
+
+	return record;
+}
+
+static void server_connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+	uint8_t channel;
+	bdaddr_t src, dst;
+	char address[18];
+	GError *gerr = NULL;
+	struct btd_device *device;
+	struct btd_service *service;
+	struct hfp_device *dev;
+	const sdp_record_t *rec;
+	sdp_list_t *list;
+	sdp_profile_desc_t *desc;
+
+	if (err) {
+		error("%s", err->message);
+		return;
+	}
+
+	bt_io_get(chan, &gerr,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_CHANNEL, &channel,
+			BT_IO_OPT_INVALID);
+	if (gerr) {
+		error("%s", gerr->message);
+		g_error_free(gerr);
+		g_io_channel_shutdown(chan, TRUE, NULL);
+		return;
+	}
+
+	ba2str(&dst, address);
+	DBG("Incoming connection from %s on Channel %d", address, channel);
+
+	device = btd_adapter_find_device(adapter_find(&src), &dst,
+							BDADDR_BREDR);
+	if (!device)
+		return;
+
+	service = btd_device_get_service(device, HFP_AG_UUID);
+	if (!service)
+		return;
+
+	dev = btd_service_get_user_data(service);
+
+	rec = btd_device_get_record(telephony_get_device(dev->telephony),
+					HFP_AG_UUID);
+	if (!rec)
+		return;
+
+	if (sdp_get_profile_descs(rec, &list) == 0) {
+		desc = list->data;
+		dev->version = desc->version;
+	}
+	sdp_list_free(list, free);
+
+	telephony_register_interface(dev->telephony);
+
+	connect_cb(chan, err, dev);
+}
+
+static GIOChannel *server_socket(struct btd_adapter *adapter)
+{
+	GIOChannel *io;
+	GError *err = NULL;
+
+	io = bt_io_listen(server_connect_cb, NULL, NULL, NULL, &err,
+		BT_IO_OPT_SOURCE_BDADDR,
+		btd_adapter_get_address(adapter),
+		BT_IO_OPT_CHANNEL, HFP_HF_DEFAULT_CHANNEL,
+		BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+		BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+	}
+
+	return io;
+}
+
+static int hfp_adapter_probe(struct btd_profile *p,
+				struct btd_adapter *adapter)
+{
+	struct hfp_server *server;
+	sdp_record_t *record;
+
+	DBG("path %s", adapter_get_path(adapter));
+
+	server = find_server(servers, adapter);
+	if (server != NULL)
+		goto done;
+
+	server = g_new0(struct hfp_server, 1);
+
+	server->io = server_socket(adapter);
+	if (!server->io) {
+		g_free(server);
+		return -1;
+	}
+
+done:
+	record = hfp_record();
+	if (!record) {
+		error("Unable to allocate new service record");
+		g_free(server);
+		return -1;
+	}
+
+	if (adapter_service_add(adapter, record) < 0) {
+		error("Unable to register HFP HF service record");
+		sdp_record_free(record);
+		g_free(server);
+		return -1;
+	}
+	server->record_id = record->handle;
+
+	server->adapter = btd_adapter_ref(adapter);
+
+	servers = g_slist_append(servers, server);
+
+	return 0;
+}
+
+static void hfp_adapter_remove(struct btd_profile *p,
+				struct btd_adapter *adapter)
+{
+	struct hfp_server *server;
+
+	DBG("path %s", adapter_get_path(adapter));
+
+	server = find_server(servers, adapter);
+	if (!server)
+		return;
+
+	if (server->io) {
+		g_io_channel_shutdown(server->io, TRUE, NULL);
+		g_io_channel_unref(server->io);
+	}
+
+	if (server->record_id != 0) {
+		adapter_service_remove(adapter, server->record_id);
+		server->record_id = 0;
+	}
+
+	servers = g_slist_remove(servers, server);
+
+	btd_adapter_unref(server->adapter);
+	g_free(server);
+}
+
 static struct btd_profile hfp_hf_profile = {
 	.name		= "hfp",
 	.priority	= BTD_PROFILE_PRIORITY_MEDIUM,
@@ -1439,6 +1689,9 @@ static struct btd_profile hfp_hf_profile = {
 	.connect	= hfp_connect,
 	.disconnect	= hfp_disconnect,
 
+	.adapter_probe  = hfp_adapter_probe,
+	.adapter_remove = hfp_adapter_remove,
+
 	.experimental	= true,
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 20/27] audio/hfp-hf: Add operator name support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (18 preceding siblings ...)
  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 ` 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
                   ` (7 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

This adds the AT+COPS command.
---
 profiles/audio/hfp-hf.c | 48 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index b49d40a43..ebe87d8db 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -107,6 +107,8 @@
 				HFP_HF_FEAT_ENHANCED_CALL_STATUS |\
 				HFP_HF_FEAT_ESCO_S4_T2)
 
+#define MAX_OPERATOR_NAME_LEN 17
+
 enum hfp_indicator {
 	HFP_INDICATOR_SERVICE = 0,
 	HFP_INDICATOR_CALL,
@@ -354,6 +356,49 @@ static void ciev_cb(struct hfp_context *context, void *user_data)
 	}
 }
 
+static void cops_cb(struct hfp_context *context, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+	unsigned int format;
+	char name[MAX_OPERATOR_NAME_LEN];
+
+	DBG("");
+
+	/* Not interested in mode */
+	hfp_context_skip_field(context);
+
+	if (!hfp_context_get_number(context, &format))
+		return;
+
+	if (format != 0) {
+		warn("hf-client: Not correct string format in +COPS");
+		return;
+	}
+
+	if (!hfp_context_get_string(context, name, MAX_OPERATOR_NAME_LEN)) {
+		error("hf-client: incorrect +COPS response");
+		return;
+	}
+
+	telephony_set_operator_name(dev->telephony, name);
+}
+
+static void cops_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: COPS error: %d", result);
+		return;
+	}
+
+	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev, "AT+COPS?"))
+		info("hf-client: Could not send AT+COPS?");
+}
+
 static void slc_completed(struct hfp_device *dev)
 {
 	int i;
@@ -376,8 +421,9 @@ static void slc_completed(struct hfp_device *dev)
 	/* TODO: register unsolicited results handlers */
 
 	hfp_hf_register(dev->hf, ciev_cb, "+CIEV", dev, NULL);
+	hfp_hf_register(dev->hf, cops_cb, "+COPS", dev, NULL);
 
-	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+COPS=3,0"))
+	if (!hfp_hf_send_command(dev->hf, cops_resp, dev, "AT+COPS=3,0"))
 		info("hf-client: Could not send AT+COPS=3,0");
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 21/27] audio/telephony: Add call line identication property support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (19 preceding siblings ...)
  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 ` 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
                   ` (6 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/telephony.c | 16 ++++++++++++++++
 profiles/audio/telephony.h |  1 +
 2 files changed, 17 insertions(+)

diff --git a/profiles/audio/telephony.c b/profiles/audio/telephony.c
index 5e88240e6..a5dace7c3 100644
--- a/profiles/audio/telephony.c
+++ b/profiles/audio/telephony.c
@@ -775,3 +775,19 @@ void telephony_call_set_state(struct call *call, enum call_state state)
 			call->path, TELEPHONY_CALL_INTERFACE,
 			"State");
 }
+
+void telephony_call_set_line_id(struct call *call, const char *line_id)
+{
+	if (call->line_id && g_str_equal(call->line_id, line_id))
+		return;
+
+	DBG("device %s call id %s -> %s", call->path, call->line_id, line_id);
+
+	if (call->line_id)
+		g_free(call->line_id);
+	call->line_id = g_strdup(line_id);
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+			call->path, TELEPHONY_CALL_INTERFACE,
+			"LineIdentification");
+}
diff --git a/profiles/audio/telephony.h b/profiles/audio/telephony.h
index 3f7580e80..5f2a4ae4c 100644
--- a/profiles/audio/telephony.h
+++ b/profiles/audio/telephony.h
@@ -114,3 +114,4 @@ int telephony_call_register_interface(struct call *call);
 void telephony_call_unregister_interface(struct call *call);
 
 void telephony_call_set_state(struct call *call, enum call_state state);
+void telephony_call_set_line_id(struct call *call, const char *line_id);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 22/27] audio/hfp-hf: Add call line idenfication support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (20 preceding siblings ...)
  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 ` 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
                   ` (5 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/hfp-hf.c | 43 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index ebe87d8db..287523f28 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -107,6 +107,7 @@
 				HFP_HF_FEAT_ENHANCED_CALL_STATUS |\
 				HFP_HF_FEAT_ESCO_S4_T2)
 
+#define MAX_NUMBER_LEN 33
 #define MAX_OPERATOR_NAME_LEN 17
 
 enum hfp_indicator {
@@ -356,6 +357,29 @@ static void ciev_cb(struct hfp_context *context, void *user_data)
 	}
 }
 
+static void clip_cb(struct hfp_context *context, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+	char number[MAX_NUMBER_LEN];
+	GSList *l;
+
+	DBG("");
+
+	if (!hfp_context_get_string(context, number, MAX_NUMBER_LEN)) {
+		error("hf-client: incorrect +CLIP event");
+		return;
+	}
+
+	for (l = dev->calls; l; l = l->next) {
+		struct call *call = l->data;
+
+		if (call->state == CALL_STATE_INCOMING) {
+			telephony_call_set_line_id(call, number);
+			break;
+		}
+	}
+}
+
 static void cops_cb(struct hfp_context *context, void *user_data)
 {
 	struct hfp_device *dev = user_data;
@@ -383,6 +407,22 @@ static void cops_cb(struct hfp_context *context, void *user_data)
 	telephony_set_operator_name(dev->telephony, name);
 }
 
+static void cops_status_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: COPS? error: %d", result);
+		return;
+	}
+
+	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev, "AT+CLIP=1"))
+		info("hf-client: Could not send AT+CLIP=1");
+}
+
 static void cops_resp(enum hfp_result result, enum hfp_error cme_err,
 							void *user_data)
 {
@@ -395,7 +435,7 @@ static void cops_resp(enum hfp_result result, enum hfp_error cme_err,
 		return;
 	}
 
-	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev, "AT+COPS?"))
+	if (!hfp_hf_send_command(dev->hf, cops_status_resp, dev, "AT+COPS?"))
 		info("hf-client: Could not send AT+COPS?");
 }
 
@@ -421,6 +461,7 @@ static void slc_completed(struct hfp_device *dev)
 	/* TODO: register unsolicited results handlers */
 
 	hfp_hf_register(dev->hf, ciev_cb, "+CIEV", dev, NULL);
+	hfp_hf_register(dev->hf, clip_cb, "+CLIP", dev, NULL);
 	hfp_hf_register(dev->hf, cops_cb, "+COPS", dev, NULL);
 
 	if (!hfp_hf_send_command(dev->hf, cops_resp, dev, "AT+COPS=3,0"))
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 23/27] audio/hfp-hf: Disable NREC during connection setup
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (21 preceding siblings ...)
  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 ` 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
                   ` (4 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/hfp-hf.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 287523f28..5e3c9fcba 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -407,6 +407,26 @@ static void cops_cb(struct hfp_context *context, void *user_data)
 	telephony_set_operator_name(dev->telephony, name);
 }
 
+static void clip_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: CLIP error: %d", result);
+		return;
+	}
+
+	if ((dev->hfp_hf_features & HFP_HF_FEAT_ECNR) &&
+			(dev->features & HFP_AG_FEAT_ECNR)) {
+		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
+								"AT+NREC=0"))
+			info("hf-client: Could not send AT+NREC=0");
+	}
+}
+
 static void cops_status_resp(enum hfp_result result, enum hfp_error cme_err,
 							void *user_data)
 {
@@ -419,7 +439,7 @@ static void cops_status_resp(enum hfp_result result, enum hfp_error cme_err,
 		return;
 	}
 
-	if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev, "AT+CLIP=1"))
+	if (!hfp_hf_send_command(dev->hf, clip_resp, dev, "AT+CLIP=1"))
 		info("hf-client: Could not send AT+CLIP=1");
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 24/27] audio/hfp-hf: Enable waiting call if supported by remote AG
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (22 preceding siblings ...)
  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 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 25/27] audio/hfp-hf: Enable extended error " Frédéric Danis
                   ` (3 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/hfp-hf.c | 71 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 69 insertions(+), 2 deletions(-)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 5e3c9fcba..afe06296d 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -107,6 +107,9 @@
 				HFP_HF_FEAT_ENHANCED_CALL_STATUS |\
 				HFP_HF_FEAT_ESCO_S4_T2)
 
+#define CHLD_3WAY_FEATURES	(CHLD_FEAT_REL | CHLD_FEAT_REL_ACC |\
+				CHLD_FEAT_HOLD_ACC | CHLD_FEAT_MERGE)
+
 #define MAX_NUMBER_LEN 33
 #define MAX_OPERATOR_NAME_LEN 17
 
@@ -331,6 +334,46 @@ static uint8_t next_index(struct hfp_device *dev)
 	return 0;
 }
 
+static void ccwa_cb(struct hfp_context *context, void *user_data)
+{
+	struct hfp_device *dev = user_data;
+	char number[MAX_NUMBER_LEN];
+	GSList *l;
+	bool found = false;
+
+	DBG("");
+
+	if (!hfp_context_get_string(context, number, MAX_NUMBER_LEN)) {
+		error("hf-client: incorrect +CCWA event");
+		return;
+	}
+
+	for (l = dev->calls; l; l = l->next) {
+		struct call *call = l->data;
+
+		if (call->state == CALL_STATE_WAITING) {
+			info("hf-client: waiting call in progress (id: %d)",
+				call->idx);
+			found = true;
+			break;
+		}
+	}
+
+	if (!found) {
+		struct call *call;
+		uint8_t idx = next_index(dev);
+
+		call = telephony_new_call(dev->telephony, idx,
+						CALL_STATE_WAITING, NULL);
+		call->line_id = g_strdup(number);
+		if (telephony_call_register_interface(call)) {
+			telephony_free_call(call);
+			return;
+		}
+		dev->calls = g_slist_append(dev->calls, call);
+	}
+}
+
 static void ciev_cb(struct hfp_context *context, void *user_data)
 {
 	struct hfp_device *dev = user_data;
@@ -407,6 +450,25 @@ static void cops_cb(struct hfp_context *context, void *user_data)
 	telephony_set_operator_name(dev->telephony, name);
 }
 
+static void nrec_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: CLIP error: %d", result);
+		return;
+	}
+
+	if ((dev->chld_features & CHLD_3WAY_FEATURES) == CHLD_3WAY_FEATURES) {
+		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
+								"AT+CCWA=1"))
+			info("hf-client: Could not send AT+CCWA=1");
+	}
+}
+
 static void clip_resp(enum hfp_result result, enum hfp_error cme_err,
 							void *user_data)
 {
@@ -421,9 +483,13 @@ static void clip_resp(enum hfp_result result, enum hfp_error cme_err,
 
 	if ((dev->hfp_hf_features & HFP_HF_FEAT_ECNR) &&
 			(dev->features & HFP_AG_FEAT_ECNR)) {
-		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
-								"AT+NREC=0"))
+		if (!hfp_hf_send_command(dev->hf, nrec_resp, dev, "AT+NREC=0"))
 			info("hf-client: Could not send AT+NREC=0");
+	} else if ((dev->chld_features & CHLD_3WAY_FEATURES) ==
+			CHLD_3WAY_FEATURES) {
+		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
+								"AT+CCWA=1"))
+			info("hf-client: Could not send AT+CCWA=1");
 	}
 }
 
@@ -480,6 +546,7 @@ static void slc_completed(struct hfp_device *dev)
 
 	/* TODO: register unsolicited results handlers */
 
+	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, clip_cb, "+CLIP", dev, NULL);
 	hfp_hf_register(dev->hf, cops_cb, "+COPS", dev, NULL);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 25/27] audio/hfp-hf: Enable extended error if supported by remote AG
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (23 preceding siblings ...)
  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 ` 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
                   ` (2 subsequent siblings)
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/hfp-hf.c | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)

diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index afe06296d..5542bce96 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -450,6 +450,25 @@ static void cops_cb(struct hfp_context *context, void *user_data)
 	telephony_set_operator_name(dev->telephony, name);
 }
 
+static void ccwa_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: CCWA error: %d", result);
+		return;
+	}
+
+	if (dev->features & HFP_AG_FEAT_EXTENDED_RES_CODE) {
+		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
+								"AT+CMEE=1"))
+			info("hf-client: Could not send AT+CMEE=1");
+	}
+}
+
 static void nrec_resp(enum hfp_result result, enum hfp_error cme_err,
 							void *user_data)
 {
@@ -463,9 +482,12 @@ static void nrec_resp(enum hfp_result result, enum hfp_error cme_err,
 	}
 
 	if ((dev->chld_features & CHLD_3WAY_FEATURES) == CHLD_3WAY_FEATURES) {
-		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
-								"AT+CCWA=1"))
+		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"))
+			info("hf-client: Could not send AT+CMEE=1");
 	}
 }
 
@@ -487,12 +509,12 @@ static void clip_resp(enum hfp_result result, enum hfp_error cme_err,
 			info("hf-client: Could not send AT+NREC=0");
 	} else if ((dev->chld_features & CHLD_3WAY_FEATURES) ==
 			CHLD_3WAY_FEATURES) {
-		if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, dev,
-								"AT+CCWA=1"))
+		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"))
+			info("hf-client: Could not send AT+CMEE=1");
 static void cops_status_resp(enum hfp_result result, enum hfp_error cme_err,
 							void *user_data)
 {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 26/27] audio/telephony: Add call multiparty property support
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (24 preceding siblings ...)
  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 ` Frédéric Danis
  2025-06-27 14:51 ` [RFC BlueZ v2 27/27] audio/hfp-hf: Enable enhanced call status if supported by remote AG Frédéric Danis
  2025-07-02 17:50 ` [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Luiz Augusto von Dentz
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

---
 profiles/audio/telephony.c | 15 +++++++++++++++
 profiles/audio/telephony.h |  1 +
 2 files changed, 16 insertions(+)

diff --git a/profiles/audio/telephony.c b/profiles/audio/telephony.c
index a5dace7c3..9e10a94a4 100644
--- a/profiles/audio/telephony.c
+++ b/profiles/audio/telephony.c
@@ -791,3 +791,18 @@ void telephony_call_set_line_id(struct call *call, const char *line_id)
 			call->path, TELEPHONY_CALL_INTERFACE,
 			"LineIdentification");
 }
+
+void telephony_call_set_multiparty(struct call *call, bool multiparty)
+{
+	if (call->multiparty == multiparty)
+		return;
+
+	DBG("device %s multiparty %u -> %u", call->path, call->multiparty,
+		multiparty);
+
+	call->multiparty = multiparty;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+			call->path, TELEPHONY_CALL_INTERFACE,
+			"Multiparty");
+}
diff --git a/profiles/audio/telephony.h b/profiles/audio/telephony.h
index 5f2a4ae4c..9541d3edd 100644
--- a/profiles/audio/telephony.h
+++ b/profiles/audio/telephony.h
@@ -115,3 +115,4 @@ void telephony_call_unregister_interface(struct call *call);
 
 void telephony_call_set_state(struct call *call, enum call_state state);
 void telephony_call_set_line_id(struct call *call, const char *line_id);
+void telephony_call_set_multiparty(struct call *call, bool multiparty);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [RFC BlueZ v2 27/27] audio/hfp-hf: Enable enhanced call status if supported by remote AG
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (25 preceding siblings ...)
  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
  2025-07-02 17:50 ` [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Luiz Augusto von Dentz
  27 siblings, 0 replies; 32+ messages in thread
From: Frédéric Danis @ 2025-06-27 14:51 UTC (permalink / raw)
  To: linux-bluetooth

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


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* RE: New Telephony interface for HSP, HFP and CCP
  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   ` bluez.test.bot
  2025-07-02 17:38   ` [RFC BlueZ v2 01/27] doc: Add new telephony related profiles interfaces Luiz Augusto von Dentz
  1 sibling, 0 replies; 32+ messages in thread
From: bluez.test.bot @ 2025-06-27 16:15 UTC (permalink / raw)
  To: linux-bluetooth, frederic.danis

[-- Attachment #1: Type: text/plain, Size: 1930 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=976686

---Test result---

Test Summary:
CheckPatch                    PENDING   0.38 seconds
GitLint                       PENDING   0.28 seconds
BuildEll                      PASS      20.14 seconds
BluezMake                     PASS      2783.77 seconds
MakeCheck                     PASS      20.18 seconds
MakeDistcheck                 FAIL      51.64 seconds
CheckValgrind                 PASS      278.24 seconds
CheckSmatch                   PASS      310.19 seconds
bluezmakeextell               PASS      129.73 seconds
IncrementalBuild              PENDING   0.26 seconds
ScanBuild                     PASS      924.19 seconds

Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:

##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:

##############################
Test: MakeDistcheck - FAIL
Desc: Run Bluez Make Distcheck
Output:

Package cups was not found in the pkg-config search path.
Perhaps you should add the directory containing `cups.pc'
to the PKG_CONFIG_PATH environment variable
No package 'cups' found
../../profiles/audio/telephony.c:45:10: fatal error: telephony.h: No such file or directory
   45 | #include "telephony.h"
      |          ^~~~~~~~~~~~~
compilation terminated.
make[2]: *** [Makefile:10827: profiles/audio/bluetoothd-telephony.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [Makefile:4708: all] Error 2
make: *** [Makefile:12323: distcheck] Error 1
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:



---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [RFC BlueZ v2 01/27] doc: Add new telephony related profiles interfaces
  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   ` Luiz Augusto von Dentz
  1 sibling, 0 replies; 32+ messages in thread
From: Luiz Augusto von Dentz @ 2025-07-02 17:38 UTC (permalink / raw)
  To: Frédéric Danis; +Cc: linux-bluetooth

Hi Frédéric,

On Fri, Jun 27, 2025 at 10:52 AM Frédéric Danis
<frederic.danis@collabora.com> wrote:
>
> These are interfaces are meant to be generic to the telephony related
> "headset" profiles like HSP HS, HFP HF, and CCP.
> ---
> v1->v2:
>   - Rename org.bluez.TelephonyCall1 to org.bluez.Call1
>   - Remove reference to profiles in org.bluez.TelephonyAg1 object path
>   - Add profile UUID property to org.bluez.TelephonyAg1
>   - Add OperatorName property to org.bluez.TelephonyAg1
>
>  Makefile.am                   |   4 +
>  doc/org.bluez.Call.rst        | 136 ++++++++++++++++++++++
>  doc/org.bluez.TelephonyAg.rst | 207 ++++++++++++++++++++++++++++++++++
>  3 files changed, 347 insertions(+)
>  create mode 100644 doc/org.bluez.Call.rst
>  create mode 100644 doc/org.bluez.TelephonyAg.rst
>
> diff --git a/Makefile.am b/Makefile.am
> index 02ad23cf2..12714ecf8 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -388,6 +388,7 @@ man_MANS += doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \
>                 doc/org.bluez.obex.Message.5 \
>                 doc/org.bluez.obex.AgentManager.5 doc/org.bluez.obex.Agent.5 \
>                 doc/org.bluez.obex.Image.5
> +man_MANS += doc/org.bluez.TelephonyAg.5 doc/org.bluez.Call.5
>  endif
>  manual_pages += src/bluetoothd.8
>  manual_pages += doc/hci.7 doc/mgmt.7 doc/l2cap.7 doc/rfcomm.7 doc/sco.7
> @@ -422,6 +423,7 @@ manual_pages += doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \
>                 doc/org.bluez.obex.Message.5 \
>                 doc/org.bluez.obex.AgentManager.5 doc/org.bluez.obex.Agent.5 \
>                 doc/org.bluez.obex.Image.5
> +manual_pages += doc/org.bluez.TelephonyAg.5 doc/org.bluez.Call.5
>
>  EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
>                         src/main.conf profiles/network/network.conf \
> @@ -505,6 +507,8 @@ EXTRA_DIST += doc/org.bluez.obex.Client.rst doc/org.bluez.obex.Session.rst \
>                 doc/org.bluez.obex.AgentManager.rst doc/org.bluez.obex.Agent.rst \
>                 doc/org.bluez.obex.Image.rst
>
> +EXTRA_DIST += doc/org.bluez.TelephonyAg.rst doc/org.bluez.Call.rst
> +
>  EXTRA_DIST += doc/pics-opp.txt doc/pixit-opp.txt \
>                 doc/pts-opp.txt
>
> diff --git a/doc/org.bluez.Call.rst b/doc/org.bluez.Call.rst
> new file mode 100644
> index 000000000..3fcd6f6ea
> --- /dev/null
> +++ b/doc/org.bluez.Call.rst
> @@ -0,0 +1,136 @@
> +===============
> +org.bluez.Call1
> +===============
> +
> +--------------------------------------------
> +BlueZ D-Bus Telephony Call API documentation
> +--------------------------------------------
> +
> +:Version: BlueZ
> +:Date: May 2025
> +:Manual section: 5
> +:Manual group: Linux System Administration
> +
> +Interface
> +=========
> +
> +:Service:      org.bluez
> +:Interface:    org.bluez.Call1 [experimental]
> +:Object path:  [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony_ag#/call#
> +
> +Methods
> +-------
> +
> +void Answer()
> +`````````````
> +
> +Answers an incoming call. Only valid if the state of the call is "incoming".
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +void Hangup()
> +`````````````
> +
> +Hangs up the call.
> +
> +For an incoming call, the call is hung up using ATH or equivalent. For a
> +waiting call, the remote party is notified by using the User Determined User
> +Busy (UDUB) condition. This is generally implemented using CHLD=0.
> +
> +Please note that the GSM specification does not allow the release of a held
> +call when a waiting call exists. This is because 27.007 allows CHLD=1X to
> +operate only on active calls. Hence a held call cannot be hung up without
> +affecting the state of the incoming call (e.g. using other CHLD alternatives).
> +Most manufacturers provide vendor extensions that do allow the state of the
> +held call to be modified using CHLD=1X or equivalent. It should be noted that
> +Bluetooth HFP specifies the classic 27.007 behavior and does not allow CHLD=1X
> +to modify the state of held calls.
> +
> +Based on the discussion above, it should also be noted that releasing a
> +particular party of a held multiparty call might not be possible on some
> +implementations. It is recommended for the applications to structure their UI
> +accordingly.
> +
> +NOTE: Releasing active calls does not produce side-effects. That is the state
> +of held or waiting calls is not affected. As an exception, in the case where a
> +single active call and a waiting call are present, releasing the active call
> +will result in the waiting call transitioning to the 'incoming' state.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +Properties
> +----------
> +
> +string LineIdentification [readonly]
> +````````````````````````````````````
> +
> +Contains the Line Identification information returned by the network, if
> +present. For incoming calls this is effectively the CLIP. For outgoing calls
> +this attribute will hold the dialed number, or the COLP if received by the
> +audio gateway.
> +
> +Please note that COLP may be different from the dialed number. A special
> +"withheld" value means the remote party refused to provide caller ID and the
> +"override category" option was not provisioned for the current subscriber.
> +
> +string IncomingLine [readonly, optional]
> +````````````````````````````````````````
> +
> +Contains the Called Line Identification information returned by the network.
> +This is only available for incoming calls and indicates the local subscriber
> +number which was dialed by the remote party. This is useful for subscribers
> +which have a multiple line service with their network provider and would like
> +to know what line the call is coming in on.
> +
> +string Name [readonly]
> +``````````````````````
> +
> +Contains the Name Identification information returned by the network, if
> +present.
> +
> +boolean Multiparty [readonly]
> +`````````````````````````````
> +
> +Contains the indication if the call is part of a multiparty call or not.
> +
> +Notifications if a call becomes part or leaves a multiparty call are sent.
> +
> +string State [readonly]
> +```````````````````````
> +
> +Contains the state of the current call.
> +
> +Possible values:
> +
> +:"active":
> +
> +       The call is active
> +
> +:"held":
> +
> +       The call is on hold
> +
> +:"dialing":
> +
> +       The call is being dialed
> +
> +:"alerting":
> +
> +       The remote party is being alerted
> +
> +:"incoming":
> +
> +       Incoming call in progress
> +
> +:"waiting":
> +
> +       Call is waiting
> +
> +:"disconnected":
> +
> +       No further use of this object is allowed, it will be
> +       destroyed shortly
> diff --git a/doc/org.bluez.TelephonyAg.rst b/doc/org.bluez.TelephonyAg.rst
> new file mode 100644
> index 000000000..ddb5eec0f
> --- /dev/null
> +++ b/doc/org.bluez.TelephonyAg.rst
> @@ -0,0 +1,207 @@
> +======================
> +org.bluez.TelephonyAg1
> +======================

Id call it just org.bluez.Telephony1.

> +-----------------------------------------------------
> +BlueZ D-Bus Telephony Audio Gateway API documentation
> +-----------------------------------------------------
> +
> +:Version: BlueZ
> +:Date: May 2025
> +:Manual section: 5
> +:Manual group: Linux System Administration
> +
> +Interface
> +=========
> +
> +:Service:      org.bluez
> +:Interface:    org.bluez.TelephonyAg1 [experimental]
> +:Object path:  [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony_ag#
> +
> +Methods
> +-------
> +
> +object Dial(string number)
> +``````````````````````````
> +
> +Call number, if number is void try to call last dialed number.
> +Initiates a new outgoing call. Returns the object path to the newly created
> +call.
> +
> +The number must be a string containing the following characters:
> +`[0-9+*#,ABCD]{1,80}` The character set can contain numbers, `+`, `*`, `#`,
> +`,` and the letters `A` to `D`. Besides this sanity checking no further number
> +validation is performed. It is assumed that the gateway and/or the network
> +will perform further validation.
> +
> +If number is an empty string, it will try to call last dialed number.
> +
> +NOTE: If an active call (single or multiparty) exists, then it is
> +automatically put on hold if the dial procedure is successful.
> +
> +Possible Errors:
> +
> +:org.bluez.Error.InvalidState:
> +:org.bluez.Error.InvalidArguments:
> +:org.bluez.Error.NotSupported:
> +:org.bluez.Error.Failed:
> +
> +void SwapCalls()
> +````````````````
> +
> +Swaps Active and Held calls. The effect of this is that all calls (0 or more
> +including calls in a multi-party conversation) that were Active are now Held,
> +and all calls (0 or more) that were Held are now Active.
> +
> +GSM specification does not allow calls to be swapped in the case where Held,
> +Active and Waiting calls exist. Some modems implement this anyway, thus it is
> +manufacturer specific whether this method will succeed in the case of Held,
> +Active and Waiting calls.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +void ReleaseAndAnswer()
> +```````````````````````
> +
> +Releases currently active call (0 or more) and answers the currently waiting
> +call. Please note that if the current call is a multiparty call, then all
> +parties in the multi-party call will be released.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +void ReleaseAndSwap()
> +`````````````````````
> +
> +Releases currently active call (0 or more) and activates any currently held
> +calls. Please note that if the current call is a multiparty call, then all
> +parties in the multi-party call will be released.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +void HoldAndAnswer()
> +````````````````````
> +
> +Puts the current call (including multi-party calls) on hold and answers the
> +currently waiting call. Calling this function when a user already has a both
> +Active and Held calls is invalid, since in GSM a user can have only a single
> +Held call at a time.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +void HangupAll()
> +````````````````
> +
> +Releases all calls except waiting calls. This includes multiparty calls.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +void HangupActive()
> +```````````````````
> +
> +Releases active calls. This includes multiparty active calls.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +void HangupHeld()
> +`````````````````
> +
> +Releases held calls except waiting calls. This includes multiparty held calls.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +array{object} CreateMultiparty()
> +````````````````````````````````
> +
> +Joins active and held calls together into a multi-party call. If one of the
> +calls is already a multi-party call, then the other call is added to the
> +multiparty conversation. Returns the new list of calls participating in the
> +multiparty call.
> +
> +There can only be one subscriber controlled multi-party call according to the
> +GSM specification.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.Failed
> +
> +void SendTones(string tones)
> +````````````````````````````
> +
> +Sends the DTMF tones to the network. The tones have a fixed duration.
> +Tones can be one of: '0' - '9', '*', '#', 'A', 'B', 'C', 'D'. The last four
> +are typically not used in normal circumstances.
> +
> +Possible Errors:
> +:org.bluez.Error.InvalidState
> +:org.bluez.Error.InvalidArgs
> +:org.bluez.Error.Failed
> +
> +Properties
> +----------
> +
> +string UUID [readonly]
> +``````````````````````
> +
> +UUID of the profile which the Telephony Audio Gateway is for.
> +
> +string State [readonly]
> +```````````````````````
> +
> +Contains the state of the current connection.
> +
> +Possible values:
> +
> +:"connecting":
> +
> +       RFComm connection in progress
> +
> +:"slc_connecting":
> +
> +       Service Level Connection in progress
> +
> +:"connected":
> +
> +       RFComm and Service Level Connection are connected
> +
> +:"disconnecting":
> +
> +       No further use of this object is allowed, it will be destroyed shortly
> +
> +boolean Service [readonly]
> +``````````````````````````
> +
> +Network service availability.
> +
> +byte Signal [readonly]
> +``````````````````````
> +
> +Network level signal from 0 to 5.
> +
> +boolean Roaming [readonly]
> +``````````````````````````
> +
> +Network roaming usage.
> +
> +byte BattChg [readonly]
> +```````````````````````
> +
> +Battery level from 0 to 5.
> +
> +string OperatorName [readonly, optional]
> +````````````````````````````````````````
> +
> +Operator name
> --
> 2.43.0
>
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP
  2025-06-27 14:51 [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Frédéric Danis
                   ` (26 preceding siblings ...)
  2025-06-27 14:51 ` [RFC BlueZ v2 27/27] audio/hfp-hf: Enable enhanced call status if supported by remote AG Frédéric Danis
@ 2025-07-02 17:50 ` Luiz Augusto von Dentz
  27 siblings, 0 replies; 32+ messages in thread
From: Luiz Augusto von Dentz @ 2025-07-02 17:50 UTC (permalink / raw)
  To: Frédéric Danis; +Cc: linux-bluetooth

Hi Frédéric,

On Fri, Jun 27, 2025 at 10:52 AM Frédéric Danis
<frederic.danis@collabora.com> wrote:
>
> This will introduce a new Telephony interface wich is intended to be
> shared by the profiles able to control telephony calls.
>
> The idea is to split the call control interface from the audio streaming,
> as it is done for AVRCP and A2DP.
> As for A2DP, the audio part will be delegated to the audio daemon (like
> PipeWire) by the creation of new endpoints for CVSD and mSBC, LC3 endpoint
> already exists.
>
> The interface is mostly based on the one done for PipeWire's native
> backend.
>
> This will simplify the qualification of the telephony related profiles as
> the qualification will no more depend on external projects, and calls can
> be controlled from bluetoothctl.
>
> A first implementation allows to dial or hangup a call using HFP.
>
> v1->v2:
>   - Rename org.bluez.TelephonyCall1 to org.bluez.Call1
>   - Remove reference to profiles in org.bluez.TelephonyAg1 object path
>   - Add profile UUID property to org.bluez.TelephonyAg1
>   - Add OperatorName property to org.bluez.TelephonyAg1
>   - Rename telephony_set_call_state() to telephony_call_set_state()
>   - Use first available index of call for new call
>   - Fix DBus message memory leak in hfp_dial_cb()
>   - Display UUID and OperatorName in bluetoothctl telephony.show command
>   - Add hangup-active and hangup-held support
>   - Add SendTones support
>   - Remove HFP specific comments in documentation
>   - Add HFP HF server and related SDP record
>   - Add OperatorName support to HFP HF
>   - Add call line identification property support to HFP HF
>   - Disable NREC during HFP HF connection phase
>   - Enable Waiting call event to HFP HF
>   - Enable Extended error support in HFP HF
>   - Add telephony_call_set_multiparty() to telephony API
>   - Enable Enhanced call status support in HFP HF, and use it to update
>     calls status if available on both side
>
> Frédéric Danis (27):
>   doc: Add new telephony related profiles interfaces
>   audio/telephony: Add shared interfaces implementation
>   audio/telephony: Add skeleton for HFP profile
>   audio/hfp-hf: Add HFP SLC connection and event support
>   audio/hfp-hf: Add dial support
>   audio/hfp-hf: Add hangup all calls support
>   audio/hfp-hf: Add answer a specific call support
>   client/telephony: Add new submenu
>   audio/hfp-hf: Remove call interface during profile disconnection
>   audio/hfp-hf: Create existing call during SLC phase
>   audio/telephony: Add hangup_active and hangup_held functions
>   audio/hfp-hf: Add hangup_active and hangup_held support
>   client/telephony: Add hangup_active and hangup_held support
>   audio/hfp-hf: Add SendTones support
>   client/telephony: Add SendTones support
>   doc: Make telephony docs more generic
>   client/telephony: Remove IncomingLine
>   audio/telephony: Remove IncomingLine
>   audio/hfp-hf: Add HFP HF server and SDP record
>   audio/hfp-hf: Add operator name support
>   audio/telephony: Add call line identication property support
>   audio/hfp-hf: Add call line idenfication support
>   audio/hfp-hf: Disable NREC during connection setup
>   audio/hfp-hf: Enable waiting call if supported by remote AG
>   audio/hfp-hf: Enable extended error if supported by remote AG
>   audio/telephony: Add call multiparty property support
>   audio/hfp-hf: Enable enhanced call status if supported by remote AG

This is sort of too big to review all at once, Id start just with
documentation so we can nail down the interfaces first, then we can
proceed to other details, anyway from the brief looking at it some
things already show in the design:

1. The usage of glib function is not recommended on new code, things
like g_new0/g_free, etc, shall be replaced with util helpers or just
plain libc function.
2. There doesn't seem to be anything added to src/shared/hfp.h which
will make it really tricky to do unit testing since a lot of things
seems to be done in plugin, if the idea is to really have something
that is qualifiable Id look into expanding test-hfp to handle HFP
testing spec test cases which should enable us to determine what needs
to be inside shared/hfp and what doesn't.

-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 32+ messages in thread

end of thread, other threads:[~2025-07-02 17:50 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [RFC BlueZ v2 27/27] audio/hfp-hf: Enable enhanced call status if supported by remote AG Frédéric Danis
2025-07-02 17:50 ` [RFC BlueZ v2 00/27] New Telephony interface for HSP, HFP and CCP Luiz Augusto von Dentz
  -- strict thread matches above, loose matches on Subject: below --
2025-05-28  8:59 [RFC BlueZ 01/10] doc: Add new telephony related profiles interfaces Frédéric Danis
2025-05-28 10:33 ` New Telephony interface for HSP, HFP and CCP bluez.test.bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox