| /* -*- 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); |
| } |