diff mbox series

[RFC,BlueZ,02/10] audio/telephony: Add shared interfaces implementation

Message ID 20250528085930.227816-3-frederic.danis@collabora.com
State New
Headers show
Series New Telephony interface for HSP, HFP and CCP | expand

Commit Message

Frédéric Danis May 28, 2025, 8:59 a.m. UTC
---
 profiles/audio/telephony.c | 713 +++++++++++++++++++++++++++++++++++++
 profiles/audio/telephony.h | 110 ++++++
 2 files changed, 823 insertions(+)
 create mode 100644 profiles/audio/telephony.c
 create mode 100644 profiles/audio/telephony.h
diff mbox series

Patch

diff --git a/profiles/audio/telephony.c b/profiles/audio/telephony.c
new file mode 100644
index 000000000..34f690c99
--- /dev/null
+++ b/profiles/audio/telephony.c
@@ -0,0 +1,713 @@ 
+/* 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.TelephonyCall1"
+
+struct telephony {
+	struct btd_service		*service;
+	struct btd_device		*device;
+	char				*path;
+	uint8_t				id;
+	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;
+};
+
+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 btd_profile *p = btd_service_get_profile(service);
+	struct telephony *ag;
+
+	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/%s", path, p->name);
+	ag->profile_data = profile_data;
+	ag->cbs = cbs;
+
+	return ag;
+}
+
+void telephony_free(struct telephony *telephony)
+{
+	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_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 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[] = {
+	{ "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 },
+	{ }
+};
+
+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;
+}
+
+struct call *telephony_new_call(struct telephony *telephony,
+				enum call_state state,
+				void *user_data)
+{
+	struct call *call;
+
+	call = g_new0(struct call, 1);
+	call->device = telephony;
+	call->state = state;
+	call->idx = telephony->id++;
+	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_set_call_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->device->path, TELEPHONY_CALL_INTERFACE,
+			"State");
+}
+
+enum call_state telephony_get_call_state(struct call *call)
+{
+	return call->state;
+}
diff --git a/profiles/audio/telephony.h b/profiles/audio/telephony.h
new file mode 100644
index 000000000..362b163ab
--- /dev/null
+++ b/profiles/audio/telephony.h
@@ -0,0 +1,110 @@ 
+/* 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);
+
+struct call *telephony_new_call(struct telephony *telephony,
+	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_set_call_state(struct call *call, enum call_state state);
+enum call_state telephony_get_call_state(struct call *call);