cinterion: support ^SLCC URCs as part of voice management

This command will give us URCs whenever the extended list of current
calls changes, which includes information about the actual state of
each call, even for calls in waiting state.

Therefore, as this is a URC that applies to all calls, it's enabled
and disabled as part of the modem voice interface, instead of doing it
as part of the call object itself (i.e. not treated as an in-call URC).
This commit is contained in:
Aleksander Morgado
2019-06-14 17:53:09 +02:00
parent e931c0ae9c
commit 47dd9fffac
7 changed files with 797 additions and 1 deletions

View File

@@ -34,6 +34,7 @@
#include "mm-iface-modem-3gpp.h" #include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-messaging.h" #include "mm-iface-modem-messaging.h"
#include "mm-iface-modem-location.h" #include "mm-iface-modem-location.h"
#include "mm-iface-modem-voice.h"
#include "mm-base-modem-at.h" #include "mm-base-modem-at.h"
#include "mm-broadband-modem-cinterion.h" #include "mm-broadband-modem-cinterion.h"
#include "mm-modem-helpers-cinterion.h" #include "mm-modem-helpers-cinterion.h"
@@ -44,17 +45,20 @@ static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
static void iface_modem_location_init (MMIfaceModemLocation *iface); static void iface_modem_location_init (MMIfaceModemLocation *iface);
static void iface_modem_voice_init (MMIfaceModemVoice *iface);
static void shared_cinterion_init (MMSharedCinterion *iface); static void shared_cinterion_init (MMSharedCinterion *iface);
static MMIfaceModem *iface_modem_parent; static MMIfaceModem *iface_modem_parent;
static MMIfaceModem3gpp *iface_modem_3gpp_parent; static MMIfaceModem3gpp *iface_modem_3gpp_parent;
static MMIfaceModemLocation *iface_modem_location_parent; static MMIfaceModemLocation *iface_modem_location_parent;
static MMIfaceModemVoice *iface_modem_voice_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion, MM_TYPE_BROADBAND_MODEM, 0, G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion, MM_TYPE_BROADBAND_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init)) G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
typedef enum { typedef enum {
@@ -1956,10 +1960,32 @@ peek_parent_location_interface (MMSharedCinterion *self)
return iface_modem_location_parent; return iface_modem_location_parent;
} }
static void
iface_modem_voice_init (MMIfaceModemVoice *iface)
{
iface_modem_voice_parent = g_type_interface_peek_parent (iface);
iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
}
static MMIfaceModemVoice *
peek_parent_voice_interface (MMSharedCinterion *self)
{
return iface_modem_voice_parent;
}
static void static void
shared_cinterion_init (MMSharedCinterion *iface) shared_cinterion_init (MMSharedCinterion *iface)
{ {
iface->peek_parent_location_interface = peek_parent_location_interface; iface->peek_parent_location_interface = peek_parent_location_interface;
iface->peek_parent_voice_interface = peek_parent_voice_interface;
} }
static void static void

View File

@@ -26,16 +26,20 @@
#include "mm-log.h" #include "mm-log.h"
#include "mm-errors-types.h" #include "mm-errors-types.h"
#include "mm-iface-modem-location.h" #include "mm-iface-modem-location.h"
#include "mm-iface-modem-voice.h"
#include "mm-broadband-modem-qmi-cinterion.h" #include "mm-broadband-modem-qmi-cinterion.h"
#include "mm-shared-cinterion.h" #include "mm-shared-cinterion.h"
static void iface_modem_location_init (MMIfaceModemLocation *iface); static void iface_modem_location_init (MMIfaceModemLocation *iface);
static void iface_modem_voice_init (MMIfaceModemVoice *iface);
static void shared_cinterion_init (MMSharedCinterion *iface); static void shared_cinterion_init (MMSharedCinterion *iface);
static MMIfaceModemLocation *iface_modem_location_parent; static MMIfaceModemLocation *iface_modem_location_parent;
static MMIfaceModemVoice *iface_modem_voice_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiCinterion, mm_broadband_modem_qmi_cinterion, MM_TYPE_BROADBAND_MODEM_QMI, 0, G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiCinterion, mm_broadband_modem_qmi_cinterion, MM_TYPE_BROADBAND_MODEM_QMI, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init)) G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
/*****************************************************************************/ /*****************************************************************************/
@@ -80,10 +84,32 @@ peek_parent_location_interface (MMSharedCinterion *self)
return iface_modem_location_parent; return iface_modem_location_parent;
} }
static void
iface_modem_voice_init (MMIfaceModemVoice *iface)
{
iface_modem_voice_parent = g_type_interface_peek_parent (iface);
iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
}
static MMIfaceModemVoice *
peek_parent_voice_interface (MMSharedCinterion *self)
{
return iface_modem_voice_parent;
}
static void static void
shared_cinterion_init (MMSharedCinterion *iface) shared_cinterion_init (MMSharedCinterion *iface)
{ {
iface->peek_parent_location_interface = peek_parent_location_interface; iface->peek_parent_location_interface = peek_parent_location_interface;
iface->peek_parent_voice_interface = peek_parent_voice_interface;
} }
static void static void

