Rather than make all of them rely on MMBaseModem for it. This lets us disentangle dependencies for easier unit testing. For interfaces, rather than casting directly to MMBaseModem use intermediate interfaces (MMIfaceAuth and MMIfaceOpLock) that tests can fake out. Signed-off-by: Dan Williams <dan@ioncontrol.co>
3209 lines
108 KiB
C
3209 lines
108 KiB
C
/* -*- 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-error-helpers.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;
|
|
|
|
G_DEFINE_INTERFACE (MMIfaceModemVoice, mm_iface_modem_voice, MM_TYPE_IFACE_MODEM)
|
|
|
|
static void setup_call_list_polling (MMCallList *call_list,
|
|
const gchar *call_path_added,
|
|
MMIfaceModemVoice *self);
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_IFACE (self)->create_call != NULL);
|
|
|
|
call = MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->create_call != NULL);
|
|
call = MM_IFACE_MODEM_VOICE_GET_IFACE (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 (MMIfaceAuth *auth,
|
|
GAsyncResult *res,
|
|
HandleDeleteContext *ctx)
|
|
{
|
|
MMIfaceModemVoice *self = MM_IFACE_MODEM_VOICE (auth);
|
|
MMCallList *list = NULL;
|
|
GError *error = NULL;
|
|
|
|
if (!mm_iface_auth_authorize_finish (auth, res, &error)) {
|
|
mm_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) {
|
|
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
|
|
"Cannot delete call: missing call list");
|
|
handle_delete_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_obj_info (self, "processing user request to delete voice call '%s'...", ctx->path);
|
|
if (!mm_call_list_delete_call (list, ctx->path, &error))
|
|
mm_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_iface_auth_authorize (MM_IFACE_AUTH (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 (MMIfaceAuth *auth,
|
|
GAsyncResult *res,
|
|
HandleCreateContext *ctx)
|
|
{
|
|
MMIfaceModemVoice *self = MM_IFACE_MODEM_VOICE (auth);
|
|
MMCallList *list = NULL;
|
|
GError *error = NULL;
|
|
MMCallProperties *properties;
|
|
MMBaseCall *call;
|
|
|
|
if (!mm_iface_auth_authorize_finish (auth, res, &error)) {
|
|
mm_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) {
|
|
mm_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);
|
|
mm_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);
|
|
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
|
|
"Cannot create call: missing call list");
|
|
handle_create_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_obj_info (self, "processing user request to create voice call...");
|
|
|
|
/* 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_iface_auth_authorize (MM_IFACE_AUTH (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) {
|
|
mm_dbus_method_invocation_return_error_literal (invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
|
|
"Cannot list call: missing call list");
|
|
return TRUE;
|
|
}
|
|
|
|
mm_obj_info (self, "processing user request to list voice calls...");
|
|
|
|
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_IFACE (self)->hold_and_accept_finish (self, res, &error)) {
|
|
mm_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 (MMIfaceAuth *auth,
|
|
GAsyncResult *res,
|
|
HandleHoldAndAcceptContext *ctx)
|
|
{
|
|
MMIfaceModemVoice *self = MM_IFACE_MODEM_VOICE (auth);
|
|
GError *error = NULL;
|
|
MMCallList *list = NULL;
|
|
|
|
if (!mm_iface_auth_authorize_finish (auth, res, &error)) {
|
|
mm_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_hold_and_accept_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
if (!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->hold_and_accept ||
|
|
!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->hold_and_accept_finish) {
|
|
mm_dbus_method_invocation_return_error_literal (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) {
|
|
mm_dbus_method_invocation_return_error_literal (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_obj_info (self, "processing user request to hold and accept voice call...");
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_iface_auth_authorize (MM_IFACE_AUTH (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_IFACE (self)->hangup_and_accept_finish (self, res, &error)) {
|
|
mm_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 (MMIfaceAuth *auth,
|
|
GAsyncResult *res,
|
|
HandleHangupAndAcceptContext *ctx)
|
|
{
|
|
MMIfaceModemVoice *self = MM_IFACE_MODEM_VOICE (auth);
|
|
GError *error = NULL;
|
|
MMCallList *list = NULL;
|
|
|
|
if (!mm_iface_auth_authorize_finish (auth, res, &error)) {
|
|
mm_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_hangup_and_accept_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
if (!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->hangup_and_accept ||
|
|
!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->hangup_and_accept_finish) {
|
|
mm_dbus_method_invocation_return_error_literal (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) {
|
|
mm_dbus_method_invocation_return_error_literal (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_obj_info (self, "processing user request to hangup and accept voice call...");
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_iface_auth_authorize (MM_IFACE_AUTH (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_IFACE (self)->hangup_all_finish (self, res, &error)) {
|
|
mm_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 implementers, 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 (MMIfaceAuth *auth,
|
|
GAsyncResult *res,
|
|
HandleHangupAllContext *ctx)
|
|
{
|
|
MMIfaceModemVoice *self = MM_IFACE_MODEM_VOICE (auth);
|
|
GError *error = NULL;
|
|
MMCallList *list = NULL;
|
|
|
|
if (!mm_iface_auth_authorize_finish (auth, res, &error)) {
|
|
mm_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_hangup_all_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
if (!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->hangup_all ||
|
|
!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->hangup_all_finish) {
|
|
mm_dbus_method_invocation_return_error_literal (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) {
|
|
mm_dbus_method_invocation_return_error_literal (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_obj_info (self, "processing user request to hangup all voice calls...");
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_iface_auth_authorize (MM_IFACE_AUTH (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_IFACE (self)->transfer_finish (self, res, &error)) {
|
|
mm_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 (MMIfaceAuth *auth,
|
|
GAsyncResult *res,
|
|
HandleTransferContext *ctx)
|
|
{
|
|
MMIfaceModemVoice *self = MM_IFACE_MODEM_VOICE (auth);
|
|
GError *error = NULL;
|
|
MMCallList *list = NULL;
|
|
|
|
if (!mm_iface_auth_authorize_finish (auth, res, &error)) {
|
|
mm_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_transfer_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
if (!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->transfer ||
|
|
!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->transfer_finish) {
|
|
mm_dbus_method_invocation_return_error_literal (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) {
|
|
mm_dbus_method_invocation_return_error_literal (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_obj_info (self, "processing user request to transfer voice call...");
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_iface_auth_authorize (MM_IFACE_AUTH (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_IFACE (self)->call_waiting_setup_finish (self, res, &error)) {
|
|
mm_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 (MMIfaceAuth *auth,
|
|
GAsyncResult *res,
|
|
HandleCallWaitingSetupContext *ctx)
|
|
{
|
|
MMIfaceModemVoice *self = MM_IFACE_MODEM_VOICE (auth);
|
|
GError *error = NULL;
|
|
|
|
if (!mm_iface_auth_authorize_finish (auth, res, &error)) {
|
|
mm_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_call_waiting_setup_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
if (!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->call_waiting_setup ||
|
|
!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->call_waiting_setup_finish) {
|
|
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot setup call waiting: unsupported");
|
|
handle_call_waiting_setup_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_obj_info (self, "processing user request to setup voice call waiting...");
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_iface_auth_authorize (MM_IFACE_AUTH (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_IFACE (self)->call_waiting_query_finish (self, res, &status, &error)) {
|
|
mm_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 (MMIfaceAuth *auth,
|
|
GAsyncResult *res,
|
|
HandleCallWaitingQueryContext *ctx)
|
|
{
|
|
MMIfaceModemVoice *self = MM_IFACE_MODEM_VOICE (auth);
|
|
GError *error = NULL;
|
|
|
|
if (!mm_iface_auth_authorize_finish (auth, res, &error)) {
|
|
mm_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_call_waiting_query_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
if (!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->call_waiting_query ||
|
|
!MM_IFACE_MODEM_VOICE_GET_IFACE (self)->call_waiting_query_finish) {
|
|
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot query call waiting: unsupported");
|
|
handle_call_waiting_query_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_obj_info (self, "processing user request to query voice call waiting state...");
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_iface_auth_authorize (MM_IFACE_AUTH (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_IFACE (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_IFACE (self)->leave_multiparty ||
|
|
!MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (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_IFACE (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_IFACE (self)->join_multiparty ||
|
|
!MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (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_CHECK_POLLING,
|
|
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_IFACE (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_IFACE (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_IFACE (self)->setup_in_call_unsolicited_events &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->setup_in_call_unsolicited_events_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->setup_in_call_audio_channel &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->setup_in_call_audio_channel_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_CHECK_POLLING:
|
|
setup_call_list_polling (NULL, NULL, self);
|
|
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_IFACE (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_IFACE (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_IFACE (self)->cleanup_in_call_audio_channel &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->cleanup_in_call_audio_channel_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->cleanup_in_call_unsolicited_events &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->cleanup_in_call_unsolicited_events_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->load_call_list_finish);
|
|
if (!MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->load_call_list);
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->load_call_list_finish);
|
|
if (!MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (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_IFACE (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_IFACE (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_IFACE (self)->disable_unsolicited_events &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->disable_unsolicited_events_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->cleanup_unsolicited_events &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->cleanup_unsolicited_events_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (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_IFACE (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_IFACE (self)->setup_unsolicited_events &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->setup_unsolicited_events_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->enable_unsolicited_events &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->enable_unsolicited_events_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (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_IFACE (self)->check_support &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (self)->check_support_finish) {
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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_IFACE (self)->load_call_list &&
|
|
MM_IFACE_MODEM_VOICE_GET_IFACE (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
|
|
mm_iface_modem_voice_default_init (MMIfaceModemVoiceInterface *iface)
|
|
{
|
|
static gsize initialized = 0;
|
|
|
|
if (!g_once_init_enter (&initialized))
|
|
return;
|
|
|
|
/* Properties */
|
|
g_object_interface_install_property (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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));
|
|
|
|
g_once_init_leave (&initialized, 1);
|
|
}
|