diff options
Diffstat (limited to 'src/iface-voice')
-rw-r--r-- | src/iface-voice/Makefile.am | 36 | ||||
-rw-r--r-- | src/iface-voice/mm-base-call.c | 1230 | ||||
-rw-r--r-- | src/iface-voice/mm-base-call.h | 116 | ||||
-rw-r--r-- | src/iface-voice/mm-call-list.c | 441 | ||||
-rw-r--r-- | src/iface-voice/mm-call-list.h | 82 | ||||
-rw-r--r-- | src/iface-voice/mm-iface-modem-voice.c | 1125 | ||||
-rw-r--r-- | src/iface-voice/mm-iface-modem-voice.h | 137 |
7 files changed, 3167 insertions, 0 deletions
diff --git a/src/iface-voice/Makefile.am b/src/iface-voice/Makefile.am new file mode 100644 index 000000000..2b7dafc72 --- /dev/null +++ b/src/iface-voice/Makefile.am @@ -0,0 +1,36 @@ + +AM_CFLAGS = $(CODE_COVERAGE_CFLAGS) +AM_LDFLAGS = $(CODE_COVERAGE_LDFLAGS) + +noinst_LTLIBRARIES = libmmifacevoice.la + +libmmifacevoice_la_CPPFLAGS = \ + $(MM_CFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src \ + -I$(top_srcdir)/src/port \ + -I$(top_builddir)/src/port \ + -I$(top_srcdir)/src/helpers \ + -I$(top_builddir)/src/helpers \ + -I$(top_srcdir)/libmm-glib \ + -I${top_srcdir}/libmm-glib/generated \ + -I${top_builddir}/libmm-glib/generated + +if WITH_QMI +libmmifacevoice_la_CPPFLAGS += $(QMI_CFLAGS) +endif + +if WITH_MBIM +libmmifacevoice_la_CPPFLAGS += $(MBIM_CFLAGS) +endif + +libmmifacevoice_la_SOURCES = \ + mm-base-call.h \ + mm-base-call.c \ + mm-call-list.h \ + mm-call-list.c \ + mm-iface-modem-voice.h \ + mm-iface-modem-voice.c diff --git a/src/iface-voice/mm-base-call.c b/src/iface-voice/mm-base-call.c new file mode 100644 index 000000000..07b8bf16e --- /dev/null +++ b/src/iface-voice/mm-base-call.c @@ -0,0 +1,1230 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2015 Riccardo Vangelisti <riccardo.vangelisti@sadel.it> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-call.h" +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-voice.h" +#include "mm-base-modem-at.h" +#include "mm-base-modem.h" +#include "mm-log.h" +#include "mm-modem-helpers.h" + +G_DEFINE_TYPE (MMBaseCall, mm_base_call, MM_GDBUS_TYPE_CALL_SKELETON) + +enum { + PROP_0, + PROP_PATH, + PROP_CONNECTION, + PROP_MODEM, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _MMBaseCallPrivate { + /* The connection to the system bus */ + GDBusConnection *connection; + /* The modem which owns this call */ + MMBaseModem *modem; + /* The path where the call object is exported */ + gchar *path; +}; + +/*****************************************************************************/ +/* Start call (DBus call handling) */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GDBusMethodInvocation *invocation; +} HandleStartContext; + +static void +handle_start_context_free (HandleStartContext *ctx) +{ + g_object_unref (ctx->invocation); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static void +handle_start_ready (MMBaseCall *self, + GAsyncResult *res, + HandleStartContext *ctx) +{ + GError *error = NULL; + + if (!MM_BASE_CALL_GET_CLASS (self)->start_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + } else { + /* Transition from Unknown->Dialing */ + if (mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)) == MM_CALL_STATE_UNKNOWN ) { + /* Update state */ + mm_base_call_change_state (self, MM_CALL_STATE_DIALING, MM_CALL_STATE_REASON_OUTGOING_STARTED); + } + mm_gdbus_call_complete_start (MM_GDBUS_CALL (ctx->self), ctx->invocation); + } + + handle_start_context_free (ctx); +} + +static void +handle_start_auth_ready (MMBaseModem *modem, + GAsyncResult *res, + HandleStartContext *ctx) +{ + MMCallState state; + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (modem, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_start_context_free (ctx); + return; + } + + /* We can only start call created by the user */ + state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)); + + if (state != MM_CALL_STATE_UNKNOWN) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "This call was not in unknown state, cannot start it"); + handle_start_context_free (ctx); + return; + } + + /* Check if we do support doing it */ + if (!MM_BASE_CALL_GET_CLASS (ctx->self)->start || + !MM_BASE_CALL_GET_CLASS (ctx->self)->start_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Starting call is not supported by this modem"); + handle_start_context_free (ctx); + return; + } + + MM_BASE_CALL_GET_CLASS (ctx->self)->start (ctx->self, + (GAsyncReadyCallback)handle_start_ready, + ctx); +} + +static gboolean +handle_start (MMBaseCall *self, + GDBusMethodInvocation *invocation) +{ + HandleStartContext *ctx; + + ctx = g_new0 (HandleStartContext, 1); + ctx->self = g_object_ref (self); + ctx->invocation = g_object_ref (invocation); + g_object_get (self, + MM_BASE_CALL_MODEM, &ctx->modem, + NULL); + + mm_base_modem_authorize (ctx->modem, + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_start_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ +/* Accept call (DBus call handling) */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GDBusMethodInvocation *invocation; +} HandleAcceptContext; + +static void +handle_accept_context_free (HandleAcceptContext *ctx) +{ + g_object_unref (ctx->invocation); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static void +handle_accept_ready (MMBaseCall *self, + GAsyncResult *res, + HandleAcceptContext *ctx) +{ + GError *error = NULL; + + if (!MM_BASE_CALL_GET_CLASS (self)->accept_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + } else { + /* Transition from Unknown->Dialing */ + if (mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)) == MM_CALL_STATE_RINGING_IN) { + /* Update state */ + mm_base_call_change_state (self, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED); + } + mm_gdbus_call_complete_accept (MM_GDBUS_CALL (ctx->self), ctx->invocation); + } + + handle_accept_context_free (ctx); +} + +static void +handle_accept_auth_ready (MMBaseModem *modem, + GAsyncResult *res, + HandleAcceptContext *ctx) +{ + MMCallState state; + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (modem, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_accept_context_free (ctx); + return; + } + + state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)); + + /* We can only accept incoming call in ringing state */ + if (state != MM_CALL_STATE_RINGING_IN) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "This call was not ringing, cannot accept "); + handle_accept_context_free (ctx); + return; + } + + /* Check if we do support doing it */ + if (!MM_BASE_CALL_GET_CLASS (ctx->self)->accept || + !MM_BASE_CALL_GET_CLASS (ctx->self)->accept_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Accepting call is not supported by this modem"); + handle_accept_context_free (ctx); + return; + } + + MM_BASE_CALL_GET_CLASS (ctx->self)->accept (ctx->self, + (GAsyncReadyCallback)handle_accept_ready, + ctx); +} + +static gboolean +handle_accept (MMBaseCall *self, + GDBusMethodInvocation *invocation) +{ + HandleAcceptContext *ctx; + + ctx = g_new0 (HandleAcceptContext, 1); + ctx->self = g_object_ref (self); + ctx->invocation = g_object_ref (invocation); + g_object_get (self, + MM_BASE_CALL_MODEM, &ctx->modem, + NULL); + + mm_base_modem_authorize (ctx->modem, + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_accept_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +/* Hangup call (DBus call handling) */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GDBusMethodInvocation *invocation; +} HandleHangupContext; + +static void +handle_hangup_context_free (HandleHangupContext *ctx) +{ + g_object_unref (ctx->invocation); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static void +handle_hangup_ready (MMBaseCall *self, + GAsyncResult *res, + HandleHangupContext *ctx) +{ + GError *error = NULL; + + if (!MM_BASE_CALL_GET_CLASS (self)->hangup_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + } else { + /* Transition from Unknown->Dialing */ + if (mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)) != MM_CALL_STATE_TERMINATED || + mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)) != MM_CALL_STATE_UNKNOWN) { + /* Update state */ + mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED); + } + mm_gdbus_call_complete_hangup (MM_GDBUS_CALL (ctx->self), ctx->invocation); + } + + handle_hangup_context_free (ctx); +} + +static void +handle_hangup_auth_ready (MMBaseModem *modem, + GAsyncResult *res, + HandleHangupContext *ctx) +{ + MMCallState state; + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (modem, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_hangup_context_free (ctx); + return; + } + + state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)); + + /* We can only hangup call in a valid state */ + if (state == MM_CALL_STATE_TERMINATED || state == MM_CALL_STATE_UNKNOWN) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "This call was not active, cannot hangup"); + handle_hangup_context_free (ctx); + return; + } + + /* Check if we do support doing it */ + if (!MM_BASE_CALL_GET_CLASS (ctx->self)->hangup || + !MM_BASE_CALL_GET_CLASS (ctx->self)->hangup_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Hanguping call is not supported by this modem"); + handle_hangup_context_free (ctx); + return; + } + + MM_BASE_CALL_GET_CLASS (ctx->self)->hangup (ctx->self, + (GAsyncReadyCallback)handle_hangup_ready, + ctx); +} + +static gboolean +handle_hangup (MMBaseCall *self, + GDBusMethodInvocation *invocation) +{ + HandleHangupContext *ctx; + + ctx = g_new0 (HandleHangupContext, 1); + ctx->self = g_object_ref (self); + ctx->invocation = g_object_ref (invocation); + g_object_get (self, + MM_BASE_CALL_MODEM, &ctx->modem, + NULL); + + mm_base_modem_authorize (ctx->modem, + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_hangup_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ +/* Send dtmf (DBus call handling) */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GDBusMethodInvocation *invocation; + gchar *dtmf; +} HandleSendDtmfContext; + +static void +handle_send_dtmf_context_free (HandleSendDtmfContext *ctx) +{ + g_object_unref (ctx->invocation); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free(ctx->dtmf); + g_free (ctx); +} + +static void +handle_send_dtmf_ready (MMBaseCall *self, + GAsyncResult *res, + HandleSendDtmfContext *ctx) +{ + GError *error = NULL; + + if (!MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + } else { + mm_gdbus_call_complete_send_dtmf (MM_GDBUS_CALL (ctx->self), ctx->invocation); + } + + handle_send_dtmf_context_free (ctx); +} + +static void +handle_send_dtmf_auth_ready (MMBaseModem *modem, + GAsyncResult *res, + HandleSendDtmfContext *ctx) +{ + MMCallState state; + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (modem, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_send_dtmf_context_free (ctx); + return; + } + + state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)); + + /* Check if we do support doing it */ + if (!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf || + !MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Sending dtmf is not supported by this modem"); + handle_send_dtmf_context_free (ctx); + return; + } + + /* We can only send_dtmf when call is in ACTIVE state */ + if (state != MM_CALL_STATE_ACTIVE ){ + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "This call was not active, cannot send dtmf"); + handle_send_dtmf_context_free (ctx); + return; + } + + MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf (ctx->self, ctx->dtmf, + (GAsyncReadyCallback)handle_send_dtmf_ready, + ctx); +} + +static gboolean +handle_send_dtmf (MMBaseCall *self, + GDBusMethodInvocation *invocation, + const gchar *dtmf) +{ + HandleSendDtmfContext *ctx; + + ctx = g_new0 (HandleSendDtmfContext, 1); + ctx->self = g_object_ref (self); + ctx->invocation = g_object_ref (invocation); + + ctx->dtmf = g_strdup(dtmf); + g_object_get (self, + MM_BASE_CALL_MODEM, &ctx->modem, + NULL); + + mm_base_modem_authorize (ctx->modem, + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_send_dtmf_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +void +mm_base_call_export (MMBaseCall *self) +{ + static guint id = 0; + gchar *path; + + path = g_strdup_printf (MM_DBUS_CALL_PREFIX "/%d", id++); + g_object_set (self, + MM_BASE_CALL_PATH, path, + NULL); + g_free (path); +} + +void +mm_base_call_unexport (MMBaseCall *self) +{ + g_object_set (self, + MM_BASE_CALL_PATH, NULL, + NULL); +} + +/*****************************************************************************/ + +static void +call_dbus_export (MMBaseCall *self) +{ + GError *error = NULL; + + /* Handle method invocations */ + g_signal_connect (self, + "handle-start", + G_CALLBACK (handle_start), + NULL); + g_signal_connect (self, + "handle-accept", + G_CALLBACK (handle_accept), + NULL); + g_signal_connect (self, + "handle-hangup", + G_CALLBACK (handle_hangup), + NULL); + g_signal_connect (self, + "handle-send-dtmf", + G_CALLBACK (handle_send_dtmf), + NULL); + + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self), + self->priv->connection, + self->priv->path, + &error)) { + mm_warn ("couldn't export call at '%s': '%s'", + self->priv->path, + error->message); + g_error_free (error); + } +} + +static void +call_dbus_unexport (MMBaseCall *self) +{ + /* Only unexport if currently exported */ + if (g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (self))) + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self)); +} + +/*****************************************************************************/ + +const gchar * +mm_base_call_get_path (MMBaseCall *self) +{ + return self->priv->path; +} + +void +mm_base_call_change_state(MMBaseCall *self, MMCallState new_state, MMCallStateReason reason) +{ + int old_state = mm_gdbus_call_get_state (MM_GDBUS_CALL (self)); + + g_object_set (self, + "state", new_state, + "state-reason", reason, + NULL); + + mm_gdbus_call_set_state (MM_GDBUS_CALL (self), new_state); + mm_gdbus_call_set_state_reason(MM_GDBUS_CALL (self), reason); + + mm_gdbus_call_emit_state_changed(MM_GDBUS_CALL (self), + old_state, + new_state, + reason); +} + +void mm_base_call_received_dtmf (MMBaseCall *self, gchar *dtmf) +{ + mm_gdbus_call_emit_dtmf_received(MM_GDBUS_CALL (self), dtmf); +} + +/*****************************************************************************/ +/* Start the CALL */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GSimpleAsyncResult *result; +} CallStartContext; + +static void +call_start_context_complete_and_free (CallStartContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +call_start_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +call_start_ready (MMBaseModem *modem, + GAsyncResult *res, + CallStartContext *ctx) +{ + GError *error = NULL; + const gchar *response = NULL; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (error) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { + /* something is wrong, serial timeout could never occurs */ + } + + if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_DIALTONE)) { + /* Update state */ + mm_base_call_change_state(ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_ERROR); + } + + if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_BUSY) || + g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_ANSWER) || + g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_CARRIER) ) + { + /* Update state */ + mm_base_call_change_state(ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_REFUSED_OR_BUSY); + } + + mm_dbg ("Couldn't start call : '%s'", error->message); + g_simple_async_result_take_error (ctx->result, error); + call_start_context_complete_and_free (ctx); + return; + } + + /* check response for error */ + if (response && strlen (response) > 0 ) { + g_set_error (&error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't start the call: " + "Modem response '%s'", response); + /* Update state */ + mm_base_call_change_state(ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_REFUSED_OR_BUSY); + } else { + /* Update state */ + mm_base_call_change_state(ctx->self, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED); + } + + if (error) { + g_simple_async_result_take_error (ctx->result, error); + call_start_context_complete_and_free (ctx); + return; + } + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + call_start_context_complete_and_free (ctx); +} + +static void +call_start (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CallStartContext *ctx; + gchar *cmd; + + /* Setup the context */ + ctx = g_new0 (CallStartContext, 1); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + call_start); + ctx->self = g_object_ref (self); + ctx->modem = g_object_ref (self->priv->modem); + + cmd = g_strdup_printf ("ATD%s;", mm_gdbus_call_get_number (MM_GDBUS_CALL (self)) ); + mm_base_modem_at_command (ctx->modem, + cmd, + 90, + FALSE, + (GAsyncReadyCallback)call_start_ready, + ctx); + + /* Update state */ + mm_base_call_change_state(self, MM_CALL_STATE_RINGING_OUT, MM_CALL_STATE_REASON_OUTGOING_STARTED); + g_free (cmd); +} + +/*****************************************************************************/ + +/* Accept the CALL */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GSimpleAsyncResult *result; +} CallAcceptContext; + +static void +call_accept_context_complete_and_free (CallAcceptContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +call_accept_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +call_accept_ready (MMBaseModem *modem, + GAsyncResult *res, + CallAcceptContext *ctx) +{ + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (error) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { + g_simple_async_result_take_error (ctx->result, error); + call_accept_context_complete_and_free (ctx); + return; + } + + mm_dbg ("Couldn't accept call : '%s'", error->message); + g_error_free (error); + return; + } + + /* check response for error */ + if( response && strlen(response) > 0 ) { + g_set_error (&error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't accept the call: " + "Unhandled response '%s'", response); + + /* Update state */ + mm_base_call_change_state(ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_ERROR); + } else { + + /* Update state */ + mm_base_call_change_state(ctx->self, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED); + } + + if (error) { + g_simple_async_result_take_error (ctx->result, error); + call_accept_context_complete_and_free (ctx); + return; + } + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + call_accept_context_complete_and_free (ctx); +} + +static void +call_accept (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CallAcceptContext *ctx; + gchar *cmd; + + /* Setup the context */ + ctx = g_new0 (CallAcceptContext, 1); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + call_accept); + ctx->self = g_object_ref (self); + ctx->modem = g_object_ref (self->priv->modem); + + cmd = g_strdup_printf ("ATA"); + mm_base_modem_at_command (ctx->modem, + cmd, + 2, + FALSE, + (GAsyncReadyCallback)call_accept_ready, + ctx); + g_free (cmd); +} + +/*****************************************************************************/ + +/* Hangup the CALL */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GSimpleAsyncResult *result; +} CallHangupContext; + +static void +call_hangup_context_complete_and_free (CallHangupContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +call_hangup_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +call_hangup_ready (MMBaseModem *modem, + GAsyncResult *res, + CallHangupContext *ctx) +{ + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (error) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { + g_simple_async_result_take_error (ctx->result, error); + call_hangup_context_complete_and_free (ctx); + return; + } + + mm_dbg ("Couldn't hangup call : '%s'", error->message); + g_error_free (error); + return; + } + + /* Update state */ + mm_base_call_change_state(ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED); + + if (error) { + g_simple_async_result_take_error (ctx->result, error); + call_hangup_context_complete_and_free (ctx); + return; + } + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + call_hangup_context_complete_and_free (ctx); +} + +static void +call_hangup (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CallHangupContext *ctx; + gchar *cmd; + + /* Setup the context */ + ctx = g_new0 (CallHangupContext, 1); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + call_hangup); + ctx->self = g_object_ref (self); + ctx->modem = g_object_ref (self->priv->modem); + + cmd = g_strdup_printf ("+CHUP"); + mm_base_modem_at_command (ctx->modem, + cmd, + 2, + FALSE, + (GAsyncReadyCallback)call_hangup_ready, + ctx); + g_free (cmd); +} + +/*****************************************************************************/ +/* Send DTMF tone to call */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GSimpleAsyncResult *result; +} CallSendDtmfContext; + +static void +call_send_dtmf_context_complete_and_free (CallSendDtmfContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +call_send_dtmf_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +call_send_dtmf_ready (MMBaseModem *modem, + GAsyncResult *res, + CallSendDtmfContext *ctx) +{ + GError *error = NULL; + const gchar *response = NULL; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (error) { + mm_dbg ("Couldn't send_dtmf: '%s'", error->message); + g_simple_async_result_take_error (ctx->result, error); + call_send_dtmf_context_complete_and_free (ctx); + return; + } + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + call_send_dtmf_context_complete_and_free (ctx); +} + +static void +call_send_dtmf (MMBaseCall *self, + const gchar *dtmf, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CallSendDtmfContext *ctx; + gchar *cmd; + + /* Setup the context */ + ctx = g_new0 (CallSendDtmfContext, 1); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + call_send_dtmf); + ctx->self = g_object_ref (self); + ctx->modem = g_object_ref (self->priv->modem); + + cmd = g_strdup_printf ("AT+VTS=%c", dtmf[0]); + mm_base_modem_at_command (ctx->modem, + cmd, + 3, + FALSE, + (GAsyncReadyCallback)call_send_dtmf_ready, + ctx); + + g_free (cmd); +} + +/*****************************************************************************/ +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GSimpleAsyncResult *result; +} CallDeleteContext; + +static void +call_delete (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CallDeleteContext *ctx; + + ctx = g_new0 (CallDeleteContext, 1); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + call_delete); + ctx->self = g_object_ref (self); + ctx->modem = g_object_ref (self->priv->modem); + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +call_delete_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +/*****************************************************************************/ + +gboolean +mm_base_call_delete_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + if (MM_BASE_CALL_GET_CLASS (self)->delete_finish) { + gboolean deleted; + + deleted = MM_BASE_CALL_GET_CLASS (self)->delete_finish (self, res, error); + if (deleted) + /* We do change the state of this call back to UNKNOWN */ + mm_base_call_change_state(self, MM_CALL_STATE_UNKNOWN, MM_CALL_STATE_REASON_UNKNOWN); + + return deleted; + } + + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +void +mm_base_call_delete (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + if (MM_BASE_CALL_GET_CLASS (self)->delete && + MM_BASE_CALL_GET_CLASS (self)->delete_finish) { + MM_BASE_CALL_GET_CLASS (self)->delete (self, callback, user_data); + return; + } + + g_simple_async_report_error_in_idle (G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Deleting call is not supported by this modem"); +} + +/*****************************************************************************/ + +MMBaseCall * +mm_base_call_new (MMBaseModem *modem) +{ + return MM_BASE_CALL (g_object_new (MM_TYPE_BASE_CALL, + MM_BASE_CALL_MODEM, modem, + NULL)); +} + +MMBaseCall * +mm_base_call_new_from_properties (MMBaseModem *modem, + MMCallProperties *properties, + GError **error) +{ + MMBaseCall *self; + const gchar *number; + MMCallDirection direction; + + g_assert (MM_IS_IFACE_MODEM_VOICE (modem)); + + number = mm_call_properties_get_number (properties); + direction = mm_call_properties_get_direction(properties); + + /* Don't create CALL from properties if either number is missing */ + if ( !number ) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Cannot create call: mandatory parameter 'number' is missing"); + return NULL; + } + + /* if no direction is specified force to outgoing */ + if(direction == MM_CALL_DIRECTION_UNKNOWN ) { + direction = MM_CALL_DIRECTION_OUTGOING; + } + + /* Create a call object as defined by the interface */ + self = mm_iface_modem_voice_create_call (MM_IFACE_MODEM_VOICE (modem)); + g_object_set (self, + "state", mm_call_properties_get_state(properties), + "state-reason", mm_call_properties_get_state_reason(properties), + "direction", direction, + "number", number, + NULL); + + /* Only export once properly created */ + mm_base_call_export (self); + + return self; +} + +/*****************************************************************************/ + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMBaseCall *self = MM_BASE_CALL (object); + + switch (prop_id) { + case PROP_PATH: + g_free (self->priv->path); + self->priv->path = g_value_dup_string (value); + + /* Export when we get a DBus connection AND we have a path */ + if (!self->priv->path) + call_dbus_unexport (self); + else if (self->priv->connection) + call_dbus_export (self); + break; + case PROP_CONNECTION: + g_clear_object (&self->priv->connection); + self->priv->connection = g_value_dup_object (value); + + /* Export when we get a DBus connection AND we have a path */ + if (!self->priv->connection) + call_dbus_unexport (self); + else if (self->priv->path) + call_dbus_export (self); + break; + case PROP_MODEM: + g_clear_object (&self->priv->modem); + self->priv->modem = g_value_dup_object (value); + if (self->priv->modem) { + /* Bind the modem's connection (which is set when it is exported, + * and unset when unexported) to the call's connection */ + g_object_bind_property (self->priv->modem, MM_BASE_MODEM_CONNECTION, + self, MM_BASE_CALL_CONNECTION, + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MMBaseCall *self = MM_BASE_CALL (object); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string (value, self->priv->path); + break; + case PROP_CONNECTION: + g_value_set_object (value, self->priv->connection); + break; + case PROP_MODEM: + g_value_set_object (value, self->priv->modem); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +mm_base_call_init (MMBaseCall *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BASE_CALL, MMBaseCallPrivate); + /* Defaults */ +} + +static void +finalize (GObject *object) +{ + MMBaseCall *self = MM_BASE_CALL (object); + + g_free (self->priv->path); + + G_OBJECT_CLASS (mm_base_call_parent_class)->finalize (object); +} + +static void +dispose (GObject *object) +{ + MMBaseCall *self = MM_BASE_CALL (object); + + if (self->priv->connection) { + /* If we arrived here with a valid connection, make sure we unexport + * the object */ + call_dbus_unexport (self); + g_clear_object (&self->priv->connection); + } + + g_clear_object (&self->priv->modem); + + G_OBJECT_CLASS (mm_base_call_parent_class)->dispose (object); +} + +static void +mm_base_call_class_init (MMBaseCallClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBaseCallPrivate)); + + /* Virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + object_class->dispose = dispose; + + klass->start = call_start; + klass->start_finish = call_start_finish; + klass->accept = call_accept; + klass->accept_finish = call_accept_finish; + klass->hangup = call_hangup; + klass->hangup_finish = call_hangup_finish; + klass->delete = call_delete; + klass->delete_finish = call_delete_finish; + klass->send_dtmf = call_send_dtmf; + klass->send_dtmf_finish = call_send_dtmf_finish; + + + properties[PROP_CONNECTION] = + g_param_spec_object (MM_BASE_CALL_CONNECTION, + "Connection", + "GDBus connection to the system bus.", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]); + + properties[PROP_PATH] = + g_param_spec_string (MM_BASE_CALL_PATH, + "Path", + "DBus path of the call", + NULL, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]); + + properties[PROP_MODEM] = + g_param_spec_object (MM_BASE_CALL_MODEM, + "Modem", + "The Modem which owns this call", + MM_TYPE_BASE_MODEM, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]); +} diff --git a/src/iface-voice/mm-base-call.h b/src/iface-voice/mm-base-call.h new file mode 100644 index 000000000..ff57fb594 --- /dev/null +++ b/src/iface-voice/mm-base-call.h @@ -0,0 +1,116 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2015 Riccardo Vangelisti <riccardo.vangelisti@sadel.it> + */ + +#ifndef MM_BASE_CALL_H +#define MM_BASE_CALL_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-modem.h" + +#define MM_TYPE_BASE_CALL (mm_base_call_get_type ()) +#define MM_BASE_CALL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BASE_CALL, MMBaseCall)) +#define MM_BASE_CALL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BASE_CALL, MMBaseCallClass)) +#define MM_IS_BASE_CALL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BASE_CALL)) +#define MM_IS_BASE_CALL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BASE_CALL)) +#define MM_BASE_CALL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BASE_CALL, MMBaseCallClass)) + +typedef struct _MMBaseCall MMBaseCall; +typedef struct _MMBaseCallClass MMBaseCallClass; +typedef struct _MMBaseCallPrivate MMBaseCallPrivate; + +#define MM_BASE_CALL_PATH "call-path" +#define MM_BASE_CALL_CONNECTION "call-connection" +#define MM_BASE_CALL_MODEM "call-modem" + +struct _MMBaseCall { + MmGdbusCallSkeleton parent; + MMBaseCallPrivate *priv; +}; + +struct _MMBaseCallClass { + MmGdbusCallSkeletonClass parent; + + /* Start the call */ + void (* start) (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* start_finish) (MMBaseCall *self, + GAsyncResult *res, + GError **error); + + /* Accept the call */ + void (* accept) (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* accept_finish) (MMBaseCall *self, + GAsyncResult *res, + GError **error); + + /* Hangup the call */ + void (* hangup) (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* hangup_finish) (MMBaseCall *self, + GAsyncResult *res, + GError **error); + + /* Send a DTMF tone */ + void (* send_dtmf) (MMBaseCall *self, + const gchar *dtmf, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* send_dtmf_finish) (MMBaseCall *self, + GAsyncResult *res, + GError **error); + + /* Delete the call */ + void (* delete) (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* delete_finish) (MMBaseCall *self, + GAsyncResult *res, + GError **error); +}; + +GType mm_base_call_get_type (void); + +/* This one can be overriden by plugins */ +MMBaseCall *mm_base_call_new (MMBaseModem *modem); +MMBaseCall *mm_base_call_new_from_properties (MMBaseModem *modem, + MMCallProperties *properties, + GError **error); + +void mm_base_call_export (MMBaseCall *self); +void mm_base_call_unexport (MMBaseCall *self); +const gchar *mm_base_call_get_path (MMBaseCall *self); +void mm_base_call_change_state (MMBaseCall *self, + MMCallState new_state, + MMCallStateReason reason); +void mm_base_call_received_dtmf (MMBaseCall *self, + gchar *dtmf); + +void mm_base_call_delete (MMBaseCall *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_base_call_delete_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_BASE_CALL_H */ diff --git a/src/iface-voice/mm-call-list.c b/src/iface-voice/mm-call-list.c new file mode 100644 index 000000000..056e2932c --- /dev/null +++ b/src/iface-voice/mm-call-list.c @@ -0,0 +1,441 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it> + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-iface-modem-messaging.h" +#include "mm-call-list.h" +#include "mm-base-call.h" +#include "mm-log.h" + +G_DEFINE_TYPE (MMCallList, mm_call_list, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_MODEM, + PROP_LAST +}; +static GParamSpec *properties[PROP_LAST]; + +enum { + SIGNAL_CALL_ADDED, + SIGNAL_CALL_DELETED, + SIGNAL_LAST +}; +static guint signals[SIGNAL_LAST]; + +struct _MMCallListPrivate { + /* The owner modem */ + MMBaseModem *modem; + /* List of call objects */ + GList *list; +}; + +/*****************************************************************************/ + +guint +mm_call_list_get_count (MMCallList *self) +{ + return g_list_length (self->priv->list); +} + +GStrv +mm_call_list_get_paths (MMCallList *self) +{ + GStrv path_list = NULL; + GList *l; + guint i; + + path_list = g_new0 (gchar *, + 1 + g_list_length (self->priv->list)); + + for (i = 0, l = self->priv->list; l; l = g_list_next (l)) { + const gchar *path; + + /* Don't try to add NULL paths (not yet exported CALL objects) */ + path = mm_base_call_get_path (MM_BASE_CALL (l->data)); + if (path) + path_list[i++] = g_strdup (path); + } + + return path_list; +} + +/*****************************************************************************/ + +MMBaseCall* mm_call_list_get_new_incoming(MMCallList *self) +{ + MMBaseCall *call = NULL; + GList *l; + guint i; + + for (i = 0, l = self->priv->list; l && !call; l = g_list_next (l)) { + + MMCallState state; + MMCallStateReason reason; + MMCallDirection direct; + + g_object_get (MM_BASE_CALL (l->data), + "state" , &state, + "state-reason", &reason, + "direction" , &direct, + NULL); + + if( direct == MM_CALL_DIRECTION_INCOMING && + state == MM_CALL_STATE_RINGING_IN && + reason == MM_CALL_STATE_REASON_INCOMING_NEW ) { + + call = MM_BASE_CALL (l->data); + break; + } + } + + return call; +} + +MMBaseCall* mm_call_list_get_first_ringing_call(MMCallList *self) +{ + MMBaseCall *call = NULL; + GList *l; + guint i; + + for (i = 0, l = self->priv->list; l && !call; l = g_list_next (l)) { + + MMCallState state; + + g_object_get (MM_BASE_CALL (l->data), + "state" , &state, + NULL); + + if( state == MM_CALL_STATE_RINGING_IN || + state == MM_CALL_STATE_RINGING_OUT ) { + + call = MM_BASE_CALL (l->data); + break; + } + } + + return call; +} + +MMBaseCall* mm_call_list_get_first_outgoing_dialing_call(MMCallList *self) +{ + MMBaseCall *call = NULL; + GList *l; + guint i; + + for (i = 0, l = self->priv->list; l && !call; l = g_list_next (l)) { + + MMCallState state; + MMCallDirection direct; + + g_object_get (MM_BASE_CALL (l->data), + "state" , &state, + "direction" , &direct, + NULL); + + if( direct == MM_CALL_DIRECTION_OUTGOING && + state == MM_CALL_STATE_DIALING ) { + + call = MM_BASE_CALL (l->data); + break; + } + } + + return call; +} + +MMBaseCall* mm_call_list_get_first_non_terminated_call(MMCallList *self) +{ + MMBaseCall *call = NULL; + GList *l; + guint i; + + for (i = 0, l = self->priv->list; l && !call; l = g_list_next (l)) { + + MMCallState state; + + g_object_get (MM_BASE_CALL (l->data), + "state" , &state, + NULL); + + if( state != MM_CALL_STATE_TERMINATED ) { + call = MM_BASE_CALL (l->data); + break; + } + } + + return call; +} + +gboolean mm_call_list_send_dtmf_to_active_calls(MMCallList *self, gchar *dtmf) +{ + gboolean signaled = FALSE; + GList *l; + guint i; + + for (i = 0, l = self->priv->list; l; l = g_list_next (l)) { + + MMCallState state; + + g_object_get (MM_BASE_CALL (l->data), + "state" , &state, + NULL); + + if( state == MM_CALL_STATE_ACTIVE ) { + signaled = TRUE; + mm_base_call_received_dtmf(MM_BASE_CALL (l->data), dtmf); + } + } + + return signaled; +} + +/*****************************************************************************/ + +typedef struct { + MMCallList *self; + GSimpleAsyncResult *result; + gchar *path; +} DeleteCallContext; + +static void +delete_call_context_complete_and_free (DeleteCallContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_free (ctx->path); + g_free (ctx); +} + +gboolean +mm_call_list_delete_call_finish (MMCallList *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static guint +cmp_call_by_path (MMBaseCall *call, + const gchar *path) +{ + return g_strcmp0 (mm_base_call_get_path (call), path); +} + +static void +delete_ready (MMBaseCall *call, + GAsyncResult *res, + DeleteCallContext *ctx) +{ + GError *error = NULL; + GList *l; + + if (!mm_base_call_delete_finish (call, res, &error)) { + /* We report the error */ + g_simple_async_result_take_error (ctx->result, error); + delete_call_context_complete_and_free (ctx); + return; + } + + /* The CALL was properly deleted, we now remove it from our list */ + l = g_list_find_custom (ctx->self->priv->list, + ctx->path, + (GCompareFunc)cmp_call_by_path); + if (l) { + g_object_unref (MM_BASE_CALL (l->data)); + ctx->self->priv->list = g_list_delete_link (ctx->self->priv->list, l); + } + + /* We don't need to unref the CALL any more, but we can use the + * reference we got in the method, which is the one kept alive + * during the async operation. */ + mm_base_call_unexport (call); + + g_signal_emit (ctx->self, + signals[SIGNAL_CALL_DELETED], 0, + ctx->path); + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + delete_call_context_complete_and_free (ctx); +} + +void +mm_call_list_delete_call (MMCallList *self, + const gchar *call_path, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DeleteCallContext *ctx; + GList *l; + + l = g_list_find_custom (self->priv->list, + (gpointer)call_path, + (GCompareFunc)cmp_call_by_path); + if (!l) { + g_simple_async_report_error_in_idle (G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No CALL found with path '%s'", + call_path); + return; + } + + /* Delete all CALL parts */ + ctx = g_new0 (DeleteCallContext, 1); + ctx->self = g_object_ref (self); + ctx->path = g_strdup (call_path); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + mm_call_list_delete_call); + + mm_base_call_delete (MM_BASE_CALL (l->data), + (GAsyncReadyCallback)delete_ready, + ctx); +} + +/*****************************************************************************/ + +void +mm_call_list_add_call (MMCallList *self, + MMBaseCall *call) +{ + self->priv->list = g_list_prepend (self->priv->list, g_object_ref (call)); + g_signal_emit (self, signals[SIGNAL_CALL_ADDED], 0, + mm_base_call_get_path (call), + FALSE); +} + +/*****************************************************************************/ + +MMCallList * +mm_call_list_new (MMBaseModem *modem) +{ + /* Create the object */ + return g_object_new (MM_TYPE_CALL_LIST, + MM_CALL_LIST_MODEM, modem, + NULL); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMCallList *self = MM_CALL_LIST (object); + + switch (prop_id) { + case PROP_MODEM: + g_clear_object (&self->priv->modem); + self->priv->modem = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MMCallList *self = MM_CALL_LIST (object); + + switch (prop_id) { + case PROP_MODEM: + g_value_set_object (value, self->priv->modem); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +mm_call_list_init (MMCallList *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_CALL_LIST, + MMCallListPrivate); +} + +static void +dispose (GObject *object) +{ + MMCallList *self = MM_CALL_LIST (object); + + g_clear_object (&self->priv->modem); + g_list_free_full (self->priv->list, (GDestroyNotify)g_object_unref); + + G_OBJECT_CLASS (mm_call_list_parent_class)->dispose (object); +} + +static void +mm_call_list_class_init (MMCallListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMCallListPrivate)); + + /* Virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + /* Properties */ + properties[PROP_MODEM] = + g_param_spec_object (MM_CALL_LIST_MODEM, + "Modem", + "The Modem which owns this CALL list", + MM_TYPE_BASE_MODEM, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]); + + /* Signals */ + signals[SIGNAL_CALL_ADDED] = + g_signal_new (MM_CALL_ADDED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMCallListClass, call_added), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[SIGNAL_CALL_DELETED] = + g_signal_new (MM_CALL_DELETED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMCallListClass, call_deleted), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 1, G_TYPE_STRING); +} diff --git a/src/iface-voice/mm-call-list.h b/src/iface-voice/mm-call-list.h new file mode 100644 index 000000000..583516662 --- /dev/null +++ b/src/iface-voice/mm-call-list.h @@ -0,0 +1,82 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it> + */ + +#ifndef MM_CALL_LIST_H +#define MM_CALL_LIST_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-modem.h" +#include "mm-base-call.h" + +#define MM_TYPE_CALL_LIST (mm_call_list_get_type ()) +#define MM_CALL_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CALL_LIST, MMCallList)) +#define MM_CALL_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_CALL_LIST, MMCallListClass)) +#define MM_IS_CALL_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CALL_LIST)) +#define MM_IS_CALL_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CALL_LIST)) +#define MM_CALL_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CALL_LIST, MMCallListClass)) + +typedef struct _MMCallList MMCallList; +typedef struct _MMCallListClass MMCallListClass; +typedef struct _MMCallListPrivate MMCallListPrivate; + +#define MM_CALL_LIST_MODEM "call-list-modem" + +#define MM_CALL_ADDED "call-added" +#define MM_CALL_DELETED "call-deleted" + +struct _MMCallList { + GObject parent; + MMCallListPrivate *priv; +}; + +struct _MMCallListClass { + GObjectClass parent; + + /* Signals */ + void (*call_added) (MMCallList *self, + const gchar *call_path, + gboolean received); + void (*call_deleted) (MMCallList *self, + const gchar *call_path); +}; + +GType mm_call_list_get_type (void); + +MMCallList *mm_call_list_new (MMBaseModem *modem); + +GStrv mm_call_list_get_paths (MMCallList *self); +guint mm_call_list_get_count (MMCallList *self); + +void mm_call_list_add_call (MMCallList *self, + MMBaseCall *call); + +void mm_call_list_delete_call (MMCallList *self, + const gchar *call_path, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_call_list_delete_call_finish (MMCallList *self, + GAsyncResult *res, + GError **error); + +MMBaseCall* mm_call_list_get_new_incoming (MMCallList *self); +MMBaseCall* mm_call_list_get_first_ringing_call (MMCallList *self); +MMBaseCall* mm_call_list_get_first_outgoing_dialing_call(MMCallList *self); +MMBaseCall* mm_call_list_get_first_non_terminated_call (MMCallList *self); +gboolean mm_call_list_send_dtmf_to_active_calls (MMCallList *self, + gchar *dtmf); + +#endif /* MM_CALL_LIST_H */ diff --git a/src/iface-voice/mm-iface-modem-voice.c b/src/iface-voice/mm-iface-modem-voice.c new file mode 100644 index 000000000..ca65238b7 --- /dev/null +++ b/src/iface-voice/mm-iface-modem-voice.c @@ -0,0 +1,1125 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2015 - Marco Bascetta <marco.bascetta@sadel.it> + */ + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-iface-modem.h" +#include "mm-iface-modem-voice.h" +#include "mm-call-list.h" +#include "mm-log.h" + +#define SUPPORT_CHECKED_TAG "voice-support-checked-tag" +#define SUPPORTED_TAG "voice-supported-tag" + +static GQuark support_checked_quark; +static GQuark supported_quark; + +/*****************************************************************************/ + +void +mm_iface_modem_voice_bind_simple_status (MMIfaceModemVoice *self, + MMSimpleStatus *status) +{ +} + +/*****************************************************************************/ + +MMBaseCall * +mm_iface_modem_voice_create_call (MMIfaceModemVoice *self) +{ + g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call != NULL); + + return MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self); +} + +MMBaseCall * +mm_iface_modem_voice_create_incoming_call (MMIfaceModemVoice *self) +{ + MMBaseCall *call = NULL; + MMCallList *list = NULL; + + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + if (list) { + call = mm_call_list_get_new_incoming (list); + + if (!call) { + mm_dbg("Incoming call does not exist; create it"); + + call = mm_base_call_new (MM_BASE_MODEM (self)); + g_object_set (call, + "state", MM_CALL_STATE_RINGING_IN, + "state-reason", MM_CALL_STATE_REASON_INCOMING_NEW, + "direction", MM_CALL_DIRECTION_INCOMING, + NULL); + + /* Only export once properly created */ + mm_base_call_export (call); + mm_dbg ("New call exported to DBus"); + + mm_call_list_add_call (list, call); + mm_dbg ("Call added to the list"); + } + + g_object_unref (list); + } + + return call; +} + +gboolean +mm_iface_modem_voice_update_incoming_call_number (MMIfaceModemVoice *self, + gchar *number, + guint type, + guint validity) +{ + gboolean updated = FALSE; + MMBaseCall *call = NULL; + MMCallList *list = NULL; + + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + if (list) { + call = mm_call_list_get_new_incoming (list); + + if (call) { + g_object_set (call, "number", number, NULL); + mm_gdbus_call_set_number (MM_GDBUS_CALL (call), number); + + /* + * TODO: Maybe also this parameters should be used: + * - type + * - validity + */ + + updated = TRUE; + } else { + mm_dbg ("Incoming call does not exist yet"); + } + } + + return updated; +} + +gboolean +mm_iface_modem_voice_call_dialing_to_ringing (MMIfaceModemVoice *self) +{ + gboolean updated = FALSE; + MMBaseCall *call = NULL; + MMCallList *list = NULL; + + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + if (list) { + call = mm_call_list_get_first_outgoing_dialing_call (list); + + if (call) { + mm_base_call_change_state (call, MM_CALL_STATE_RINGING_OUT, MM_CALL_STATE_REASON_OUTGOING_STARTED); + updated = TRUE; + } else { + mm_dbg ("Outgoing dialing call does not exist"); + } + } + + return updated; +} + +gboolean +mm_iface_modem_voice_call_ringing_to_active (MMIfaceModemVoice *self) +{ + gboolean updated = FALSE; + MMBaseCall *call = NULL; + MMCallList *list = NULL; + + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + if (list) { + call = mm_call_list_get_first_ringing_call (list); + + if (call) { + mm_base_call_change_state (call, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED); + updated = TRUE; + } else { + mm_dbg ("Ringing call does not exist"); + } + } + + return updated; +} + +gboolean +mm_iface_modem_voice_network_hangup (MMIfaceModemVoice *self) +{ + gboolean updated = FALSE; + MMBaseCall *call = NULL; + MMCallList *list = NULL; + + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + if (list) { + call = mm_call_list_get_first_non_terminated_call (list); + + if (call) { + mm_base_call_change_state (call, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED); + updated = TRUE; + } else { + mm_dbg ("No call to hangup"); + } + } + + return updated; +} + +gboolean +mm_iface_modem_voice_received_dtmf (MMIfaceModemVoice *self, + gchar *dtmf) +{ + gboolean updated = FALSE; + MMCallList *list = NULL; + + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + if (list) { + updated = mm_call_list_send_dtmf_to_active_calls (list, dtmf); + } + + return updated; +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemVoice *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemVoice *self; + gchar *path; +} HandleDeleteContext; + +static void +handle_delete_context_free (HandleDeleteContext *ctx) +{ + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_free (ctx->path); + g_free (ctx); +} + +static void +handle_delete_ready (MMCallList *list, + GAsyncResult *res, + HandleDeleteContext *ctx) +{ + GError *error = NULL; + + if (!mm_call_list_delete_call_finish (list, res, &error)) + g_dbus_method_invocation_take_error (ctx->invocation, error); + else + mm_gdbus_modem_voice_complete_delete_call (ctx->skeleton, ctx->invocation); + + handle_delete_context_free (ctx); +} + +static void +handle_delete_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleDeleteContext *ctx) +{ + MMModemState modem_state = MM_MODEM_STATE_UNKNOWN; + MMCallList *list = NULL; + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_delete_context_free (ctx); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_STATE, &modem_state, + NULL); + + if (modem_state < MM_MODEM_STATE_ENABLED) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_WRONG_STATE, + "Cannot delete CALL: device not yet enabled"); + handle_delete_context_free (ctx); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_WRONG_STATE, + "Cannot delete CALL: missing CALL list"); + handle_delete_context_free (ctx); + return; + } + + mm_call_list_delete_call (list, + ctx->path, + (GAsyncReadyCallback)handle_delete_ready, + ctx); + g_object_unref (list); +} + +static gboolean +handle_delete (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + const gchar *path, + MMIfaceModemVoice *self) +{ + HandleDeleteContext *ctx; + + ctx = g_new (HandleDeleteContext, 1); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + ctx->path = g_strdup (path); + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_delete_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemVoice *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemVoice *self; + GVariant *dictionary; +} HandleCreateContext; + +static void +handle_create_context_free (HandleCreateContext *ctx) +{ + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_variant_unref (ctx->dictionary); + g_free (ctx); +} + +static void +handle_create_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleCreateContext *ctx) +{ + MMModemState modem_state = MM_MODEM_STATE_UNKNOWN; + MMCallList *list = NULL; + GError *error = NULL; + MMCallProperties *properties; + MMBaseCall *call; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_create_context_free (ctx); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_STATE, &modem_state, + NULL); + + if (modem_state < MM_MODEM_STATE_ENABLED) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_WRONG_STATE, + "Cannot create CALL: device not yet enabled"); + handle_create_context_free (ctx); + return; + } + + /* Parse input properties */ + properties = mm_call_properties_new_from_dictionary (ctx->dictionary, &error); + if (!properties) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_create_context_free (ctx); + return; + } + + call = mm_base_call_new_from_properties (MM_BASE_MODEM (self), + properties, + &error); + if (!call) { + g_object_unref (properties); + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_create_context_free (ctx); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + g_object_unref (properties); + g_object_unref (call); + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_WRONG_STATE, + "Cannot create CALL: missing CALL list"); + handle_create_context_free (ctx); + return; + } + + /* Add it to the list */ + mm_call_list_add_call (list, call); + + /* Complete the DBus call */ + mm_gdbus_modem_voice_complete_create_call (ctx->skeleton, + ctx->invocation, + mm_base_call_get_path (call)); + g_object_unref (call); + + g_object_unref (properties); + g_object_unref (list); + + handle_create_context_free (ctx); +} + +static gboolean +handle_create (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + GVariant *dictionary, + MMIfaceModemVoice *self) +{ + HandleCreateContext *ctx; + + ctx = g_new (HandleCreateContext, 1); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + ctx->dictionary = g_variant_ref (dictionary); + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_create_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +handle_list (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + MMIfaceModemVoice *self) +{ + GStrv paths; + MMCallList *list = NULL; + MMModemState modem_state; + + modem_state = MM_MODEM_STATE_UNKNOWN; + g_object_get (self, + MM_IFACE_MODEM_STATE, &modem_state, + NULL); + + if (modem_state < MM_MODEM_STATE_ENABLED) { + g_dbus_method_invocation_return_error (invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_WRONG_STATE, + "Cannot list CALL messages: " + "device not yet enabled"); + return TRUE; + } + + g_object_get (self, + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + g_dbus_method_invocation_return_error (invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_WRONG_STATE, + "Cannot list CALL: missing CALL list"); + return TRUE; + } + + paths = mm_call_list_get_paths (list); + mm_gdbus_modem_voice_complete_list_calls (skeleton, + invocation, + (const gchar *const *)paths); + g_strfreev (paths); + g_object_unref (list); + return TRUE; +} + +/*****************************************************************************/ + +static void +update_message_list (MmGdbusModemVoice *skeleton, + MMCallList *list) +{ + gchar **paths; + + paths = mm_call_list_get_paths (list); + mm_gdbus_modem_voice_set_calls (skeleton, (const gchar *const *)paths); + g_strfreev (paths); +} + +static void +call_added (MMCallList *list, + const gchar *call_path, + MmGdbusModemVoice *skeleton) +{ + mm_dbg ("Added CALL at '%s'", call_path); + update_message_list (skeleton, list); + mm_gdbus_modem_voice_emit_call_added (skeleton, call_path); +} + +static void +call_deleted (MMCallList *list, + const gchar *call_path, + MmGdbusModemVoice *skeleton) +{ + mm_dbg ("Deleted CALL at '%s'", call_path); + update_message_list (skeleton, list); + mm_gdbus_modem_voice_emit_call_deleted (skeleton, call_path); +} + +/*****************************************************************************/ + +typedef struct _DisablingContext DisablingContext; +static void interface_disabling_step (DisablingContext *ctx); + +typedef enum { + DISABLING_STEP_FIRST, + DISABLING_STEP_DISABLE_UNSOLICITED_EVENTS, + DISABLING_STEP_CLEANUP_UNSOLICITED_EVENTS, + DISABLING_STEP_LAST +} DisablingStep; + +struct _DisablingContext { + MMIfaceModemVoice *self; + DisablingStep step; + GSimpleAsyncResult *result; + MmGdbusModemVoice *skeleton; +}; + +static void +disabling_context_complete_and_free (DisablingContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->self); + g_object_unref (ctx->result); + if (ctx->skeleton) + g_object_unref (ctx->skeleton); + g_free (ctx); +} + +gboolean +mm_iface_modem_voice_disable_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +disable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + DisablingContext *ctx) +{ + GError *error = NULL; + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->disable_unsolicited_events_finish (self, res, &error); + if (error) { + g_simple_async_result_take_error (ctx->result, error); + disabling_context_complete_and_free (ctx); + return; + } + + /* Go on to next step */ + ctx->step++; + interface_disabling_step (ctx); +} + +static void +cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + DisablingContext *ctx) +{ + GError *error = NULL; + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_unsolicited_events_finish (self, res, &error); + if (error) { + g_simple_async_result_take_error (ctx->result, error); + disabling_context_complete_and_free (ctx); + return; + } + + /* Go on to next step */ + ctx->step++; + interface_disabling_step (ctx); +} + +static void +interface_disabling_step (DisablingContext *ctx) +{ + switch (ctx->step) { + case DISABLING_STEP_FIRST: + /* Fall down to next step */ + ctx->step++; + + case DISABLING_STEP_DISABLE_UNSOLICITED_EVENTS: + /* Allow cleaning up unsolicited events */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->disable_unsolicited_events && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->disable_unsolicited_events_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->disable_unsolicited_events ( + ctx->self, + (GAsyncReadyCallback)disable_unsolicited_events_ready, + ctx); + return; + } + /* Fall down to next step */ + ctx->step++; + + case DISABLING_STEP_CLEANUP_UNSOLICITED_EVENTS: + /* Allow cleaning up unsolicited events */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->cleanup_unsolicited_events && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->cleanup_unsolicited_events_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->cleanup_unsolicited_events ( + ctx->self, + (GAsyncReadyCallback)cleanup_unsolicited_events_ready, + ctx); + return; + } + /* Fall down to next step */ + ctx->step++; + + case DISABLING_STEP_LAST: + /* Clear CALL list */ + g_object_set (ctx->self, + MM_IFACE_MODEM_VOICE_CALL_LIST, NULL, + NULL); + + /* We are done without errors! */ + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + disabling_context_complete_and_free (ctx); + return; + } + + g_assert_not_reached (); +} + +void +mm_iface_modem_voice_disable (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DisablingContext *ctx; + + ctx = g_new0 (DisablingContext, 1); + ctx->self = g_object_ref (self); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + mm_iface_modem_voice_disable); + ctx->step = DISABLING_STEP_FIRST; + g_object_get (ctx->self, + MM_IFACE_MODEM_VOICE_DBUS_SKELETON, &ctx->skeleton, + NULL); + if (!ctx->skeleton) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get interface skeleton"); + disabling_context_complete_and_free (ctx); + return; + } + + interface_disabling_step (ctx); +} + +/*****************************************************************************/ + +typedef struct _EnablingContext EnablingContext; +static void interface_enabling_step (EnablingContext *ctx); + +typedef enum { + ENABLING_STEP_FIRST, + ENABLING_STEP_SETUP_UNSOLICITED_EVENTS, + ENABLING_STEP_ENABLE_UNSOLICITED_EVENTS, + ENABLING_STEP_LAST +} EnablingStep; + +struct _EnablingContext { + MMIfaceModemVoice *self; + EnablingStep step; + GSimpleAsyncResult *result; + GCancellable *cancellable; + MmGdbusModemVoice *skeleton; + guint mem1_storage_index; +}; + +static void +enabling_context_complete_and_free (EnablingContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->self); + g_object_unref (ctx->result); + g_object_unref (ctx->cancellable); + if (ctx->skeleton) + g_object_unref (ctx->skeleton); + g_free (ctx); +} + +static gboolean +enabling_context_complete_and_free_if_cancelled (EnablingContext *ctx) +{ + if (!g_cancellable_is_cancelled (ctx->cancellable)) + return FALSE; + + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_CANCELLED, + "Interface enabling cancelled"); + enabling_context_complete_and_free (ctx); + return TRUE; +} + +gboolean +mm_iface_modem_voice_enable_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +setup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + EnablingContext *ctx) +{ + GError *error = NULL; + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_unsolicited_events_finish (self, res, &error); + if (error) { + g_simple_async_result_take_error (ctx->result, error); + enabling_context_complete_and_free (ctx); + return; + } + + /* Go on to next step */ + ctx->step++; + interface_enabling_step (ctx); +} + +static void +enable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + EnablingContext *ctx) +{ + GError *error = NULL; + + /* Not critical! */ + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->enable_unsolicited_events_finish (self, res, &error)) { + mm_dbg ("Couldn't enable unsolicited events: '%s'", error->message); + g_error_free (error); + } + + /* Go on with next step */ + ctx->step++; + interface_enabling_step (ctx); +} + +static void +interface_enabling_step (EnablingContext *ctx) +{ + /* Don't run new steps if we're cancelled */ + if (enabling_context_complete_and_free_if_cancelled (ctx)) + return; + + switch (ctx->step) { + case ENABLING_STEP_FIRST: { + MMCallList *list; + + list = mm_call_list_new (MM_BASE_MODEM (ctx->self)); + g_object_set (ctx->self, + MM_IFACE_MODEM_VOICE_CALL_LIST, list, + NULL); + + /* Connect to list's signals */ + g_signal_connect (list, + MM_CALL_ADDED, + G_CALLBACK (call_added), + ctx->skeleton); + g_signal_connect (list, + MM_CALL_DELETED, + G_CALLBACK (call_deleted), + ctx->skeleton); + + g_object_unref (list); + + /* Fall down to next step */ + ctx->step++; + } + + case ENABLING_STEP_SETUP_UNSOLICITED_EVENTS: + /* Allow setting up unsolicited events */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->setup_unsolicited_events && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->setup_unsolicited_events_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->setup_unsolicited_events ( + ctx->self, + (GAsyncReadyCallback)setup_unsolicited_events_ready, + ctx); + return; + } + /* Fall down to next step */ + ctx->step++; + + case ENABLING_STEP_ENABLE_UNSOLICITED_EVENTS: + /* Allow setting up unsolicited events */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->enable_unsolicited_events && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->enable_unsolicited_events_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->enable_unsolicited_events ( + ctx->self, + (GAsyncReadyCallback)enable_unsolicited_events_ready, + ctx); + return; + } + /* Fall down to next step */ + ctx->step++; + + case ENABLING_STEP_LAST: + /* We are done without errors! */ + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + enabling_context_complete_and_free (ctx); + return; + } + + g_assert_not_reached (); +} + +void +mm_iface_modem_voice_enable (MMIfaceModemVoice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnablingContext *ctx; + + ctx = g_new0 (EnablingContext, 1); + ctx->self = g_object_ref (self); + ctx->cancellable = g_object_ref (cancellable); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + mm_iface_modem_voice_enable); + ctx->step = ENABLING_STEP_FIRST; + g_object_get (ctx->self, + MM_IFACE_MODEM_VOICE_DBUS_SKELETON, &ctx->skeleton, + NULL); + if (!ctx->skeleton) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get interface skeleton"); + enabling_context_complete_and_free (ctx); + return; + } + + interface_enabling_step (ctx); +} + +/*****************************************************************************/ + +typedef struct _InitializationContext InitializationContext; +static void interface_initialization_step (InitializationContext *ctx); + +typedef enum { + INITIALIZATION_STEP_FIRST, + INITIALIZATION_STEP_CHECK_SUPPORT, + INITIALIZATION_STEP_FAIL_IF_UNSUPPORTED, + INITIALIZATION_STEP_LAST +} InitializationStep; + +struct _InitializationContext { + MMIfaceModemVoice *self; + MmGdbusModemVoice *skeleton; + GCancellable *cancellable; + GSimpleAsyncResult *result; + InitializationStep step; +}; + +static void +initialization_context_complete_and_free (InitializationContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->self); + g_object_unref (ctx->result); + g_object_unref (ctx->cancellable); + g_object_unref (ctx->skeleton); + g_free (ctx); +} + +static gboolean +initialization_context_complete_and_free_if_cancelled (InitializationContext *ctx) +{ + if (!g_cancellable_is_cancelled (ctx->cancellable)) + return FALSE; + + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_CANCELLED, + "Interface initialization cancelled"); + initialization_context_complete_and_free (ctx); + return TRUE; +} + +static void +check_support_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + InitializationContext *ctx) +{ + GError *error = NULL; + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support_finish (self, + res, + &error)) { + if (error) { + /* This error shouldn't be treated as critical */ + mm_dbg ("Voice support check failed: '%s'", error->message); + g_error_free (error); + } + } else { + /* Voice is supported! */ + g_object_set_qdata (G_OBJECT (self), + supported_quark, + GUINT_TO_POINTER (TRUE)); + } + + /* Go on to next step */ + ctx->step++; + interface_initialization_step (ctx); +} + +static void +interface_initialization_step (InitializationContext *ctx) +{ + /* Don't run new steps if we're cancelled */ + if (initialization_context_complete_and_free_if_cancelled (ctx)) + return; + + switch (ctx->step) { + case INITIALIZATION_STEP_FIRST: + /* Setup quarks if we didn't do it before */ + if (G_UNLIKELY (!support_checked_quark)) + support_checked_quark = (g_quark_from_static_string ( + SUPPORT_CHECKED_TAG)); + if (G_UNLIKELY (!supported_quark)) + supported_quark = (g_quark_from_static_string ( + SUPPORTED_TAG)); + + /* Fall down to next step */ + ctx->step++; + + case INITIALIZATION_STEP_CHECK_SUPPORT: + if (!GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (ctx->self), + support_checked_quark))) { + /* Set the checked flag so that we don't run it again */ + g_object_set_qdata (G_OBJECT (ctx->self), + support_checked_quark, + GUINT_TO_POINTER (TRUE)); + /* Initially, assume we don't support it */ + g_object_set_qdata (G_OBJECT (ctx->self), + supported_quark, + GUINT_TO_POINTER (FALSE)); + + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->check_support && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->check_support_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (ctx->self)->check_support ( + ctx->self, + (GAsyncReadyCallback)check_support_ready, + ctx); + return; + } + + /* If there is no implementation to check support, assume we DON'T + * support it. */ + } + /* Fall down to next step */ + ctx->step++; + + case INITIALIZATION_STEP_FAIL_IF_UNSUPPORTED: + if (!GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (ctx->self), + supported_quark))) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Voice not supported"); + initialization_context_complete_and_free (ctx); + return; + } + /* Fall down to next step */ + ctx->step++; + + case INITIALIZATION_STEP_LAST: + /* We are done without errors! */ + + /* Handle method invocations */ + g_signal_connect (ctx->skeleton, + "handle-create-call", + G_CALLBACK (handle_create), + ctx->self); + g_signal_connect (ctx->skeleton, + "handle-delete-call", + G_CALLBACK (handle_delete), + ctx->self); + g_signal_connect (ctx->skeleton, + "handle-list-calls", + G_CALLBACK (handle_list), + ctx->self); + + /* Finally, export the new interface */ + mm_gdbus_object_skeleton_set_modem_voice (MM_GDBUS_OBJECT_SKELETON (ctx->self), + MM_GDBUS_MODEM_VOICE (ctx->skeleton)); + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + initialization_context_complete_and_free (ctx); + return; + } + + g_assert_not_reached (); +} + +gboolean +mm_iface_modem_voice_initialize_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +void +mm_iface_modem_voice_initialize (MMIfaceModemVoice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + InitializationContext *ctx; + MmGdbusModemVoice *skeleton = NULL; + + /* Did we already create it? */ + g_object_get (self, + MM_IFACE_MODEM_VOICE_DBUS_SKELETON, &skeleton, + NULL); + if (!skeleton) { + skeleton = mm_gdbus_modem_voice_skeleton_new (); + + g_object_set (self, + MM_IFACE_MODEM_VOICE_DBUS_SKELETON, skeleton, + NULL); + } + + /* Perform async initialization here */ + + ctx = g_new0 (InitializationContext, 1); + ctx->self = g_object_ref (self); + ctx->cancellable = g_object_ref (cancellable); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + mm_iface_modem_voice_initialize); + ctx->step = INITIALIZATION_STEP_FIRST; + ctx->skeleton = skeleton; + + interface_initialization_step (ctx); +} + +void +mm_iface_modem_voice_shutdown (MMIfaceModemVoice *self) +{ + /* Unexport DBus interface and remove the skeleton */ + mm_gdbus_object_skeleton_set_modem_voice (MM_GDBUS_OBJECT_SKELETON (self), NULL); + g_object_set (self, + MM_IFACE_MODEM_VOICE_DBUS_SKELETON, NULL, + NULL); +} + +/*****************************************************************************/ + +static void +iface_modem_voice_init (gpointer g_iface) +{ + static gboolean initialized = FALSE; + + if (initialized) + return; + + /* Properties */ + g_object_interface_install_property + (g_iface, + g_param_spec_object (MM_IFACE_MODEM_VOICE_DBUS_SKELETON, + "Voice DBus skeleton", + "DBus skeleton for the Voice interface", + MM_GDBUS_TYPE_MODEM_VOICE_SKELETON, + G_PARAM_READWRITE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_object (MM_IFACE_MODEM_VOICE_CALL_LIST, + "CALL list", + "List of CALL objects managed in the interface", + MM_TYPE_CALL_LIST, + G_PARAM_READWRITE)); + + initialized = TRUE; +} + +GType +mm_iface_modem_voice_get_type (void) +{ + static GType iface_modem_voice_type = 0; + + if (!G_UNLIKELY (iface_modem_voice_type)) { + static const GTypeInfo info = { + sizeof (MMIfaceModemVoice), /* class_size */ + iface_modem_voice_init, /* base_init */ + NULL, /* base_finalize */ + }; + + iface_modem_voice_type = g_type_register_static (G_TYPE_INTERFACE, + "MMIfaceModemVoice", + &info, + 0); + + g_type_interface_add_prerequisite (iface_modem_voice_type, MM_TYPE_IFACE_MODEM); + } + + return iface_modem_voice_type; +} diff --git a/src/iface-voice/mm-iface-modem-voice.h b/src/iface-voice/mm-iface-modem-voice.h new file mode 100644 index 000000000..e182060a8 --- /dev/null +++ b/src/iface-voice/mm-iface-modem-voice.h @@ -0,0 +1,137 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2015 - Marco Bascetta <marco.bascetta@sadel.it> + */ + +#ifndef MM_IFACE_MODEM_VOICE_H +#define MM_IFACE_MODEM_VOICE_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-base-call.h" + +#define MM_TYPE_IFACE_MODEM_VOICE (mm_iface_modem_voice_get_type ()) +#define MM_IFACE_MODEM_VOICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_IFACE_MODEM_VOICE, MMIfaceModemVoice)) +#define MM_IS_IFACE_MODEM_VOICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_IFACE_MODEM_VOICE)) +#define MM_IFACE_MODEM_VOICE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_IFACE_MODEM_VOICE, MMIfaceModemVoice)) + +#define MM_IFACE_MODEM_VOICE_DBUS_SKELETON "iface-modem-voice-dbus-skeleton" +#define MM_IFACE_MODEM_VOICE_CALL_LIST "iface-modem-voice-call-list" + +typedef struct _MMIfaceModemVoice MMIfaceModemVoice; + +struct _MMIfaceModemVoice { + GTypeInterface g_iface; + + /* Check for Voice support (async) */ + void (* check_support) (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*check_support_finish) (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + + /* Asynchronous setting up unsolicited CALL reception events */ + void (*setup_unsolicited_events) (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*setup_unsolicited_events_finish) (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + + /* Asynchronous cleaning up of unsolicited CALL reception events */ + void (*cleanup_unsolicited_events) (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*cleanup_unsolicited_events_finish) (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + + /* Asynchronous enabling unsolicited CALL reception events */ + void (* enable_unsolicited_events) (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* enable_unsolicited_events_finish) (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + + /* Asynchronous disabling unsolicited CALL reception events */ + void (* disable_unsolicited_events) (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* disable_unsolicited_events_finish) (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + + /* Create CALL objects */ + MMBaseCall * (* create_call) (MMIfaceModemVoice *self); +}; + +GType mm_iface_modem_voice_get_type (void); + +/* Initialize Voice interface (async) */ +void mm_iface_modem_voice_initialize (MMIfaceModemVoice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_iface_modem_voice_initialize_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +/* Enable Voice interface (async) */ +void mm_iface_modem_voice_enable (MMIfaceModemVoice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_iface_modem_voice_enable_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +/* Disable Voice interface (async) */ +void mm_iface_modem_voice_disable (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_iface_modem_voice_disable_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + +/* Shutdown Voice interface */ +void mm_iface_modem_voice_shutdown (MMIfaceModemVoice *self); + +/* Bind properties for simple GetStatus() */ +void mm_iface_modem_voice_bind_simple_status (MMIfaceModemVoice *self, + MMSimpleStatus *status); + +/* CALL creation */ +MMBaseCall *mm_iface_modem_voice_create_call (MMIfaceModemVoice *self); +MMBaseCall *mm_iface_modem_voice_create_incoming_call (MMIfaceModemVoice *self); +gboolean mm_iface_modem_voice_update_incoming_call_number (MMIfaceModemVoice *self, + gchar *number, + guint type, + guint validity); +gboolean mm_iface_modem_voice_call_dialing_to_ringing (MMIfaceModemVoice *self); +gboolean mm_iface_modem_voice_call_ringing_to_active (MMIfaceModemVoice *self); +gboolean mm_iface_modem_voice_network_hangup (MMIfaceModemVoice *self); +gboolean mm_iface_modem_voice_received_dtmf (MMIfaceModemVoice *self, + gchar *dtmf); + +/* Look for a new valid multipart reference */ +guint8 mm_iface_modem_voice_get_local_multipart_reference (MMIfaceModemVoice *self, + const gchar *number, + GError **error); + +#endif /* MM_IFACE_MODEM_VOICE_H */ |