View File

@@ -667,3 +667,131 @@ mm_cinterion_get_access_technology_from_sind_psinfo (guint val)
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
} }
} }
/*****************************************************************************/
/* ^SLCC psinfo helper */
GRegex *
mm_cinterion_get_slcc_regex (void)
{
/* The list of active calls displayed with this URC will always be terminated
* with an empty line preceded by prefix "^SLCC: ", in order to indicate the end
* of the list.
*/
return g_regex_new ("\\r\\n(\\^SLCC: .*\\r\\n)*\\^SLCC: \\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
}
static void
cinterion_call_info_free (MMCallInfo *info)
{
if (!info)
return;
g_free (info->number);
g_slice_free (MMCallInfo, info);
}
gboolean
mm_cinterion_parse_slcc_list (const gchar *str,
GList **out_list,
GError **error)
{
GRegex *r;
GList *list = NULL;
GError *inner_error = NULL;
GMatchInfo *match_info = NULL;
static const MMCallDirection cinterion_call_direction[] = {
[0] = MM_CALL_DIRECTION_OUTGOING,
[1] = MM_CALL_DIRECTION_INCOMING,
};
static const MMCallState cinterion_call_state[] = {
[0] = MM_CALL_STATE_ACTIVE,
[1] = MM_CALL_STATE_HELD,
[2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */
[3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */
[4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */
[5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */
};
g_assert (out_list);
/*
* 1 2 3 4 5 6 7 8 9
* ^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]]
* [^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]]]
* [... ]
* ^SLCC :
*/
r = g_regex_new ("\\^SLCC:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)" /* mandatory fields */
"(?:,\\s*([^,]*),\\s*(\\d+)" /* number and type */
"(?:,\\s*([^,]*)" /* alpha */
")?)?$",
G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF,
G_REGEX_MATCH_NEWLINE_CRLF,
NULL);
g_assert (r != NULL);
g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error);
if (inner_error)
goto out;
/* Parse the results */
while (g_match_info_matches (match_info)) {
MMCallInfo *call_info;
guint aux;
call_info = g_slice_new0 (MMCallInfo);
if (!mm_get_uint_from_match_info (match_info, 1, &call_info->index)) {
mm_warn ("couldn't parse call index from ^SLCC line");
goto next;
}
if (!mm_get_uint_from_match_info (match_info, 2, &aux) ||
(aux >= G_N_ELEMENTS (cinterion_call_direction))) {
mm_warn ("couldn't parse call direction from ^SLCC line");
goto next;
}
call_info->direction = cinterion_call_direction[aux];
if (!mm_get_uint_from_match_info (match_info, 3, &aux) ||
(aux >= G_N_ELEMENTS (cinterion_call_state))) {
mm_warn ("couldn't parse call state from ^SLCC line");
goto next;
}
call_info->state = cinterion_call_state[aux];
if (g_match_info_get_match_count (match_info) >= 8)
call_info->number = mm_get_string_unquoted_from_match_info (match_info, 7);
list = g_list_append (list, call_info);
call_info = NULL;
next:
cinterion_call_info_free (call_info);
g_match_info_next (match_info, NULL);
}
out:
g_clear_pointer (&match_info, g_match_info_free);
g_regex_unref (r);
if (inner_error) {
mm_cinterion_call_info_list_free (list);
g_propagate_error (error, inner_error);
return FALSE;
}
*out_list = list;
return TRUE;
}
void
mm_cinterion_call_info_list_free (GList *call_info_list)
{
g_list_free_full (call_info_list, (GDestroyNotify) cinterion_call_info_free);
}

View File

@@ -87,4 +87,15 @@ gboolean mm_cinterion_parse_smong_response (const gchar *response,
MMModemAccessTechnology mm_cinterion_get_access_technology_from_sind_psinfo (guint val); MMModemAccessTechnology mm_cinterion_get_access_technology_from_sind_psinfo (guint val);
/*****************************************************************************/
/* ^SLCC URC helpers */
GRegex *mm_cinterion_get_slcc_regex (void);
/* MMCallInfo list management */
gboolean mm_cinterion_parse_slcc_list (const gchar *str,
GList **out_list,
GError **error);
void mm_cinterion_call_info_list_free (GList *call_info_list);
#endif /* MM_MODEM_HELPERS_CINTERION_H */ #endif /* MM_MODEM_HELPERS_CINTERION_H */

View File

@@ -28,6 +28,7 @@
#include "mm-base-modem.h" #include "mm-base-modem.h"
#include "mm-base-modem-at.h" #include "mm-base-modem-at.h"
#include "mm-shared-cinterion.h" #include "mm-shared-cinterion.h"
#include "mm-modem-helpers-cinterion.h"
/*****************************************************************************/ /*****************************************************************************/
/* Private data context */ /* Private data context */
@@ -42,16 +43,22 @@ typedef enum {
} FeatureSupport; } FeatureSupport;
typedef struct { typedef struct {
/* location */
MMIfaceModemLocation *iface_modem_location_parent; MMIfaceModemLocation *iface_modem_location_parent;
MMModemLocationSource supported_sources; MMModemLocationSource supported_sources;
MMModemLocationSource enabled_sources; MMModemLocationSource enabled_sources;
FeatureSupport sgpss_support; FeatureSupport sgpss_support;
FeatureSupport sgpsc_support; FeatureSupport sgpsc_support;
/* voice */
MMIfaceModemVoice *iface_modem_voice_parent;
FeatureSupport slcc_support;
GRegex *slcc_regex;
} Private; } Private;
static void static void
private_free (Private *ctx) private_free (Private *ctx)
{ {
g_regex_unref (ctx->slcc_regex);
g_slice_free (Private, ctx); g_slice_free (Private, ctx);
} }
@@ -71,11 +78,17 @@ get_private (MMSharedCinterion *self)
priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
priv->sgpss_support = FEATURE_SUPPORT_UNKNOWN; priv->sgpss_support = FEATURE_SUPPORT_UNKNOWN;
priv->sgpsc_support = FEATURE_SUPPORT_UNKNOWN; priv->sgpsc_support = FEATURE_SUPPORT_UNKNOWN;
priv->slcc_support = FEATURE_SUPPORT_UNKNOWN;
priv->slcc_regex = mm_cinterion_get_slcc_regex ();
/* Setup parent class' MMIfaceModemLocation and MMIfaceModemVoice */
/* Setup parent class' MMIfaceModemLocation */
g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface); g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface);
priv->iface_modem_location_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface (self); priv->iface_modem_location_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface (self);
g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_voice_interface);
priv->iface_modem_voice_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_voice_interface (self);
g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
} }
@@ -785,6 +798,437 @@ mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self,
enable_location_gathering_context_gps_step (task); enable_location_gathering_context_gps_step (task);
} }
/*****************************************************************************/
/* Common enable/disable voice unsolicited events */
typedef struct {
gboolean enable;
MMPortSerialAt *primary;
MMPortSerialAt *secondary;
gchar *slcc_command;
gboolean slcc_primary_done;
gboolean slcc_secondary_done;
} VoiceUnsolicitedEventsContext;
static void
voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx)
{
g_clear_object (&ctx->secondary);
g_clear_object (&ctx->primary);
g_free (ctx->slcc_command);
g_slice_free (VoiceUnsolicitedEventsContext, ctx);
}
static gboolean
common_voice_enable_disable_unsolicited_events_finish (MMSharedCinterion *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void run_voice_enable_disable_unsolicited_events (GTask *task);
static void
slcc_command_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
Private *priv;
VoiceUnsolicitedEventsContext *ctx;
GError *error = NULL;
priv = get_private (MM_SHARED_CINTERION (self));
ctx = g_task_get_task_data (task);
if (!mm_base_modem_at_command_finish (self, res, &error)) {
if (priv->slcc_support == FEATURE_SUPPORT_UNKNOWN)
priv->slcc_support = FEATURE_NOT_SUPPORTED;
mm_dbg ("Couldn't %s ^SLCC reporting: '%s'",
ctx->enable ? "enable" : "disable",
error->message);
g_error_free (error);
} else if (priv->slcc_support == FEATURE_SUPPORT_UNKNOWN)
priv->slcc_support = FEATURE_SUPPORTED;
/* Continue on next port */
run_voice_enable_disable_unsolicited_events (task);
}
static void
run_voice_enable_disable_unsolicited_events (GTask *task)
{
MMSharedCinterion *self;
Private *priv;
VoiceUnsolicitedEventsContext *ctx;
MMPortSerialAt *port = NULL;
self = MM_SHARED_CINTERION (g_task_get_source_object (task));
priv = get_private (self);
ctx = g_task_get_task_data (task);
/* If not ^SLCC supported, we're done */
if (priv->slcc_support == FEATURE_NOT_SUPPORTED) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
if (!ctx->slcc_primary_done && ctx->primary) {
mm_dbg ("%s ^SLCC extended list of current calls reporting in primary port...",
ctx->enable ? "Enabling" : "Disabling");
ctx->slcc_primary_done = TRUE;
port = ctx->primary;
} else if (!ctx->slcc_secondary_done && ctx->secondary) {
mm_dbg ("%s ^SLCC extended list of current calls reporting in secondary port...",
ctx->enable ? "Enabling" : "Disabling");
ctx->slcc_secondary_done = TRUE;
port = ctx->secondary;
}
if (port) {
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
port,
ctx->slcc_command,
3,
FALSE,
FALSE,
NULL,
(GAsyncReadyCallback)slcc_command_ready,
task);
return;
}
/* Fully done now */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
common_voice_enable_disable_unsolicited_events (MMSharedCinterion *self,
gboolean enable,
GAsyncReadyCallback callback,
gpointer user_data)
{
VoiceUnsolicitedEventsContext *ctx;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
ctx->enable = enable;
if (enable)
ctx->slcc_command = g_strdup ("^SLCC=1");
else
ctx->slcc_command = g_strdup ("^SLCC=0");
ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
run_voice_enable_disable_unsolicited_events (task);
}
/*****************************************************************************/
/* Disable unsolicited events (Voice interface) */
gboolean
mm_shared_cinterion_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
Private *priv;
priv = get_private (MM_SHARED_CINTERION (self));
if (!priv->iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) {
mm_warn ("Couldn't disable parent voice unsolicited events: %s", error->message);
g_error_free (error);
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
voice_disable_unsolicited_events_ready (MMSharedCinterion *self,
GAsyncResult *res,
GTask *task)
{
Private *priv;
GError *error = NULL;
if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
mm_warn ("Couldn't disable Cinterion-specific voice unsolicited events: %s", error->message);
g_error_free (error);
}
priv = get_private (MM_SHARED_CINTERION (self));
g_assert (priv->iface_modem_voice_parent);
g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events);
g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events_finish);
/* Chain up parent's disable */
priv->iface_modem_voice_parent->disable_unsolicited_events (
MM_IFACE_MODEM_VOICE (self),
(GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
task);
}
void
mm_shared_cinterion_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* our own disabling first */
common_voice_enable_disable_unsolicited_events (MM_SHARED_CINTERION (self),
FALSE,
(GAsyncReadyCallback) voice_disable_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Enable unsolicited events (Voice interface) */
gboolean
mm_shared_cinterion_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
voice_enable_unsolicited_events_ready (MMSharedCinterion *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
mm_warn ("Couldn't enable Cinterion-specific voice unsolicited events: %s", error->message);
g_error_free (error);
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
Private *priv;
priv = get_private (MM_SHARED_CINTERION (self));
if (!priv->iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) {
mm_warn ("Couldn't enable parent voice unsolicited events: %s", error->message);
g_error_free (error);
}
/* our own enabling next */
common_voice_enable_disable_unsolicited_events (MM_SHARED_CINTERION (self),
TRUE,
(GAsyncReadyCallback) voice_enable_unsolicited_events_ready,
task);
}
void
mm_shared_cinterion_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
Private *priv;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
priv = get_private (MM_SHARED_CINTERION (self));
g_assert (priv->iface_modem_voice_parent);
g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events);
g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events_finish);
/* chain up parent's enable first */
priv->iface_modem_voice_parent->enable_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Common setup/cleanup voice unsolicited events */
static void
slcc_received (MMPortSerialAt *port,
GMatchInfo *match_info,
MMSharedCinterion *self)
{
gchar *full;
GError *error = NULL;
GList *call_info_list = NULL;
full = g_match_info_fetch (match_info, 0);
if (!mm_cinterion_parse_slcc_list (full, &call_info_list, &error)) {
mm_warn ("couldn't parse ^SLCC list: %s", error->message);
g_error_free (error);
} else
mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list);
mm_cinterion_call_info_list_free (call_info_list);
g_free (full);
}
static void
common_voice_setup_cleanup_unsolicited_events (MMSharedCinterion *self,
gboolean enable)
{
Private *priv;
MMPortSerialAt *ports[2];
guint i;
priv = get_private (MM_SHARED_CINTERION (self));
ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
for (i = 0; i < G_N_ELEMENTS (ports); i++) {
if (!ports[i])
continue;
mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
priv->slcc_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)slcc_received : NULL,
enable ? self : NULL,
NULL);
}
}
/*****************************************************************************/
/* Cleanup unsolicited events (Voice interface) */
gboolean
mm_shared_cinterion_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
Private *priv;
priv = get_private (MM_SHARED_CINTERION (self));
if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) {
mm_warn ("Couldn't cleanup parent voice unsolicited events: %s", error->message);
g_error_free (error);
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
void
mm_shared_cinterion_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
Private *priv;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
priv = get_private (MM_SHARED_CINTERION (self));
g_assert (priv->iface_modem_voice_parent);
g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events);
g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish);
/* our own cleanup first */
common_voice_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), FALSE);
/* Chain up parent's cleanup */
priv->iface_modem_voice_parent->cleanup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Setup unsolicited events (Voice interface) */
gboolean
mm_shared_cinterion_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
Private *priv;
priv = get_private (MM_SHARED_CINTERION (self));
if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) {
mm_warn ("Couldn't cleanup parent voice unsolicited events: %s", error->message);
g_error_free (error);
}
/* our own setup next */
common_voice_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), TRUE);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
void
mm_shared_cinterion_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
Private *priv;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
priv = get_private (MM_SHARED_CINTERION (self));
g_assert (priv->iface_modem_voice_parent);
g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events);
g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events_finish);
/* chain up parent's setup first */
priv->iface_modem_voice_parent->setup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
task);
}
/*****************************************************************************/ /*****************************************************************************/
static void static void

