diff options
-rw-r--r-- | cli/mmcli-call.c | 106 | ||||
-rw-r--r-- | docs/reference/libmm-glib/libmm-glib-sections.txt | 14 | ||||
-rw-r--r-- | introspection/org.freedesktop.ModemManager1.Call.xml | 27 | ||||
-rw-r--r-- | libmm-glib/mm-call.c | 150 | ||||
-rw-r--r-- | libmm-glib/mm-call.h | 25 | ||||
-rw-r--r-- | src/mm-base-call.c | 159 | ||||
-rw-r--r-- | src/mm-iface-modem-voice.c | 282 | ||||
-rw-r--r-- | src/mm-iface-modem-voice.h | 38 |
8 files changed, 801 insertions, 0 deletions
diff --git a/cli/mmcli-call.c b/cli/mmcli-call.c index f3fd9382..99c8b764 100644 --- a/cli/mmcli-call.c +++ b/cli/mmcli-call.c @@ -50,6 +50,8 @@ static gboolean info_flag; /* set when no action found */ static gboolean start_flag; static gboolean accept_flag; static gchar *deflect_str; +static gboolean join_multiparty_flag; +static gboolean leave_multiparty_flag; static gboolean hangup_flag; static gchar *dtmf_request; @@ -66,6 +68,14 @@ static GOptionEntry entries[] = { "Deflect the incoming call", "[NUMBER]", }, + { "join-multiparty", 0, 0, G_OPTION_ARG_NONE, &join_multiparty_flag, + "Join multiparty call", + NULL, + }, + { "leave-multiparty", 0, 0, G_OPTION_ARG_NONE, &leave_multiparty_flag, + "Leave multiparty call", + NULL, + }, { "hangup", 0, 0, G_OPTION_ARG_NONE, &hangup_flag, "Hang up the call", NULL, @@ -105,6 +115,8 @@ mmcli_call_options_enabled (void) n_actions = (start_flag + accept_flag + !!deflect_str + + join_multiparty_flag + + leave_multiparty_flag + hangup_flag + !!dtmf_request); @@ -262,6 +274,60 @@ deflect_ready (MMCall *call, } static void +join_multiparty_process_reply (gboolean result, + const GError *error) +{ + if (!result) { + g_printerr ("error: couldn't join multiparty call: '%s'\n", + error ? error->message : "unknown error"); + exit (EXIT_FAILURE); + } + + g_print ("successfully joined multiparty call\n"); +} + +static void +join_multiparty_ready (MMCall *call, + GAsyncResult *result, + gpointer nothing) +{ + gboolean operation_result; + GError *error = NULL; + + operation_result = mm_call_join_multiparty_finish (call, result, &error); + join_multiparty_process_reply (operation_result, error); + + mmcli_async_operation_done (); +} + +static void +leave_multiparty_process_reply (gboolean result, + const GError *error) +{ + if (!result) { + g_printerr ("error: couldn't leave multiparty call: '%s'\n", + error ? error->message : "unknown error"); + exit (EXIT_FAILURE); + } + + g_print ("successfully left multiparty call\n"); +} + +static void +leave_multiparty_ready (MMCall *call, + GAsyncResult *result, + gpointer nothing) +{ + gboolean operation_result; + GError *error = NULL; + + operation_result = mm_call_leave_multiparty_finish (call, result, &error); + leave_multiparty_process_reply (operation_result, error); + + mmcli_async_operation_done (); +} + +static void hangup_process_reply (gboolean result, const GError *error) { @@ -357,6 +423,24 @@ get_call_ready (GObject *source, return; } + /* Requesting to join multiparty call? */ + if (join_multiparty_flag) { + mm_call_join_multiparty (ctx->call, + ctx->cancellable, + (GAsyncReadyCallback)join_multiparty_ready, + NULL); + return; + } + + /* Requesting to leave multiparty call? */ + if (leave_multiparty_flag) { + mm_call_leave_multiparty (ctx->call, + ctx->cancellable, + (GAsyncReadyCallback)leave_multiparty_ready, + NULL); + return; + } + /* Requesting to hangup the call? */ if (hangup_flag) { mm_call_hangup (ctx->call, @@ -454,6 +538,28 @@ mmcli_call_run_synchronous (GDBusConnection *connection) return; } + /* Requesting to join multiparty call? */ + if (join_multiparty_flag) { + gboolean operation_result; + + operation_result = mm_call_join_multiparty_sync (ctx->call, + NULL, + &error); + join_multiparty_process_reply (operation_result, error); + return; + } + + /* Requesting to leave multiparty call? */ + if (leave_multiparty_flag) { + gboolean operation_result; + + operation_result = mm_call_leave_multiparty_sync (ctx->call, + NULL, + &error); + leave_multiparty_process_reply (operation_result, error); + return; + } + /* Requesting to hangup the call? */ if (hangup_flag) { gboolean operation_result; diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt index dc92cec5..690db8b4 100644 --- a/docs/reference/libmm-glib/libmm-glib-sections.txt +++ b/docs/reference/libmm-glib/libmm-glib-sections.txt @@ -1349,6 +1349,12 @@ mm_call_send_dtmf_sync mm_call_deflect mm_call_deflect_finish mm_call_deflect_sync +mm_call_join_multiparty +mm_call_join_multiparty_finish +mm_call_join_multiparty_sync +mm_call_leave_multiparty +mm_call_leave_multiparty_finish +mm_call_leave_multiparty_sync <SUBSECTION Standard> MMCallClass MMCallPrivate @@ -3264,6 +3270,12 @@ mm_gdbus_call_call_send_dtmf_sync mm_gdbus_call_call_deflect mm_gdbus_call_call_deflect_finish mm_gdbus_call_call_deflect_sync +mm_gdbus_call_call_join_multiparty +mm_gdbus_call_call_join_multiparty_finish +mm_gdbus_call_call_join_multiparty_sync +mm_gdbus_call_call_leave_multiparty +mm_gdbus_call_call_leave_multiparty_finish +mm_gdbus_call_call_leave_multiparty_sync <SUBSECTION Private> mm_gdbus_call_set_direction mm_gdbus_call_set_number @@ -3277,6 +3289,8 @@ mm_gdbus_call_complete_hangup mm_gdbus_call_complete_send_dtmf mm_gdbus_call_complete_start mm_gdbus_call_complete_deflect +mm_gdbus_call_complete_join_multiparty +mm_gdbus_call_complete_leave_multiparty mm_gdbus_call_interface_info mm_gdbus_call_override_properties mm_gdbus_call_emit_dtmf_received diff --git a/introspection/org.freedesktop.ModemManager1.Call.xml b/introspection/org.freedesktop.ModemManager1.Call.xml index d32b0836..34c01aac 100644 --- a/introspection/org.freedesktop.ModemManager1.Call.xml +++ b/introspection/org.freedesktop.ModemManager1.Call.xml @@ -53,6 +53,33 @@ </method> <!-- + JoinMultiparty: + + Join the currently held call into a single multiparty call with another + already active call. + + The calls will be flagged with the + '<link linkend="gdbus-property-org-freedesktop-ModemManager1-Call.Multiparty">Multiparty</link>' + property while they are part of the multiparty call. + + Applicable only if state is <link linkend="MM-CALL-STATE-HELD:CAPS"><constant>MM_CALL_STATE_HELD</constant></link>. + --> + <method name="JoinMultiparty" /> + + <!-- + LeaveMultiparty: + + If this call is part of an ongoing multiparty call, detach it from the multiparty call, + put the multiparty call on hold, and activate this one alone. This operation makes this + call private again between both ends of the call. + + Applicable only if state is <link linkend="MM-CALL-STATE-ACTIVE:CAPS"><constant>MM_CALL_STATE_ACTIVE</constant></link> or + <link linkend="MM-CALL-STATE-HELD:CAPS"><constant>MM_CALL_STATE_HELD</constant></link> and + the call is a multiparty call. + --> + <method name="LeaveMultiparty"/> + + <!-- Hangup: Hangup the active call. diff --git a/libmm-glib/mm-call.c b/libmm-glib/mm-call.c index 1ee624e2..91439f5a 100644 --- a/libmm-glib/mm-call.c +++ b/libmm-glib/mm-call.c @@ -592,6 +592,156 @@ mm_call_deflect_sync (MMCall *self, error); } +/*****************************************************************************/ + +/** + * mm_call_join_multiparty_finish: + * @self: A #MMCall. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_call_join_multiparty(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with mm_call_join_multiparty(). + * + * Returns: %TRUE if the operation succeeded, %FALSE if @error is set. + */ +gboolean +mm_call_join_multiparty_finish (MMCall *self, + GAsyncResult *res, + GError **error) +{ + g_return_val_if_fail (MM_IS_CALL (self), FALSE); + + return mm_gdbus_call_call_join_multiparty_finish (MM_GDBUS_CALL (self), res, error); +} + +/** + * mm_call_join_multiparty: + * @self: A #MMCall. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Synchronously requests to join this call into a multiparty call. + * + * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from. + * You can then call mm_call_join_multiparty_finish() to get the result of the operation. + * + * See mm_call_join_multiparty_sync() for the synchronous, blocking version of this method. + */ +void +mm_call_join_multiparty (MMCall *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_CALL (self)); + + mm_gdbus_call_call_join_multiparty (MM_GDBUS_CALL (self), + cancellable, + callback, + user_data); +} + +/** + * mm_call_join_multiparty_sync: + * @self: A #MMCall.g + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously requests to join this call into a multiparty call. + * + * The calling thread is blocked until an incoming call is ready. + * See mm_call_join_multiparty() for the asynchronous version of this method. + * + * Returns: %TRUE if the operation succeeded, %FALSE if @error is set. + */ +gboolean +mm_call_join_multiparty_sync (MMCall *self, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (MM_IS_CALL (self), FALSE); + + return mm_gdbus_call_call_join_multiparty_sync (MM_GDBUS_CALL (self), + cancellable, + error); +} + +/** + * mm_call_leave_multiparty_finish: + * @self: A #MMCall. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_call_leave_multiparty(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with mm_call_leave_multiparty(). + * + * Returns: %TRUE if the operation succeeded, %FALSE if @error is set. + */ +gboolean +mm_call_leave_multiparty_finish (MMCall *self, + GAsyncResult *res, + GError **error) +{ + g_return_val_if_fail (MM_IS_CALL (self), FALSE); + + return mm_gdbus_call_call_leave_multiparty_finish (MM_GDBUS_CALL (self), res, error); +} + +/** + * mm_call_leave_multiparty: + * @self: A #MMCall. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Synchronously requests to make this call private again by leaving the + * multiparty call. + * + * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from. + * You can then call mm_call_leave_multiparty_finish() to get the result of the operation. + * + * See mm_call_leave_multiparty_sync() for the synchronous, blocking version of this method. + */ +void +mm_call_leave_multiparty (MMCall *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_CALL (self)); + + mm_gdbus_call_call_leave_multiparty (MM_GDBUS_CALL (self), + cancellable, + callback, + user_data); +} + +/** + * mm_call_leave_multiparty_sync: + * @self: A #MMCall. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously requests to make this call private again by leaving the + * multiparty call. + * + * The calling thread is blocked until an incoming call is ready. + * See mm_call_leave_multiparty() for the asynchronous version of this method. + * + * Returns: %TRUE if the operation succeeded, %FALSE if @error is set. + */ +gboolean +mm_call_leave_multiparty_sync (MMCall *self, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (MM_IS_CALL (self), FALSE); + + return mm_gdbus_call_call_leave_multiparty_sync (MM_GDBUS_CALL (self), + cancellable, + error); +} + /*****************************************************************************/ /** diff --git a/libmm-glib/mm-call.h b/libmm-glib/mm-call.h index d073120f..3ae5a775 100644 --- a/libmm-glib/mm-call.h +++ b/libmm-glib/mm-call.h @@ -127,6 +127,31 @@ gboolean mm_call_deflect_sync (MMCall *self, GCancellable *cancellable, GError **error); +void mm_call_join_multiparty (MMCall *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean mm_call_join_multiparty_finish (MMCall *self, + GAsyncResult *res, + GError **error); + +gboolean mm_call_join_multiparty_sync (MMCall *self, + GCancellable *cancellable, + GError **error); + +void mm_call_leave_multiparty (MMCall *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean mm_call_leave_multiparty_finish (MMCall *self, + GAsyncResult *res, + GError **error); + +gboolean mm_call_leave_multiparty_sync (MMCall *self, + GCancellable *cancellable, + GError **error); void mm_call_hangup (MMCall *self, GCancellable *cancellable, diff --git a/src/mm-base-call.c b/src/mm-base-call.c index ccf1222f..a07e9c63 100644 --- a/src/mm-base-call.c +++ b/src/mm-base-call.c @@ -462,6 +462,157 @@ handle_deflect (MMBaseCall *self, } /*****************************************************************************/ +/* Join multiparty call (DBus call handling) */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GDBusMethodInvocation *invocation; +} HandleJoinMultipartyContext; + +static void +handle_join_multiparty_context_free (HandleJoinMultipartyContext *ctx) +{ + g_object_unref (ctx->invocation); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static void +modem_voice_join_multiparty_ready (MMIfaceModemVoice *modem, + GAsyncResult *res, + HandleJoinMultipartyContext *ctx) +{ + GError *error = NULL; + + if (!mm_iface_modem_voice_join_multiparty_finish (modem, res, &error)) + g_dbus_method_invocation_take_error (ctx->invocation, error); + else + mm_gdbus_call_complete_join_multiparty (MM_GDBUS_CALL (ctx->self), ctx->invocation); + handle_join_multiparty_context_free (ctx); +} + +static void +handle_join_multiparty_auth_ready (MMBaseModem *modem, + GAsyncResult *res, + HandleJoinMultipartyContext *ctx) +{ + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (modem, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_join_multiparty_context_free (ctx); + return; + } + + /* This action is provided in the Call API, but implemented in the Modem.Voice interface + * logic, because the action affects not only one call object, but all call objects that + * are part of the multiparty call. */ + mm_iface_modem_voice_join_multiparty (MM_IFACE_MODEM_VOICE (ctx->modem), + ctx->self, + (GAsyncReadyCallback)modem_voice_join_multiparty_ready, + ctx); +} + +static gboolean +handle_join_multiparty (MMBaseCall *self, + GDBusMethodInvocation *invocation) +{ + HandleJoinMultipartyContext *ctx; + + ctx = g_new0 (HandleJoinMultipartyContext, 1); + ctx->self = g_object_ref (self); + ctx->invocation = g_object_ref (invocation); + g_object_get (self, + MM_BASE_CALL_MODEM, &ctx->modem, + NULL); + + mm_base_modem_authorize (ctx->modem, + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_join_multiparty_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ +/* Leave multiparty call (DBus call handling) */ + +typedef struct { + MMBaseCall *self; + MMBaseModem *modem; + GDBusMethodInvocation *invocation; +} HandleLeaveMultipartyContext; + +static void +handle_leave_multiparty_context_free (HandleLeaveMultipartyContext *ctx) +{ + g_object_unref (ctx->invocation); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static void +modem_voice_leave_multiparty_ready (MMIfaceModemVoice *modem, + GAsyncResult *res, + HandleLeaveMultipartyContext *ctx) +{ + GError *error = NULL; + + if (!mm_iface_modem_voice_leave_multiparty_finish (modem, res, &error)) + g_dbus_method_invocation_take_error (ctx->invocation, error); + else + mm_gdbus_call_complete_leave_multiparty (MM_GDBUS_CALL (ctx->self), ctx->invocation); + + handle_leave_multiparty_context_free (ctx); +} + +static void +handle_leave_multiparty_auth_ready (MMBaseModem *modem, + GAsyncResult *res, + HandleLeaveMultipartyContext *ctx) +{ + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (modem, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + handle_leave_multiparty_context_free (ctx); + return; + } + + /* This action is provided in the Call API, but implemented in the Modem.Voice interface + * logic, because the action affects not only one call object, but all call objects that + * are part of the multiparty call. */ + mm_iface_modem_voice_leave_multiparty (MM_IFACE_MODEM_VOICE (ctx->modem), + ctx->self, + (GAsyncReadyCallback)modem_voice_leave_multiparty_ready, + ctx); +} + +static gboolean +handle_leave_multiparty (MMBaseCall *self, + GDBusMethodInvocation *invocation) +{ + HandleLeaveMultipartyContext *ctx; + + ctx = g_new0 (HandleLeaveMultipartyContext, 1); + ctx->self = g_object_ref (self); + ctx->invocation = g_object_ref (invocation); + g_object_get (self, + MM_BASE_CALL_MODEM, &ctx->modem, + NULL); + + mm_base_modem_authorize (ctx->modem, + invocation, + MM_AUTHORIZATION_VOICE, + (GAsyncReadyCallback)handle_leave_multiparty_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ /* Hangup call (DBus call handling) */ typedef struct { @@ -710,6 +861,14 @@ call_dbus_export (MMBaseCall *self) G_CALLBACK (handle_deflect), NULL); g_signal_connect (self, + "handle-join-multiparty", + G_CALLBACK (handle_join_multiparty), + NULL); + g_signal_connect (self, + "handle-leave-multiparty", + G_CALLBACK (handle_leave_multiparty), + NULL); + g_signal_connect (self, "handle-hangup", G_CALLBACK (handle_hangup), NULL); diff --git a/src/mm-iface-modem-voice.c b/src/mm-iface-modem-voice.c index b8fa2525..5ced2dd9 100644 --- a/src/mm-iface-modem-voice.c +++ b/src/mm-iface-modem-voice.c @@ -1262,6 +1262,288 @@ handle_transfer (MmGdbusModemVoice *skeleton, } /*****************************************************************************/ +/* 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; + default: + break; + } +} + +gboolean +mm_iface_modem_voice_leave_multiparty_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +leave_multiparty_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + LeaveMultipartyContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* If there is only one remaining call that was part of the multiparty, consider that + * one also no longer part of any multiparty, and put it on hold right away */ + if (g_list_length (ctx->other_calls) == 1) { + mm_base_call_set_multiparty (MM_BASE_CALL (ctx->other_calls->data), FALSE); + mm_base_call_change_state (MM_BASE_CALL (ctx->other_calls->data), MM_CALL_STATE_HELD, MM_CALL_STATE_REASON_UNKNOWN); + } + /* If there are still more than one calls in the multiparty, just change state of all + * of them. */ + else { + GList *l; + + for (l = ctx->other_calls; l; l = g_list_next (l)) + mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_HELD, MM_CALL_STATE_REASON_UNKNOWN); + } + + /* The call that left would now be active */ + mm_base_call_set_multiparty (ctx->call, FALSE); + mm_base_call_change_state (ctx->call, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_UNKNOWN); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_iface_modem_voice_leave_multiparty (MMIfaceModemVoice *self, + MMBaseCall *call, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LeaveMultipartyContext *ctx; + MMCallList *list = NULL; + MMCallState call_state; + + task = g_task_new (self, NULL, callback, user_data); + + /* validate multiparty status */ + if (!mm_base_call_get_multiparty (call)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "this call is not part of a multiparty call"); + g_object_unref (task); + return; + } + /* validate call state */ + call_state = mm_base_call_get_state (call); + if ((call_state != MM_CALL_STATE_ACTIVE) && (call_state != MM_CALL_STATE_HELD)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "invalid call state (%s): must be either active or held", + mm_call_state_get_string (call_state)); + g_object_unref (task); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty_finish) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot leave multiparty: unsupported"); + g_object_unref (task); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "Cannot leave multiparty: missing call list"); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (LeaveMultipartyContext); + ctx->call = g_object_ref (call); + g_task_set_task_data (task, ctx, (GDestroyNotify) leave_multiparty_context_free); + + mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_leave_multiparty_foreach, ctx); + g_object_unref (list); + + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty (self, + call, + (GAsyncReadyCallback)leave_multiparty_ready, + task); +} + +/*****************************************************************************/ +/* Join calls into a multiparty call */ + +typedef struct { + MMBaseCall *call; + GList *all_calls; + gboolean added; +} JoinMultipartyContext; + +static void +join_multiparty_context_free (JoinMultipartyContext *ctx) +{ + g_list_free_full (ctx->all_calls, g_object_unref); + g_object_unref (ctx->call); + g_slice_free (JoinMultipartyContext, ctx); +} + +static void +prepare_join_multiparty_foreach (MMBaseCall *call, + JoinMultipartyContext *ctx) +{ + /* always add call that is being added */ + if ((call == ctx->call) || (g_strcmp0 (mm_base_call_get_path (call), mm_base_call_get_path (ctx->call)) == 0)) + ctx->added = TRUE; + + /* ignore calls not currently ongoing */ + switch (mm_base_call_get_state (call)) { + case MM_CALL_STATE_ACTIVE: + case MM_CALL_STATE_HELD: + ctx->all_calls = g_list_append (ctx->all_calls, g_object_ref (call)); + break; + default: + break; + } +} + +gboolean +mm_iface_modem_voice_join_multiparty_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +join_multiparty_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + JoinMultipartyContext *ctx; + GList *l; + + ctx = g_task_get_task_data (task); + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty_finish (self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + for (l = ctx->all_calls; l; l = g_list_next (l)) { + mm_base_call_set_multiparty (MM_BASE_CALL (l->data), TRUE); + mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_UNKNOWN); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_iface_modem_voice_join_multiparty (MMIfaceModemVoice *self, + MMBaseCall *call, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + JoinMultipartyContext *ctx; + MMCallList *list = NULL; + MMCallState call_state; + + task = g_task_new (self, NULL, callback, user_data); + + /* validate multiparty status */ + if (mm_base_call_get_multiparty (call)) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "this call is already part of a multiparty call"); + g_object_unref (task); + return; + } + /* validate call state */ + call_state = mm_base_call_get_state (call); + if (call_state != MM_CALL_STATE_HELD) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "invalid call state (%s): must be held", + mm_call_state_get_string (call_state)); + g_object_unref (task); + return; + } + + if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty || + !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty_finish) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot join multiparty: unsupported"); + g_object_unref (task); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_VOICE_CALL_LIST, &list, + NULL); + if (!list) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "Cannot join multiparty: missing call list"); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (JoinMultipartyContext); + ctx->call = g_object_ref (call); + g_task_set_task_data (task, ctx, (GDestroyNotify) join_multiparty_context_free); + + mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_join_multiparty_foreach, ctx); + g_object_unref (list); + + /* our logic makes sure that we would be adding the incoming call into the multiparty call */ + g_assert (ctx->added); + + /* NOTE: we do not give the call we want to join, because the join operation acts on all + * active/held calls. */ + MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty (self, + (GAsyncReadyCallback)join_multiparty_ready, + task); +} + +/*****************************************************************************/ /* In-call setup operation * * It will setup URC handlers for all in-call URCs, and also setup the audio diff --git a/src/mm-iface-modem-voice.h b/src/mm-iface-modem-voice.h index 1f28ea36..fc7b0476 100644 --- a/src/mm-iface-modem-voice.h +++ b/src/mm-iface-modem-voice.h @@ -150,6 +150,23 @@ struct _MMIfaceModemVoice { GAsyncResult *res, GError **error); + /* Join multiparty */ + void (* join_multiparty) (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* join_multiparty_finish) (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + + /* Leave multiparty */ + void (* leave_multiparty) (MMIfaceModemVoice *self, + MMBaseCall *call, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* leave_multiparty_finish) (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + /* Transfer */ void (* transfer) (MMIfaceModemVoice *self, GAsyncReadyCallback callback, @@ -207,4 +224,25 @@ void mm_iface_modem_voice_received_dtmf (MMIfaceModemVoice *self, guint index, const gchar *dtmf); +/* Join/Leave multiparty calls + * + * These actions are provided in the Call API, but implemented in the + * modem Voice interface because they really affect multiple calls at + * the same time. + */ +void mm_iface_modem_voice_join_multiparty (MMIfaceModemVoice *self, + MMBaseCall *call, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_iface_modem_voice_join_multiparty_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); +void mm_iface_modem_voice_leave_multiparty (MMIfaceModemVoice *self, + MMBaseCall *call, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_iface_modem_voice_leave_multiparty_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error); + #endif /* MM_IFACE_MODEM_VOICE_H */ |