diff options
-rw-r--r-- | plugins/cinterion/mm-broadband-modem-cinterion.c | 26 | ||||
-rw-r--r-- | plugins/cinterion/mm-broadband-modem-qmi-cinterion.c | 26 | ||||
-rw-r--r-- | plugins/cinterion/mm-modem-helpers-cinterion.c | 128 | ||||
-rw-r--r-- | plugins/cinterion/mm-modem-helpers-cinterion.h | 11 | ||||
-rw-r--r-- | plugins/cinterion/mm-shared-cinterion.c | 446 | ||||
-rw-r--r-- | plugins/cinterion/mm-shared-cinterion.h | 32 | ||||
-rw-r--r-- | plugins/cinterion/tests/test-modem-helpers-cinterion.c | 129 |
7 files changed, 797 insertions, 1 deletions
diff --git a/plugins/cinterion/mm-broadband-modem-cinterion.c b/plugins/cinterion/mm-broadband-modem-cinterion.c index 5025e3e0..01531a01 100644 --- a/plugins/cinterion/mm-broadband-modem-cinterion.c +++ b/plugins/cinterion/mm-broadband-modem-cinterion.c @@ -34,6 +34,7 @@ #include "mm-iface-modem-3gpp.h" #include "mm-iface-modem-messaging.h" #include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" #include "mm-base-modem-at.h" #include "mm-broadband-modem-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_messaging_init (MMIfaceModemMessaging *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 MMIfaceModem *iface_modem_parent; static MMIfaceModem3gpp *iface_modem_3gpp_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_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_MESSAGING, iface_modem_messaging_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)) typedef enum { @@ -1957,9 +1961,31 @@ peek_parent_location_interface (MMSharedCinterion *self) } 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 shared_cinterion_init (MMSharedCinterion *iface) { iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; } static void diff --git a/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c b/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c index 6048ccac..a2d45e67 100644 --- a/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c +++ b/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c @@ -26,16 +26,20 @@ #include "mm-log.h" #include "mm-errors-types.h" #include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" #include "mm-broadband-modem-qmi-cinterion.h" #include "mm-shared-cinterion.h" static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); static void shared_cinterion_init (MMSharedCinterion *iface); 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_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)) /*****************************************************************************/ @@ -81,9 +85,31 @@ peek_parent_location_interface (MMSharedCinterion *self) } 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 shared_cinterion_init (MMSharedCinterion *iface) { iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; } static void diff --git a/plugins/cinterion/mm-modem-helpers-cinterion.c b/plugins/cinterion/mm-modem-helpers-cinterion.c index c7b0c44d..7d583c81 100644 --- a/plugins/cinterion/mm-modem-helpers-cinterion.c +++ b/plugins/cinterion/mm-modem-helpers-cinterion.c @@ -667,3 +667,131 @@ mm_cinterion_get_access_technology_from_sind_psinfo (guint val) 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); +} diff --git a/plugins/cinterion/mm-modem-helpers-cinterion.h b/plugins/cinterion/mm-modem-helpers-cinterion.h index 2ec05157..43a39d18 100644 --- a/plugins/cinterion/mm-modem-helpers-cinterion.h +++ b/plugins/cinterion/mm-modem-helpers-cinterion.h @@ -87,4 +87,15 @@ gboolean mm_cinterion_parse_smong_response (const gchar *response, 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 */ diff --git a/plugins/cinterion/mm-shared-cinterion.c b/plugins/cinterion/mm-shared-cinterion.c index ab95140a..59bf3266 100644 --- a/plugins/cinterion/mm-shared-cinterion.c +++ b/plugins/cinterion/mm-shared-cinterion.c @@ -28,6 +28,7 @@ #include "mm-base-modem.h" #include "mm-base-modem-at.h" #include "mm-shared-cinterion.h" +#include "mm-modem-helpers-cinterion.h" /*****************************************************************************/ /* Private data context */ @@ -42,16 +43,22 @@ typedef enum { } FeatureSupport; typedef struct { + /* location */ MMIfaceModemLocation *iface_modem_location_parent; MMModemLocationSource supported_sources; MMModemLocationSource enabled_sources; FeatureSupport sgpss_support; FeatureSupport sgpsc_support; + /* voice */ + MMIfaceModemVoice *iface_modem_voice_parent; + FeatureSupport slcc_support; + GRegex *slcc_regex; } Private; static void private_free (Private *ctx) { + g_regex_unref (ctx->slcc_regex); g_slice_free (Private, ctx); } @@ -71,11 +78,17 @@ get_private (MMSharedCinterion *self) priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; priv->sgpss_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); 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); } @@ -786,6 +799,437 @@ mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self, } /*****************************************************************************/ +/* 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 shared_cinterion_init (gpointer g_iface) diff --git a/plugins/cinterion/mm-shared-cinterion.h b/plugins/cinterion/mm-shared-cinterion.h index 310a5383..f1dbac25 100644 --- a/plugins/cinterion/mm-shared-cinterion.h +++ b/plugins/cinterion/mm-shared-cinterion.h @@ -26,6 +26,7 @@ #include "mm-broadband-modem.h" #include "mm-iface-modem.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_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 */ 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); @@ -66,4 +70,32 @@ gboolean mm_shared_cinterion_disable_location_gathering_finish (MMI GAsyncResult *res, 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 */ diff --git a/plugins/cinterion/tests/test-modem-helpers-cinterion.c b/plugins/cinterion/tests/test-modem-helpers-cinterion.c index 2578eb00..fd05019e 100644 --- a/plugins/cinterion/tests/test-modem-helpers-cinterion.c +++ b/plugins/cinterion/tests/test-modem-helpers-cinterion.c @@ -668,6 +668,131 @@ test_smong_response_other (void) } /*****************************************************************************/ +/* 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 _mm_log (const char *loc, @@ -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/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/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 (); } |