View File

@@ -26,6 +26,7 @@
#include "mm-broadband-modem.h" #include "mm-broadband-modem.h"
#include "mm-iface-modem.h" #include "mm-iface-modem.h"
#include "mm-iface-modem-location.h" #include "mm-iface-modem-location.h"
#include "mm-iface-modem-voice.h"
#define MM_TYPE_SHARED_CINTERION (mm_shared_cinterion_get_type ()) #define MM_TYPE_SHARED_CINTERION (mm_shared_cinterion_get_type ())
#define MM_SHARED_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_CINTERION, MMSharedCinterion)) #define MM_SHARED_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_CINTERION, MMSharedCinterion))
@@ -39,6 +40,9 @@ struct _MMSharedCinterion {
/* Peek location interface of the parent class of the object */ /* Peek location interface of the parent class of the object */
MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedCinterion *self); MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedCinterion *self);
/* Peek voice interface of the parent class of the object */
MMIfaceModemVoice * (* peek_parent_voice_interface) (MMSharedCinterion *self);
}; };
GType mm_shared_cinterion_get_type (void); GType mm_shared_cinterion_get_type (void);
@@ -66,4 +70,32 @@ gboolean mm_shared_cinterion_disable_location_gathering_finish (MMI
GAsyncResult *res, GAsyncResult *res,
GError **error); GError **error);
void mm_shared_cinterion_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean mm_shared_cinterion_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error);
void mm_shared_cinterion_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean mm_shared_cinterion_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error);
void mm_shared_cinterion_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean mm_shared_cinterion_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error);
void mm_shared_cinterion_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean mm_shared_cinterion_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error);
#endif /* MM_SHARED_CINTERION_H */ #endif /* MM_SHARED_CINTERION_H */

