aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mm-iface-modem-voice.c577
-rw-r--r--src/mm-iface-modem-voice.h34
2 files changed, 609 insertions, 2 deletions
diff --git a/src/mm-iface-modem-voice.c b/src/mm-iface-modem-voice.c
index abaa64a5..7e91fbd8 100644
--- a/src/mm-iface-modem-voice.c
+++ b/src/mm-iface-modem-voice.c
@@ -25,10 +25,12 @@
#define SUPPORT_CHECKED_TAG "voice-support-checked-tag"
#define SUPPORTED_TAG "voice-supported-tag"
#define CALL_LIST_POLLING_CONTEXT_TAG "voice-call-list-polling-context-tag"
+#define IN_CALL_EVENT_CONTEXT_TAG "voice-in-call-event-context-tag"
static GQuark support_checked_quark;
static GQuark supported_quark;
static GQuark call_list_polling_context_quark;
+static GQuark in_call_event_context_quark;
/*****************************************************************************/
@@ -40,13 +42,21 @@ mm_iface_modem_voice_bind_simple_status (MMIfaceModemVoice *self,
/*****************************************************************************/
+/* new calls will inherit audio settings if the modem is already in-call state */
+static void update_audio_settings_in_call (MMIfaceModemVoice *self,
+ MMBaseCall *call);
+
static MMBaseCall *
create_incoming_call (MMIfaceModemVoice *self,
const gchar *number)
{
+ MMBaseCall *call;
+
g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call != NULL);
- return MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self, MM_CALL_DIRECTION_INCOMING, number);
+ call = MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self, MM_CALL_DIRECTION_INCOMING, number);
+ update_audio_settings_in_call (self, call);
+ return call;
}
static MMBaseCall *
@@ -54,6 +64,7 @@ create_outgoing_call_from_properties (MMIfaceModemVoice *self,
MMCallProperties *properties,
GError **error)
{
+ MMBaseCall *call;
const gchar *number;
/* Don't create CALL from properties if either number is missing */
@@ -68,7 +79,9 @@ create_outgoing_call_from_properties (MMIfaceModemVoice *self,
/* Create a call object as defined by the interface */
g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call != NULL);
- return MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self, MM_CALL_DIRECTION_OUTGOING, number);
+ call = MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self, MM_CALL_DIRECTION_OUTGOING, number);
+ update_audio_settings_in_call (self, call);
+ return call;
}
/*****************************************************************************/
@@ -1113,6 +1126,560 @@ handle_transfer (MmGdbusModemVoice *skeleton,
}
/*****************************************************************************/
+/* In-call setup operation
+ *
+ * It will setup URC handlers for all in-call URCs, and also setup the audio
+ * channel if the plugin requires to do so.
+ */
+
+typedef enum {
+ IN_CALL_SETUP_STEP_FIRST,
+ IN_CALL_SETUP_STEP_UNSOLICITED_EVENTS,
+ IN_CALL_SETUP_STEP_AUDIO_CHANNEL,
+ IN_CALL_SETUP_STEP_LAST,
+} InCallSetupStep;
+
+typedef struct {
+ InCallSetupStep step;
+ MMPort *audio_port;
+ MMCallAudioFormat *audio_format;
+} InCallSetupContext;
+
+static void
+in_call_setup_context_free (InCallSetupContext *ctx)
+{
+ g_clear_object (&ctx->audio_port);
+ g_clear_object (&ctx->audio_format);
+ g_slice_free (InCallSetupContext, ctx);
+}
+
+static gboolean
+in_call_setup_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ MMPort **audio_port, /* optional */
+ MMCallAudioFormat **audio_format, /* optional */
+ GError **error)
+{
+ InCallSetupContext *ctx;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ ctx = g_task_get_task_data (G_TASK (res));
+ if (audio_port) {
+ *audio_port = ctx->audio_port;
+ ctx->audio_port = NULL;
+ }
+ if (audio_format) {
+ *audio_format = ctx->audio_format;
+ ctx->audio_format = NULL;
+ }
+
+ return TRUE;
+}
+
+static void in_call_setup_context_step (GTask *task);
+
+static void
+setup_in_call_audio_channel_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ InCallSetupContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_audio_channel_finish (self,
+ res,
+ &ctx->audio_port,
+ &ctx->audio_format,
+ &error)) {
+ mm_warn ("Couldn't setup in-call audio channel: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ ctx->step++;
+ in_call_setup_context_step (task);
+}
+
+static void
+setup_in_call_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ InCallSetupContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_unsolicited_events_finish (self, res, &error)) {
+ mm_warn ("Couldn't setup in-call unsolicited events: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ ctx->step++;
+ in_call_setup_context_step (task);
+}
+
+static void
+in_call_setup_context_step (GTask *task)
+{
+ MMIfaceModemVoice *self;
+ InCallSetupContext *ctx;
+
+ if (g_task_return_error_if_cancelled (task))
+ return;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case IN_CALL_SETUP_STEP_FIRST:
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_SETUP_STEP_UNSOLICITED_EVENTS:
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_unsolicited_events &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_unsolicited_events_finish) {
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_unsolicited_events (
+ self,
+ (GAsyncReadyCallback) setup_in_call_unsolicited_events_ready,
+ task);
+ break;
+ }
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_SETUP_STEP_AUDIO_CHANNEL:
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_audio_channel &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_audio_channel_finish) {
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_audio_channel (
+ self,
+ (GAsyncReadyCallback) setup_in_call_audio_channel_ready,
+ task);
+ break;
+ }
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_SETUP_STEP_LAST:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+}
+
+static void
+in_call_setup (MMIfaceModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ InCallSetupContext *ctx;
+
+ ctx = g_slice_new0 (InCallSetupContext);
+ ctx->step = IN_CALL_SETUP_STEP_FIRST;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) in_call_setup_context_free);
+
+ in_call_setup_context_step (task);
+}
+
+/*****************************************************************************/
+/* In-call cleanup operation
+ *
+ * It will cleanup audio channel settings and remove all in-call URC handlers.
+ */
+
+typedef enum {
+ IN_CALL_CLEANUP_STEP_FIRST,
+ IN_CALL_CLEANUP_STEP_AUDIO_CHANNEL,
+ IN_CALL_CLEANUP_STEP_UNSOLICITED_EVENTS,
+ IN_CALL_CLEANUP_STEP_LAST,
+} InCallCleanupStep;
+
+typedef struct {
+ InCallCleanupStep step;
+} InCallCleanupContext;
+
+static gboolean
+in_call_cleanup_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void in_call_cleanup_context_step (GTask *task);
+
+static void
+cleanup_in_call_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ InCallCleanupContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_unsolicited_events_finish (self, res, &error)) {
+ mm_warn ("Couldn't cleanup in-call unsolicited events: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ ctx->step++;
+ in_call_cleanup_context_step (task);
+}
+
+static void
+cleanup_in_call_audio_channel_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ InCallCleanupContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_audio_channel_finish (self, res, &error)) {
+ mm_warn ("Couldn't cleanup in-call audio channel: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ ctx->step++;
+ in_call_cleanup_context_step (task);
+}
+
+static void
+in_call_cleanup_context_step (GTask *task)
+{
+ MMIfaceModemVoice *self;
+ InCallCleanupContext *ctx;
+
+ if (g_task_return_error_if_cancelled (task))
+ return;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case IN_CALL_CLEANUP_STEP_FIRST:
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_CLEANUP_STEP_AUDIO_CHANNEL:
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_audio_channel &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_audio_channel_finish) {
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_audio_channel (
+ self,
+ (GAsyncReadyCallback) cleanup_in_call_audio_channel_ready,
+ task);
+ break;
+ }
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_CLEANUP_STEP_UNSOLICITED_EVENTS:
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_unsolicited_events &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_unsolicited_events_finish) {
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_unsolicited_events (
+ self,
+ (GAsyncReadyCallback) cleanup_in_call_unsolicited_events_ready,
+ task);
+ break;
+ }
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_CLEANUP_STEP_LAST:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+}
+
+static void
+in_call_cleanup (MMIfaceModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ InCallCleanupContext *ctx;
+
+ ctx = g_new0 (InCallCleanupContext, 1);
+ ctx->step = IN_CALL_CLEANUP_STEP_FIRST;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ in_call_cleanup_context_step (task);
+}
+
+/*****************************************************************************/
+/* In-call event handling logic
+ *
+ * This procedure will run a in-call setup async function whenever we detect
+ * that there is at least one call that is ongoing. This setup function will
+ * try to setup in-call unsolicited events as well as any audio channel
+ * requirements.
+ *
+ * The procedure will run a in-call cleanup async function whenever we detect
+ * that there are no longer any ongoing calls. The cleanup function will
+ * cleanup the audio channel and remove the in-call unsolicited event handlers.
+ */
+
+typedef struct {
+ guint check_id;
+ GCancellable *setup_cancellable;
+ GCancellable *cleanup_cancellable;
+ gboolean in_call_state;
+ MMPort *audio_port;
+ MMCallAudioFormat *audio_format;
+} InCallEventContext;
+
+static void
+in_call_event_context_free (InCallEventContext *ctx)
+{
+ if (ctx->check_id)
+ g_source_remove (ctx->check_id);
+ if (ctx->cleanup_cancellable) {
+ g_cancellable_cancel (ctx->cleanup_cancellable);
+ g_clear_object (&ctx->cleanup_cancellable);
+ }
+ if (ctx->setup_cancellable) {
+ g_cancellable_cancel (ctx->setup_cancellable);
+ g_clear_object (&ctx->setup_cancellable);
+ }
+ g_clear_object (&ctx->audio_port);
+ g_clear_object (&ctx->audio_format);
+ g_slice_free (InCallEventContext, ctx);
+}
+
+static InCallEventContext *
+get_in_call_event_context (MMIfaceModemVoice *self)
+{
+ InCallEventContext *ctx;
+
+ if (G_UNLIKELY (!in_call_event_context_quark))
+ in_call_event_context_quark = g_quark_from_static_string (IN_CALL_EVENT_CONTEXT_TAG);
+
+ ctx = g_object_get_qdata (G_OBJECT (self), in_call_event_context_quark);
+ if (!ctx) {
+ /* Create context and keep it as object data */
+ ctx = g_slice_new0 (InCallEventContext);
+ g_object_set_qdata_full (
+ G_OBJECT (self),
+ in_call_event_context_quark,
+ ctx,
+ (GDestroyNotify)in_call_event_context_free);
+ }
+
+ return ctx;
+}
+
+static void
+call_list_foreach_audio_settings (MMBaseCall *call,
+ InCallEventContext *ctx)
+{
+ if (mm_base_call_get_state (call) != MM_CALL_STATE_TERMINATED)
+ return;
+ mm_base_call_change_audio_settings (call, ctx->audio_port, ctx->audio_format);
+}
+
+static void
+update_audio_settings_in_ongoing_calls (MMIfaceModemVoice *self)
+{
+ MMCallList *list = NULL;
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+
+ g_object_get (MM_BASE_MODEM (self),
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ mm_warn ("Cannot update audio settings in active calls: missing internal call list");
+ return;
+ }
+
+ mm_call_list_foreach (list, (MMCallListForeachFunc) call_list_foreach_audio_settings, ctx);
+ g_clear_object (&list);
+}
+
+static void
+update_audio_settings_in_call (MMIfaceModemVoice *self,
+ MMBaseCall *call)
+{
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+ mm_base_call_change_audio_settings (call, ctx->audio_port, ctx->audio_format);
+}
+
+static void
+call_list_foreach_count_in_call (MMBaseCall *call,
+ gpointer user_data)
+{
+ guint *n_calls_in_call = (guint *)user_data;
+
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_DIALING:
+ case MM_CALL_STATE_RINGING_OUT:
+ case MM_CALL_STATE_HELD:
+ case MM_CALL_STATE_ACTIVE:
+ *n_calls_in_call = *n_calls_in_call + 1;
+ break;
+ case MM_CALL_STATE_RINGING_IN:
+ case MM_CALL_STATE_WAITING:
+ /* NOTE: ringing-in and waiting calls are NOT yet in-call, e.g. there must
+ * be no audio settings enabled and we must not enable in-call URC handling
+ * yet. */
+ default:
+ break;
+ }
+}
+
+static void
+in_call_cleanup_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+
+ if (!in_call_cleanup_finish (self, res, &error)) {
+ /* ignore cancelled operations */
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && !g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED))
+ mm_warn ("Cannot cleanup in-call modem state: %s", error->message);
+ g_clear_error (&error);
+ } else {
+ mm_dbg ("modem is no longer in-call state");
+ ctx->in_call_state = FALSE;
+ g_clear_object (&ctx->audio_port);
+ g_clear_object (&ctx->audio_format);
+ }
+
+ g_clear_object (&ctx->cleanup_cancellable);
+}
+
+static void
+in_call_setup_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+
+ if (!in_call_setup_finish (self, res, &ctx->audio_port, &ctx->audio_format, &error)) {
+ /* ignore cancelled operations */
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && !g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED))
+ mm_warn ("Cannot setup in-call modem state: %s", error->message);
+ g_clear_error (&error);
+ } else {
+ mm_dbg ("modem is now in-call state");
+ ctx->in_call_state = TRUE;
+ update_audio_settings_in_ongoing_calls (self);
+ }
+
+ g_clear_object (&ctx->setup_cancellable);
+}
+
+static gboolean
+call_list_check_in_call_events (MMIfaceModemVoice *self)
+{
+ InCallEventContext *ctx;
+ MMCallList *list = NULL;
+ guint n_calls_in_call = 0;
+
+ ctx = get_in_call_event_context (self);
+ ctx->check_id = 0;
+
+ g_object_get (MM_BASE_MODEM (self),
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ mm_warn ("Cannot update in-call state: missing internal call list");
+ goto out;
+ }
+
+ mm_call_list_foreach (list, (MMCallListForeachFunc) call_list_foreach_count_in_call, &n_calls_in_call);
+
+ /* Need to setup in-call events? */
+ if (n_calls_in_call > 0 && !ctx->in_call_state) {
+ /* if setup already ongoing, do nothing */
+ if (ctx->setup_cancellable)
+ goto out;
+
+ /* cancel ongoing cleanup if any */
+ if (ctx->cleanup_cancellable) {
+ g_cancellable_cancel (ctx->cleanup_cancellable);
+ g_clear_object (&ctx->cleanup_cancellable);
+ }
+
+ /* run setup */
+ mm_dbg ("Setting up in-call state...");
+ ctx->setup_cancellable = g_cancellable_new ();
+ in_call_setup (self, ctx->setup_cancellable, (GAsyncReadyCallback) in_call_setup_ready, NULL);
+ goto out;
+ }
+
+ /* Need to cleanup in-call events? */
+ if (n_calls_in_call == 0 && ctx->in_call_state) {
+ /* if cleanup already ongoing, do nothing */
+ if (ctx->cleanup_cancellable)
+ goto out;
+
+ /* cancel ongoing setup if any */
+ if (ctx->setup_cancellable) {
+ g_cancellable_cancel (ctx->setup_cancellable);
+ g_clear_object (&ctx->setup_cancellable);
+ }
+
+ /* run cleanup */
+ mm_dbg ("Cleaning up in-call state...");
+ ctx->cleanup_cancellable = g_cancellable_new ();
+ in_call_cleanup (self, ctx->cleanup_cancellable, (GAsyncReadyCallback) in_call_cleanup_ready, NULL);
+ goto out;
+ }
+
+ out:
+ g_clear_object (&list);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+call_state_changed (MMIfaceModemVoice *self)
+{
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+ if (ctx->check_id)
+ return;
+
+ /* Process check for in-call events in an idle, so that we can combine
+ * together in the same check multiple call state updates happening
+ * at the same time for different calls (e.g. when swapping active/held
+ * calls). */
+ ctx->check_id = g_idle_add ((GSourceFunc)call_list_check_in_call_events, self);
+}
+
+static void
+setup_in_call_event_handling (MMCallList *call_list,
+ const gchar *call_path_added,
+ MMIfaceModemVoice *self)
+{
+ MMBaseCall *call;
+
+ call = mm_call_list_get_call (call_list, call_path_added);
+ g_assert (call);
+
+ g_signal_connect_swapped (call,
+ "state-changed",
+ G_CALLBACK (call_state_changed),
+ self);
+}
+
+/*****************************************************************************/
/* Call list polling logic
*
* The call list polling is exclusively used to detect detailed call state
@@ -1568,6 +2135,12 @@ interface_enabling_step (GTask *task)
G_CALLBACK (call_deleted),
ctx->skeleton);
+ /* Setup monitoring for in-call event handling */
+ g_signal_connect (list,
+ MM_CALL_ADDED,
+ G_CALLBACK (setup_in_call_event_handling),
+ self);
+
/* Unless we're told not to, setup call list polling logic */
if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list &&
MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list_finish) {
diff --git a/src/mm-iface-modem-voice.h b/src/mm-iface-modem-voice.h
index f3d4ce76..7cc82601 100644
--- a/src/mm-iface-modem-voice.h
+++ b/src/mm-iface-modem-voice.h
@@ -78,6 +78,40 @@ struct _MMIfaceModemVoice {
GAsyncResult *res,
GError **error);
+ /* Asynchronous setup of in-call unsolicited events */
+ void (* setup_in_call_unsolicited_events) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* setup_in_call_unsolicited_events_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Asynchronous cleanup of in-call unsolicited events */
+ void (* cleanup_in_call_unsolicited_events) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* cleanup_in_call_unsolicited_events_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Asynchronous setup of in-call audio channel */
+ void (* setup_in_call_audio_channel) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* setup_in_call_audio_channel_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ MMPort **audio_port, /* optional */
+ MMCallAudioFormat **audio_format, /* optional */
+ GError **error);
+
+ /* Asynchronous cleanup of in-call audio channel */
+ void (* cleanup_in_call_audio_channel) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* cleanup_in_call_audio_channel_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
/* Load full list of calls (MMCallInfo list) */
void (* load_call_list) (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,