aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2019-07-02 17:53:25 +0200
committerAleksander Morgado <aleksander@aleksander.es>2019-07-11 23:21:00 +0200
commit511b0ff2442f26257be442963ac833f5ba06f80c (patch)
tree8ce57d58f35f162ebb5d3a5be23cbabc51c20a67
parent6f235192393fbc345bf79bcf8205d7279f584f91 (diff)
api,call: new JoinMultiparty() and LeaveMultiparty() methods
-rw-r--r--cli/mmcli-call.c106
-rw-r--r--docs/reference/libmm-glib/libmm-glib-sections.txt14
-rw-r--r--introspection/org.freedesktop.ModemManager1.Call.xml27
-rw-r--r--libmm-glib/mm-call.c150
-rw-r--r--libmm-glib/mm-call.h25
-rw-r--r--src/mm-base-call.c159
-rw-r--r--src/mm-iface-modem-voice.c282
-rw-r--r--src/mm-iface-modem-voice.h38
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 */