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:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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'),
|
||||
|
||||
+353
-366
@@ -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,
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
+133
-90
@@ -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
|
||||
@@ -409,73 +452,48 @@ voice_start_continuous_dtmf_ready (QmiClientVoice *client,
|
||||
if (!output) {
|
||||
g_prefix_error (&error, "QMI operation failed: ");
|
||||
g_task_return_error (task, 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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!qmi_message_voice_start_continuous_dtmf_output_get_result (output, &error)) {
|
||||
g_prefix_error (&error, "Couldn't send DTMF character: ");
|
||||
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
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
@@ -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) */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user