aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
3 files changed, 479 insertions, 0 deletions
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 */