Merge request !1336 from 'dtmf-serialize'

Serialize DTMF requests and allow setting DTMF duration at call creation time

https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/1336

Closes #970
This commit is contained in:
Dan Williams
2025-05-30 18:54:05 -05:00
30 changed files with 2995 additions and 619 deletions
@@ -103,9 +103,15 @@
<!--
SendDtmf:
@dtmf: DTMF tone identifier [0-9A-D*#].
@dtmf: A string of DTMF tone identifiers [0-9A-D*#] and/or pause characters [,].
Send a DTMF tone (Dual Tone Multi-Frequency) (only on supported modem).
Send one or more DTMF tones (Dual Tone Multi-Frequency) (only on supported modems).
Before 1.26 only the first character in @dtmf was sent to the modem;
all others were discarded.
Since 1.26 up to 50 tone identifiers are accepted and each will be
sent to the modem in the order given. The comma [,] character pauses
DTMF tones for two-seconds then continues with the remaining characters.
Applicable only if state is <link linkend="MM-CALL-STATE-ACTIVE:CAPS"><constant>MM_CALL_STATE_ACTIVE</constant></link>.
@@ -119,7 +125,7 @@
DtmfReceived:
@dtmf: DTMF tone identifier [0-9A-D*#].
Emitted when a DTMF tone is received (only on supported modem)
Emitted when a DTMF tone is received (only on supported modems)
Since: 1.6
-->
@@ -232,5 +238,18 @@
Since: 1.10
-->
<property name="AudioFormat" type="a{sv}" access="read" />
<!--
DtmfToneDuration:
The length of DTMF tones, in milliseconds. Valid range is 100ms to
1000ms and is rounded up to the next 100ms if not evenly divisble
by 100. Set the duration by passing this property to
<link linkend="gdbus-method-org-freedesktop-ModemManager1-Voice.CreateCall">CreateCall()</link>
at call creation time.
Since: 1.26
-->
<property name="DtmfToneDuration" type="u" access="read" />
</interface>
</node>
+77 -2
View File
@@ -45,9 +45,11 @@
G_DEFINE_TYPE (MMCallProperties, mm_call_properties, G_TYPE_OBJECT)
#define PROPERTY_NUMBER "number"
#define PROPERTY_DTMF_TONE_DURATION "dtmf-tone-duration"
struct _MMCallPropertiesPrivate {
gchar *number;
guint dtmf_tone_duration;
};
/*****************************************************************************/
@@ -92,6 +94,43 @@ mm_call_properties_get_number (MMCallProperties *self)
/*****************************************************************************/
/**
* mm_call_properties_set_dtmf_tone_duration:
* @self: A #MMCallProperties.
* @duration_ms: The desired duration of DTMF tones in milliseconds.
*
* Sets the DTMF tone duration if supported by the modem.
*
* Since: 1.26
*/
void
mm_call_properties_set_dtmf_tone_duration (MMCallProperties *self,
const guint duration_ms)
{
g_return_if_fail (MM_IS_CALL_PROPERTIES (self));
self->priv->dtmf_tone_duration = duration_ms;
}
/**
* mm_call_properties_get_dtmf_tone_duration:
* @self: A #MMCallProperties.
*
* Gets the desired DTMF tone duration in milliseconds.
*
* Returns: the DTMF tone duration in milliseconds.
*
* Since: 1.26
*/
guint
mm_call_properties_get_dtmf_tone_duration (MMCallProperties *self)
{
g_return_val_if_fail (MM_IS_CALL_PROPERTIES (self), 0);
return self->priv->dtmf_tone_duration;
}
/*****************************************************************************/
/*
* mm_call_properties_get_dictionary: (skip)
*/
@@ -114,6 +153,12 @@ mm_call_properties_get_dictionary (MMCallProperties *self)
PROPERTY_NUMBER,
g_variant_new_string (self->priv->number));
if (self->priv->dtmf_tone_duration)
g_variant_builder_add (&builder,
"{sv}",
PROPERTY_DTMF_TONE_DURATION,
g_variant_new_uint32 (self->priv->dtmf_tone_duration));
return g_variant_ref_sink (g_variant_builder_end (&builder));
}
@@ -126,6 +171,18 @@ consume_string (MMCallProperties *self,
{
if (g_str_equal (key, PROPERTY_NUMBER)) {
mm_call_properties_set_number (self, value);
} else if (g_str_equal (key, PROPERTY_DTMF_TONE_DURATION)) {
guint num;
if (!mm_get_uint_from_str (value, &num)) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Failed to parse DTMF tone duration from value '%s'",
value);
return FALSE;
}
mm_call_properties_set_dtmf_tone_duration (self, num);
} else {
g_set_error (error,
MM_CORE_ERROR,
@@ -189,11 +246,29 @@ consume_variant (MMCallProperties *properties,
GVariant *value,
GError **error)
{
if (g_str_equal (key, PROPERTY_NUMBER))
if (g_str_equal (key, PROPERTY_NUMBER)) {
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Invalid properties dictionary; number not a string");
return FALSE;
}
mm_call_properties_set_number (
properties,
g_variant_get_string (value, NULL));
else {
} else if (g_str_equal (key, PROPERTY_DTMF_TONE_DURATION)) {
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32)) {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Invalid properties dictionary; dtmf-tone-duration not a uint32");
return FALSE;
}
mm_call_properties_set_dtmf_tone_duration (
properties,
g_variant_get_uint32 (value));
} else {
/* Set error */
g_set_error (error,
MM_CORE_ERROR,
+4
View File
@@ -68,6 +68,10 @@ void mm_call_properties_set_number (MMCallProperties *self,
const gchar *text);
const gchar *mm_call_properties_get_number (MMCallProperties *self);
void mm_call_properties_set_dtmf_tone_duration (MMCallProperties *self,
const guint duration_ms);
guint mm_call_properties_get_dtmf_tone_duration (MMCallProperties *self);
/*****************************************************************************/
/* ModemManager/libmm-glib/mmcli specific methods */
+59 -14
View File
@@ -315,17 +315,11 @@ daemon_enums_types_dep = declare_dependency(
include_directories: '.',
)
# Additional vendor plugins
subdir('plugins')
# ModemManager daemon
sources = files(
'main.c',
'mm-auth-provider.c',
base_sources = files(
'mm-base-bearer.c',
'mm-base-call.c',
'mm-call-at.c',
'mm-base-cbm.c',
'mm-base-manager.c',
'mm-base-modem-at.c',
'mm-base-modem.c',
'mm-base-sim.c',
@@ -340,7 +334,6 @@ sources = files(
'mm-dispatcher-connection.c',
'mm-dispatcher-fcc-unlock.c',
'mm-dispatcher-modem-setup.c',
'mm-filter.c',
'mm-iface-modem-3gpp.c',
'mm-iface-modem-3gpp-profile-manager.c',
'mm-iface-modem-3gpp-ussd.c',
@@ -358,15 +351,68 @@ sources = files(
'mm-iface-modem-voice.c',
'mm-iface-op-lock.c',
'mm-log-helpers.c',
'mm-plugin.c',
'mm-plugin-manager.c',
'mm-port-probe.c',
'mm-port-probe-at.c',
'mm-private-boxed-types.c',
'mm-sleep-context.c',
)
# MM base library (used by MM and tests)
incs = [
top_inc,
kerneldevice_inc,
]
deps = [
libmm_glib_dep,
libhelpers_dep,
libauth_dep,
libport_dep,
libqcdm_dep,
daemon_enums_types_dep,
]
private_deps = []
c_args = [
'-DMM_COMPILATION',
'-DPLUGINDIR="@0@"'.format(mm_prefix / mm_pkglibdir),
'-DMODEMSETUPDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'modem-setup.d'),
'-DMODEMSETUPDIRUSER="@0@"'.format(mm_prefix / mm_pkgsysconfdir / 'modem-setup.d'),
'-DFCCUNLOCKDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'fcc-unlock.d'),
'-DFCCUNLOCKDIRUSER="@0@"'.format(mm_prefix / mm_pkgsysconfdir / 'fcc-unlock.d'),
'-DCONNECTIONDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'connection.d'),
'-DCONNECTIONDIRUSER="@0@"'.format(mm_prefix / mm_pkgsysconfdir / 'connection.d'),
]
libmmbase = static_library(
'mmbase',
sources: base_sources + daemon_enums_sources,
include_directories: incs,
dependencies: deps,
c_args: c_args,
)
libmmbase_dep = declare_dependency(
include_directories: ['.', kerneldevice_inc],
dependencies: deps,
link_with: libmmbase,
)
# Additional vendor plugins
subdir('plugins')
# ModemManager daemon
sources = files(
'main.c',
'mm-base-manager.c',
'mm-filter.c',
'mm-plugin.c',
'mm-plugin-manager.c',
'mm-port-probe.c',
'mm-port-probe-at.c',
)
sources += daemon_enums_sources
sources += base_sources
deps = [
gmodule_dep,
@@ -382,7 +428,6 @@ endif
c_args = [
'-DMM_COMPILATION',
'-DPLUGINDIR="@0@"'.format(mm_prefix / mm_pkglibdir),
'-DMODEMSETUPDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'modem-setup.d'),
'-DMODEMSETUPDIRUSER="@0@"'.format(mm_prefix / mm_pkgsysconfdir / 'modem-setup.d'),
'-DFCCUNLOCKDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'fcc-unlock.d'),
+354 -367
View File
@@ -28,10 +28,8 @@
#include "mm-base-call.h"
#include "mm-broadband-modem.h"
#include "mm-iface-modem.h"
#include "mm-auth-provider.h"
#include "mm-iface-modem-voice.h"
#include "mm-base-modem-at.h"
#include "mm-base-modem.h"
#include "mm-log-object.h"
#include "mm-modem-helpers.h"
#include "mm-error-helpers.h"
@@ -49,7 +47,7 @@ enum {
PROP_PATH,
PROP_CONNECTION,
PROP_BIND_TO,
PROP_MODEM,
PROP_IFACE_MODEM_VOICE,
PROP_SKIP_INCOMING_TIMEOUT,
PROP_SUPPORTS_DIALING_TO_RINGING,
PROP_SUPPORTS_RINGING_TO_ACTIVE,
@@ -70,8 +68,8 @@ struct _MMBaseCallPrivate {
/* The object this Call is bound to */
GObject *bind_to;
/* The modem which owns this call */
MMBaseModem *modem;
/* The voice interface which owns this call */
MMIfaceModemVoice *iface;
/* The path where the call object is exported */
gchar *path;
/* Features */
@@ -91,6 +89,9 @@ struct _MMBaseCallPrivate {
* 'terminated' is coming asynchronously (e.g. via in-call state
* update notifications) */
GCancellable *start_cancellable;
/* DTMF support */
GQueue *dtmf_queue;
};
/*****************************************************************************/
@@ -239,7 +240,7 @@ handle_start_auth_ready (MMAuthProvider *authp,
mm_obj_info (ctx->self, "processing user request to start voice call...");
/* Disallow non-emergency calls when in emergency-only state */
if (!mm_iface_modem_voice_authorize_outgoing_call (MM_IFACE_MODEM_VOICE (ctx->self->priv->modem), ctx->self, &error)) {
if (!mm_iface_modem_voice_authorize_outgoing_call (ctx->self->priv->iface, ctx->self, &error)) {
mm_base_call_change_state (ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_UNKNOWN);
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_start_context_free (ctx);
@@ -534,7 +535,7 @@ handle_join_multiparty_auth_ready (MMAuthProvider *authp,
/* 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->self->priv->modem),
mm_iface_modem_voice_join_multiparty (ctx->self->priv->iface,
ctx->self,
(GAsyncReadyCallback)modem_voice_join_multiparty_ready,
ctx);
@@ -608,7 +609,7 @@ handle_leave_multiparty_auth_ready (MMAuthProvider *authp,
/* 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->self->priv->modem),
mm_iface_modem_voice_leave_multiparty (ctx->self->priv->iface,
ctx->self,
(GAsyncReadyCallback)modem_voice_leave_multiparty_ready,
ctx);
@@ -730,6 +731,283 @@ handle_hangup (MMBaseCall *self,
/*****************************************************************************/
/* Send dtmf (DBus call handling) */
typedef enum {
DTMF_STEP_FIRST,
DTMF_STEP_START,
DTMF_STEP_TIMEOUT,
DTMF_STEP_STOP,
DTMF_STEP_NEXT,
DTMF_STEP_LAST,
} DtmfStep;
typedef struct {
DtmfStep step;
GError *saved_error;
guint8 call_id;
/* Array of DTMF runs; split by pauses */
GPtrArray *dtmfs;
/* index into dtmfs */
guint cur_dtmf;
/* index into cur dtmf run string */
gchar *cur_tone;
guint timeout_id;
} SendDtmfContext;
static void
send_dtmf_context_clear_timeout (SendDtmfContext *ctx)
{
if (ctx->timeout_id) {
g_source_remove (ctx->timeout_id);
ctx->timeout_id = 0;
}
}
static void
send_dtmf_context_free (SendDtmfContext *ctx)
{
send_dtmf_context_clear_timeout (ctx);
g_ptr_array_foreach (ctx->dtmfs, (GFunc) g_free, NULL);
g_ptr_array_free (ctx->dtmfs, TRUE);
g_assert (!ctx->saved_error);
g_slice_free (SendDtmfContext, ctx);
}
static void send_dtmf_task_step_next (GTask *task);
static void
stop_dtmf_ignore_ready (MMBaseCall *self,
GAsyncResult *res,
gpointer unused)
{
/* Ignore the result and error */
MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish (self, res, NULL);
}
static void
send_dtmf_task_cancel (GTask *task)
{
MMBaseCall *self;
SendDtmfContext *ctx;
ctx = g_task_get_task_data (task);
self = g_task_get_source_object (task);
send_dtmf_context_clear_timeout (ctx);
if (ctx->step > DTMF_STEP_FIRST && ctx->step < DTMF_STEP_STOP) {
if (MM_BASE_CALL_GET_CLASS (self)->stop_dtmf) {
MM_BASE_CALL_GET_CLASS (self)->stop_dtmf (self,
(GAsyncReadyCallback)stop_dtmf_ignore_ready,
NULL);
}
}
g_assert (ctx->step != DTMF_STEP_LAST);
ctx->step = DTMF_STEP_LAST;
send_dtmf_task_step_next (task);
}
static void
stop_dtmf_ready (MMBaseCall *self,
GAsyncResult *res,
GTask *task)
{
SendDtmfContext *ctx;
GError *error = NULL;
gboolean success;
ctx = g_task_get_task_data (task);
success = MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish (self, res, &error);
if (ctx->step == DTMF_STEP_STOP) {
if (!success) {
g_propagate_error (&ctx->saved_error, error);
ctx->step = DTMF_STEP_LAST;
} else
ctx->step++;
send_dtmf_task_step_next (task);
}
/* Balance stop_dtmf() */
g_object_unref (task);
}
static gboolean
dtmf_timeout (GTask *task)
{
SendDtmfContext *ctx;
ctx = g_task_get_task_data (task);
/* If this was a pause character; move past it */
if (ctx->cur_tone[0] == MM_CALL_DTMF_PAUSE_CHAR)
ctx->cur_tone++;
send_dtmf_context_clear_timeout (ctx);
ctx->step++;
send_dtmf_task_step_next (task);
return G_SOURCE_REMOVE;
}
static void
send_dtmf_ready (MMBaseCall *self,
GAsyncResult *res,
GTask *task)
{
SendDtmfContext *ctx;
GError *error = NULL;
gssize num_sent;
ctx = g_task_get_task_data (task);
num_sent = MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error);
if (ctx->step == DTMF_STEP_START) {
if (num_sent < 0) {
g_propagate_error (&ctx->saved_error, error);
ctx->step = DTMF_STEP_LAST;
} else {
g_assert (num_sent > 0);
g_assert ((guint) num_sent <= strlen (ctx->cur_tone));
ctx->cur_tone += num_sent;
ctx->step++;
}
send_dtmf_task_step_next (task);
}
/* Balance send_dtmf() */
g_object_unref (task);
}
static void
send_dtmf_task_step_next (GTask *task)
{
SendDtmfContext *ctx;
gboolean need_stop;
MMBaseCall *self;
gboolean is_pause;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
is_pause = (ctx->cur_tone[0] == MM_CALL_DTMF_PAUSE_CHAR);
need_stop = MM_BASE_CALL_GET_CLASS (self)->stop_dtmf &&
MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish;
switch (ctx->step) {
case DTMF_STEP_FIRST:
ctx->step++;
/* Fall through */
case DTMF_STEP_START:
if (!is_pause) {
MM_BASE_CALL_GET_CLASS (self)->send_dtmf (self,
ctx->cur_tone,
(GAsyncReadyCallback)send_dtmf_ready,
g_object_ref (task));
return;
}
/* Fall through */
case DTMF_STEP_TIMEOUT:
if (need_stop || is_pause) {
guint duration;
duration = is_pause ? 2000 : mm_base_call_get_dtmf_tone_duration (self);
/* Disable DTMF press after DTMF tone duration elapses */
ctx->timeout_id = g_timeout_add (duration,
(GSourceFunc) dtmf_timeout,
task);
return;
}
/* Fall through */
case DTMF_STEP_STOP:
send_dtmf_context_clear_timeout (ctx);
if (need_stop && !is_pause) {
MM_BASE_CALL_GET_CLASS (self)->stop_dtmf (self,
(GAsyncReadyCallback)stop_dtmf_ready,
g_object_ref (task));
return;
}
/* Fall through */
case DTMF_STEP_NEXT:
/* Advance to next DTMF run? */
if (ctx->cur_tone[0] == '\0') {
ctx->cur_dtmf++;
if (ctx->cur_dtmf < ctx->dtmfs->len)
ctx->cur_tone = g_ptr_array_index (ctx->dtmfs, ctx->cur_dtmf);
}
/* More to send? */
if (ctx->cur_tone[0]) {
ctx->step = DTMF_STEP_START;
send_dtmf_task_step_next (task);
return;
}
/* no more DTMF characters to send */
/* Fall through */
case DTMF_STEP_LAST:
send_dtmf_context_clear_timeout (ctx);
if (ctx->saved_error)
g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
/* Start the next tone if any are queued */
g_queue_remove (self->priv->dtmf_queue, task);
task = g_queue_peek_head (self->priv->dtmf_queue);
if (task)
send_dtmf_task_step_next (task);
break;
default:
g_assert_not_reached ();
}
}
static gboolean
send_dtmf_task_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static GTask *
send_dtmf_task_new (MMBaseCall *self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data,
GError **error)
{
GTask *task;
SendDtmfContext *ctx;
guint8 call_id;
call_id = mm_base_call_get_index (self);
if (call_id == 0) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Invalid call index");
return NULL;
}
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (SendDtmfContext);
ctx->call_id = call_id;
/* Split DTMF into runs of DTMF characters interrupted by pauses */
ctx->dtmfs = mm_dtmf_split (dtmf);
g_assert (ctx->dtmfs->len > 0);
ctx->cur_tone = g_ptr_array_index (ctx->dtmfs, ctx->cur_dtmf);
g_task_set_task_data (task, ctx, (GDestroyNotify) send_dtmf_context_free);
return task;
}
/*****************************************************************************/
/* Send DTMF D-Bus request handling */
typedef struct {
MMBaseCall *self;
GDBusMethodInvocation *invocation;
@@ -752,7 +1030,7 @@ handle_send_dtmf_ready (MMBaseCall *self,
{
GError *error = NULL;
if (!MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error)) {
if (!send_dtmf_task_finish (self, res, &error)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
} else {
mm_gdbus_call_complete_send_dtmf (MM_GDBUS_CALL (ctx->self), ctx->invocation);
@@ -768,6 +1046,7 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
{
MMCallState state;
GError *error = NULL;
GTask *task;
if (!mm_auth_provider_authorize_finish (authp, res, &error)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -777,7 +1056,23 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self));
/* Check if we do support doing it */
/* Ensure there are DTMF characters to send */
if (!ctx->dtmf || !ctx->dtmf[0]) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"No DTMF characters given");
handle_send_dtmf_context_free (ctx);
return;
}
/* And that there aren't too many */
if (strlen (ctx->dtmf) > 50) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"Too many DTMF characters");
handle_send_dtmf_context_free (ctx);
return;
}
/* Check if we do support doing DTMF at all */
if (!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf ||
!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf_finish) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
@@ -787,7 +1082,7 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
}
/* We can only send_dtmf when call is in ACTIVE state */
if (state != MM_CALL_STATE_ACTIVE ){
if (state != MM_CALL_STATE_ACTIVE) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"This call was not active, cannot send dtmf");
handle_send_dtmf_context_free (ctx);
@@ -795,9 +1090,20 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
}
mm_obj_info (ctx->self, "processing user request to send DTMF...");
MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf (ctx->self, ctx->dtmf,
task = send_dtmf_task_new (ctx->self,
ctx->dtmf,
(GAsyncReadyCallback)handle_send_dtmf_ready,
ctx);
ctx,
&error);
if (!task) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_send_dtmf_context_free (ctx);
return;
}
g_queue_push_tail (ctx->self->priv->dtmf_queue, task);
if (g_queue_get_length (ctx->self->priv->dtmf_queue) == 1)
send_dtmf_task_step_next (task);
}
static gboolean
@@ -924,6 +1230,20 @@ mm_base_call_set_multiparty (MMBaseCall *self,
return mm_gdbus_call_set_multiparty (MM_GDBUS_CALL (self), multiparty);
}
guint
mm_base_call_get_dtmf_tone_duration (MMBaseCall *self)
{
return mm_dtmf_duration_normalize (mm_gdbus_call_get_dtmf_tone_duration (MM_GDBUS_CALL (self)));
}
void
mm_base_call_set_dtmf_tone_duration (MMBaseCall *self,
guint duration_ms)
{
return mm_gdbus_call_set_dtmf_tone_duration (MM_GDBUS_CALL (self),
mm_dtmf_duration_normalize (duration_ms));
}
/*****************************************************************************/
/* Current call index, only applicable while the call is ongoing
* See 3GPP TS 22.030 [27], subclause 6.5.5.1.
@@ -988,314 +1308,6 @@ mm_base_call_received_dtmf (MMBaseCall *self,
mm_gdbus_call_emit_dtmf_received (MM_GDBUS_CALL (self), dtmf);
}
/*****************************************************************************/
/* Start the CALL */
static gboolean
call_start_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
call_start_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
const gchar *response = NULL;
response = mm_base_modem_at_command_finish (modem, res, &error);
/* check response for error */
if (response && response[0])
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't start the call: Unhandled response '%s'", response);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_start (MMBaseCall *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GError *error = NULL;
GTask *task;
gchar *cmd;
MMIfacePortAt *port;
task = g_task_new (self, NULL, callback, user_data);
port = mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self->priv->modem), &error);
if (!port) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
cmd = g_strdup_printf ("ATD%s;", mm_gdbus_call_get_number (MM_GDBUS_CALL (self)));
mm_base_modem_at_command_full (self->priv->modem,
port,
cmd,
90,
FALSE, /* no cached */
FALSE, /* no raw */
cancellable,
(GAsyncReadyCallback)call_start_ready,
task);
g_free (cmd);
}
/*****************************************************************************/
/* Accept the call */
static gboolean
call_accept_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
call_accept_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
const gchar *response;
response = mm_base_modem_at_command_finish (modem, res, &error);
/* check response for error */
if (response && response[0])
g_set_error (&error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't accept the call: Unhandled response '%s'", response);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_accept (MMBaseCall *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
mm_base_modem_at_command (self->priv->modem,
"ATA",
2,
FALSE,
(GAsyncReadyCallback)call_accept_ready,
task);
}
/*****************************************************************************/
/* Deflect the call */
static gboolean
call_deflect_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
call_deflect_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_finish (modem, res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_deflect (MMBaseCall *self,
const gchar *number,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
gchar *cmd;
task = g_task_new (self, NULL, callback, user_data);
cmd = g_strdup_printf ("+CTFR=%s", number);
mm_base_modem_at_command (self->priv->modem,
cmd,
20,
FALSE,
(GAsyncReadyCallback)call_deflect_ready,
task);
g_free (cmd);
}
/*****************************************************************************/
/* Hangup the call */
static gboolean
call_hangup_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
chup_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_finish (modem, res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
chup_fallback (GTask *task)
{
MMBaseCall *self;
self = g_task_get_source_object (task);
mm_base_modem_at_command (self->priv->modem,
"+CHUP",
2,
FALSE,
(GAsyncReadyCallback)chup_ready,
task);
}
static void
chld_hangup_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMBaseCall *self;
GError *error = NULL;
self = g_task_get_source_object (task);
mm_base_modem_at_command_finish (modem, res, &error);
if (error) {
mm_obj_warn (self, "couldn't hangup single call with call id '%u': %s",
self->priv->index, error->message);
g_error_free (error);
chup_fallback (task);
return;
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_hangup (MMBaseCall *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Try to hangup the single call id */
if (self->priv->index) {
gchar *cmd;
cmd = g_strdup_printf ("+CHLD=1%u", self->priv->index);
mm_base_modem_at_command (self->priv->modem,
cmd,
2,
FALSE,
(GAsyncReadyCallback)chld_hangup_ready,
task);
g_free (cmd);
return;
}
/* otherwise terminate all */
chup_fallback (task);
}
/*****************************************************************************/
/* Send DTMF tone to call */
static gboolean
call_send_dtmf_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
call_send_dtmf_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMBaseCall *self;
GError *error = NULL;
self = g_task_get_source_object (task);
mm_base_modem_at_command_finish (modem, res, &error);
if (error) {
mm_obj_dbg (self, "couldn't send dtmf: %s", error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_send_dtmf (MMBaseCall *self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
gchar *cmd;
task = g_task_new (self, NULL, callback, user_data);
cmd = g_strdup_printf ("AT+VTS=%c", dtmf[0]);
mm_base_modem_at_command (self->priv->modem,
cmd,
3,
FALSE,
(GAsyncReadyCallback)call_send_dtmf_ready,
task);
g_free (cmd);
}
/*****************************************************************************/
static gchar *
@@ -1309,28 +1321,6 @@ log_object_build_id (MMLogObject *_self)
/*****************************************************************************/
MMBaseCall *
mm_base_call_new (MMBaseModem *modem,
GObject *bind_to,
MMCallDirection direction,
const gchar *number,
gboolean skip_incoming_timeout,
gboolean supports_dialing_to_ringing,
gboolean supports_ringing_to_active)
{
return MM_BASE_CALL (g_object_new (MM_TYPE_BASE_CALL,
MM_BASE_CALL_MODEM, modem,
MM_BIND_TO, bind_to,
"direction", direction,
"number", number,
MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, skip_incoming_timeout,
MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, supports_dialing_to_ringing,
MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, supports_ringing_to_active,
NULL));
}
/*****************************************************************************/
static void
set_property (GObject *object,
guint prop_id,
@@ -1365,9 +1355,9 @@ set_property (GObject *object,
self->priv->bind_to = g_value_dup_object (value);
mm_bind_to (MM_BIND (self), MM_BASE_CALL_CONNECTION, self->priv->bind_to);
break;
case PROP_MODEM:
g_clear_object (&self->priv->modem);
self->priv->modem = g_value_dup_object (value);
case PROP_IFACE_MODEM_VOICE:
g_clear_object (&self->priv->iface);
self->priv->iface = g_value_dup_object (value);
break;
case PROP_SKIP_INCOMING_TIMEOUT:
self->priv->skip_incoming_timeout = g_value_get_boolean (value);
@@ -1402,8 +1392,8 @@ get_property (GObject *object,
case PROP_BIND_TO:
g_value_set_object (value, self->priv->bind_to);
break;
case PROP_MODEM:
g_value_set_object (value, self->priv->modem);
case PROP_IFACE_MODEM_VOICE:
g_value_set_object (value, self->priv->iface);
break;
case PROP_SKIP_INCOMING_TIMEOUT:
g_value_set_boolean (value, self->priv->skip_incoming_timeout);
@@ -1434,6 +1424,8 @@ mm_base_call_init (MMBaseCall *self)
/* Setup authorization provider */
self->priv->authp = mm_auth_provider_get ();
self->priv->authp_cancellable = g_cancellable_new ();
self->priv->dtmf_queue = g_queue_new ();
}
static void
@@ -1441,6 +1433,9 @@ finalize (GObject *object)
{
MMBaseCall *self = MM_BASE_CALL (object);
g_assert (g_queue_get_length (self->priv->dtmf_queue) == 0);
g_queue_free (g_steal_pointer (&self->priv->dtmf_queue));
g_assert (!self->priv->start_cancellable);
g_free (self->priv->path);
@@ -1466,11 +1461,14 @@ dispose (GObject *object)
g_clear_object (&self->priv->connection);
}
g_clear_object (&self->priv->modem);
g_clear_object (&self->priv->iface);
g_clear_object (&self->priv->bind_to);
g_cancellable_cancel (self->priv->authp_cancellable);
g_clear_object (&self->priv->authp_cancellable);
g_queue_foreach (self->priv->dtmf_queue, (GFunc) send_dtmf_task_cancel, NULL);
g_queue_clear (self->priv->dtmf_queue);
G_OBJECT_CLASS (mm_base_call_parent_class)->dispose (object);
}
@@ -1498,17 +1496,6 @@ mm_base_call_class_init (MMBaseCallClass *klass)
object_class->finalize = finalize;
object_class->dispose = dispose;
klass->start = call_start;
klass->start_finish = call_start_finish;
klass->accept = call_accept;
klass->accept_finish = call_accept_finish;
klass->deflect = call_deflect;
klass->deflect_finish = call_deflect_finish;
klass->hangup = call_hangup;
klass->hangup_finish = call_hangup_finish;
klass->send_dtmf = call_send_dtmf;
klass->send_dtmf_finish = call_send_dtmf_finish;
properties[PROP_CONNECTION] =
g_param_spec_object (MM_BASE_CALL_CONNECTION,
"Connection",
@@ -1527,13 +1514,13 @@ mm_base_call_class_init (MMBaseCallClass *klass)
g_object_class_override_property (object_class, PROP_BIND_TO, MM_BIND_TO);
properties[PROP_MODEM] =
g_param_spec_object (MM_BASE_CALL_MODEM,
"Modem",
"The Modem which owns this call",
MM_TYPE_BASE_MODEM,
properties[PROP_IFACE_MODEM_VOICE] =
g_param_spec_object (MM_BASE_CALL_IFACE_MODEM_VOICE,
"Modem Voice Interface",
"The Modem voice interface which owns this call",
MM_TYPE_IFACE_MODEM_VOICE,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]);
g_object_class_install_property (object_class, PROP_IFACE_MODEM_VOICE, properties[PROP_IFACE_MODEM_VOICE]);
properties[PROP_SKIP_INCOMING_TIMEOUT] =
g_param_spec_boolean (MM_BASE_CALL_SKIP_INCOMING_TIMEOUT,
+22 -13
View File
@@ -23,8 +23,8 @@
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-base-modem.h"
#include "mm-call-audio-format.h"
#include "mm-port.h"
#define MM_TYPE_BASE_CALL (mm_base_call_get_type ())
#define MM_BASE_CALL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BASE_CALL, MMBaseCall))
@@ -37,9 +37,13 @@ typedef struct _MMBaseCall MMBaseCall;
typedef struct _MMBaseCallClass MMBaseCallClass;
typedef struct _MMBaseCallPrivate MMBaseCallPrivate;
#define MM_CALL_DIRECTION "direction"
#define MM_CALL_NUMBER "number"
#define MM_CALL_DTMF_TONE_DURATION "dtmf-tone-duration"
#define MM_BASE_CALL_PATH "call-path"
#define MM_BASE_CALL_CONNECTION "call-connection"
#define MM_BASE_CALL_MODEM "call-modem"
#define MM_BASE_CALL_IFACE_MODEM_VOICE "call-iface-modem-voice"
#define MM_BASE_CALL_SKIP_INCOMING_TIMEOUT "call-skip-incoming-timeout"
#define MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING "call-supports-dialing-to-ringing"
#define MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE "call-supports-ringing-to-active"
@@ -86,28 +90,28 @@ struct _MMBaseCallClass {
GAsyncResult *res,
GError **error);
/* Send a DTMF tone */
/* DTMF tone handling */
void (* send_dtmf) (MMBaseCall *self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean (* send_dtmf_finish) (MMBaseCall *self,
/* Returns the number of DTMF characters sent, or < 1 on error */
gssize (* send_dtmf_finish) (MMBaseCall *self,
GAsyncResult *res,
GError **error);
void (* stop_dtmf) (MMBaseCall *self,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean (* stop_dtmf_finish) (MMBaseCall *self,
GAsyncResult *res,
GError **error);
};
GType mm_base_call_get_type (void);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMBaseCall, g_object_unref)
/* This one can be overridden by plugins */
MMBaseCall *mm_base_call_new (MMBaseModem *modem,
GObject *bind_to,
MMCallDirection direction,
const gchar *number,
gboolean skip_incoming_timeout,
gboolean supports_dialing_to_ringing,
gboolean supports_ringing_to_active);
void mm_base_call_export (MMBaseCall *self);
void mm_base_call_unexport (MMBaseCall *self);
@@ -138,4 +142,9 @@ void mm_base_call_received_dtmf (MMBaseCall *self,
void mm_base_call_incoming_refresh (MMBaseCall *self);
guint mm_base_call_get_dtmf_tone_duration (MMBaseCall *self);
void mm_base_call_set_dtmf_tone_duration (MMBaseCall *self,
guint duration_ms);
#endif /* MM_BASE_CALL_H */
+4 -2
View File
@@ -12190,11 +12190,13 @@ modem_voice_load_call_list (MMIfaceModemVoice *self,
static MMBaseCall *
modem_voice_create_call (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number)
const gchar *number,
const guint dtmf_tone_duration)
{
return mm_call_qmi_new (MM_BASE_MODEM (self),
direction,
number);
number,
dtmf_tone_duration);
}
/*****************************************************************************/
+5 -2
View File
@@ -50,6 +50,7 @@
#include "mm-bearer-list.h"
#include "mm-cbm-list.h"
#include "mm-cbm-part.h"
#include "mm-call-at.h"
#include "mm-sms-list.h"
#include "mm-sms-part-3gpp.h"
#include "mm-sms-at.h"
@@ -8898,14 +8899,16 @@ modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
static MMBaseCall *
modem_voice_create_call (MMIfaceModemVoice *_self,
MMCallDirection direction,
const gchar *number)
const gchar *number,
const guint dtmf_tone_duration)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
return mm_base_call_new (MM_BASE_MODEM (self),
return mm_call_at_new (MM_BASE_MODEM (self),
G_OBJECT (self),
direction,
number,
dtmf_tone_duration,
/* If +CLCC is supported, we want no incoming timeout.
* Also, we're able to support detailed call state updates without
* additional vendor-specific commands. */
+474
View File
@@ -0,0 +1,474 @@
/* -*- 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 Riccardo Vangelisti <riccardo.vangelisti@sadel.it>
* Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
* Copyright (C) 2019 Purism SPC
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-call-at.h"
#include "mm-base-modem-at.h"
#include "mm-base-modem.h"
#include "mm-modem-helpers.h"
#include "mm-error-helpers.h"
#include "mm-log-object.h"
#include "mm-bind.h"
G_DEFINE_TYPE (MMCallAt, mm_call_at, MM_TYPE_BASE_CALL)
typedef enum {
FEATURE_SUPPORT_UNKNOWN,
FEATURE_NOT_SUPPORTED,
FEATURE_SUPPORTED,
} FeatureSupport;
struct _MMCallAtPrivate {
/* The modem which owns this call */
MMBaseModem *modem;
/* DTMF support */
FeatureSupport vtd_supported;
};
/*****************************************************************************/
/* Start the CALL */
static gboolean
call_start_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
call_start_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
const gchar *response = NULL;
response = mm_base_modem_at_command_finish (modem, res, &error);
/* check response for error */
if (response && response[0])
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't start the call: Unhandled response '%s'", response);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_start (MMBaseCall *_self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMCallAt *self = MM_CALL_AT (_self);
GError *error = NULL;
GTask *task;
gchar *cmd;
MMIfacePortAt *port;
task = g_task_new (self, NULL, callback, user_data);
port = mm_base_modem_peek_best_at_port (self->priv->modem, &error);
if (!port) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
cmd = g_strdup_printf ("ATD%s;", mm_gdbus_call_get_number (MM_GDBUS_CALL (self)));
mm_base_modem_at_command_full (self->priv->modem,
port,
cmd,
90,
FALSE, /* no cached */
FALSE, /* no raw */
cancellable,
(GAsyncReadyCallback)call_start_ready,
task);
g_free (cmd);
}
/*****************************************************************************/
/* Accept the call */
static gboolean
call_accept_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
call_accept_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
const gchar *response;
response = mm_base_modem_at_command_finish (modem, res, &error);
/* check response for error */
if (response && response[0])
g_set_error (&error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't accept the call: Unhandled response '%s'", response);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_accept (MMBaseCall *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMCallAt *self = MM_CALL_AT (_self);
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
mm_base_modem_at_command (self->priv->modem,
"ATA",
2,
FALSE,
(GAsyncReadyCallback)call_accept_ready,
task);
}
/*****************************************************************************/
/* Deflect the call */
static gboolean
call_deflect_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
call_deflect_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_finish (modem, res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_deflect (MMBaseCall *_self,
const gchar *number,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMCallAt *self = MM_CALL_AT (_self);
GTask *task;
gchar *cmd;
task = g_task_new (self, NULL, callback, user_data);
cmd = g_strdup_printf ("+CTFR=%s", number);
mm_base_modem_at_command (self->priv->modem,
cmd,
20,
FALSE,
(GAsyncReadyCallback)call_deflect_ready,
task);
g_free (cmd);
}
/*****************************************************************************/
/* Hangup the call */
static gboolean
call_hangup_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
chup_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_finish (modem, res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
chup_fallback (GTask *task)
{
MMCallAt *self;
self = MM_CALL_AT (g_task_get_source_object (task));
mm_base_modem_at_command (self->priv->modem,
"+CHUP",
2,
FALSE,
(GAsyncReadyCallback)chup_ready,
task);
}
static void
chld_hangup_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMBaseCall *self;
GError *error = NULL;
self = g_task_get_source_object (task);
mm_base_modem_at_command_finish (modem, res, &error);
if (error) {
mm_obj_warn (self, "couldn't hangup single call with call id '%u': %s",
mm_base_call_get_index (MM_BASE_CALL (self)), error->message);
g_error_free (error);
chup_fallback (task);
return;
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_hangup (MMBaseCall *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMCallAt *self = MM_CALL_AT (_self);
GTask *task;
guint index;
task = g_task_new (self, NULL, callback, user_data);
/* Try to hangup the single call id */
index = mm_base_call_get_index (MM_BASE_CALL (self));
if (index) {
gchar *cmd;
cmd = g_strdup_printf ("+CHLD=1%u", index);
mm_base_modem_at_command (self->priv->modem,
cmd,
2,
FALSE,
(GAsyncReadyCallback)chld_hangup_ready,
task);
g_free (cmd);
return;
}
/* otherwise terminate all */
chup_fallback (task);
}
/*****************************************************************************/
/* Send DTMF tone to call */
static gssize
call_send_dtmf_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_int (G_TASK (res), error);
}
static void
call_send_dtmf_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMBaseCall *self;
GError *error = NULL;
self = g_task_get_source_object (task);
mm_base_modem_at_command_finish (modem, res, &error);
if (error) {
mm_obj_dbg (self, "couldn't send dtmf: %s", error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* We sent one character */
g_task_return_int (task, 1);
g_object_unref (task);
}
static void
send_dtmf_digit (MMCallAt *self,
GTask *task,
const gchar dtmf_digit)
{
g_autofree gchar *cmd = NULL;
cmd = g_strdup_printf ("AT+VTS=%c", dtmf_digit);
mm_base_modem_at_command (self->priv->modem,
cmd,
3,
FALSE,
(GAsyncReadyCallback)call_send_dtmf_ready,
task);
}
static void
call_dtmf_vtd_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMCallAt *self;
g_autoptr(GError) error = NULL;
gchar dtmf_digit;
self = g_task_get_source_object (task);
mm_base_modem_at_command_finish (modem, res, &error);
self->priv->vtd_supported = error ? FEATURE_NOT_SUPPORTED : FEATURE_SUPPORTED;
dtmf_digit = (gchar) GPOINTER_TO_UINT (g_task_get_task_data (task));
send_dtmf_digit (self, task, dtmf_digit);
}
static void
call_send_dtmf (MMBaseCall *_self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMCallAt *self = MM_CALL_AT (_self);
GTask *task;
g_autofree gchar *cmd = NULL;
task = g_task_new (self, NULL, callback, user_data);
if (self->priv->vtd_supported == FEATURE_NOT_SUPPORTED) {
send_dtmf_digit (self, task, dtmf[0]);
return;
}
g_task_set_task_data (task, GUINT_TO_POINTER (dtmf[0]), NULL);
/* Otherwise try to set duration */
cmd = g_strdup_printf ("AT+VTD=%u", mm_base_call_get_dtmf_tone_duration (_self));
mm_base_modem_at_command (self->priv->modem,
cmd,
3,
FALSE,
(GAsyncReadyCallback)call_dtmf_vtd_ready,
task);
}
/*****************************************************************************/
MMBaseCall *
mm_call_at_new (MMBaseModem *modem,
GObject *bind_to,
MMCallDirection direction,
const gchar *number,
const guint dtmf_tone_duration,
gboolean skip_incoming_timeout,
gboolean supports_dialing_to_ringing,
gboolean supports_ringing_to_active)
{
MMBaseCall *call;
call = MM_BASE_CALL (g_object_new (MM_TYPE_CALL_AT,
MM_BASE_CALL_IFACE_MODEM_VOICE, modem,
MM_BIND_TO, bind_to,
MM_CALL_DIRECTION, direction,
MM_CALL_NUMBER, number,
MM_CALL_DTMF_TONE_DURATION, dtmf_tone_duration,
MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, skip_incoming_timeout,
MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, supports_dialing_to_ringing,
MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, supports_ringing_to_active,
NULL));
MM_CALL_AT (call)->priv->modem = g_object_ref (modem);
return call;
}
static void
mm_call_at_init (MMCallAt *self)
{
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_AT, MMCallAtPrivate);
}
static void
dispose (GObject *object)
{
MMCallAt *self = MM_CALL_AT (object);
g_clear_object (&self->priv->modem);
G_OBJECT_CLASS (mm_call_at_parent_class)->dispose (object);
}
static void
mm_call_at_class_init (MMCallAtClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MMBaseCallClass *base_call_class = MM_BASE_CALL_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMCallAtPrivate));
object_class->dispose = dispose;
base_call_class->start = call_start;
base_call_class->start_finish = call_start_finish;
base_call_class->accept = call_accept;
base_call_class->accept_finish = call_accept_finish;
base_call_class->deflect = call_deflect;
base_call_class->deflect_finish = call_deflect_finish;
base_call_class->hangup = call_hangup;
base_call_class->hangup_finish = call_hangup_finish;
base_call_class->send_dtmf = call_send_dtmf;
base_call_class->send_dtmf_finish = call_send_dtmf_finish;
}
+61
View File
@@ -0,0 +1,61 @@
/* -*- 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 Riccardo Vangelisti <riccardo.vangelisti@sadel.it>
* Copyright (C) 2019 Purism SPC
*/
#ifndef MM_CALL_AT_H
#define MM_CALL_AT_H
#include <glib.h>
#include <glib-object.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-base-call.h"
#include "mm-base-modem.h"
#define MM_TYPE_CALL_AT (mm_call_at_get_type ())
#define MM_CALL_AT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CALL_AT, MMCallAt))
#define MM_CALL_AT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_CALL_AT, MMCallAtClass))
#define MM_IS_CALL_AT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CALL_AT))
#define MM_IS_CALL_AT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CALL_AT))
#define MM_CALL_AT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CALL_AT, MMCallAtClass))
typedef struct _MMCallAt MMCallAt;
typedef struct _MMCallAtClass MMCallAtClass;
typedef struct _MMCallAtPrivate MMCallAtPrivate;
struct _MMCallAt {
MMBaseCall parent;
MMCallAtPrivate *priv;
};
struct _MMCallAtClass {
MMBaseCallClass parent;
};
GType mm_call_at_get_type (void);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCallAt, g_object_unref)
MMBaseCall *mm_call_at_new (MMBaseModem *modem,
GObject *bind_to,
MMCallDirection direction,
const gchar *number,
const guint dtmf_tone_duration,
gboolean skip_incoming_timeout,
gboolean supports_dialing_to_ringing,
gboolean supports_ringing_to_active);
#endif /* MM_CALL_AT_H */
+2 -63
View File
@@ -24,20 +24,12 @@
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-iface-modem-messaging.h"
#include "mm-call-list.h"
#include "mm-base-call.h"
#include "mm-log.h"
G_DEFINE_TYPE (MMCallList, mm_call_list, G_TYPE_OBJECT)
enum {
PROP_0,
PROP_MODEM,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
enum {
SIGNAL_CALL_ADDED,
SIGNAL_CALL_DELETED,
@@ -46,8 +38,6 @@ enum {
static guint signals[SIGNAL_LAST];
struct _MMCallListPrivate {
/* The owner modem */
MMBaseModem *modem;
/* List of call objects */
GList *list;
};
@@ -213,49 +203,10 @@ mm_call_list_add_call (MMCallList *self,
/*****************************************************************************/
MMCallList *
mm_call_list_new (MMBaseModem *modem)
mm_call_list_new (void)
{
/* Create the object */
return g_object_new (MM_TYPE_CALL_LIST,
MM_CALL_LIST_MODEM, modem,
NULL);
}
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MMCallList *self = MM_CALL_LIST (object);
switch (prop_id) {
case PROP_MODEM:
g_clear_object (&self->priv->modem);
self->priv->modem = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MMCallList *self = MM_CALL_LIST (object);
switch (prop_id) {
case PROP_MODEM:
g_value_set_object (value, self->priv->modem);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
return g_object_new (MM_TYPE_CALL_LIST, NULL);
}
static void
@@ -272,7 +223,6 @@ dispose (GObject *object)
{
MMCallList *self = MM_CALL_LIST (object);
g_clear_object (&self->priv->modem);
g_list_free_full (self->priv->list, g_object_unref);
self->priv->list = NULL;
@@ -287,19 +237,8 @@ mm_call_list_class_init (MMCallListClass *klass)
g_type_class_add_private (object_class, sizeof (MMCallListPrivate));
/* Virtual methods */
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->dispose = dispose;
/* Properties */
properties[PROP_MODEM] =
g_param_spec_object (MM_CALL_LIST_MODEM,
"Modem",
"The Modem which owns this CALL list",
MM_TYPE_BASE_MODEM,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]);
/* Signals */
signals[SIGNAL_CALL_ADDED] =
g_signal_new (MM_CALL_ADDED,
+1 -4
View File
@@ -19,7 +19,6 @@
#include <glib.h>
#include <glib-object.h>
#include "mm-base-modem.h"
#include "mm-base-call.h"
#define MM_TYPE_CALL_LIST (mm_call_list_get_type ())
@@ -33,8 +32,6 @@ typedef struct _MMCallList MMCallList;
typedef struct _MMCallListClass MMCallListClass;
typedef struct _MMCallListPrivate MMCallListPrivate;
#define MM_CALL_LIST_MODEM "call-list-modem"
#define MM_CALL_ADDED "call-added"
#define MM_CALL_DELETED "call-deleted"
@@ -57,7 +54,7 @@ struct _MMCallListClass {
GType mm_call_list_get_type (void);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCallList, g_object_unref)
MMCallList *mm_call_list_new (MMBaseModem *modem);
MMCallList *mm_call_list_new (void);
GStrv mm_call_list_get_paths (MMCallList *self);
guint mm_call_list_get_count (MMCallList *self);
+136 -93
View File
@@ -35,6 +35,11 @@
G_DEFINE_TYPE (MMCallQmi, mm_call_qmi, MM_TYPE_BASE_CALL)
struct _MMCallQmiPrivate {
/* The modem which owns this call */
MMBaseModem *modem;
};
/*****************************************************************************/
static gboolean
@@ -48,12 +53,7 @@ ensure_qmi_client (MMCallQmi *self,
QmiClient *client;
MMPortQmi *port;
g_object_get (self,
MM_BASE_CALL_MODEM, &modem,
NULL);
g_assert (MM_IS_BASE_MODEM (modem));
port = mm_broadband_modem_qmi_peek_port_qmi (MM_BROADBAND_MODEM_QMI (modem));
port = mm_broadband_modem_qmi_peek_port_qmi (MM_BROADBAND_MODEM_QMI (self->priv->modem));
g_object_unref (modem);
if (!port) {
@@ -331,22 +331,41 @@ call_hangup (MMBaseCall *self,
}
/*****************************************************************************/
/* Send DTMF character */
/* DTMF handling */
typedef struct {
QmiClient *client;
guint8 call_id;
} SendDtmfContext;
static void
send_dtmf_context_free (SendDtmfContext *ctx)
static gboolean
get_client_and_call_id (MMCallQmi *self,
GAsyncReadyCallback callback,
gpointer user_data,
QmiClient **client,
guint *call_id)
{
g_clear_object (&ctx->client);
g_slice_free (SendDtmfContext, ctx);
g_return_val_if_fail (client, FALSE);
g_return_val_if_fail (call_id, FALSE);
/* Ensure Voice client */
if (!ensure_qmi_client (self,
QMI_SERVICE_VOICE, client,
callback, user_data))
return FALSE;
*call_id = mm_base_call_get_index (MM_BASE_CALL (self));
if (*call_id == 0) {
g_task_report_new_error (self,
callback,
user_data,
(gpointer) __func__,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Invalid call index");
return FALSE;
}
return TRUE;
}
static gboolean
call_send_dtmf_finish (MMBaseCall *self,
call_stop_dtmf_finish (MMBaseCall *call,
GAsyncResult *res,
GError **error)
{
@@ -366,35 +385,59 @@ voice_stop_continuous_dtmf_ready (QmiClientVoice *client,
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_voice_stop_continuous_dtmf_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't send DTMF character: ");
g_prefix_error (&error, "Couldn't stop DTMF character: ");
g_task_return_error (task, error);
} else {
g_task_return_boolean (task, TRUE);
}
g_object_unref (task);
}
static gboolean
voice_stop_continuous_dtmf (GTask *task)
static void
call_stop_dtmf (MMBaseCall *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
SendDtmfContext *ctx;
MMCallQmi *self = MM_CALL_QMI (_self);
GTask *task;
QmiClient *client = NULL;
guint call_id = 0;
GError *error = NULL;
g_autoptr (QmiMessageVoiceStopContinuousDtmfInput) input = NULL;
ctx = g_task_get_task_data (task);
if (!get_client_and_call_id (self,
callback,
user_data,
&client,
&call_id))
return;
task = g_task_new (self, NULL, callback, user_data);
input = qmi_message_voice_stop_continuous_dtmf_input_new ();
qmi_message_voice_stop_continuous_dtmf_input_set_data (input, ctx->call_id, &error);
if (!qmi_message_voice_stop_continuous_dtmf_input_set_data (input,
call_id,
&error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
qmi_client_voice_stop_continuous_dtmf (QMI_CLIENT_VOICE (ctx->client),
/* Stop sending DTMF tone */
qmi_client_voice_stop_continuous_dtmf (QMI_CLIENT_VOICE (client),
input,
5,
NULL,
(GAsyncReadyCallback) voice_stop_continuous_dtmf_ready,
task);
}
return G_SOURCE_REMOVE;
static gssize
call_send_dtmf_finish (MMBaseCall *call,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_int (G_TASK (res), error);
}
static void
@@ -402,80 +445,55 @@ voice_start_continuous_dtmf_ready (QmiClientVoice *client,
GAsyncResult *res,
GTask *task)
{
g_autoptr(QmiMessageVoiceStartContinuousDtmfOutput) output = NULL;
g_autoptr (QmiMessageVoiceStartContinuousDtmfOutput) output = NULL;
GError *error = NULL;
output = qmi_client_voice_start_continuous_dtmf_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!qmi_message_voice_start_continuous_dtmf_output_get_result (output, &error)) {
} else if (!qmi_message_voice_start_continuous_dtmf_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't send DTMF character: ");
g_task_return_error (task, error);
} else {
g_task_return_int (task, 1);
}
g_object_unref (task);
}
static void
call_send_dtmf (MMBaseCall *_self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMCallQmi *self = MM_CALL_QMI (_self);
GTask *task;
QmiClient *client = NULL;
guint call_id = 0;
GError *error = NULL;
g_autoptr (QmiMessageVoiceStartContinuousDtmfInput) input = NULL;
if (!get_client_and_call_id (self,
callback,
user_data,
&client,
&call_id))
return;
task = g_task_new (self, NULL, callback, user_data);
input = qmi_message_voice_start_continuous_dtmf_input_new ();
if (!qmi_message_voice_start_continuous_dtmf_input_set_data (input,
call_id,
dtmf[0],
&error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Disable DTMF press after 500 ms */
g_timeout_add (500, (GSourceFunc) voice_stop_continuous_dtmf, task);
}
static void
call_send_dtmf (MMBaseCall *self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GError *error = NULL;
SendDtmfContext *ctx;
QmiClient *client = NULL;
guint8 call_id;
g_autoptr (QmiMessageVoiceStartContinuousDtmfInput) input = NULL;
/* Ensure Voice client */
if (!ensure_qmi_client (MM_CALL_QMI (self),
QMI_SERVICE_VOICE, &client,
callback, user_data))
return;
task = g_task_new (self, NULL, callback, user_data);
call_id = mm_base_call_get_index (self);
if (call_id == 0) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Invalid call index");
g_object_unref (task);
return;
}
ctx = g_slice_new0 (SendDtmfContext);
ctx->client = g_object_ref (client);
ctx->call_id = call_id;
g_task_set_task_data (task, ctx, (GDestroyNotify) send_dtmf_context_free);
/* Send DTMF character as ASCII number */
input = qmi_message_voice_start_continuous_dtmf_input_new ();
qmi_message_voice_start_continuous_dtmf_input_set_data (input,
call_id,
(guint8) dtmf[0],
&error);
if (error) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"DTMF data build failed");
g_object_unref (task);
return;
}
qmi_client_voice_start_continuous_dtmf (QMI_CLIENT_VOICE (client),
input,
5,
@@ -489,29 +507,52 @@ call_send_dtmf (MMBaseCall *self,
MMBaseCall *
mm_call_qmi_new (MMBaseModem *modem,
MMCallDirection direction,
const gchar *number)
const gchar *number,
const guint dtmf_tone_duration)
{
return MM_BASE_CALL (g_object_new (MM_TYPE_CALL_QMI,
MM_BASE_CALL_MODEM, modem,
MM_BIND_TO, G_OBJECT (modem),
"direction", direction,
"number", number,
MMBaseCall *call;
call = MM_BASE_CALL (g_object_new (MM_TYPE_CALL_QMI,
MM_BASE_CALL_IFACE_MODEM_VOICE, modem,
MM_BIND_TO, modem,
MM_CALL_DIRECTION, direction,
MM_CALL_NUMBER, number,
MM_CALL_DTMF_TONE_DURATION, dtmf_tone_duration,
MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, TRUE,
MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE,
MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE,
NULL));
MM_CALL_QMI (call)->priv->modem = g_object_ref (modem);
return call;
}
static void
mm_call_qmi_init (MMCallQmi *self)
{
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_QMI, MMCallQmiPrivate);
}
static void
dispose (GObject *object)
{
MMCallQmi *self = MM_CALL_QMI (object);
g_clear_object (&self->priv->modem);
G_OBJECT_CLASS (mm_call_qmi_parent_class)->dispose (object);
}
static void
mm_call_qmi_class_init (MMCallQmiClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MMBaseCallClass *base_call_class = MM_BASE_CALL_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMCallQmiPrivate));
object_class->dispose = dispose;
base_call_class->start = call_start;
base_call_class->start_finish = call_start_finish;
base_call_class->accept = call_accept;
@@ -520,4 +561,6 @@ mm_call_qmi_class_init (MMCallQmiClass *klass)
base_call_class->hangup_finish = call_hangup_finish;
base_call_class->send_dtmf = call_send_dtmf;
base_call_class->send_dtmf_finish = call_send_dtmf_finish;
base_call_class->stop_dtmf = call_stop_dtmf;
base_call_class->stop_dtmf_finish = call_stop_dtmf_finish;
}
+4 -1
View File
@@ -33,9 +33,11 @@
typedef struct _MMCallQmi MMCallQmi;
typedef struct _MMCallQmiClass MMCallQmiClass;
typedef struct _MMCallQmiPrivate MMCallQmiPrivate;
struct _MMCallQmi {
MMBaseCall parent;
MMCallQmiPrivate *priv;
};
struct _MMCallQmiClass {
@@ -47,6 +49,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCallQmi, g_object_unref)
MMBaseCall *mm_call_qmi_new (MMBaseModem *modem,
MMCallDirection direction,
const gchar *number);
const gchar *number,
const guint dtmf_tone_duration);
#endif /* MM_CALL_QMI_H */
+9 -3
View File
@@ -161,7 +161,7 @@ create_incoming_call (MMIfaceModemVoice *self,
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);
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;
}
@@ -173,6 +173,7 @@ create_outgoing_call_from_properties (MMIfaceModemVoice *self,
{
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) ;
@@ -184,9 +185,14 @@ create_outgoing_call_from_properties (MMIfaceModemVoice *self,
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);
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;
}
@@ -3012,7 +3018,7 @@ interface_initialization_step (GTask *task)
/* Create a new call list if not already available (this initialization
* may be called multiple times) */
if (!list) {
list = mm_call_list_new (MM_BASE_MODEM (self));
list = mm_call_list_new ();
g_object_set (self,
MM_IFACE_MODEM_VOICE_CALL_LIST, list,
NULL);
+2 -1
View File
@@ -123,7 +123,8 @@ struct _MMIfaceModemVoiceInterface {
/* Create call objects */
MMBaseCall * (* create_call) (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number);
const gchar *number,
const guint dtmf_tone_duration);
/* Hold and accept */
void (* hold_and_accept) (MMIfaceModemVoice *self,
+50
View File
@@ -5871,3 +5871,53 @@ mm_parse_cpin_response (const gchar *response,
return MM_MODEM_LOCK_UNKNOWN;
}
/*****************************************************************************/
guint
mm_dtmf_duration_normalize (guint duration_ms)
{
/* Default to 500ms */
if (duration_ms == 0)
return 500;
/* round to next highest 100ms */
if (duration_ms % 100)
duration_ms = ((duration_ms + 100) / 100) * 100;
return CLAMP (duration_ms, 100, 1000);
}
GPtrArray *
mm_dtmf_split (const gchar *dtmf)
{
GPtrArray *array;
const gchar *p = dtmf;
GString *cur = NULL;
array = g_ptr_array_new ();
while (*p) {
if (*p == MM_CALL_DTMF_PAUSE_CHAR) {
if (cur) {
g_ptr_array_add (array, g_string_free (cur, FALSE));
cur = NULL;
}
g_ptr_array_add (array, g_strdup (","));
} else {
if (!cur)
cur = g_string_new (NULL);
g_string_append_c (cur, *p);
}
p++;
}
if (cur)
g_ptr_array_add (array, g_string_free (cur, FALSE));
if (array->len == 0) {
g_ptr_array_free (array, TRUE);
return NULL;
}
return array;
}
+9
View File
@@ -643,4 +643,13 @@ guint mm_string_uint_map_lookup (const MMStringUintMap *map,
MMModemLock mm_parse_cpin_response (const gchar *response,
gboolean expect_cpin_prefix);
/*****************************************************************************/
/* Helper to clamp duration and round to next 100ms */
guint mm_dtmf_duration_normalize (guint duration_ms);
#define MM_CALL_DTMF_PAUSE_CHAR ','
GPtrArray *mm_dtmf_split (const gchar *dtmf);
#endif /* MM_MODEM_HELPERS_H */
+52
View File
@@ -0,0 +1,52 @@
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
*/
#ifndef MM_TEST_UTILS_H
#define MM_TEST_UTILS_H
#include <glib.h>
#include <glib-object.h>
/*****************************************************************************/
/* cmp = TRUE (s1 contains s2) or FALSE (s1 does not contain s2) */
#define mm_assert_strstr(s1, cmp, s2) \
G_STMT_START { \
const char *__s1 = (s1), *__s2 = (s2); \
if (strstr (__s1, __s2) == cmp) ; else \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#s1 " %s " #s2, \
__s1, \
cmp ? "contains" : "does not contain", \
__s2); \
} G_STMT_END
/* Asserts that err's message contains s1 */
#define mm_assert_error_str(err, s1) \
G_STMT_START { \
if (!err || !err->message || !strstr (err->message, s1)) { \
GString *gstring; \
gstring = g_string_new ("assertion failed "); \
if (err) \
g_string_append_printf (gstring, "%s (%s, %d) did not contain '%s'", \
err->message, g_quark_to_string (err->domain), err->code, s1); \
else \
g_string_append_printf (gstring, "%s is NULL", #err); \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, gstring->str); \
g_string_free (gstring, TRUE); \
} \
} G_STMT_END
#endif /* MM_TEST_UTILS_H */
+6 -3
View File
@@ -31,6 +31,7 @@
#include "mm-base-modem-at.h"
#include "mm-shared-cinterion.h"
#include "mm-modem-helpers-cinterion.h"
#include "mm-call-at.h"
G_DEFINE_INTERFACE (MMSharedCinterion, mm_shared_cinterion, MM_TYPE_IFACE_MODEM)
@@ -997,7 +998,8 @@ mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self,
MMBaseCall *
mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number)
const gchar *number,
const guint dtmf_tone_duration)
{
Private *priv;
@@ -1005,10 +1007,11 @@ mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
priv = get_private (MM_SHARED_CINTERION (self));
if (priv->slcc_support == FEATURE_SUPPORTED) {
mm_obj_dbg (self, "created new call with ^SLCC support");
return mm_base_call_new (MM_BASE_MODEM (self),
return mm_call_at_new (MM_BASE_MODEM (self),
G_OBJECT (self),
direction,
number,
dtmf_tone_duration,
/* When SLCC is supported we have support for detailed
* call list events via call list report URCs */
TRUE, /* incoming timeout not required */
@@ -1019,7 +1022,7 @@ mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
/* otherwise, run parent's generic base call logic */
g_assert (priv->iface_modem_voice_parent);
g_assert (priv->iface_modem_voice_parent->create_call);
return priv->iface_modem_voice_parent->create_call (self, direction, number);
return priv->iface_modem_voice_parent->create_call (self, direction, number, dtmf_tone_duration);
}
/*****************************************************************************/
+2 -1
View File
@@ -105,7 +105,8 @@ gboolean mm_shared_cinterion_disable_location_gathering_finish (MMI
MMBaseCall *mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number);
const gchar *number,
const guint dtmf_tone_duration);
void mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
@@ -49,6 +49,7 @@
#include "mm-broadband-bearer.h"
#include "mm-bearer-list.h"
#include "mm-sim-huawei.h"
#include "mm-call-at.h"
static void iface_modem_init (MMIfaceModemInterface *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gppInterface *iface);
@@ -3878,12 +3879,14 @@ modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
static MMBaseCall *
create_call (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number)
const gchar *number,
const guint dtmf_tone_duration)
{
return mm_base_call_new (MM_BASE_MODEM (self),
return mm_call_at_new (MM_BASE_MODEM (self),
G_OBJECT (self),
direction,
number,
dtmf_tone_duration,
TRUE, /* skip_incoming_timeout */
TRUE, /* supports_dialing_to_ringing */
TRUE); /* supports_ringing_to_active) */
+5 -2
View File
@@ -33,6 +33,7 @@
#include "mm-sim-ublox.h"
#include "mm-modem-helpers-ublox.h"
#include "mm-ublox-enums-types.h"
#include "mm-call-at.h"
static void iface_modem_init (MMIfaceModemInterface *iface);
static void iface_modem_voice_init (MMIfaceModemVoiceInterface *iface);
@@ -1503,12 +1504,14 @@ modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
static MMBaseCall *
create_call (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number)
const gchar *number,
const guint dtmf_tone_duration)
{
return mm_base_call_new (MM_BASE_MODEM (self),
return mm_call_at_new (MM_BASE_MODEM (self),
G_OBJECT (self),
direction,
number,
dtmf_tone_duration,
TRUE, /* skip_incoming_timeout */
TRUE, /* supports_dialing_to_ringing */
TRUE); /* supports_ringing_to_active */
+409
View File
@@ -0,0 +1,409 @@
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
*/
#include <glib.h>
#include <glib-object.h>
#include <string.h>
#include <stdio.h>
#include <locale.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "fake-call.h"
G_DEFINE_TYPE (MMFakeCall, mm_fake_call, MM_TYPE_BASE_CALL)
/*****************************************************************************/
/* Start the CALL */
static gboolean
call_start_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static gboolean
call_start_ready (GTask *task)
{
MMFakeCall *self;
self = g_task_get_source_object (task);
self->priv->idle_id = 0;
if (self->priv->start_error_msg) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
self->priv->start_error_msg);
} else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return G_SOURCE_REMOVE;
}
static void
call_start (MMBaseCall *_self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMFakeCall *self = MM_FAKE_CALL (_self);
GTask *task;
g_assert_cmpint (self->priv->idle_id, ==, 0);
task = g_task_new (self, NULL, callback, user_data);
self->priv->idle_id = g_idle_add ((GSourceFunc) call_start_ready, task);
}
/*****************************************************************************/
/* Accept the call */
static gboolean
call_accept_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static gboolean
call_accept_ready (GTask *task)
{
MMFakeCall *self;
self = g_task_get_source_object (task);
self->priv->idle_id = 0;
if (self->priv->accept_error_msg) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
self->priv->accept_error_msg);
} else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return G_SOURCE_REMOVE;
}
static void
call_accept (MMBaseCall *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMFakeCall *self = MM_FAKE_CALL (_self);
GTask *task;
g_assert_cmpint (self->priv->idle_id, ==, 0);
task = g_task_new (self, NULL, callback, user_data);
self->priv->idle_id = g_idle_add ((GSourceFunc) call_accept_ready, task);
}
/*****************************************************************************/
/* Deflect the call */
static gboolean
call_deflect_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static gboolean
call_deflect_ready (GTask *task)
{
MMFakeCall *self;
self = g_task_get_source_object (task);
self->priv->idle_id = 0;
if (self->priv->deflect_error_msg) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
self->priv->deflect_error_msg);
} else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return G_SOURCE_REMOVE;
}
static void
call_deflect (MMBaseCall *_self,
const gchar *number,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMFakeCall *self = MM_FAKE_CALL (_self);
GTask *task;
g_assert_cmpint (self->priv->idle_id, ==, 0);
task = g_task_new (self, NULL, callback, user_data);
self->priv->idle_id = g_idle_add ((GSourceFunc) call_deflect_ready, task);
}
/*****************************************************************************/
/* Hangup the call */
static gboolean
call_hangup_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static gboolean
call_hangup_ready (GTask *task)
{
MMFakeCall *self;
self = g_task_get_source_object (task);
self->priv->idle_id = 0;
if (self->priv->hangup_error_msg) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
self->priv->hangup_error_msg);
} else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return G_SOURCE_REMOVE;
}
static void
call_hangup (MMBaseCall *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMFakeCall *self = MM_FAKE_CALL (_self);
GTask *task;
g_assert_cmpint (self->priv->idle_id, ==, 0);
task = g_task_new (self, NULL, callback, user_data);
self->priv->idle_id = g_idle_add ((GSourceFunc) call_hangup_ready, task);
}
/*****************************************************************************/
/* Send DTMF tone to call */
static gssize
call_send_dtmf_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_int (G_TASK (res), error);
}
static gboolean
call_send_dtmf_ready (GTask *task)
{
MMFakeCall *self;
self = g_task_get_source_object (task);
self->priv->idle_id = 0;
if (self->priv->dtmf_error_msg) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
self->priv->dtmf_error_msg);
} else
g_task_return_int (task, self->priv->dtmf_num_accepted);
self->priv->dtmf_in_send = FALSE;
g_object_unref (task);
return G_SOURCE_REMOVE;
}
static void
call_send_dtmf (MMBaseCall *_self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMFakeCall *self = MM_FAKE_CALL (_self);
GTask *task;
g_assert_cmpint (self->priv->idle_id, ==, 0);
task = g_task_new (self, NULL, callback, user_data);
if (!self->priv->dtmf_sent)
self->priv->dtmf_sent = g_string_new ("");
self->priv->dtmf_num_accepted = MIN (self->priv->dtmf_accept_len, strlen (dtmf));
g_string_append_len (self->priv->dtmf_sent, dtmf, self->priv->dtmf_num_accepted);
self->priv->idle_id = g_idle_add ((GSourceFunc) call_send_dtmf_ready, task);
self->priv->dtmf_in_send = TRUE;
}
/*****************************************************************************/
/* Stop DTMF tone */
static gboolean
call_stop_dtmf_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static gboolean
call_stop_dtmf_ready (GTask *task)
{
MMFakeCall *self;
self = g_task_get_source_object (task);
self->priv->idle_id = 0;
self->priv->dtmf_stop_called = TRUE;
if (self->priv->dtmf_stop_error_msg) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
self->priv->dtmf_stop_error_msg);
} else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return G_SOURCE_REMOVE;
}
static void
call_stop_dtmf (MMBaseCall *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMFakeCall *self = MM_FAKE_CALL (_self);
GTask *task;
g_assert_cmpint (self->priv->idle_id, ==, 0);
g_assert_false (self->priv->dtmf_in_send);
task = g_task_new (self, NULL, callback, user_data);
self->priv->idle_id = g_idle_add ((GSourceFunc) call_stop_dtmf_ready, task);
}
void
mm_fake_call_enable_dtmf_stop (MMFakeCall *self,
gboolean enable)
{
MMBaseCallClass *base_call_class = MM_BASE_CALL_GET_CLASS (self);
base_call_class->stop_dtmf = enable ? call_stop_dtmf : NULL;
base_call_class->stop_dtmf_finish = enable ? call_stop_dtmf_finish : NULL;
}
/*****************************************************************************/
MMFakeCall *
mm_fake_call_new (GDBusConnection *connection,
MMIfaceModemVoice *voice,
MMCallDirection direction,
const gchar *number,
const guint dtmf_tone_duration)
{
MMFakeCall *call;
call = MM_FAKE_CALL (g_object_new (MM_TYPE_FAKE_CALL,
MM_BASE_CALL_CONNECTION, connection,
MM_BASE_CALL_IFACE_MODEM_VOICE, voice,
MM_CALL_DIRECTION, direction,
MM_CALL_NUMBER, number,
MM_CALL_DTMF_TONE_DURATION, dtmf_tone_duration,
MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, TRUE,
MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE,
MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE,
NULL));
return call;
}
static void
mm_fake_call_init (MMFakeCall *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_FAKE_CALL, MMFakeCallPrivate);
}
static void
dispose (GObject *object)
{
MMFakeCall *self = MM_FAKE_CALL (object);
if (self->priv->idle_id)
g_source_remove (self->priv->idle_id);
self->priv->idle_id = 0;
if (self->priv->dtmf_sent) {
g_string_free (self->priv->dtmf_sent, TRUE);
self->priv->dtmf_sent = NULL;
}
G_OBJECT_CLASS (mm_fake_call_parent_class)->dispose (object);
}
static void
finalize (GObject *object)
{
G_OBJECT_CLASS (mm_fake_call_parent_class)->finalize (object);
}
static void
mm_fake_call_class_init (MMFakeCallClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MMBaseCallClass *base_call_class = MM_BASE_CALL_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMFakeCallPrivate));
object_class->dispose = dispose;
object_class->finalize = finalize;
base_call_class->start = call_start;
base_call_class->start_finish = call_start_finish;
base_call_class->accept = call_accept;
base_call_class->accept_finish = call_accept_finish;
base_call_class->deflect = call_deflect;
base_call_class->deflect_finish = call_deflect_finish;
base_call_class->hangup = call_hangup;
base_call_class->hangup_finish = call_hangup_finish;
base_call_class->send_dtmf = call_send_dtmf;
base_call_class->send_dtmf_finish = call_send_dtmf_finish;
}
+77
View File
@@ -0,0 +1,77 @@
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
*/
#ifndef MM_FAKE_CALL_H
#define MM_FAKE_CALL_H
#include <glib.h>
#include <glib-object.h>
#include "mm-base-call.h"
#include "mm-iface-modem-voice.h"
#define MM_TYPE_FAKE_CALL (mm_fake_call_get_type ())
#define MM_FAKE_CALL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_FAKE_CALL, MMFakeCall))
#define MM_FAKE_CALL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_FAKE_CALL, MMFakeCallClass))
#define MM_IS_FAKE_CALL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_FAKE_CALL))
#define MM_IS_FAKE_CALL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_FAKE_CALL))
#define MM_FAKE_CALL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_FAKE_CALL, MMFakeCallClass))
typedef struct _MMFakeCall MMFakeCall;
typedef struct _MMFakeCallClass MMFakeCallClass;
typedef struct _MMFakeCallPrivate MMFakeCallPrivate;
struct _MMFakeCallPrivate {
const gchar *start_error_msg;
const gchar *accept_error_msg;
const gchar *deflect_error_msg;
const gchar *hangup_error_msg;
/* DTMF */
const gchar *dtmf_error_msg;
const gchar *dtmf_stop_error_msg;
/* How many DTMF characters we can accept at a time */
guint dtmf_accept_len;
/* How many DTMF characters were actually accepted */
guint dtmf_num_accepted;
GString *dtmf_sent;
gboolean dtmf_in_send;
gboolean dtmf_stop_called;
guint idle_id;
};
struct _MMFakeCall {
MMBaseCall parent;
MMFakeCallPrivate *priv;
};
struct _MMFakeCallClass {
MMBaseCallClass parent;
};
GType mm_fake_call_get_type (void);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMFakeCall, g_object_unref)
MMFakeCall *mm_fake_call_new (GDBusConnection *connection,
MMIfaceModemVoice *voice,
MMCallDirection direction,
const gchar *number,
const guint dtmf_tone_duration);
void mm_fake_call_enable_dtmf_stop (MMFakeCall *self,
gboolean enable);
#endif /* MM_FAKE_CALL_H */
+463
View File
@@ -0,0 +1,463 @@
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
*/
#include <glib.h>
#include <glib-object.h>
#include <string.h>
#include <stdio.h>
#include <locale.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "fake-modem.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-voice.h"
#include "mm-call-list.h"
#include "fake-call.h"
#include "mm-bind.h"
#define MM_FAKE_MODEM_PATH "fake-modem-path"
static void iface_modem_init (MMIfaceModemInterface *iface);
static void iface_modem_voice_init (MMIfaceModemVoiceInterface *iface);
G_DEFINE_TYPE_EXTENDED (MMFakeModem, mm_fake_modem, MM_TYPE_BASE_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init))
enum {
PROP_0,
PROP_CONNECTION,
PROP_PATH,
PROP_MODEM_DBUS_SKELETON,
PROP_MODEM_STATE,
PROP_MODEM_SIM,
PROP_MODEM_SIM_SLOTS,
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
PROP_MODEM_CARRIER_CONFIG_MAPPING,
PROP_MODEM_BEARER_LIST,
PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED,
PROP_MODEM_VOICE_DBUS_SKELETON,
PROP_MODEM_VOICE_CALL_LIST,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
struct _MMFakeModemPrivate {
GDBusConnection *connection;
guint dbus_id;
gchar *path;
GObject *modem_dbus_skeleton;
MMModemState modem_state;
GObject *modem_sim;
GPtrArray *modem_sim_slots;
gboolean sim_hot_swap_supported;
gboolean periodic_signal_check_disabled;
gboolean periodic_access_tech_check_disabled;
gboolean periodic_call_list_check_disabled;
gchar *carrier_config_mapping;
MMBearerList *modem_bearer_list;
gboolean indication_call_list_reload_enabled;
GObject *modem_voice_dbus_skeleton;
MMCallList *modem_voice_call_list;
};
/*****************************************************************************/
const gchar *
mm_fake_modem_get_path (MMFakeModem *self)
{
return self->priv->path;
}
MMCallList *
mm_fake_modem_get_call_list (MMFakeModem *self)
{
return self->priv->modem_voice_call_list;
}
gboolean
mm_fake_modem_export_interfaces (MMFakeModem *self, GError **error)
{
g_assert (self->priv->path);
g_assert (self->priv->connection);
if (self->priv->modem_dbus_skeleton) {
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->priv->modem_dbus_skeleton),
self->priv->connection,
self->priv->path,
error))
return FALSE;
}
if (self->priv->modem_voice_dbus_skeleton) {
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->priv->modem_voice_dbus_skeleton),
self->priv->connection,
self->priv->path,
error))
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
static gboolean
modem_voice_check_support_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
gboolean foobar;
foobar = g_task_propagate_boolean (G_TASK (res), error);
return foobar;
}
static void
modem_voice_check_support (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
/*****************************************************************************/
static MMBaseCall *
modem_voice_create_call (MMIfaceModemVoice *_self,
MMCallDirection direction,
const gchar *number,
const guint dtmf_tone_duration)
{
MMFakeModem *self = MM_FAKE_MODEM (_self);
return MM_BASE_CALL (mm_fake_call_new (self->priv->connection,
_self,
direction,
number,
dtmf_tone_duration));
}
/*****************************************************************************/
MMFakeModem *
mm_fake_modem_new (GDBusConnection *connection)
{
return MM_FAKE_MODEM (g_object_new (MM_TYPE_FAKE_MODEM,
MM_BINDABLE_CONNECTION, connection,
NULL));
}
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MMFakeModem *self = MM_FAKE_MODEM (object);
switch (prop_id) {
case PROP_PATH:
g_free (self->priv->path);
self->priv->path = g_value_dup_string (value);
break;
case PROP_CONNECTION:
g_clear_object (&self->priv->connection);
self->priv->connection = g_value_dup_object (value);
break;
case PROP_MODEM_DBUS_SKELETON:
g_clear_object (&self->priv->modem_dbus_skeleton);
self->priv->modem_dbus_skeleton = g_value_dup_object (value);
break;
case PROP_MODEM_STATE:
self->priv->modem_state = g_value_get_enum (value);
break;
case PROP_MODEM_SIM:
g_clear_object (&self->priv->modem_sim);
self->priv->modem_sim = g_value_dup_object (value);
break;
case PROP_MODEM_SIM_SLOTS:
g_clear_pointer (&self->priv->modem_sim_slots, g_ptr_array_unref);
self->priv->modem_sim_slots = g_value_dup_boxed (value);
break;
case PROP_MODEM_SIM_HOT_SWAP_SUPPORTED:
self->priv->sim_hot_swap_supported = g_value_get_boolean (value);
break;
case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
self->priv->periodic_signal_check_disabled = g_value_get_boolean (value);
break;
case PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED:
self->priv->periodic_access_tech_check_disabled = g_value_get_boolean (value);
break;
case PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED:
self->priv->periodic_call_list_check_disabled = g_value_get_boolean (value);
break;
case PROP_MODEM_CARRIER_CONFIG_MAPPING:
self->priv->carrier_config_mapping = g_value_dup_string (value);
break;
case PROP_MODEM_BEARER_LIST:
g_clear_object (&self->priv->modem_bearer_list);
self->priv->modem_bearer_list = g_value_dup_object (value);
break;
case PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED:
self->priv->indication_call_list_reload_enabled = g_value_get_boolean (value);
break;
case PROP_MODEM_VOICE_DBUS_SKELETON:
g_clear_object (&self->priv->modem_voice_dbus_skeleton);
self->priv->modem_voice_dbus_skeleton = g_value_dup_object (value);
break;
case PROP_MODEM_VOICE_CALL_LIST:
g_clear_object (&self->priv->modem_voice_call_list);
self->priv->modem_voice_call_list = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MMFakeModem *self = MM_FAKE_MODEM (object);
switch (prop_id) {
case PROP_PATH:
g_value_set_string (value, self->priv->path);
break;
case PROP_CONNECTION:
g_value_set_object (value, self->priv->connection);
break;
case PROP_MODEM_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_dbus_skeleton);
break;
case PROP_MODEM_STATE:
g_value_set_enum (value, self->priv->modem_state);
break;
case PROP_MODEM_SIM:
g_value_set_object (value, self->priv->modem_sim);
break;
case PROP_MODEM_SIM_SLOTS:
g_value_set_boxed (value, self->priv->modem_sim_slots);
break;
case PROP_MODEM_SIM_HOT_SWAP_SUPPORTED:
g_value_set_boolean (value, self->priv->sim_hot_swap_supported);
break;
case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
g_value_set_boolean (value, self->priv->periodic_signal_check_disabled);
break;
case PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED:
g_value_set_boolean (value, self->priv->periodic_access_tech_check_disabled);
break;
case PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED:
g_value_set_boolean (value, self->priv->periodic_call_list_check_disabled);
break;
case PROP_MODEM_CARRIER_CONFIG_MAPPING:
g_value_set_string (value, self->priv->carrier_config_mapping);
break;
case PROP_MODEM_BEARER_LIST:
g_value_set_object (value, self->priv->modem_bearer_list);
break;
case PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED:
g_value_set_boolean (value, self->priv->indication_call_list_reload_enabled);
break;
case PROP_MODEM_VOICE_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_voice_dbus_skeleton);
break;
case PROP_MODEM_VOICE_CALL_LIST:
g_value_set_object (value, self->priv->modem_voice_call_list);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
mm_fake_modem_init (MMFakeModem *self)
{
static guint id = 0;
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
MM_TYPE_FAKE_MODEM,
MMFakeModemPrivate);
/* Each modem is given a unique id to build its own DBus path */
self->priv->dbus_id = id++;
self->priv->path = g_strdup_printf (MM_DBUS_MODEM_PREFIX "/%d", self->priv->dbus_id);
}
static void
finalize (GObject *object)
{
MMFakeModem *self = MM_FAKE_MODEM (object);
g_free (self->priv->path);
g_free (self->priv->carrier_config_mapping);
G_OBJECT_CLASS (mm_fake_modem_parent_class)->finalize (object);
}
static void
dispose (GObject *object)
{
MMFakeModem *self = MM_FAKE_MODEM (object);
if (self->priv->modem_dbus_skeleton) {
mm_iface_modem_shutdown (MM_IFACE_MODEM (object));
g_clear_object (&self->priv->modem_dbus_skeleton);
}
if (self->priv->modem_voice_dbus_skeleton) {
mm_iface_modem_voice_shutdown (MM_IFACE_MODEM_VOICE (object));
g_clear_object (&self->priv->modem_voice_dbus_skeleton);
}
g_clear_object (&self->priv->modem_sim);
g_clear_pointer (&self->priv->modem_sim_slots, g_ptr_array_unref);
g_clear_object (&self->priv->modem_bearer_list);
g_clear_object (&self->priv->modem_voice_call_list);
g_clear_object (&self->priv->connection);
G_OBJECT_CLASS (mm_fake_modem_parent_class)->dispose (object);
}
static void
iface_modem_init (MMIfaceModemInterface *iface)
{
}
static void
iface_modem_voice_init (MMIfaceModemVoiceInterface *iface)
{
iface->check_support = modem_voice_check_support;
iface->check_support_finish = modem_voice_check_support_finish;
iface->create_call = modem_voice_create_call;
#if 0
iface->load_call_list = modem_voice_load_call_list;
iface->load_call_list_finish = modem_voice_load_call_list_finish;
iface->hold_and_accept = modem_voice_hold_and_accept;
iface->hold_and_accept_finish = modem_voice_hold_and_accept_finish;
iface->hangup_and_accept = modem_voice_hangup_and_accept;
iface->hangup_and_accept_finish = modem_voice_hangup_and_accept_finish;
iface->hangup_all = modem_voice_hangup_all;
iface->hangup_all_finish = modem_voice_hangup_all_finish;
iface->join_multiparty = modem_voice_join_multiparty;
iface->join_multiparty_finish = modem_voice_join_multiparty_finish;
iface->leave_multiparty = modem_voice_leave_multiparty;
iface->leave_multiparty_finish = modem_voice_leave_multiparty_finish;
iface->transfer = modem_voice_transfer;
iface->transfer_finish = modem_voice_transfer_finish;
iface->call_waiting_setup = modem_voice_call_waiting_setup;
iface->call_waiting_setup_finish = modem_voice_call_waiting_setup_finish;
iface->call_waiting_query = modem_voice_call_waiting_query;
iface->call_waiting_query_finish = modem_voice_call_waiting_query_finish;
#endif
}
static void
mm_fake_modem_class_init (MMFakeModemClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMFakeModemPrivate));
object_class->set_property = set_property;
object_class->get_property = get_property;
object_class->finalize = finalize;
object_class->dispose = dispose;
properties[PROP_CONNECTION] =
g_param_spec_object (MM_BINDABLE_CONNECTION,
"Connection",
"GDBus connection to the system bus.",
G_TYPE_DBUS_CONNECTION,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]);
properties[PROP_PATH] =
g_param_spec_string (MM_FAKE_MODEM_PATH,
"Path",
"DBus path of the call",
NULL,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]);
g_object_class_override_property (object_class,
PROP_MODEM_DBUS_SKELETON,
MM_IFACE_MODEM_DBUS_SKELETON);
g_object_class_override_property (object_class,
PROP_MODEM_STATE,
MM_IFACE_MODEM_STATE);
g_object_class_override_property (object_class,
PROP_MODEM_SIM,
MM_IFACE_MODEM_SIM);
g_object_class_override_property (object_class,
PROP_MODEM_SIM_SLOTS,
MM_IFACE_MODEM_SIM_SLOTS);
g_object_class_override_property (object_class,
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED);
g_object_class_override_property (object_class,
PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED);
g_object_class_override_property (object_class,
PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED);
g_object_class_override_property (object_class,
PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED);
g_object_class_override_property (object_class,
PROP_MODEM_CARRIER_CONFIG_MAPPING,
MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING);
g_object_class_override_property (object_class,
PROP_MODEM_BEARER_LIST,
MM_IFACE_MODEM_BEARER_LIST);
g_object_class_override_property (object_class,
PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED,
MM_IFACE_MODEM_VOICE_INDICATION_CALL_LIST_RELOAD_ENABLED);
g_object_class_override_property (object_class,
PROP_MODEM_VOICE_DBUS_SKELETON,
MM_IFACE_MODEM_VOICE_DBUS_SKELETON);
g_object_class_override_property (object_class,
PROP_MODEM_VOICE_CALL_LIST,
MM_IFACE_MODEM_VOICE_CALL_LIST);
}
+57
View File
@@ -0,0 +1,57 @@
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
*/
#ifndef MM_FAKE_MODEM_H
#define MM_FAKE_MODEM_H
#include <glib.h>
#include <glib-object.h>
#include "mm-base-modem.h"
#include "mm-call-list.h"
#define MM_TYPE_FAKE_MODEM (mm_fake_modem_get_type ())
#define MM_FAKE_MODEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_FAKE_MODEM, MMFakeModem))
#define MM_FAKE_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_FAKE_MODEM, MMFakeModemClass))
#define MM_IS_FAKE_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_FAKE_MODEM))
#define MM_IS_FAKE_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_FAKE_MODEM))
#define MM_FAKE_MODEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_FAKE_MODEM, MMFakeModemClass))
typedef struct _MMFakeModem MMFakeModem;
typedef struct _MMFakeModemClass MMFakeModemClass;
typedef struct _MMFakeModemPrivate MMFakeModemPrivate;
struct _MMFakeModem {
MMBaseModem parent;
MMFakeModemPrivate *priv;
};
struct _MMFakeModemClass {
MMBaseModemClass parent;
};
GType mm_fake_modem_get_type (void);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMFakeModem, g_object_unref)
MMFakeModem *mm_fake_modem_new (GDBusConnection *connection);
const gchar *mm_fake_modem_get_path (MMFakeModem *self);
gboolean mm_fake_modem_export_interfaces (MMFakeModem *self,
GError **error);
MMCallList *mm_fake_modem_get_call_list (MMFakeModem *self);
#endif /* MM_FAKE_MODEM_H */
+18 -1
View File
@@ -31,6 +31,11 @@ if enable_mbim
test_units += {'modem-helpers-mbim': libkerneldevice_dep}
endif
c_args = [
'-DTEST_SERVICES="@0@"'.format(build_root / 'data/tests'),
'-DTESTUDEVRULESDIR="@0@"'.format(src_dir),
]
foreach test_unit, test_deps: test_units
test_name = 'test-' + test_unit
@@ -39,12 +44,24 @@ foreach test_unit, test_deps: test_units
sources: test_name + '.c',
include_directories: top_inc,
dependencies: test_deps,
c_args: '-DTESTUDEVRULESDIR="@0@"'.format(src_dir)
c_args: c_args,
)
test(test_name, exe)
endforeach
# base call test
exe = executable(
'test-base-call',
sources: [ 'test-base-call.c', 'fake-modem.c', 'fake-call.c' ],
include_directories: top_inc,
dependencies: libmmbase_dep,
c_args: c_args,
)
test('test-base-call', exe)
if get_option('fuzzer')
fuzzer_tests = ['test-sms-part-3gpp-fuzzer',
'test-sms-part-3gpp-tr-fuzzer',
+519
View File
@@ -0,0 +1,519 @@
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
*/
#include <glib.h>
#include <glib-object.h>
#include <string.h>
#include <stdio.h>
#include <locale.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-base-call.h"
#include "mm-context.h"
#include "mm-call-list.h"
#include "mm-iface-modem-voice.h"
#include "fake-modem.h"
#include "fake-call.h"
#include "mm-log.h"
#include "mm-test-utils.h"
/****************************************************************/
/* Make the linker happy */
#if defined WITH_QMI
typedef struct MMBroadbandModemQmi MMBroadbandModemQmi;
GType mm_broadband_modem_qmi_get_type (void);
MMPortQmi *mm_broadband_modem_qmi_peek_port_qmi (MMBroadbandModemQmi *self);
GType
mm_broadband_modem_qmi_get_type (void)
{
return G_TYPE_INVALID;
}
MMPortQmi *
mm_broadband_modem_qmi_peek_port_qmi (MMBroadbandModemQmi *self)
{
return NULL;
}
#endif /* WITH_QMI */
#if defined WITH_MBIM
typedef struct MMBroadbandModemMbim MMBroadbandModemMbim;
GType mm_broadband_modem_mbim_get_type (void);
MMPortMbim *mm_broadband_modem_mbim_peek_port_mbim (MMBroadbandModemMbim *self);
GType
mm_broadband_modem_mbim_get_type (void)
{
return G_TYPE_INVALID;
}
MMPortMbim *
mm_broadband_modem_mbim_peek_port_mbim (MMBroadbandModemMbim *self)
{
return NULL;
}
#endif /* WITH_MBIM */
/****************************************************************/
typedef struct {
const gchar *desc;
const gchar *start_error_msg;
const gchar *accept_error_msg;
const gchar *deflect_error_msg;
const gchar *hangup_error_msg;
const gchar *number;
/* DTMF */
const gchar *dtmf_error_msg;
const gchar *dtmf_stop_error_msg;
const guint dtmf_accept_len; /* how many chars modem can accept at a time */
const guint dtmf_tone_duration;
const gchar *dtmf;
const guint dtmf_min_duration;
} Testcase;
typedef struct {
GTestDBus *dbus;
GDBusConnection *connection;
GMainLoop *loop;
guint name_id;
MmGdbusModemVoice *voice_proxy;
MMFakeModem *modem;
MmGdbusCall *call_proxy;
MMFakeCall *call;
GError *error;
const Testcase *tc;
} TestFixture;
/****************************************************************/
static MMFakeCall *
get_call (TestFixture *tf)
{
MMCallList *list;
MMFakeCall *call;
const gchar *call_path;
list = mm_fake_modem_get_call_list (tf->modem);
g_assert (list);
g_assert (tf->call_proxy);
call_path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (tf->call_proxy));
call = (MMFakeCall *) mm_call_list_get_call (list, call_path);
g_assert (call);
return call;
}
/****************************************************************/
static void
dtmf_send_ready (MmGdbusCall *call,
GAsyncResult *res,
TestFixture *tf)
{
if (!mm_gdbus_call_call_send_dtmf_finish (call, res, &tf->error))
g_assert_true (tf->error);
g_main_loop_quit (tf->loop);
}
static void
dtmf_call_start_ready (MmGdbusCall *call,
GAsyncResult *res,
TestFixture *tf)
{
gboolean success;
g_autoptr(GError) error = NULL;
const MMCallInfo cinfo = {
.index = 1,
.number = (gchar *) tf->tc->number,
.direction = MM_CALL_DIRECTION_OUTGOING,
.state = MM_CALL_STATE_ACTIVE,
};
success = mm_gdbus_call_call_start_finish (call, res, &error);
g_assert_no_error (error);
g_assert_true (success);
/* Set the call active */
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (tf->modem), &cinfo);
g_main_loop_quit (tf->loop);
}
static void
dtmf_proxy_ready (gpointer unused,
GAsyncResult *res,
TestFixture *tf)
{
g_autoptr(GError) error = NULL;
g_autoptr(MMCallList) list = NULL;
tf->call_proxy = mm_gdbus_call_proxy_new_for_bus_finish (res, &error);
g_assert_no_error (error);
g_assert (tf->call_proxy);
g_main_loop_quit (tf->loop);
}
static void
dtmf_create_call_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
TestFixture *tf)
{
g_autoptr(GError) error = NULL;
g_autofree gchar *call_path = NULL;
gboolean success;
success = mm_gdbus_modem_voice_call_create_call_finish (MM_GDBUS_MODEM_VOICE (self),
&call_path,
res,
&error);
g_assert_true (success);
g_assert_no_error (error);
/* Create our call proxy */
mm_gdbus_call_proxy_new_for_bus (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
"org.freedesktop.ModemManager1",
call_path,
NULL,
(GAsyncReadyCallback) dtmf_proxy_ready,
tf);
}
static void
test_dtmf (TestFixture *tf, const Testcase *tc, gboolean test_stop)
{
g_autoptr(GError) error = NULL;
g_autoptr(GVariant) dictionary = NULL;
g_autoptr(MMCallProperties) call_props = NULL;
MMFakeCall *call;
guint dtmf_len_no_pause = 0;
const gchar *p;
gint64 call_start_time;
tf->tc = tc;
call_props = mm_call_properties_new ();
mm_call_properties_set_number (call_props, tf->tc->number);
mm_call_properties_set_dtmf_tone_duration (call_props, tf->tc->dtmf_tone_duration);
dictionary = mm_call_properties_get_dictionary (call_props);
/* Create the voice call we'll use for DTMF */
mm_gdbus_modem_voice_call_create_call (tf->voice_proxy,
dictionary,
NULL,
(GAsyncReadyCallback)dtmf_create_call_ready,
tf);
g_main_loop_run (tf->loop);
/* start the voice call */
mm_gdbus_call_call_start (tf->call_proxy,
NULL,
(GAsyncReadyCallback) dtmf_call_start_ready,
tf);
g_main_loop_run (tf->loop);
/* Get the call and copy expectations into it */
call = get_call (tf);
call->priv->dtmf_accept_len = tf->tc->dtmf_accept_len;
call->priv->start_error_msg = tf->tc->start_error_msg;
call->priv->accept_error_msg = tf->tc->accept_error_msg;
call->priv->deflect_error_msg = tf->tc->deflect_error_msg;
call->priv->hangup_error_msg = tf->tc->hangup_error_msg;
call->priv->dtmf_error_msg = tf->tc->dtmf_error_msg;
call->priv->dtmf_stop_error_msg = tf->tc->dtmf_stop_error_msg;
mm_fake_call_enable_dtmf_stop (call, test_stop);
g_message ("####### about to run %s", tf->tc->desc);
/* Run the test */
call_start_time = g_get_real_time ();
mm_gdbus_call_call_send_dtmf (tf->call_proxy,
tf->tc->dtmf,
NULL,
(GAsyncReadyCallback) dtmf_send_ready,
tf);
g_main_loop_run (tf->loop);
/* Validate results */
if (tf->tc->start_error_msg)
mm_assert_error_str (tf->error, tf->tc->start_error_msg);
else if (tf->tc->accept_error_msg)
mm_assert_error_str (tf->error, tf->tc->accept_error_msg);
else if (tf->tc->deflect_error_msg)
mm_assert_error_str (tf->error, tf->tc->deflect_error_msg);
else if (tf->tc->hangup_error_msg)
mm_assert_error_str (tf->error, tf->tc->hangup_error_msg);
else if (tf->tc->dtmf_error_msg)
mm_assert_error_str (tf->error, tf->tc->dtmf_error_msg);
else if (test_stop && tf->tc->dtmf_stop_error_msg)
mm_assert_error_str (tf->error, tf->tc->dtmf_stop_error_msg);
else {
p = tf->tc->dtmf;
while (*p++) {
if (*p != MM_CALL_DTMF_PAUSE_CHAR)
dtmf_len_no_pause++;
}
g_assert_cmpint (strlen (call->priv->dtmf_sent->str), ==, dtmf_len_no_pause);
if (tf->tc->dtmf_min_duration) {
g_assert_cmpint (call_start_time + (tf->tc->dtmf_min_duration * G_USEC_PER_SEC), <=, g_get_real_time ());
}
}
if (test_stop && !tf->tc->dtmf_error_msg)
g_assert_true (call->priv->dtmf_stop_called);
}
static void
test_dtmf_nostop (TestFixture *tf, gconstpointer user_data)
{
test_dtmf (tf, (const Testcase *) user_data, FALSE);
}
static void
test_dtmf_need_stop (TestFixture *tf, gconstpointer user_data)
{
test_dtmf (tf, (const Testcase *) user_data, TRUE);
}
/************************************************************/
static void
on_bus_acquired (GDBusConnection *connection,
const gchar *name,
TestFixture *tf)
{
tf->connection = connection;
}
static void
name_acquired_cb (GDBusConnection *connection,
const gchar *name,
TestFixture *tf)
{
g_main_loop_quit (tf->loop);
}
static void
voice_init_ready (MMIfaceModemVoice *_self,
GAsyncResult *result,
TestFixture *tf)
{
g_autoptr(GError) error = NULL;
gboolean success;
success = mm_iface_modem_voice_initialize_finish (_self, result, &error);
g_assert_no_error (error);
g_assert (success);
g_main_loop_quit (tf->loop);
}
static void
voice_proxy_ready (gpointer unused,
GAsyncResult *res,
TestFixture *tf)
{
g_autoptr(GError) error = NULL;
tf->voice_proxy = mm_gdbus_modem_voice_proxy_new_for_bus_finish (res, &error);
g_assert_no_error (error);
g_assert (tf->voice_proxy);
g_main_loop_quit (tf->loop);
}
static void
test_fixture_setup (TestFixture *tf, gconstpointer unused)
{
g_autoptr(GError) error = NULL;
gboolean success;
success = mm_log_setup (mm_context_get_log_level (),
mm_context_get_log_file (),
mm_context_get_log_journal (),
mm_context_get_log_timestamps (),
mm_context_get_log_relative_timestamps (),
mm_context_get_log_personal_info (),
&error);
g_assert_no_error (error);
g_assert (success);
tf->loop = g_main_loop_new (NULL, FALSE);
/* Create the global dbus-daemon for this test suite */
tf->dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
g_assert (tf->dbus);
/* Add the private directory with our in-tree service files,
* TEST_SERVICES is defined by the build system to point
* to the right directory. */
g_test_dbus_add_service_dir (tf->dbus, TEST_SERVICES);
/* Start the private DBus daemon */
g_test_dbus_up (tf->dbus);
/* Acquire name, don't allow replacement */
tf->name_id = g_bus_own_name (G_BUS_TYPE_SESSION,
MM_DBUS_SERVICE,
G_BUS_NAME_OWNER_FLAGS_NONE,
(GBusAcquiredCallback) on_bus_acquired,
(GBusNameAcquiredCallback) name_acquired_cb,
NULL,
tf,
NULL);
/* Wait for name acquired */
g_main_loop_run (tf->loop);
/* Create and export the server-side modem voice interface */
g_assert (tf->connection);
tf->modem = mm_fake_modem_new (tf->connection);
mm_iface_modem_voice_initialize (MM_IFACE_MODEM_VOICE (tf->modem),
NULL,
(GAsyncReadyCallback) voice_init_ready,
tf);
g_main_loop_run (tf->loop);
if (!mm_fake_modem_export_interfaces (tf->modem, &error))
g_assert_no_error (error);
/* Create client-side modem proxy */
mm_gdbus_modem_voice_proxy_new_for_bus (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
"org.freedesktop.ModemManager1",
mm_fake_modem_get_path (tf->modem),
NULL,
(GAsyncReadyCallback) voice_proxy_ready,
tf);
g_main_loop_run (tf->loop);
}
static void
test_fixture_cleanup (TestFixture *tf, gconstpointer unused)
{
g_bus_unown_name (tf->name_id);
mm_iface_modem_voice_shutdown (MM_IFACE_MODEM_VOICE (tf->modem));
g_clear_error (&tf->error);
g_clear_object (&tf->call_proxy);
g_clear_object (&tf->call);
g_clear_object (&tf->voice_proxy);
/* Run dispose to break a ref cycle in case there's still a FakeCall hanging around */
g_object_run_dispose (G_OBJECT (tf->modem));
g_clear_object (&tf->modem);
g_dbus_connection_close_sync (tf->connection, NULL, NULL);
g_test_dbus_stop (tf->dbus);
g_test_dbus_down (tf->dbus);
g_clear_object (&tf->dbus);
g_main_loop_unref (tf->loop);
mm_log_shutdown ();
}
/****************************************************************/
static const Testcase tests[] = {
{
.desc = "/MM/Call/DTMF/send-one-accept-len",
.number = "911",
.dtmf_accept_len = 1,
.dtmf_tone_duration = 300,
.dtmf = "987654321",
},
{
.desc = "/MM/Call/DTMF/send-single-tone",
.number = "911",
.dtmf_accept_len = 3,
.dtmf_tone_duration = 300,
.dtmf = "9",
},
{
.desc = "/MM/Call/DTMF/send-multi-tone",
.number = "911",
.dtmf_accept_len = 3,
.dtmf_tone_duration = 300,
.dtmf = "123",
},
{
.desc = "/MM/Call/DTMF/send-pause",
.number = "911",
.dtmf_accept_len = 10,
.dtmf_tone_duration = 300,
.dtmf = "123,,4",
.dtmf_min_duration = 4,
},
/* Error testing */
{
.desc = "/MM/Call/DTMF/send-error",
.number = "911",
.dtmf_accept_len = 1,
.dtmf = "123",
.dtmf_error_msg = "send failure",
},
{
.desc = "/MM/Call/DTMF/stop-error",
.number = "911",
.dtmf_accept_len = 1,
.dtmf = "123",
.dtmf_stop_error_msg = "stop failure",
},
};
#define TCASE(n, d, f) \
g_test_add (n, \
TestFixture, \
d, \
test_fixture_setup, \
f, \
test_fixture_cleanup); \
#define TCASE_DTMF_STOP(n, d, f) \
{ \
g_autofree gchar *desc = g_strdup_printf ("%s-stop", n); \
TCASE(desc, d, f); \
}
int main (int argc, char **argv)
{
const gchar *test_args[] = { argv[0], "--test-session" };
guint i;
setlocale (LC_ALL, "");
g_test_init (&argc, &argv, NULL);
mm_context_init (G_N_ELEMENTS (test_args), (gchar **) test_args);
for (i = 0; i < G_N_ELEMENTS (tests); i++) {
TCASE(tests[i].desc, &tests[i], test_dtmf_nostop);
/* Test everything again for paired start/stop (eg QMI) */
TCASE_DTMF_STOP(tests[i].desc, &tests[i], test_dtmf_need_stop);
}
return g_test_run ();
}
+45
View File
@@ -5120,6 +5120,44 @@ test_cpin_response (void)
/*****************************************************************************/
typedef struct {
const char *desc;
const char *dtmf;
const char *expected[5];
} DtmfTestData;
static const DtmfTestData test_dtmf_data[] = {
{ "/MM/ModemHelpers/DTMF/empty", "", { NULL } },
{ "/MM/ModemHelpers/DTMF/one", "1", { "1", NULL } },
{ "/MM/ModemHelpers/DTMF/no-pause", "1234", { "1234", NULL } },
{ "/MM/ModemHelpers/DTMF/mid-pause", "123,,456", { "123", ",", ",", "456", NULL } },
{ "/MM/ModemHelpers/DTMF/end-pause", "123,,", { "123", ",", ",", NULL } },
};
static void
test_dtmf_split (gpointer user_data)
{
DtmfTestData *td = user_data;
GPtrArray *split;
guint expected_len;
guint i;
split = mm_dtmf_split (td->dtmf);
expected_len = g_strv_length ((gchar **) td->expected);
if (expected_len == 0)
g_assert_true (split == NULL);
else {
g_assert_true (split != NULL);
g_assert_cmpint (split->len, ==, expected_len);
}
for (i = 0; i < expected_len; i++)
g_assert_cmpstr (td->expected[i], ==, g_ptr_array_index (split, i));
}
/*****************************************************************************/
#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (GTestFixtureFunc) t, NULL)
int main (int argc, char **argv)
@@ -5128,6 +5166,7 @@ int main (int argc, char **argv)
RegTestData *reg_data;
gint result;
DevidItem *item = &devids[0];
guint i;
g_test_init (&argc, &argv, NULL);
@@ -5384,6 +5423,12 @@ int main (int argc, char **argv)
g_test_suite_add (suite, TESTCASE (test_cpin_response, NULL));
for (i = 0; i < G_N_ELEMENTS (test_dtmf_data); i++) {
g_test_add_data_func (test_dtmf_data[i].desc,
&test_dtmf_data[i],
(GTestDataFunc) test_dtmf_split);
}
result = g_test_run ();
reg_test_data_free (reg_data);