View File

@@ -667,6 +667,131 @@ test_smong_response_other (void)
common_test_smong_response (response, MM_MODEM_ACCESS_TECHNOLOGY_GPRS); common_test_smong_response (response, MM_MODEM_ACCESS_TECHNOLOGY_GPRS);
} }
/*****************************************************************************/
/* Test ^SLCC URCs */
static void
common_test_slcc_urc (const gchar *urc,
const MMCallInfo *expected_call_info_list,
guint expected_call_info_list_size)
{
GError *error = NULL;
GRegex *slcc_regex = NULL;
gboolean result;
GMatchInfo *match_info = NULL;
gchar *str;
GList *call_info_list = NULL;
GList *l;
slcc_regex = mm_cinterion_get_slcc_regex ();
/* Same matching logic as done in MMSerialPortAt when processing URCs! */
result = g_regex_match_full (slcc_regex, urc, -1, 0, 0, &match_info, &error);
g_assert_no_error (error);
g_assert (result);
/* read full matched content */
str = g_match_info_fetch (match_info, 0);
g_assert (str);
result = mm_cinterion_parse_slcc_list (str, &call_info_list, &error);
g_assert_no_error (error);
g_assert (result);
g_print ("found %u calls\n", g_list_length (call_info_list));
if (expected_call_info_list) {
g_assert (call_info_list);
g_assert_cmpuint (g_list_length (call_info_list), ==, expected_call_info_list_size);
} else
g_assert (!call_info_list);
for (l = call_info_list; l; l = g_list_next (l)) {
const MMCallInfo *call_info = (const MMCallInfo *)(l->data);
gboolean found = FALSE;
guint i;
g_print ("call at index %u: direction %s, state %s, number %s\n",
call_info->index,
mm_call_direction_get_string (call_info->direction),
mm_call_state_get_string (call_info->state),
call_info->number ? call_info->number : "n/a");
for (i = 0; !found && i < expected_call_info_list_size; i++)
found = ((call_info->index == expected_call_info_list[i].index) &&
(call_info->direction == expected_call_info_list[i].direction) &&
(call_info->state == expected_call_info_list[i].state) &&
(g_strcmp0 (call_info->number, expected_call_info_list[i].number) == 0));
g_assert (found);
}
g_match_info_free (match_info);
g_regex_unref (slcc_regex);
g_free (str);
mm_cinterion_call_info_list_free (call_info_list);
}
static void
test_slcc_urc_empty (void)
{
const gchar *urc = "\r\n^SLCC: \r\n";
common_test_slcc_urc (urc, NULL, 0);
}
static void
test_slcc_urc_single (void)
{
static const MMCallInfo expected_call_info_list[] = {
{ 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "123456789" }
};
const gchar *urc =
"\r\n^SLCC: 1,1,0,0,0,0,\"123456789\",161"
"\r\n^SLCC: \r\n";
common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
}
static void
test_slcc_urc_multiple (void)
{
static const MMCallInfo expected_call_info_list[] = {
{ 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, NULL },
{ 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "123456789" },
{ 3, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "987654321" },
};
const gchar *urc =
"\r\n^SLCC: 1,1,0,0,1,0" /* number unknown */
"\r\n^SLCC: 2,1,0,0,1,0,\"123456789\",161"
"\r\n^SLCC: 3,1,0,0,1,0,\"987654321\",161,\"Alice\""
"\r\n^SLCC: \r\n";
common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
}
static void
test_slcc_urc_complex (void)
{
static const MMCallInfo expected_call_info_list[] = {
{ 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "123456789" },
{ 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, "987654321" },
};
const gchar *urc =
"\r\n^CIEV: 1,0" /* some different URC before our match */
"\r\n^SLCC: 1,1,0,0,0,0,\"123456789\",161"
"\r\n^SLCC: 2,1,5,0,0,0,\"987654321\",161"
"\r\n^SLCC: \r\n"
"\r\n^CIEV: 1,0" /* some different URC after our match */;
common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
}
/*****************************************************************************/ /*****************************************************************************/
void void
@@ -706,6 +831,10 @@ int main (int argc, char **argv)
g_test_add_func ("/MM/cinterion/sind/response/simstatus", test_sind_response_simstatus); g_test_add_func ("/MM/cinterion/sind/response/simstatus", test_sind_response_simstatus);
g_test_add_func ("/MM/cinterion/smong/response/tc63i", test_smong_response_tc63i); g_test_add_func ("/MM/cinterion/smong/response/tc63i", test_smong_response_tc63i);
g_test_add_func ("/MM/cinterion/smong/response/other", test_smong_response_other); g_test_add_func ("/MM/cinterion/smong/response/other", test_smong_response_other);
g_test_add_func ("/MM/cinterion/slcc/urc/empty", test_slcc_urc_empty);
g_test_add_func ("/MM/cinterion/slcc/urc/single", test_slcc_urc_single);
g_test_add_func ("/MM/cinterion/slcc/urc/multiple", test_slcc_urc_multiple);
g_test_add_func ("/MM/cinterion/slcc/urc/complex", test_slcc_urc_complex);
return g_test_run (); return g_test_run ();
} }