diff options
Diffstat (limited to 'src/mm-iface-modem-voice.c')
-rw-r--r-- | src/mm-iface-modem-voice.c | 3217 |
1 files changed, 3217 insertions, 0 deletions
diff --git a/src/mm-iface-modem-voice.c b/src/mm-iface-modem-voice.c new file mode 100644 index 00000000..4f3b30fc --- /dev/null +++ b/src/mm-iface-modem-voice.c @@ -0,0 +1,3217 @@ +/* -*- 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> + * Copyright (C) 2019 Purism SPC + */ + +#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-object.h" + +#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 call_list_polling_context_quark; +static GQuark in_call_event_context_quark; + +/*****************************************************************************/ + +void +mm_iface_modem_voice_bind_simple_status (MMIfaceModemVoice *self, + MMSimpleStatus *status) +{ +} + +/*****************************************************************************/ + +gboolean +mm_iface_modem_voice_authorize_outgoing_call (MMIfaceModemVoice *self, + MMBaseCall *call, + GError **error) +{ + MmGdbusModemVoice *skeleton = NULL; + MMBaseSim *sim = NULL; + gboolean emergency_only = FALSE; + gboolean call_allowed = FALSE; + GError *inner_error = NULL; + guint i; + const gchar *number; + + static const gchar *always_valid_emergency_numbers[] = { "112", "911" }; + static const gchar *no_sim_valid_emergency_numbers[] = { "000", "08", "110", "999", "118", "119" }; + + g_assert (mm_base_call_get_direction (call) == MM_CALL_DIRECTION_OUTGOING); + number = mm_base_call_get_number (call); + g_assert (number); + + g_object_get (self, + MM_IFACE_MODEM_VOICE_DBUS_SKELETON, &skeleton, + NULL); + + if (!skeleton) { + g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "voice operations unsupported"); + goto out; + } + + g_object_get (skeleton, + "emergency-only", &emergency_only, + NULL); + + /* Identification of emergency numbers. 3GPP TS 22.101 + * + * a) 112 and 911 shall always be available. + * b) Any emergency call number stored on a SIM/USIM when the SIM/USIM is + * present. + * c) 000, 08, 110, 999, 118 and 119 when a SIM/USIM is not present. + * d) Additional emergency call numbers that may have been downloaded by + * the serving network when the SIM/USIM is present. + * + * In ModemManager we're not flagging any call as being "emergency" or + * "normal", but we can right away limit non-emergency calls if we're in + * "emergency-only" mode. + */ + + /* If we're not in emergency mode, the call (emergency or normal) is always allowed */ + if (!emergency_only) { + mm_obj_dbg (self, "voice call to %s allowed", number); + call_allowed = TRUE; + goto out; + } + + for (i = 0; i < G_N_ELEMENTS (always_valid_emergency_numbers); i++) { + if (g_strcmp0 (number, always_valid_emergency_numbers[i]) == 0) { + mm_obj_dbg (self, "voice call to %s allowed: emergency call number always valid", number); + call_allowed = TRUE; + goto out; + } + } + + /* Check if we have a SIM */ + g_object_get (self, + MM_IFACE_MODEM_SIM, &sim, + NULL); + if (!sim) { + /* If no SIM available, some additional numbers may be valid emergency numbers */ + for (i = 0; i < G_N_ELEMENTS (no_sim_valid_emergency_numbers); i++) { + if (g_strcmp0 (number, no_sim_valid_emergency_numbers[i]) == 0) { + mm_obj_dbg (self, "voice call to %s allowed: emergency call number valid when no SIM", number); + call_allowed = TRUE; + goto out; + } + } + + mm_obj_dbg (self, "voice call to %s NOT allowed: not a valid emergency call number when no SIM", number); + goto out; + } + + /* Check if the number is programmed in EF_ECC */ + if (mm_base_sim_is_emergency_number (sim, number)) { + mm_obj_dbg (self, "voice call to %s allowed: emergency call number programmed in the SIM", number); + call_allowed = TRUE; + } else + mm_obj_dbg (self, "voice call to %s NOT allowed: not a valid emergency call number programmed in the SIM", number); + + out: + + if (inner_error) + g_propagate_error (error, inner_error); + else if (!call_allowed) + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED, + "only emergency calls allowed"); + + g_clear_object (&skeleton); + g_clear_object (&sim); + return call_allowed; +} + +/*****************************************************************************/ + +/* 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); + + 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 * +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 */ + number = mm_call_properties_get_number (properties) ; + if (!number) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Cannot create call: mandatory parameter 'number' is missing"); + return NULL; + } + + /* Create a call object as defined by the interface */ + g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call != NULL); + 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; +} + +/*****************************************************************************/ +/* Common helper to match call info against a known call object */ + +static gboolean +match_single_call_info (MMIfaceModemVoice *self, + const MMCallInfo *call_info, + MMBaseCall *call) +{ + MMCallState state; + MMCallDirection direction; + const gchar *number; + guint idx; + gboolean match_direction_and_state = FALSE; + gboolean match_number = FALSE; + gboolean match_index = FALSE; + gboolean match_terminated = FALSE; + + /* try to look for a matching call by direction/number/index */ + state = mm_base_call_get_state (call); + direction = mm_base_call_get_direction (call); + number = mm_base_call_get_number (call); + idx = mm_base_call_get_index (call); + + /* Match index */ + if (call_info->index && (call_info->index == idx)) + match_index = TRUE; + + /* Match direction and state. + * We cannot apply this match if both call info and call have an index set + * and they're different already. */ + if ((call_info->direction == direction) && + (call_info->state == state) && + (!call_info->index || !idx || match_index)) + match_direction_and_state = TRUE; + + /* Match number */ + if (call_info->number && number && + g_strcmp0 (call_info->number, number) == 0) + match_number = TRUE; + + /* Match special terminated event. + * We cannot apply this match if the call is part of a multiparty + * call, because we don't know which of the calls in the multiparty + * is the one that finished. Must rely on other reports that do + * provide call index. */ + if ((call_info->state == MM_CALL_STATE_TERMINATED) && + (call_info->direction == MM_CALL_DIRECTION_UNKNOWN) && + !call_info->index && + !call_info->number && + !mm_base_call_get_multiparty (call)) + match_terminated = TRUE; + + /* If no clear match, nothing to do */ + if (!match_direction_and_state && + !match_number && + !match_index && + !match_terminated) + return FALSE; + + mm_obj_dbg (self, "call info matched (matched direction/state %s, matched number %s" + ", matched index %s, matched terminated %s) with call at '%s'", + match_direction_and_state ? "yes" : "no", + match_number ? "yes" : "no", + match_index ? "yes" : "no", + match_terminated ? "yes" : "no", + mm_base_call_get_path (call)); + + /* Early detect if a known incoming call that was created + * from a plain CRING URC (i.e. without caller number) + * needs to have the number provided. + */ + if (call_info->number && !number) { + mm_obj_dbg (self, " number set: %s", call_info->number); + mm_base_call_set_number (call, call_info->number); + } + + /* Early detect if a known incoming/outgoing call does + * not have a known call index yet. + */ + if (call_info->index && !idx) { + mm_obj_dbg (self, " index set: %u", call_info->index); + mm_base_call_set_index (call, call_info->index); + } + + /* Update state if it changed */ + if (call_info->state != state) { + mm_obj_dbg (self, " state updated: %s", mm_call_state_get_string (call_info->state)); + mm_base_call_change_state (call, call_info->state, MM_CALL_STATE_REASON_UNKNOWN); + } + + /* refresh if incoming and new state is not terminated */ + if ((call_info->state != MM_CALL_STATE_TERMINATED) && + (direction == MM_CALL_DIRECTION_INCOMING)) { + mm_obj_dbg (self, " incoming refreshed"); + mm_base_call_incoming_refresh (call); + } + + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + MMIfaceModemVoice *self; + const MMCallInfo *call_info; +} ReportCallForeachContext; + +static void +report_call_foreach (MMBaseCall *call, + ReportCallForeachContext *ctx) +{ + /* Do nothing if already matched */ + if (!ctx->call_info) + return; + + /* fully ignore already terminated calls */ + if (mm_base_call_get_state (call) == MM_CALL_STATE_TERMINATED) + return; + + /* Reset call info in context if the call info matches an existing call */ + if (match_single_call_info (ctx->self, ctx->call_info, call)) + ctx->call_info = NULL; +} + +void +mm_iface_modem_voice_report_call (MMIfaceModemVoice *self, + const MMCallInfo *call_info) +{ + ReportCallForeachContext ctx = { 0 }; + MMBaseCall *call = NULL; + MMCallList *list = NULL; + + /* When reporting single call, the only mandatory parameter is the state: + * - index is optional (e.g. unavailable when receiving +CLIP URCs) + * - number is optional (e.g. unavailable when receiving +CRING URCs) + * - direction is optional (e.g. unavailable when receiving some vendor-specific URCs) + */ + g_assert (call_info->state != MM_CALL_STATE_UNKNOWN); + + /* Early debugging of the call state update */ + mm_obj_dbg (self, "call at index %u: direction %s, state %s, number %s", + call_info->index, + mm_call_direction_get_string (call_info->direction), + mm_call_state_get_string (call_info->state), + call_info->number ? call_info->number : "n/a"); + + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + if (!list) { + mm_obj_warn (self, "cannot process call state update: missing call list"); + return; + } + + /* Iterate over all known calls and try to match a known one */ + ctx.self = self; + ctx.call_info = call_info; + mm_call_list_foreach (list, (MMCallListForeachFunc)report_call_foreach, &ctx); + + /* If call info matched with an existing one, the context call info would have been reseted */ + if (!ctx.call_info) + goto out; + + /* If call info didn't match with any known call, it may be because we're being + * reported a NEW incoming call. If that's not the case, we'll ignore the report. */ + if ((call_info->direction != MM_CALL_DIRECTION_INCOMING) || + ((call_info->state != MM_CALL_STATE_WAITING) && (call_info->state != MM_CALL_STATE_RINGING_IN))) { + mm_obj_dbg (self, "unhandled call state update reported: direction: %s, state %s", + mm_call_direction_get_string (call_info->direction), + mm_call_state_get_string (call_info->state)); + goto out; + } + + mm_obj_dbg (self, "creating new incoming call..."); + call = create_incoming_call (self, call_info->number); + + /* Set the state */ + mm_base_call_change_state (call, call_info->state, MM_CALL_STATE_REASON_INCOMING_NEW); + + /* Set the index, if known */ + if (call_info->index) + mm_base_call_set_index (call, call_info->index); + + /* Start its validity timeout */ + mm_base_call_incoming_refresh (call); + + /* Only export once properly created */ + mm_base_call_export (call); + mm_call_list_add_call (list, call); + g_object_unref (call); + + out: + g_object_unref (list); +} + +/*****************************************************************************/ +/* Full current call list reporting + * + * This method receives as input a list with all the currently active calls, + * including the specific state they're in. + * + * This method should: + * - Check whether we're reporting a new call (i.e. not in our internal call + * list yet). We'll create a new call object if so. + * - Check whether any of the known calls has changed state, and if so, + * update it. + * - Check whether any of the known calls is NOT given in the input list of + * call infos, which would mean the call is terminated. + */ + +typedef struct { + MMIfaceModemVoice *self; + GList *call_info_list; +} ReportAllCallsForeachContext; + +static void +report_all_calls_foreach (MMBaseCall *call, + ReportAllCallsForeachContext *ctx) +{ + MMCallState state; + GList *l; + + /* fully ignore already terminated calls */ + state = mm_base_call_get_state (call); + if (state == MM_CALL_STATE_TERMINATED) + return; + + /* Iterate over the call info list */ + for (l = ctx->call_info_list; l; l = g_list_next (l)) { + MMCallInfo *call_info = (MMCallInfo *)(l->data); + + /* if match found, delete item from list and halt iteration right away */ + if (match_single_call_info (ctx->self, call_info, call)) { + ctx->call_info_list = g_list_delete_link (ctx->call_info_list, l); + return; + } + } + + /* not found in list! this call is now terminated */ + mm_obj_dbg (ctx->self, "call '%s' with direction %s, state %s, number '%s', index %u" + " not found in list, terminating", + mm_base_call_get_path (call), + mm_call_direction_get_string (mm_base_call_get_direction (call)), + mm_call_state_get_string (state), + mm_base_call_get_number (call), + mm_base_call_get_index (call)); + mm_base_call_change_state (call, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_UNKNOWN); +} + +void +mm_iface_modem_voice_report_all_calls (MMIfaceModemVoice *self, + GList *call_info_list) +{ + ReportAllCallsForeachContext ctx = { 0 }; + MMCallList *list = NULL; + GList *l; + + /* Early debugging of the full list of calls */ + mm_obj_dbg (self, "reported %u ongoing calls", g_list_length (call_info_list)); + for (l = call_info_list; l; l = g_list_next (l)) { + MMCallInfo *call_info = (MMCallInfo *)(l->data); + + /* When reporting full list of calls, index and state are mandatory */ + g_assert (call_info->index != 0); + g_assert (call_info->state != MM_CALL_STATE_UNKNOWN); + + mm_obj_dbg (self, "call at index %u: direction %s, state %s, number %s", + call_info->index, + mm_call_direction_get_string (call_info->direction), + mm_call_state_get_string (call_info->state), + call_info->number ? call_info->number : "n/a"); + } + + /* Retrieve list of known calls */ + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + mm_obj_warn (self, "cannot report all calls: missing call list"); + return; + } + + /* Iterate over all the calls already known to us. + * Whenever a known call is updated, it will be removed from the call info list */ + ctx.self = self; + ctx.call_info_list = g_list_copy (call_info_list); + mm_call_list_foreach (list, (MMCallListForeachFunc)report_all_calls_foreach, &ctx); + + /* Once processed, the call info list will have all calls that were unknown to + * us, i.e. the new calls to create. We really only expect new incoming calls, so + * we'll warn if we get any outgoing call reported here. */ + for (l = ctx.call_info_list; l; l = g_list_next (l)) { + MMCallInfo *call_info = (MMCallInfo *)(l->data); + + /* Ignore unknown terminated calls, because these be due to an already + * processed event. */ + if (call_info->state == MM_CALL_STATE_TERMINATED) + continue; + + if (call_info->direction == MM_CALL_DIRECTION_OUTGOING) { + mm_obj_warn (self, "unexpected outgoing call to number '%s' reported in call list: state %s", + call_info->number ? call_info->number : "n/a", + mm_call_state_get_string (call_info->state)); + continue; + } + + if (call_info->direction == MM_CALL_DIRECTION_INCOMING) { + MMBaseCall *call; + + /* We only expect either RINGING-IN or WAITING states */ + if ((call_info->state != MM_CALL_STATE_RINGING_IN) && + (call_info->state != MM_CALL_STATE_WAITING)) { + mm_obj_warn (self, "unexpected incoming call to number '%s' reported in call list: state %s", + call_info->number ? call_info->number : "n/a", + mm_call_state_get_string (call_info->state)); + continue; + } + + mm_obj_dbg (self, "creating new incoming call..."); + call = create_incoming_call (self, call_info->number); + + /* Set the state and the index */ + mm_base_call_change_state (call, call_info->state, MM_CALL_STATE_REASON_INCOMING_NEW); + mm_base_call_set_index (call, call_info->index); + + /* Start its validity timeout */ + mm_base_call_incoming_refresh (call); + + /* Only export once properly created */ + mm_base_call_export (call); + mm_call_list_add_call (list, call); + g_object_unref (call); + continue; + } + + mm_obj_warn (self, "unexpected call to number '%s' reported in call list: state %s, direction unknown", + call_info->number ? call_info->number : "n/a", + mm_call_state_get_string (call_info->state)); + } + g_list_free (ctx.call_info_list); + g_object_unref (list); +} + +/*****************************************************************************/ +/* Incoming DTMF reception, not associated to a specific call */ + +typedef struct { + guint index; + const gchar *dtmf; +} ReceivedDtmfContext; + +static void +received_dtmf_foreach (MMBaseCall *call, + ReceivedDtmfContext *ctx) +{ + if ((!ctx->index || (ctx->index == mm_base_call_get_index (call))) && + (mm_base_call_get_state (call) == MM_CALL_STATE_ACTIVE)) + mm_base_call_received_dtmf (call, ctx->dtmf); +} + +void +mm_iface_modem_voice_received_dtmf (MMIfaceModemVoice *self, + guint index, + const gchar *dtmf) +{ + MMCallList *list = NULL; + ReceivedDtmfContext ctx = { + .index = index, + .dtmf = dtmf + }; + + /* Retrieve list of known calls */ + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + mm_obj_warn (self, "cannot report received DTMF: missing call list"); + return; + } + + mm_call_list_foreach (list, (MMCallListForeachFunc)received_dtmf_foreach, &ctx); + g_object_unref (list); +} + +/*****************************************************************************/ + +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_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleDeleteContext *ctx) +{ + 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_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; + } + + if (!mm_call_list_delete_call (list, ctx->path, &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); + 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) +{ + 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; + } + + /* 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 = create_outgoing_call_from_properties (MM_IFACE_MODEM_VOICE (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; + } + + /* Only export once properly created */ + mm_base_call_export (call); + 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; + + 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; +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemVoice *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemVoice *self; + GList *active_calls; + MMBaseCall *next_call; +} HandleHoldAndAcceptContext; + +static void +handle_hold_and_accept_context_free (HandleHoldAndAcceptContext *ctx) +{ + g_list_free_full (ctx->active_calls, g_object_unref); + g_clear_object (&ctx->next_call); + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_slice_free (HandleHoldAndAcceptContext, ctx); +} + +static void +hold_and_accept_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + HandleHoldAndAcceptContext *ctx) +{ + GError *error = NULL; + GList *l; + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hold_and_accept_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_hold_and_accept_context_free (ctx); + return; + } + + for (l = ctx->active_calls; l; l = g_list_next (l)) + mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_HELD, MM_CALL_STATE_REASON_UNKNOWN); + if (ctx->next_call) + mm_base_call_change_state (ctx->next_call, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED); + + mm_gdbus_modem_voice_complete_hold_and_accept (ctx->skeleton, ctx->invocation); + handle_hold_and_accept_context_free (ctx); +} + +static void +prepare_hold_and_accept_foreach (MMBaseCall *call, + HandleHoldAndAcceptContext *ctx) +{ + switch (mm_base_call_get_state (call)) { + case MM_CALL_STATE_ACTIVE: + ctx->active_calls = g_list_append (ctx->active_calls, g_object_ref (call)); + break; + case MM_CALL_STATE_WAITING: + g_clear_object (&ctx->next_call); + ctx->next_call = g_object_ref (call); + break; + case MM_CALL_STATE_HELD: + if (!ctx->next_call) + ctx->next_call = g_object_ref (call); + break; + case MM_CALL_STATE_UNKNOWN: + case MM_CALL_STATE_DIALING: + case MM_CALL_STATE_RINGING_IN: + case MM_CALL_STATE_RINGING_OUT: + case MM_CALL_STATE_TERMINATED: + default: + break; + } +} + +static void +handle_hold_and_accept_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleHoldAndAcceptContext *ctx) +{ + GError *error = NULL; + MMCallList *list = NULL; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_hold_and_accept_context_free (ctx); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hold_and_accept || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hold_and_accept_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot hold and accept: unsupported"); + handle_hold_and_accept_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 hold and accept: missing call list"); + handle_hold_and_accept_context_free (ctx); + return; + } + mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_hold_and_accept_foreach, ctx); + g_object_unref (list); + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hold_and_accept (MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)hold_and_accept_ready, + ctx); +} + +static gboolean +handle_hold_and_accept (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + MMIfaceModemVoice *self) +{ + HandleHoldAndAcceptContext *ctx; + + ctx = g_slice_new0 (HandleHoldAndAcceptContext); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_hold_and_accept_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemVoice *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemVoice *self; + GList *active_calls; + MMBaseCall *next_call; +} HandleHangupAndAcceptContext; + +static void +handle_hangup_and_accept_context_free (HandleHangupAndAcceptContext *ctx) +{ + g_list_free_full (ctx->active_calls, g_object_unref); + g_clear_object (&ctx->next_call); + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_slice_free (HandleHangupAndAcceptContext, ctx); +} + +static void +hangup_and_accept_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + HandleHangupAndAcceptContext *ctx) +{ + GError *error = NULL; + GList *l; + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_and_accept_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_hangup_and_accept_context_free (ctx); + return; + } + + for (l = ctx->active_calls; l; l = g_list_next (l)) + mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED); + if (ctx->next_call) + mm_base_call_change_state (ctx->next_call, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED); + + mm_gdbus_modem_voice_complete_hangup_and_accept (ctx->skeleton, ctx->invocation); + handle_hangup_and_accept_context_free (ctx); +} + +static void +prepare_hangup_and_accept_foreach (MMBaseCall *call, + HandleHangupAndAcceptContext *ctx) +{ + switch (mm_base_call_get_state (call)) { + case MM_CALL_STATE_ACTIVE: + ctx->active_calls = g_list_append (ctx->active_calls, g_object_ref (call)); + break; + case MM_CALL_STATE_WAITING: + g_clear_object (&ctx->next_call); + ctx->next_call = g_object_ref (call); + break; + case MM_CALL_STATE_HELD: + if (!ctx->next_call) + ctx->next_call = g_object_ref (call); + break; + case MM_CALL_STATE_UNKNOWN: + case MM_CALL_STATE_DIALING: + case MM_CALL_STATE_RINGING_IN: + case MM_CALL_STATE_RINGING_OUT: + case MM_CALL_STATE_TERMINATED: + default: + break; + } +} + +static void +handle_hangup_and_accept_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleHangupAndAcceptContext *ctx) +{ + GError *error = NULL; + MMCallList *list = NULL; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_hangup_and_accept_context_free (ctx); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_and_accept || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_and_accept_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot hangup and accept: unsupported"); + handle_hangup_and_accept_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 hangup and accept: missing call list"); + handle_hangup_and_accept_context_free (ctx); + return; + } + mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_hangup_and_accept_foreach, ctx); + g_object_unref (list); + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_and_accept (MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)hangup_and_accept_ready, + ctx); +} + +static gboolean +handle_hangup_and_accept (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + MMIfaceModemVoice *self) +{ + HandleHangupAndAcceptContext *ctx; + + ctx = g_slice_new0 (HandleHangupAndAcceptContext); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_hangup_and_accept_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemVoice *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemVoice *self; + GList *calls; +} HandleHangupAllContext; + +static void +handle_hangup_all_context_free (HandleHangupAllContext *ctx) +{ + g_list_free_full (ctx->calls, g_object_unref); + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_slice_free (HandleHangupAllContext, ctx); +} + +static void +hangup_all_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + HandleHangupAllContext *ctx) +{ + GError *error = NULL; + GList *l; + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_all_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_hangup_all_context_free (ctx); + return; + } + + for (l = ctx->calls; l; l = g_list_next (l)) + mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED); + + mm_gdbus_modem_voice_complete_hangup_all (ctx->skeleton, ctx->invocation); + handle_hangup_all_context_free (ctx); +} + +static void +prepare_hangup_all_foreach (MMBaseCall *call, + HandleHangupAllContext *ctx) +{ + /* The implementation of this operation will usually be done with +CHUP, and we + * know that +CHUP is implemented in different ways by different manufacturers. + * + * The 3GPP TS27.007 spec for +CHUP states that the "Execution command causes + * the TA to hangup the current call of the MT." This sentence leaves a bit of open + * interpretation to the implementors, because a current call can be considered only + * the active ones, or otherwise any call (active, held or waiting). + * + * And so, the u-blox TOBY-L4 takes one interpretation and "In case of multiple + * calls, all active calls will be released, while waiting and held calls are not". + * + * And the Cinterion PLS-8 takes a different interpretation and cancels all calls, + * including the waiting and held ones. + * + * In this logic, we're going to terminate exclusively the ACTIVE calls only, and we + * will leave the possible termination of waiting/held calls to be reported via + * call state updates, e.g. +CLCC polling or other plugin-specific method. In the + * case of the Cinterion PLS-8, we'll detect the termination of the waiting and + * held calls via ^SLCC URCs. + */ + switch (mm_base_call_get_state (call)) { + case MM_CALL_STATE_DIALING: + case MM_CALL_STATE_RINGING_OUT: + case MM_CALL_STATE_RINGING_IN: + case MM_CALL_STATE_ACTIVE: + ctx->calls = g_list_append (ctx->calls, g_object_ref (call)); + break; + case MM_CALL_STATE_WAITING: + case MM_CALL_STATE_HELD: + case MM_CALL_STATE_UNKNOWN: + case MM_CALL_STATE_TERMINATED: + default: + break; + } +} + +static void +handle_hangup_all_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleHangupAllContext *ctx) +{ + GError *error = NULL; + MMCallList *list = NULL; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_hangup_all_context_free (ctx); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_all || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_all_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot hangup all: unsupported"); + handle_hangup_all_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 hangup all: missing call list"); + handle_hangup_all_context_free (ctx); + return; + } + mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_hangup_all_foreach, ctx); + g_object_unref (list); + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_all (MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)hangup_all_ready, + ctx); +} + +static gboolean +handle_hangup_all (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + MMIfaceModemVoice *self) +{ + HandleHangupAllContext *ctx; + + ctx = g_slice_new0 (HandleHangupAllContext); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_hangup_all_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemVoice *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemVoice *self; + GList *calls; +} HandleTransferContext; + +static void +handle_transfer_context_free (HandleTransferContext *ctx) +{ + g_list_free_full (ctx->calls, g_object_unref); + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_slice_free (HandleTransferContext, ctx); +} + +static void +transfer_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + HandleTransferContext *ctx) +{ + GError *error = NULL; + GList *l; + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->transfer_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_transfer_context_free (ctx); + return; + } + + for (l = ctx->calls; l; l = g_list_next (l)) + mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TRANSFERRED); + + mm_gdbus_modem_voice_complete_transfer (ctx->skeleton, ctx->invocation); + handle_transfer_context_free (ctx); +} + +static void +prepare_transfer_foreach (MMBaseCall *call, + HandleTransferContext *ctx) +{ + switch (mm_base_call_get_state (call)) { + case MM_CALL_STATE_ACTIVE: + case MM_CALL_STATE_HELD: + ctx->calls = g_list_append (ctx->calls, g_object_ref (call)); + break; + case MM_CALL_STATE_UNKNOWN: + case MM_CALL_STATE_DIALING: + case MM_CALL_STATE_WAITING: + case MM_CALL_STATE_RINGING_IN: + case MM_CALL_STATE_RINGING_OUT: + case MM_CALL_STATE_TERMINATED: + default: + break; + } +} + +static void +handle_transfer_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleTransferContext *ctx) +{ + GError *error = NULL; + MMCallList *list = NULL; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_transfer_context_free (ctx); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->transfer || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->transfer_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot transfer: unsupported"); + handle_transfer_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 transfer: missing call list"); + handle_transfer_context_free (ctx); + return; + } + mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_transfer_foreach, ctx); + g_object_unref (list); + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->transfer (MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)transfer_ready, + ctx); +} + +static gboolean +handle_transfer (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + MMIfaceModemVoice *self) +{ + HandleTransferContext *ctx; + + ctx = g_slice_new0 (HandleTransferContext); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_transfer_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemVoice *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemVoice *self; + gboolean enable; +} HandleCallWaitingSetupContext; + +static void +handle_call_waiting_setup_context_free (HandleCallWaitingSetupContext *ctx) +{ + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_slice_free (HandleCallWaitingSetupContext, ctx); +} + +static void +call_waiting_setup_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + HandleCallWaitingSetupContext *ctx) +{ + GError *error = NULL; + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_setup_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_call_waiting_setup_context_free (ctx); + return; + } + + mm_gdbus_modem_voice_complete_call_waiting_setup (ctx->skeleton, ctx->invocation); + handle_call_waiting_setup_context_free (ctx); +} + +static void +handle_call_waiting_setup_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleCallWaitingSetupContext *ctx) +{ + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_call_waiting_setup_context_free (ctx); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_setup || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_setup_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot setup call waiting: unsupported"); + handle_call_waiting_setup_context_free (ctx); + return; + } + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_setup (MM_IFACE_MODEM_VOICE (self), + ctx->enable, + (GAsyncReadyCallback)call_waiting_setup_ready, + ctx); +} + +static gboolean +handle_call_waiting_setup (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + gboolean enable, + MMIfaceModemVoice *self) +{ + HandleCallWaitingSetupContext *ctx; + + ctx = g_slice_new0 (HandleCallWaitingSetupContext); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + ctx->enable = enable; + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_call_waiting_setup_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemVoice *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemVoice *self; + gboolean enable; +} HandleCallWaitingQueryContext; + +static void +handle_call_waiting_query_context_free (HandleCallWaitingQueryContext *ctx) +{ + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_slice_free (HandleCallWaitingQueryContext, ctx); +} + +static void +call_waiting_query_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + HandleCallWaitingQueryContext *ctx) +{ + GError *error = NULL; + gboolean status = FALSE; + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_query_finish (self, res, &status, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_call_waiting_query_context_free (ctx); + return; + } + + mm_gdbus_modem_voice_complete_call_waiting_query (ctx->skeleton, ctx->invocation, status); + handle_call_waiting_query_context_free (ctx); +} + +static void +handle_call_waiting_query_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleCallWaitingQueryContext *ctx) +{ + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_call_waiting_query_context_free (ctx); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_query || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_query_finish) { + g_dbus_method_invocation_return_error (ctx->invocation, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Cannot query call waiting: unsupported"); + handle_call_waiting_query_context_free (ctx); + return; + } + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_query (MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)call_waiting_query_ready, + ctx); +} + +static gboolean +handle_call_waiting_query (MmGdbusModemVoice *skeleton, + GDBusMethodInvocation *invocation, + MMIfaceModemVoice *self) +{ + HandleCallWaitingQueryContext *ctx; + + ctx = g_slice_new0 (HandleCallWaitingQueryContext); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_call_waiting_query_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ +/* Leave one of the calls from the multiparty call */ + +typedef struct { + MMBaseCall *call; + GList *other_calls; +} LeaveMultipartyContext; + +static void +leave_multiparty_context_free (LeaveMultipartyContext *ctx) +{ + g_list_free_full (ctx->other_calls, g_object_unref); + g_object_unref (ctx->call); + g_slice_free (LeaveMultipartyContext, ctx); +} + +static void +prepare_leave_multiparty_foreach (MMBaseCall *call, + LeaveMultipartyContext *ctx) +{ + /* ignore call that is leaving */ + if ((call == ctx->call) || (g_strcmp0 (mm_base_call_get_path (call), mm_base_call_get_path (ctx->call)) == 0)) + return; + + /* ignore non-multiparty calls */ + if (!mm_base_call_get_multiparty (call)) + return; + + /* ignore calls not currently ongoing */ + switch (mm_base_call_get_state (call)) { + case MM_CALL_STATE_ACTIVE: + case MM_CALL_STATE_HELD: + ctx->other_calls = g_list_append (ctx->other_calls, g_object_ref (call)); + break; + case MM_CALL_STATE_UNKNOWN: + case MM_CALL_STATE_DIALING: + case MM_CALL_STATE_WAITING: + case MM_CALL_STATE_RINGING_IN: + case MM_CALL_STATE_RINGING_OUT: + case MM_CALL_STATE_TERMINATED: + default: + break; + } +} + +gboolean +mm_iface_modem_voice_leave_multiparty_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +leave_multiparty_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + LeaveMultipartyContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* If there is only one remaining call that was part of the multiparty, consider that + * one also no longer part of any multiparty, and put it on hold right away */ + if (g_list_length (ctx->other_calls) == 1) { + mm_base_call_set_multiparty (MM_BASE_CALL (ctx->other_calls->data), FALSE); + mm_base_call_change_state (MM_BASE_CALL (ctx->other_calls->data), MM_CALL_STATE_HELD, MM_CALL_STATE_REASON_UNKNOWN); + } + /* If there are still more than one calls in the multiparty, just change state of all + * of them. */ + else { + GList *l; + + for (l = ctx->other_calls; l; l = g_list_next (l)) + mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_HELD, MM_CALL_STATE_REASON_UNKNOWN); + } + + /* The call that left would now be active */ + mm_base_call_set_multiparty (ctx->call, FALSE); + mm_base_call_change_state (ctx->call, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_UNKNOWN); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_iface_modem_voice_leave_multiparty (MMIfaceModemVoice *self, + MMBaseCall *call, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LeaveMultipartyContext *ctx; + MMCallList *list = NULL; + MMCallState call_state; + + task = g_task_new (self, NULL, callback, user_data); + + /* validate multiparty status */ + if (!mm_base_call_get_multiparty (call)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "this call is not part of a multiparty call"); + g_object_unref (task); + return; + } + /* validate call state */ + call_state = mm_base_call_get_state (call); + if ((call_state != MM_CALL_STATE_ACTIVE) && (call_state != MM_CALL_STATE_HELD)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "invalid call state (%s): must be either active or held", + mm_call_state_get_string (call_state)); + g_object_unref (task); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty_finish) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot leave multiparty: unsupported"); + g_object_unref (task); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "Cannot leave multiparty: missing call list"); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (LeaveMultipartyContext); + ctx->call = g_object_ref (call); + g_task_set_task_data (task, ctx, (GDestroyNotify) leave_multiparty_context_free); + + mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_leave_multiparty_foreach, ctx); + g_object_unref (list); + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty (self, + call, + (GAsyncReadyCallback)leave_multiparty_ready, + task); +} + +/*****************************************************************************/ +/* Join calls into a multiparty call */ + +typedef struct { + MMBaseCall *call; + GList *all_calls; + gboolean added; +} JoinMultipartyContext; + +static void +join_multiparty_context_free (JoinMultipartyContext *ctx) +{ + g_list_free_full (ctx->all_calls, g_object_unref); + g_object_unref (ctx->call); + g_slice_free (JoinMultipartyContext, ctx); +} + +static void +prepare_join_multiparty_foreach (MMBaseCall *call, + JoinMultipartyContext *ctx) +{ + /* always add call that is being added */ + if ((call == ctx->call) || (g_strcmp0 (mm_base_call_get_path (call), mm_base_call_get_path (ctx->call)) == 0)) + ctx->added = TRUE; + + /* ignore calls not currently ongoing */ + switch (mm_base_call_get_state (call)) { + case MM_CALL_STATE_ACTIVE: + case MM_CALL_STATE_HELD: + ctx->all_calls = g_list_append (ctx->all_calls, g_object_ref (call)); + break; + case MM_CALL_STATE_UNKNOWN: + case MM_CALL_STATE_DIALING: + case MM_CALL_STATE_RINGING_IN: + case MM_CALL_STATE_RINGING_OUT: + case MM_CALL_STATE_WAITING: + case MM_CALL_STATE_TERMINATED: + default: + break; + } +} + +gboolean +mm_iface_modem_voice_join_multiparty_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +join_multiparty_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + JoinMultipartyContext *ctx; + GList *l; + + ctx = g_task_get_task_data (task); + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + for (l = ctx->all_calls; l; l = g_list_next (l)) { + mm_base_call_set_multiparty (MM_BASE_CALL (l->data), TRUE); + mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_UNKNOWN); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_iface_modem_voice_join_multiparty (MMIfaceModemVoice *self, + MMBaseCall *call, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + JoinMultipartyContext *ctx; + MMCallList *list = NULL; + MMCallState call_state; + + task = g_task_new (self, NULL, callback, user_data); + + /* validate multiparty status */ + if (mm_base_call_get_multiparty (call)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "this call is already part of a multiparty call"); + g_object_unref (task); + return; + } + /* validate call state */ + call_state = mm_base_call_get_state (call); + if (call_state != MM_CALL_STATE_HELD) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "invalid call state (%s): must be held", + mm_call_state_get_string (call_state)); + g_object_unref (task); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty_finish) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot join multiparty: unsupported"); + g_object_unref (task); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "Cannot join multiparty: missing call list"); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (JoinMultipartyContext); + ctx->call = g_object_ref (call); + g_task_set_task_data (task, ctx, (GDestroyNotify) join_multiparty_context_free); + + mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_join_multiparty_foreach, ctx); + g_object_unref (list); + + /* our logic makes sure that we would be adding the incoming call into the multiparty call */ + g_assert (ctx->added); + + /* NOTE: we do not give the call we want to join, because the join operation acts on all + * active/held calls. */ + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty (self, + (GAsyncReadyCallback)join_multiparty_ready, + task); +} + +/*****************************************************************************/ +/* 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_obj_warn (self, "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_obj_warn (self, "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)) { + g_object_unref (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); + return; + } + 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); + return; + } + ctx->step++; + /* fall-through */ + case IN_CALL_SETUP_STEP_LAST: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + break; + } + + g_assert_not_reached (); +} + +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_obj_warn (self, "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_obj_warn (self, "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)) { + g_object_unref (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); + return; + } + 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); + return; + } + ctx->step++; + /* fall-through */ + case IN_CALL_CLEANUP_STEP_LAST: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + break; + } + + g_assert_not_reached (); +} + +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_obj_warn (self, "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. */ + case MM_CALL_STATE_UNKNOWN: + case MM_CALL_STATE_TERMINATED: + 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)) + mm_obj_warn (self, "cannot cleanup in-call modem state: %s", error->message); + g_clear_error (&error); + } else { + mm_obj_dbg (self, "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)) + mm_obj_warn (self, "cannot setup in-call modem state: %s", error->message); + g_clear_error (&error); + } else { + mm_obj_dbg (self, "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_obj_warn (self, "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_obj_dbg (self, "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_obj_dbg (self, "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 + * updates while a call is being established. Therefore, if there is no call + * being established (i.e. all terminated, unknown or active), then there is + * no polling to do. + * + * Any time we add a new call to the list, we'll setup polling if it's not + * already running, and the polling logic itself will decide when the polling + * should stop. + */ + +#define CALL_LIST_POLLING_TIMEOUT_SECS 2 + +typedef struct { + guint polling_id; + gboolean polling_ongoing; +} CallListPollingContext; + +static void +call_list_polling_context_free (CallListPollingContext *ctx) +{ + if (ctx->polling_id) + g_source_remove (ctx->polling_id); + g_slice_free (CallListPollingContext, ctx); +} + +static CallListPollingContext * +get_call_list_polling_context (MMIfaceModemVoice *self) +{ + CallListPollingContext *ctx; + + if (G_UNLIKELY (!call_list_polling_context_quark)) + call_list_polling_context_quark = (g_quark_from_static_string ( + CALL_LIST_POLLING_CONTEXT_TAG)); + + ctx = g_object_get_qdata (G_OBJECT (self), call_list_polling_context_quark); + if (!ctx) { + /* Create context and keep it as object data */ + ctx = g_slice_new0 (CallListPollingContext); + + g_object_set_qdata_full ( + G_OBJECT (self), + call_list_polling_context_quark, + ctx, + (GDestroyNotify)call_list_polling_context_free); + } + + return ctx; +} + +static gboolean call_list_poll (MMIfaceModemVoice *self); + +static void +load_call_list_ready (MMIfaceModemVoice *self, + GAsyncResult *res) +{ + CallListPollingContext *ctx; + GList *call_info_list = NULL; + GError *error = NULL; + + ctx = get_call_list_polling_context (self); + ctx->polling_ongoing = FALSE; + + g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list_finish); + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list_finish (self, res, &call_info_list, &error)) { + mm_obj_warn (self, "couldn't load call list: %s", error->message); + g_error_free (error); + } else { + /* Always report the list even if NULL (it would mean no ongoing calls) */ + mm_iface_modem_voice_report_all_calls (self, call_info_list); + mm_3gpp_call_info_list_free (call_info_list); + } + + /* setup the polling again, but only if it hasn't been done already while + * we reported calls (e.g. a new incoming call may have been detected that + * also triggers the poll setup) */ + if (!ctx->polling_id) + ctx->polling_id = g_timeout_add_seconds (CALL_LIST_POLLING_TIMEOUT_SECS, + (GSourceFunc) call_list_poll, + self); +} + +static void +call_list_foreach_count_establishing (MMBaseCall *call, + gpointer user_data) +{ + guint *n_calls_establishing = (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_RINGING_IN: + case MM_CALL_STATE_HELD: + case MM_CALL_STATE_WAITING: + *n_calls_establishing = *n_calls_establishing + 1; + break; + case MM_CALL_STATE_ACTIVE: + case MM_CALL_STATE_TERMINATED: + case MM_CALL_STATE_UNKNOWN: + default: + break; + } +} + +static gboolean +call_list_poll (MMIfaceModemVoice *self) +{ + CallListPollingContext *ctx; + MMCallList *list = NULL; + guint n_calls_establishing = 0; + + ctx = get_call_list_polling_context (self); + ctx->polling_id = 0; + + g_object_get (MM_BASE_MODEM (self), + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + if (!list) { + mm_obj_warn (self, "Cannot poll call list: missing internal call list"); + goto out; + } + + mm_call_list_foreach (list, (MMCallListForeachFunc) call_list_foreach_count_establishing, &n_calls_establishing); + + /* If there is at least ONE call being established, we need the call list */ + if (n_calls_establishing > 0) { + mm_obj_dbg (self, "%u calls being established: call list polling required", n_calls_establishing); + ctx->polling_ongoing = TRUE; + g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list); + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list (self, + (GAsyncReadyCallback)load_call_list_ready, + NULL); + } else + mm_obj_dbg (self, "no calls being established: call list polling stopped"); + +out: + g_clear_object (&list); + return G_SOURCE_REMOVE; +} + +static void +setup_call_list_polling (MMCallList *call_list, + const gchar *call_path_added, + MMIfaceModemVoice *self) +{ + CallListPollingContext *ctx; + + ctx = get_call_list_polling_context (self); + + if (!ctx->polling_id && !ctx->polling_ongoing) + ctx->polling_id = g_timeout_add_seconds (CALL_LIST_POLLING_TIMEOUT_SECS, + (GSourceFunc) call_list_poll, + self); +} + +/*****************************************************************************/ +/* Call list reload */ + +gboolean +mm_iface_modem_voice_reload_all_calls_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +reload_all_calls_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GList *call_info_list = NULL; + GError *error = NULL; + + g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list_finish); + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list_finish (self, res, &call_info_list, &error)) { + mm_obj_warn (self, "couldn't reload call list: %s", error->message); + + g_task_return_error (task, error); + } else { + /* Always report the list even if NULL (it would mean no ongoing calls) */ + mm_iface_modem_voice_report_all_calls (self, call_info_list); + mm_3gpp_call_info_list_free (call_info_list); + + g_task_return_boolean (task, TRUE); + } + + g_object_unref (task); +} + +void +mm_iface_modem_voice_reload_all_calls (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list (self, + (GAsyncReadyCallback)reload_all_calls_ready, + task); +} + +/*****************************************************************************/ + +static void +update_call_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); + + g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (skeleton)); +} + +static void +call_added (MMCallList *list, + const gchar *call_path, + MmGdbusModemVoice *skeleton) +{ + update_call_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) +{ + update_call_list (skeleton, list); + mm_gdbus_modem_voice_emit_call_deleted (skeleton, call_path); +} + +/*****************************************************************************/ + +typedef struct _DisablingContext DisablingContext; +static void interface_disabling_step (GTask *task); + +typedef enum { + DISABLING_STEP_FIRST, + DISABLING_STEP_DISABLE_UNSOLICITED_EVENTS, + DISABLING_STEP_CLEANUP_UNSOLICITED_EVENTS, + DISABLING_STEP_LAST +} DisablingStep; + +struct _DisablingContext { + DisablingStep step; + MmGdbusModemVoice *skeleton; +}; + +static void +disabling_context_free (DisablingContext *ctx) +{ + 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_task_propagate_boolean (G_TASK (res), error); +} + +static void +disable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + DisablingContext *ctx; + GError *error = NULL; + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->disable_unsolicited_events_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on to next step */ + ctx = g_task_get_task_data (task); + ctx->step++; + interface_disabling_step (task); +} + +static void +cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + DisablingContext *ctx; + GError *error = NULL; + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_unsolicited_events_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on to next step */ + ctx = g_task_get_task_data (task); + ctx->step++; + interface_disabling_step (task); +} + +static void +interface_disabling_step (GTask *task) +{ + MMIfaceModemVoice *self; + DisablingContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case DISABLING_STEP_FIRST: + ctx->step++; + /* fall through */ + + case DISABLING_STEP_DISABLE_UNSOLICITED_EVENTS: + /* Allow cleaning up unsolicited events */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->disable_unsolicited_events && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->disable_unsolicited_events_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->disable_unsolicited_events ( + self, + (GAsyncReadyCallback)disable_unsolicited_events_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case DISABLING_STEP_CLEANUP_UNSOLICITED_EVENTS: + /* Allow cleaning up unsolicited events */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_unsolicited_events && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_unsolicited_events_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)cleanup_unsolicited_events_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case DISABLING_STEP_LAST: + /* We are done without errors! */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + break; + } + + g_assert_not_reached (); +} + +void +mm_iface_modem_voice_disable (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DisablingContext *ctx; + GTask *task; + + ctx = g_new0 (DisablingContext, 1); + ctx->step = DISABLING_STEP_FIRST; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)disabling_context_free); + + g_object_get (self, + MM_IFACE_MODEM_VOICE_DBUS_SKELETON, &ctx->skeleton, + NULL); + if (!ctx->skeleton) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get interface skeleton"); + g_object_unref (task); + return; + } + + interface_disabling_step (task); +} + +/*****************************************************************************/ + +typedef struct _EnablingContext EnablingContext; +static void interface_enabling_step (GTask *task); + +typedef enum { + ENABLING_STEP_FIRST, + ENABLING_STEP_SETUP_UNSOLICITED_EVENTS, + ENABLING_STEP_ENABLE_UNSOLICITED_EVENTS, + ENABLING_STEP_LAST +} EnablingStep; + +struct _EnablingContext { + EnablingStep step; + MmGdbusModemVoice *skeleton; + guint mem1_storage_index; +}; + +static void +enabling_context_free (EnablingContext *ctx) +{ + if (ctx->skeleton) + g_object_unref (ctx->skeleton); + g_free (ctx); +} + +gboolean +mm_iface_modem_voice_enable_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +setup_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + EnablingContext *ctx; + GError *error = NULL; + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_unsolicited_events_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on to next step */ + ctx = g_task_get_task_data (task); + ctx->step++; + interface_enabling_step (task); +} + +static void +enable_unsolicited_events_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + EnablingContext *ctx; + GError *error = NULL; + + /* Not critical! */ + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->enable_unsolicited_events_finish (self, res, &error)) { + mm_obj_dbg (self, "couldn't enable unsolicited events: %s", error->message); + g_error_free (error); + } + + /* Go on with next step */ + ctx = g_task_get_task_data (task); + ctx->step++; + interface_enabling_step (task); +} + +static void +interface_enabling_step (GTask *task) +{ + MMIfaceModemVoice *self; + EnablingContext *ctx; + + /* Don't run new steps if we're cancelled */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case ENABLING_STEP_FIRST: + ctx->step++; + /* fall through */ + + case ENABLING_STEP_SETUP_UNSOLICITED_EVENTS: + /* Allow setting up unsolicited events to get notified of incoming calls */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_unsolicited_events && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_unsolicited_events_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)setup_unsolicited_events_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLING_STEP_ENABLE_UNSOLICITED_EVENTS: + /* Allow setting up unsolicited events to get notified of incoming calls */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->enable_unsolicited_events && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->enable_unsolicited_events_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)enable_unsolicited_events_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLING_STEP_LAST: + /* We are done without errors! */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + break; + } + + g_assert_not_reached (); +} + +void +mm_iface_modem_voice_enable (MMIfaceModemVoice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnablingContext *ctx; + GTask *task; + + ctx = g_new0 (EnablingContext, 1); + ctx->step = ENABLING_STEP_FIRST; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)enabling_context_free); + + g_object_get (self, + MM_IFACE_MODEM_VOICE_DBUS_SKELETON, &ctx->skeleton, + NULL); + if (!ctx->skeleton) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get interface skeleton"); + g_object_unref (task); + return; + } + + interface_enabling_step (task); +} + +/*****************************************************************************/ + +typedef struct _InitializationContext InitializationContext; +static void interface_initialization_step (GTask *task); + +typedef enum { + INITIALIZATION_STEP_FIRST, + INITIALIZATION_STEP_CHECK_SUPPORT, + INITIALIZATION_STEP_SETUP_CALL_LIST, + INITIALIZATION_STEP_LAST +} InitializationStep; + +struct _InitializationContext { + MmGdbusModemVoice *skeleton; + InitializationStep step; +}; + +static void +initialization_context_free (InitializationContext *ctx) +{ + g_object_unref (ctx->skeleton); + g_free (ctx); +} + +static void +check_support_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + InitializationContext *ctx; + GError *error = NULL; + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support_finish (self, res, &error)) { + if (error) { + mm_obj_dbg (self, "voice support check failed: %s", error->message); + g_error_free (error); + } + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Voice not supported"); + g_object_unref (task); + return; + } + + /* Go on to next step */ + ctx = g_task_get_task_data (task); + ctx->step++; + interface_initialization_step (task); +} + +static void +interface_initialization_step (GTask *task) +{ + MMIfaceModemVoice *self; + InitializationContext *ctx; + + /* Don't run new steps if we're cancelled */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case INITIALIZATION_STEP_FIRST: + ctx->step++; + /* fall through */ + + case INITIALIZATION_STEP_CHECK_SUPPORT: + /* Always check voice support when we run initialization, because + * the support may be different before and after SIM-PIN unlock. */ + if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support && + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support_finish) { + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support ( + self, + (GAsyncReadyCallback)check_support_ready, + task); + return; + } + + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Voice not supported"); + g_object_unref (task); + return; + + case INITIALIZATION_STEP_SETUP_CALL_LIST: { + MMCallList *list = NULL; + + g_object_get (self, + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + + /* Create a new call list if not already available (this initialization + * may be called multiple times) */ + if (!list) { + list = mm_call_list_new (MM_BASE_MODEM (self)); + g_object_set (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); + + /* 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) { + gboolean periodic_call_list_check_disabled = FALSE; + + /* Cleanup any previously configured handler, before checking if we need to + * add a new one, because the PERIODIC_CALL_LIST_CHECK_DISABLED flag may + * change before and after SIM-PIN unlock */ + g_signal_handlers_disconnect_by_func (list, G_CALLBACK (setup_call_list_polling), self); + + g_object_get (self, + MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, &periodic_call_list_check_disabled, + NULL); + if (!periodic_call_list_check_disabled) { + mm_obj_dbg (self, "periodic call list polling will be used if supported"); + g_signal_connect (list, + MM_CALL_ADDED, + G_CALLBACK (setup_call_list_polling), + self); + } + } + g_object_unref (list); + + ctx->step++; + } /* fall through */ + + case INITIALIZATION_STEP_LAST: + /* Setup all method handlers */ + g_object_connect (ctx->skeleton, + "signal::handle-create-call", G_CALLBACK (handle_create), self, + "signal::handle-delete-call", G_CALLBACK (handle_delete), self, + "signal::handle-list-calls", G_CALLBACK (handle_list), self, + "signal::handle-hangup-and-accept", G_CALLBACK (handle_hangup_and_accept), self, + "signal::handle-hold-and-accept", G_CALLBACK (handle_hold_and_accept), self, + "signal::handle-hangup-all", G_CALLBACK (handle_hangup_all), self, + "signal::handle-transfer", G_CALLBACK (handle_transfer), self, + "signal::handle-call-waiting-setup", G_CALLBACK (handle_call_waiting_setup), self, + "signal::handle-call-waiting-query", G_CALLBACK (handle_call_waiting_query), self, + NULL); + + /* Finally, export the new interface */ + mm_gdbus_object_skeleton_set_modem_voice (MM_GDBUS_OBJECT_SKELETON (self), + MM_GDBUS_MODEM_VOICE (ctx->skeleton)); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + break; + } + + g_assert_not_reached (); +} + +gboolean +mm_iface_modem_voice_initialize_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +modem_state_to_emergency_only (GBinding *binding, + const GValue *from_value, + GValue *to_value) +{ + MMModemState state; + + /* If the modem is REGISTERED, we allow any kind of call, otherwise + * only emergency calls */ + state = g_value_get_enum (from_value); + g_value_set_boolean (to_value, (state < MM_MODEM_STATE_REGISTERED)); + return TRUE; +} + +void +mm_iface_modem_voice_initialize (MMIfaceModemVoice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + InitializationContext *ctx; + MmGdbusModemVoice *skeleton = NULL; + GTask *task; + + /* 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); + + g_object_bind_property_full (self, MM_IFACE_MODEM_STATE, + skeleton, "emergency-only", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE, + (GBindingTransformFunc) modem_state_to_emergency_only, + NULL, NULL, NULL); + } + + /* Perform async initialization here */ + + ctx = g_new0 (InitializationContext, 1); + ctx->step = INITIALIZATION_STEP_FIRST; + ctx->skeleton = skeleton; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)initialization_context_free); + + interface_initialization_step (task); +} + +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)); + + g_object_interface_install_property + (g_iface, + g_param_spec_boolean (MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, + "Periodic call list checks disabled", + "Whether periodic call list check are disabled.", + FALSE, + G_PARAM_READWRITE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_boolean (MM_IFACE_MODEM_VOICE_INDICATION_CALL_LIST_RELOAD_ENABLED, + "Reload call list on call update", + "Ignore call updates and forcefully reload all calls.", + FALSE, + 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; +} |