Instead of hard-coding a 500ms DTMF tone duration, let applications use a specific duration at call setup time. Signed-off-by: Dan Williams <dan@ioncontrol.co>
3215 lines
108 KiB
C
3215 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, 0);
|
|
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;
|
|
guint dtmf_tone_duration;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
dtmf_tone_duration = mm_call_properties_get_dtmf_tone_duration (properties) ;
|
|
|
|
/* 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,
|
|
dtmf_tone_duration);
|
|
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 ();
|
|
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);
|
|
}
|