Merge commit 'cbc74c986b0418bb6b5a9cb4741a7569937fe3e4' into patch_branch
Change-Id: I0b828830d458958b486cdbf003c9abb41357dd96
diff --git a/.gitignore b/.gitignore
index d80e0ff..fa4b4bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -170,6 +170,8 @@
/test/lsudev
/test/mmtty
/test/mmrules
+/test/mmsmspdu
+/test/mmsmsmonitor
/ModemManager-*-coverage.info
/ModemManager-*-coverage/
diff --git a/README b/README
index 27d6101..ade8b77 100644
--- a/README
+++ b/README
@@ -15,7 +15,7 @@
ModemManager is a DBus system bus activated service (meaning it's started
automatically when a request arrives). It is written in C, using glib and gio.
Several GInterfaces specify different features that the modems support,
-including the generic MMIfaceModem3gpp and MMIfaceModemCdma which provice basic
+including the generic MMIfaceModem3gpp and MMIfaceModemCdma which provide basic
operations for 3GPP (GSM, UMTS, LTE) or CDMA (CDMA1x, EV-DO) modems. If a given
feature is not available in the modem, the specific interface will not be
exported in DBus.
@@ -33,4 +33,4 @@
License.
The ModemManager and mmcli binaries are both GPLv2+.
-The libmm-glib library is LGPLv2+.
\ No newline at end of file
+The libmm-glib library is LGPLv2+.
diff --git a/cli/mmcli-call.c b/cli/mmcli-call.c
index a871dbc..99c8b76 100644
--- a/cli/mmcli-call.c
+++ b/cli/mmcli-call.c
@@ -46,11 +46,14 @@
static Context *ctx;
/* Options */
-static gboolean info_flag; /* set when no action found */
-static gboolean start_flag;
-static gboolean accept_flag;
-static gboolean hangup_flag;
-static gchar *dtmf_request;
+static gboolean info_flag; /* set when no action found */
+static gboolean start_flag;
+static gboolean accept_flag;
+static gchar *deflect_str;
+static gboolean join_multiparty_flag;
+static gboolean leave_multiparty_flag;
+static gboolean hangup_flag;
+static gchar *dtmf_request;
static GOptionEntry entries[] = {
{ "start", 0, 0, G_OPTION_ARG_NONE, &start_flag,
@@ -61,6 +64,18 @@
"Accept the incoming call",
NULL,
},
+ { "deflect", 0, 0, G_OPTION_ARG_STRING, &deflect_str,
+ "Deflect the incoming call",
+ "[NUMBER]",
+ },
+ { "join-multiparty", 0, 0, G_OPTION_ARG_NONE, &join_multiparty_flag,
+ "Join multiparty call",
+ NULL,
+ },
+ { "leave-multiparty", 0, 0, G_OPTION_ARG_NONE, &leave_multiparty_flag,
+ "Leave multiparty call",
+ NULL,
+ },
{ "hangup", 0, 0, G_OPTION_ARG_NONE, &hangup_flag,
"Hang up the call",
NULL,
@@ -99,6 +114,9 @@
n_actions = (start_flag +
accept_flag +
+ !!deflect_str +
+ join_multiparty_flag +
+ leave_multiparty_flag +
hangup_flag +
!!dtmf_request);
@@ -156,6 +174,7 @@
mmcli_output_string (MMC_F_CALL_GENERAL_DBUS_PATH, mm_call_get_path (call));
mmcli_output_string (MMC_F_CALL_PROPERTIES_NUMBER, mm_call_get_number (call));
mmcli_output_string (MMC_F_CALL_PROPERTIES_DIRECTION, mm_call_direction_get_string (mm_call_get_direction (call)));
+ mmcli_output_string (MMC_F_CALL_PROPERTIES_MULTIPARTY, mm_call_get_multiparty (call) ? "yes" : "no");
mmcli_output_string (MMC_F_CALL_PROPERTIES_STATE, mm_call_state_get_string (mm_call_get_state (call)));
mmcli_output_string (MMC_F_CALL_PROPERTIES_STATE_REASON, mm_call_state_reason_get_string (mm_call_get_state_reason (call)));
mmcli_output_string (MMC_F_CALL_PROPERTIES_AUDIO_PORT, mm_call_get_audio_port (call));
@@ -228,6 +247,87 @@
}
static void
+deflect_process_reply (gboolean result,
+ const GError *error)
+{
+ if (!result) {
+ g_printerr ("error: couldn't deflect the call: '%s'\n",
+ error ? error->message : "unknown error");
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("successfully deflected the call\n");
+}
+
+static void
+deflect_ready (MMCall *call,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ gboolean operation_result;
+ GError *error = NULL;
+
+ operation_result = mm_call_deflect_finish (call, result, &error);
+ deflect_process_reply (operation_result, error);
+
+ mmcli_async_operation_done ();
+}
+
+static void
+join_multiparty_process_reply (gboolean result,
+ const GError *error)
+{
+ if (!result) {
+ g_printerr ("error: couldn't join multiparty call: '%s'\n",
+ error ? error->message : "unknown error");
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("successfully joined multiparty call\n");
+}
+
+static void
+join_multiparty_ready (MMCall *call,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ gboolean operation_result;
+ GError *error = NULL;
+
+ operation_result = mm_call_join_multiparty_finish (call, result, &error);
+ join_multiparty_process_reply (operation_result, error);
+
+ mmcli_async_operation_done ();
+}
+
+static void
+leave_multiparty_process_reply (gboolean result,
+ const GError *error)
+{
+ if (!result) {
+ g_printerr ("error: couldn't leave multiparty call: '%s'\n",
+ error ? error->message : "unknown error");
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("successfully left multiparty call\n");
+}
+
+static void
+leave_multiparty_ready (MMCall *call,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ gboolean operation_result;
+ GError *error = NULL;
+
+ operation_result = mm_call_leave_multiparty_finish (call, result, &error);
+ leave_multiparty_process_reply (operation_result, error);
+
+ mmcli_async_operation_done ();
+}
+
+static void
hangup_process_reply (gboolean result,
const GError *error)
{
@@ -313,6 +413,34 @@
return;
}
+ /* Requesting to deflect the call? */
+ if (deflect_str) {
+ mm_call_deflect (ctx->call,
+ deflect_str,
+ ctx->cancellable,
+ (GAsyncReadyCallback)deflect_ready,
+ NULL);
+ return;
+ }
+
+ /* Requesting to join multiparty call? */
+ if (join_multiparty_flag) {
+ mm_call_join_multiparty (ctx->call,
+ ctx->cancellable,
+ (GAsyncReadyCallback)join_multiparty_ready,
+ NULL);
+ return;
+ }
+
+ /* Requesting to leave multiparty call? */
+ if (leave_multiparty_flag) {
+ mm_call_leave_multiparty (ctx->call,
+ ctx->cancellable,
+ (GAsyncReadyCallback)leave_multiparty_ready,
+ NULL);
+ return;
+ }
+
/* Requesting to hangup the call? */
if (hangup_flag) {
mm_call_hangup (ctx->call,
@@ -398,6 +526,40 @@
return;
}
+ /* Requesting to deflect the call? */
+ if (deflect_str) {
+ gboolean operation_result;
+
+ operation_result = mm_call_deflect_sync (ctx->call,
+ deflect_str,
+ NULL,
+ &error);
+ deflect_process_reply (operation_result, error);
+ return;
+ }
+
+ /* Requesting to join multiparty call? */
+ if (join_multiparty_flag) {
+ gboolean operation_result;
+
+ operation_result = mm_call_join_multiparty_sync (ctx->call,
+ NULL,
+ &error);
+ join_multiparty_process_reply (operation_result, error);
+ return;
+ }
+
+ /* Requesting to leave multiparty call? */
+ if (leave_multiparty_flag) {
+ gboolean operation_result;
+
+ operation_result = mm_call_leave_multiparty_sync (ctx->call,
+ NULL,
+ &error);
+ leave_multiparty_process_reply (operation_result, error);
+ return;
+ }
+
/* Requesting to hangup the call? */
if (hangup_flag) {
gboolean operation_result;
diff --git a/cli/mmcli-common.c b/cli/mmcli-common.c
index 305e91d..c149927 100644
--- a/cli/mmcli-common.c
+++ b/cli/mmcli-common.c
@@ -695,6 +695,7 @@
typedef struct {
gchar *sim_path;
+ gchar *sim_maybe_uid;
MMManager *manager;
MMObject *current;
} GetSimContext;
@@ -721,6 +722,7 @@
g_object_unref (ctx->current);
if (ctx->manager)
g_object_unref (ctx->manager);
+ g_free (ctx->sim_maybe_uid);
g_free (ctx->sim_path);
g_free (ctx);
}
@@ -800,6 +802,14 @@
object = MM_OBJECT (l->data);
modem = mm_object_get_modem (object);
+ if (!ctx->sim_path) {
+ if (g_str_equal (ctx->sim_maybe_uid, mm_modem_get_device (modem)))
+ ctx->sim_path = g_strdup (mm_modem_get_sim_path (modem));
+ else {
+ g_object_unref (modem);
+ continue;
+ }
+ }
if (g_str_equal (ctx->sim_path, mm_modem_get_sim_path (modem))) {
ctx->current = g_object_ref (object);
mm_modem_get_sim (modem,
@@ -811,6 +821,12 @@
}
g_list_free_full (modems, g_object_unref);
+ if (!ctx->sim_path) {
+ g_printerr ("error: invalid index string specified: '%s'\n",
+ ctx->sim_maybe_uid);
+ exit (EXIT_FAILURE);
+ }
+
if (!ctx->current) {
g_printerr ("error: couldn't find sim at '%s'\n",
ctx->sim_path);
@@ -821,7 +837,7 @@
static gchar *
get_sim_path (const gchar *path_or_index)
{
- gchar *sim_path;
+ gchar *sim_path = NULL;
/* We must have a given sim specified */
if (!path_or_index) {
@@ -837,10 +853,6 @@
} else if (g_ascii_isdigit (path_or_index[0])) {
g_debug ("Assuming '%s' is the SIM index", path_or_index);
sim_path = g_strdup_printf (MM_DBUS_SIM_PREFIX "/%s", path_or_index);
- } else {
- g_printerr ("error: invalid index string specified: '%s'\n",
- path_or_index);
- exit (EXIT_FAILURE);
}
return sim_path;
@@ -848,7 +860,7 @@
void
mmcli_get_sim (GDBusConnection *connection,
- const gchar *path_or_index,
+ const gchar *path_or_index_or_uid,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
@@ -859,7 +871,8 @@
task = g_task_new (connection, cancellable, callback, user_data);
ctx = g_new0 (GetSimContext, 1);
- ctx->sim_path = get_sim_path (path_or_index);
+ ctx->sim_path = get_sim_path (path_or_index_or_uid);
+ ctx->sim_maybe_uid = g_strdup (path_or_index_or_uid);
g_task_set_task_data (task, ctx, (GDestroyNotify) get_sim_context_free);
mmcli_get_manager (connection,
@@ -870,7 +883,7 @@
MMSim *
mmcli_get_sim_sync (GDBusConnection *connection,
- const gchar *path_or_index,
+ const gchar *path_or_index_or_uid,
MMManager **o_manager,
MMObject **o_object)
{
@@ -880,7 +893,7 @@
MMSim *found = NULL;
gchar *sim_path;
- sim_path = get_sim_path (path_or_index);
+ sim_path = get_sim_path (path_or_index_or_uid);
manager = mmcli_get_manager_sync (connection);
modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (manager));
@@ -897,6 +910,16 @@
object = MM_OBJECT (l->data);
modem = mm_object_get_modem (object);
+
+ if (!sim_path) {
+ if (g_str_equal (path_or_index_or_uid, mm_modem_get_device (modem)))
+ sim_path = g_strdup (mm_modem_get_sim_path (modem));
+ else {
+ g_object_unref (modem);
+ continue;
+ }
+ }
+
if (g_str_equal (sim_path, mm_modem_get_sim_path (modem))) {
found = mm_modem_get_sim_sync (modem, NULL, &error);
if (error) {
@@ -914,6 +937,12 @@
g_object_unref (modem);
}
+ if (!sim_path) {
+ g_printerr ("error: invalid index string specified: '%s'\n",
+ path_or_index_or_uid);
+ exit (EXIT_FAILURE);
+ }
+
if (!found) {
g_printerr ("error: couldn't find sim at '%s'\n", sim_path);
exit (EXIT_FAILURE);
diff --git a/cli/mmcli-modem-location.c b/cli/mmcli-modem-location.c
index 6e7185c..5469baa 100644
--- a/cli/mmcli-modem-location.c
+++ b/cli/mmcli-modem-location.c
@@ -16,7 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2012 Google, Inc.
- * Copyright (C) 2012 Lanedo GmbH <aleksander@lanedo.com>
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2012-2019 Aleksander Morgado <aleksander@aleksander.es>
*/
#include "config.h"
@@ -49,8 +50,10 @@
static gboolean status_flag;
static gboolean enable_3gpp_flag;
static gboolean disable_3gpp_flag;
-static gboolean enable_agps_flag;
-static gboolean disable_agps_flag;
+static gboolean enable_agps_msa_flag;
+static gboolean disable_agps_msa_flag;
+static gboolean enable_agps_msb_flag;
+static gboolean disable_agps_msb_flag;
static gboolean enable_gps_nmea_flag;
static gboolean disable_gps_nmea_flag;
static gboolean enable_gps_raw_flag;
@@ -83,12 +86,20 @@
"Disable 3GPP location gathering.",
NULL
},
- { "location-enable-agps", 0, 0, G_OPTION_ARG_NONE, &enable_agps_flag,
- "Enable A-GPS location gathering.",
+ { "location-enable-agps-msa", 0, 0, G_OPTION_ARG_NONE, &enable_agps_msa_flag,
+ "Enable MSA A-GPS location gathering.",
NULL
},
- { "location-disable-agps", 0, 0, G_OPTION_ARG_NONE, &disable_agps_flag,
- "Disable A-GPS location gathering.",
+ { "location-disable-agps-msa", 0, 0, G_OPTION_ARG_NONE, &disable_agps_msa_flag,
+ "Disable MSA A-GPS location gathering.",
+ NULL
+ },
+ { "location-enable-agps-msb", 0, 0, G_OPTION_ARG_NONE, &enable_agps_msb_flag,
+ "Enable MSB A-GPS location gathering.",
+ NULL
+ },
+ { "location-disable-agps-msb", 0, 0, G_OPTION_ARG_NONE, &disable_agps_msb_flag,
+ "Disable MSB A-GPS location gathering.",
NULL
},
{ "location-enable-gps-nmea", 0, 0, G_OPTION_ARG_NONE, &enable_gps_nmea_flag,
@@ -161,6 +172,16 @@
return group;
}
+#define any_location_setup_flag ( \
+ enable_3gpp_flag || disable_3gpp_flag || \
+ enable_agps_msa_flag || disable_agps_msa_flag || \
+ enable_agps_msb_flag || disable_agps_msb_flag || \
+ enable_gps_nmea_flag || disable_gps_nmea_flag || \
+ enable_gps_raw_flag || disable_gps_raw_flag || \
+ enable_cdma_bs_flag || disable_cdma_bs_flag || \
+ enable_gps_unmanaged_flag || disable_gps_unmanaged_flag || \
+ set_enable_signal_flag || set_disable_signal_flag)
+
gboolean
mmcli_modem_location_options_enabled (void)
{
@@ -170,12 +191,13 @@
if (checked)
return !!n_actions;
- if ((enable_3gpp_flag && disable_3gpp_flag) ||
- (enable_agps_flag && disable_agps_flag) ||
- (enable_gps_nmea_flag && disable_gps_nmea_flag) ||
- (enable_gps_raw_flag && disable_gps_raw_flag) ||
+ if ((enable_3gpp_flag && disable_3gpp_flag) ||
+ (enable_agps_msa_flag && disable_agps_msa_flag) ||
+ (enable_agps_msb_flag && disable_agps_msb_flag) ||
+ (enable_gps_nmea_flag && disable_gps_nmea_flag) ||
+ (enable_gps_raw_flag && disable_gps_raw_flag) ||
(enable_gps_unmanaged_flag && disable_gps_unmanaged_flag) ||
- (enable_cdma_bs_flag && disable_cdma_bs_flag)) {
+ (enable_cdma_bs_flag && disable_cdma_bs_flag)) {
g_printerr ("error: cannot enable and disable the same source\n");
exit (EXIT_FAILURE);
}
@@ -186,20 +208,7 @@
}
n_actions = (status_flag +
- !!(enable_3gpp_flag +
- disable_3gpp_flag +
- enable_agps_flag +
- disable_agps_flag +
- enable_gps_nmea_flag +
- disable_gps_nmea_flag +
- enable_gps_raw_flag +
- disable_gps_raw_flag +
- enable_cdma_bs_flag +
- disable_cdma_bs_flag +
- enable_gps_unmanaged_flag +
- disable_gps_unmanaged_flag +
- set_enable_signal_flag +
- set_disable_signal_flag) +
+ any_location_setup_flag +
get_flag +
!!set_supl_server_str +
!!inject_assistance_data_str +
@@ -280,7 +289,7 @@
gps_refresh_rate = g_strdup_printf ("%u", rate);
/* If A-GPS supported, show SUPL server setup */
- if (mm_modem_location_get_capabilities (ctx->modem_location) & MM_MODEM_LOCATION_SOURCE_AGPS)
+ if (mm_modem_location_get_capabilities (ctx->modem_location) & (MM_MODEM_LOCATION_SOURCE_AGPS_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB))
gps_supl_server = mm_modem_location_get_supl_server (ctx->modem_location);
mask = mm_modem_location_get_supported_assistance_data (ctx->modem_location);
@@ -451,10 +460,15 @@
if (disable_3gpp_flag)
sources &= ~MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI;
- if (enable_agps_flag)
- sources |= MM_MODEM_LOCATION_SOURCE_AGPS;
- if (disable_agps_flag)
- sources &= ~MM_MODEM_LOCATION_SOURCE_AGPS;
+ if (enable_agps_msa_flag)
+ sources |= MM_MODEM_LOCATION_SOURCE_AGPS_MSA;
+ if (disable_agps_msa_flag)
+ sources &= ~MM_MODEM_LOCATION_SOURCE_AGPS_MSA;
+
+ if (enable_agps_msb_flag)
+ sources |= MM_MODEM_LOCATION_SOURCE_AGPS_MSB;
+ if (disable_agps_msb_flag)
+ sources &= ~MM_MODEM_LOCATION_SOURCE_AGPS_MSB;
if (enable_gps_nmea_flag)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
@@ -597,20 +611,7 @@
g_assert_not_reached ();
/* Request to setup location gathering? */
- if (enable_3gpp_flag ||
- disable_3gpp_flag ||
- enable_agps_flag ||
- disable_agps_flag ||
- enable_gps_nmea_flag ||
- disable_gps_nmea_flag ||
- enable_gps_raw_flag ||
- disable_gps_raw_flag ||
- enable_cdma_bs_flag ||
- disable_cdma_bs_flag ||
- enable_gps_unmanaged_flag ||
- disable_gps_unmanaged_flag ||
- set_enable_signal_flag ||
- set_disable_signal_flag) {
+ if (any_location_setup_flag) {
g_debug ("Asynchronously setting up location gathering...");
mm_modem_location_setup (ctx->modem_location,
build_sources_from_flags (),
@@ -727,20 +728,7 @@
}
/* Request to setup location gathering? */
- if (enable_3gpp_flag ||
- disable_3gpp_flag ||
- enable_agps_flag ||
- disable_agps_flag ||
- enable_gps_nmea_flag ||
- disable_gps_nmea_flag ||
- enable_gps_raw_flag ||
- disable_gps_raw_flag ||
- enable_cdma_bs_flag ||
- disable_cdma_bs_flag ||
- enable_gps_unmanaged_flag ||
- disable_gps_unmanaged_flag ||
- set_enable_signal_flag ||
- set_disable_signal_flag) {
+ if (any_location_setup_flag) {
gboolean result;
g_debug ("Synchronously setting up location gathering...");
diff --git a/cli/mmcli-modem-voice.c b/cli/mmcli-modem-voice.c
index 089523e..87c27be 100644
--- a/cli/mmcli-modem-voice.c
+++ b/cli/mmcli-modem-voice.c
@@ -50,6 +50,13 @@
static gboolean list_flag;
static gchar *create_str;
static gchar *delete_str;
+static gboolean hold_and_accept_flag;
+static gboolean hangup_and_accept_flag;
+static gboolean hangup_all_flag;
+static gboolean transfer_flag;
+static gboolean call_waiting_enable_flag;
+static gboolean call_waiting_disable_flag;
+static gboolean call_waiting_query_flag;
static GOptionEntry entries[] = {
{ "voice-list-calls", 0, 0, G_OPTION_ARG_NONE, &list_flag,
@@ -64,6 +71,34 @@
"Delete a call from a given modem",
"[PATH|INDEX]"
},
+ { "voice-hold-and-accept", 0, 0, G_OPTION_ARG_NONE, &hold_and_accept_flag,
+ "Places all active calls in hold and accepts the next waiting or held call",
+ NULL
+ },
+ { "voice-hangup-and-accept", 0, 0, G_OPTION_ARG_NONE, &hangup_and_accept_flag,
+ "Hangs up all active calls and accepts the next waiting or held call",
+ NULL
+ },
+ { "voice-hangup-all", 0, 0, G_OPTION_ARG_NONE, &hangup_all_flag,
+ "Hangs up all ongoing (active, waiting, held) calls",
+ NULL
+ },
+ { "voice-transfer", 0, 0, G_OPTION_ARG_NONE, &transfer_flag,
+ "Joins active and held calls and disconnects from them",
+ NULL
+ },
+ { "voice-enable-call-waiting", 0, 0, G_OPTION_ARG_NONE, &call_waiting_enable_flag,
+ "Enables the call waiting network service",
+ NULL
+ },
+ { "voice-disable-call-waiting", 0, 0, G_OPTION_ARG_NONE, &call_waiting_disable_flag,
+ "Disables the call waiting network service",
+ NULL
+ },
+ { "voice-query-call-waiting", 0, 0, G_OPTION_ARG_NONE, &call_waiting_query_flag,
+ "Queries the status of the call waiting network service",
+ NULL
+ },
{ NULL }
};
@@ -93,7 +128,14 @@
n_actions = (list_flag +
!!create_str +
- !!delete_str);
+ !!delete_str +
+ hold_and_accept_flag +
+ hangup_and_accept_flag +
+ hangup_all_flag +
+ transfer_flag +
+ call_waiting_enable_flag +
+ call_waiting_disable_flag +
+ call_waiting_query_flag);
if (n_actions > 1) {
g_printerr ("error: too many Voice actions requested\n");
@@ -176,6 +218,158 @@
}
static void
+call_waiting_query_process_reply (const GError *error,
+ gboolean status)
+{
+ if (error) {
+ g_printerr ("error: couldn't query call waiting network service status: '%s'\n",
+ error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("call waiting service is %s\n", status ? "enabled" : "disabled");
+}
+
+static void
+call_waiting_query_ready (MMModemVoice *modem,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ GError *error = NULL;
+ gboolean status = FALSE;
+
+ mm_modem_voice_call_waiting_query_finish (modem, result, &status, &error);
+ call_waiting_query_process_reply (error, status);
+
+ mmcli_async_operation_done ();
+}
+
+static void
+call_waiting_setup_process_reply (const GError *error)
+{
+ if (error) {
+ g_printerr ("error: couldn't setup call waiting network service: '%s'\n",
+ error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("operation successful\n");
+}
+
+static void
+call_waiting_setup_ready (MMModemVoice *modem,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ GError *error = NULL;
+
+ mm_modem_voice_call_waiting_setup_finish (modem, result, &error);
+ call_waiting_setup_process_reply (error);
+
+ mmcli_async_operation_done ();
+}
+
+static void
+transfer_process_reply (const GError *error)
+{
+ if (error) {
+ g_printerr ("error: couldn't hangup all: '%s'\n",
+ error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("operation successful\n");
+}
+
+static void
+transfer_ready (MMModemVoice *modem,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ GError *error = NULL;
+
+ mm_modem_voice_transfer_finish (modem, result, &error);
+ transfer_process_reply (error);
+
+ mmcli_async_operation_done ();
+}
+
+static void
+hangup_all_process_reply (const GError *error)
+{
+ if (error) {
+ g_printerr ("error: couldn't hangup all: '%s'\n",
+ error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("operation successful\n");
+}
+
+static void
+hangup_all_ready (MMModemVoice *modem,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ GError *error = NULL;
+
+ mm_modem_voice_hangup_all_finish (modem, result, &error);
+ hangup_all_process_reply (error);
+
+ mmcli_async_operation_done ();
+}
+
+static void
+hangup_and_accept_process_reply (const GError *error)
+{
+ if (error) {
+ g_printerr ("error: couldn't hangup and accept: '%s'\n",
+ error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("operation successful\n");
+}
+
+static void
+hangup_and_accept_ready (MMModemVoice *modem,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ GError *error = NULL;
+
+ mm_modem_voice_hangup_and_accept_finish (modem, result, &error);
+ hangup_and_accept_process_reply (error);
+
+ mmcli_async_operation_done ();
+}
+
+static void
+hold_and_accept_process_reply (const GError *error)
+{
+ if (error) {
+ g_printerr ("error: couldn't hold and accept: '%s'\n",
+ error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("operation successful\n");
+}
+
+static void
+hold_and_accept_ready (MMModemVoice *modem,
+ GAsyncResult *result,
+ gpointer nothing)
+{
+ GError *error = NULL;
+
+ mm_modem_voice_hold_and_accept_finish (modem, result, &error);
+ hold_and_accept_process_reply (error);
+
+ mmcli_async_operation_done ();
+}
+
+static void
list_process_reply (GList *result,
const GError *error)
{
@@ -334,6 +528,78 @@
return;
}
+ /* Request to hold and accept? */
+ if (hold_and_accept_flag) {
+ g_debug ("Asynchronously holding and accepting next call...");
+ mm_modem_voice_hold_and_accept (ctx->modem_voice,
+ ctx->cancellable,
+ (GAsyncReadyCallback)hold_and_accept_ready,
+ NULL);
+ return;
+ }
+
+ /* Request to hangup and accept? */
+ if (hangup_and_accept_flag) {
+ g_debug ("Asynchronously hanging up and accepting next call...");
+ mm_modem_voice_hangup_and_accept (ctx->modem_voice,
+ ctx->cancellable,
+ (GAsyncReadyCallback)hangup_and_accept_ready,
+ NULL);
+ return;
+ }
+
+ /* Request to hangup all? */
+ if (hangup_all_flag) {
+ g_debug ("Asynchronously hanging up all calls...");
+ mm_modem_voice_hangup_all (ctx->modem_voice,
+ ctx->cancellable,
+ (GAsyncReadyCallback)hangup_all_ready,
+ NULL);
+ return;
+ }
+
+ /* Request to transfer? */
+ if (transfer_flag) {
+ g_debug ("Asynchronously transferring calls...");
+ mm_modem_voice_transfer (ctx->modem_voice,
+ ctx->cancellable,
+ (GAsyncReadyCallback)transfer_ready,
+ NULL);
+ return;
+ }
+
+ /* Request to enable call waiting? */
+ if (call_waiting_enable_flag) {
+ g_debug ("Asynchronously enabling call waiting...");
+ mm_modem_voice_call_waiting_setup (ctx->modem_voice,
+ TRUE,
+ ctx->cancellable,
+ (GAsyncReadyCallback)call_waiting_setup_ready,
+ NULL);
+ return;
+ }
+
+ /* Request to disable call waiting? */
+ if (call_waiting_disable_flag) {
+ g_debug ("Asynchronously enabling call waiting...");
+ mm_modem_voice_call_waiting_setup (ctx->modem_voice,
+ FALSE,
+ ctx->cancellable,
+ (GAsyncReadyCallback)call_waiting_setup_ready,
+ NULL);
+ return;
+ }
+
+ /* Request to query call waiting? */
+ if (call_waiting_query_flag) {
+ g_debug ("Asynchronously querying call waiting status...");
+ mm_modem_voice_call_waiting_query (ctx->modem_voice,
+ ctx->cancellable,
+ (GAsyncReadyCallback)call_waiting_query_ready,
+ NULL);
+ return;
+ }
+
g_warn_if_reached ();
}
@@ -430,5 +696,63 @@
return;
}
+ /* Request to hold and accept? */
+ if (hold_and_accept_flag) {
+ g_debug ("Synchronously holding and accepting call...");
+ mm_modem_voice_hold_and_accept_sync (ctx->modem_voice, NULL, &error);
+ hold_and_accept_process_reply (error);
+ return;
+ }
+
+ /* Request to hangup and accept? */
+ if (hangup_and_accept_flag) {
+ g_debug ("Synchronously hanging up and accepting call...");
+ mm_modem_voice_hangup_and_accept_sync (ctx->modem_voice, NULL, &error);
+ hangup_and_accept_process_reply (error);
+ return;
+ }
+
+ /* Request to hangup all? */
+ if (hangup_all_flag) {
+ g_debug ("Synchronously hanging up all calls...");
+ mm_modem_voice_hangup_all_sync (ctx->modem_voice, NULL, &error);
+ hangup_all_process_reply (error);
+ return;
+ }
+
+ /* Request to transfer? */
+ if (transfer_flag) {
+ g_debug ("Synchronously transferring calls...");
+ mm_modem_voice_transfer_sync (ctx->modem_voice, NULL, &error);
+ transfer_process_reply (error);
+ return;
+ }
+
+ /* Request to enable call waiting? */
+ if (call_waiting_enable_flag) {
+ g_debug ("Synchronously enabling call waiting...");
+ mm_modem_voice_call_waiting_setup_sync (ctx->modem_voice, TRUE, NULL, &error);
+ call_waiting_setup_process_reply (error);
+ return;
+ }
+
+ /* Request to disable call waiting? */
+ if (call_waiting_disable_flag) {
+ g_debug ("Synchronously disabling call waiting...");
+ mm_modem_voice_call_waiting_setup_sync (ctx->modem_voice, FALSE, NULL, &error);
+ call_waiting_setup_process_reply (error);
+ return;
+ }
+
+ /* Request to query call waiting? */
+ if (call_waiting_query_flag) {
+ gboolean status = FALSE;
+
+ g_debug ("Synchronously querying call waiting status...");
+ mm_modem_voice_call_waiting_query_sync (ctx->modem_voice, NULL, &status, &error);
+ call_waiting_query_process_reply (error, status);
+ return;
+ }
+
g_warn_if_reached ();
}
diff --git a/cli/mmcli-output.c b/cli/mmcli-output.c
index 0242460..4fd9db3 100644
--- a/cli/mmcli-output.c
+++ b/cli/mmcli-output.c
@@ -227,6 +227,7 @@
[MMC_F_CALL_GENERAL_DBUS_PATH] = { "call.dbus-path", "dbus path", MMC_S_CALL_GENERAL, },
[MMC_F_CALL_PROPERTIES_NUMBER] = { "call.properties.number", "number", MMC_S_CALL_PROPERTIES, },
[MMC_F_CALL_PROPERTIES_DIRECTION] = { "call.properties.direction", "direction", MMC_S_CALL_PROPERTIES, },
+ [MMC_F_CALL_PROPERTIES_MULTIPARTY] = { "call.properties.multiparty", "multiparty", MMC_S_CALL_PROPERTIES, },
[MMC_F_CALL_PROPERTIES_STATE] = { "call.properties.state", "state", MMC_S_CALL_PROPERTIES, },
[MMC_F_CALL_PROPERTIES_STATE_REASON] = { "call.properties.state-reason", "state reason", MMC_S_CALL_PROPERTIES, },
[MMC_F_CALL_PROPERTIES_AUDIO_PORT] = { "call.properties.audio-port", "audio port", MMC_S_CALL_PROPERTIES, },
@@ -1071,6 +1072,129 @@
}
/******************************************************************************/
+/* JSON-friendly output */
+
+static gint
+list_sort_by_keys (const OutputItem *item_a,
+ const OutputItem *item_b)
+{
+ return g_strcmp0 (field_infos[item_a->field].key, field_infos[item_b->field].key);
+}
+
+static void
+dump_output_json (void)
+{
+ GList *l;
+ MmcF current_field = MMC_F_UNKNOWN;
+ gchar **current_path = NULL;
+ guint cur_dlen = 0;
+
+ output_items = g_list_sort (output_items, (GCompareFunc) list_sort_by_keys);
+
+ g_print("{");
+ for (l = output_items; l; l = g_list_next (l)) {
+ OutputItem *item_l = (OutputItem *)(l->data);
+
+ if (current_field != item_l->field) {
+ guint new_dlen;
+ guint iter = 0;
+ gchar **new_path;
+
+ new_path = g_strsplit (field_infos[item_l->field].key, ".", -1);
+ new_dlen = g_strv_length (new_path) - 1;
+ if (current_path) {
+ guint min_dlen;
+
+ min_dlen = MIN (cur_dlen, new_dlen);
+ while (iter < min_dlen && g_strcmp0 (current_path[iter], new_path[iter]) == 0)
+ iter++;
+
+ g_strfreev (current_path);
+
+ if (iter < min_dlen || new_dlen < cur_dlen)
+ for (min_dlen = iter; min_dlen < cur_dlen; min_dlen++)
+ g_print ("}");
+
+ g_print (",");
+ }
+
+ while (iter < new_dlen)
+ g_print ("\"%s\":{", new_path[iter++]);
+
+ cur_dlen = new_dlen;
+ current_path = new_path;
+ current_field = item_l->field;
+ } else {
+ g_print (",");
+ }
+
+ if (item_l->type == VALUE_TYPE_SINGLE) {
+ OutputItemSingle *single = (OutputItemSingle *) item_l;
+ gchar *escaped = NULL;
+
+ if (single->value)
+ escaped = g_strescape (single->value, "\v");
+
+ g_print ("\"%s\":\"%s\"", current_path[cur_dlen], escaped ? escaped : "--");
+ g_free (escaped);
+ } else if (item_l->type == VALUE_TYPE_MULTIPLE) {
+ OutputItemMultiple *multiple = (OutputItemMultiple *) item_l;
+ guint i, n;
+
+ n = multiple->values ? g_strv_length (multiple->values) : 0;
+
+ g_print ("\"%s\":[", current_path[cur_dlen]);
+ for (i = 0; i < n; i++) {
+ gchar *escaped;
+
+ escaped = g_strescape (multiple->values[i], "\v");
+ g_print("\"%s\"", escaped);
+ if (i < n - 1)
+ g_print(",");
+ g_free (escaped);
+ }
+ g_print("]");
+ } else
+ g_assert_not_reached ();
+ }
+
+ while (cur_dlen--)
+ g_print ("}");
+ g_print("}\n");
+
+ g_strfreev (current_path);
+}
+
+static void
+dump_output_list_json (MmcF field)
+{
+ GList *l;
+
+ g_assert (field != MMC_F_UNKNOWN);
+
+ g_print("{\"%s\":[", field_infos[field].key);
+
+ for (l = output_items; l; l = g_list_next (l)) {
+ OutputItem *item_l;
+ OutputItemListitem *listitem;
+
+ item_l = (OutputItem *)(l->data);
+ g_assert (item_l->type == VALUE_TYPE_LISTITEM);
+ listitem = (OutputItemListitem *)item_l;
+ g_assert (listitem->value);
+
+ /* All items must be of same type */
+ g_assert_cmpint (item_l->field, ==, field);
+ g_print("\"%s\"", listitem->value);
+
+ if (g_list_next (l))
+ g_print(",");
+ }
+
+ g_print("]}\n");
+}
+
+/******************************************************************************/
/* Dump output */
void
@@ -1085,6 +1209,9 @@
case MMC_OUTPUT_TYPE_KEYVALUE:
dump_output_keyvalue ();
break;
+ case MMC_OUTPUT_TYPE_JSON:
+ dump_output_json ();
+ break;
}
g_list_free_full (output_items, (GDestroyNotify) output_item_free);
@@ -1105,6 +1232,9 @@
case MMC_OUTPUT_TYPE_KEYVALUE:
dump_output_list_keyvalue (field);
break;
+ case MMC_OUTPUT_TYPE_JSON:
+ dump_output_list_json (field);
+ break;
}
g_list_free_full (output_items, (GDestroyNotify) output_item_free);
diff --git a/cli/mmcli-output.h b/cli/mmcli-output.h
index 7793f79..135d429 100644
--- a/cli/mmcli-output.h
+++ b/cli/mmcli-output.h
@@ -243,6 +243,7 @@
MMC_F_CALL_GENERAL_DBUS_PATH,
MMC_F_CALL_PROPERTIES_NUMBER,
MMC_F_CALL_PROPERTIES_DIRECTION,
+ MMC_F_CALL_PROPERTIES_MULTIPARTY,
MMC_F_CALL_PROPERTIES_STATE,
MMC_F_CALL_PROPERTIES_STATE_REASON,
MMC_F_CALL_PROPERTIES_AUDIO_PORT,
@@ -284,6 +285,7 @@
MMC_OUTPUT_TYPE_NONE,
MMC_OUTPUT_TYPE_HUMAN,
MMC_OUTPUT_TYPE_KEYVALUE,
+ MMC_OUTPUT_TYPE_JSON
} MmcOutputType;
void mmcli_output_set (MmcOutputType type);
diff --git a/cli/mmcli.c b/cli/mmcli.c
index 6953672..af41f0a 100644
--- a/cli/mmcli.c
+++ b/cli/mmcli.c
@@ -45,6 +45,7 @@
/* Context */
static gboolean output_keyvalue_flag;
+static gboolean output_json_flag;
static gboolean verbose_flag;
static gboolean version_flag;
static gboolean async_flag;
@@ -55,6 +56,10 @@
"Run action with machine-friendly key-value output",
NULL
},
+ { "output-json", 'J', 0, G_OPTION_ARG_NONE, &output_json_flag,
+ "Run action with machine-friendly json output",
+ NULL
+ },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag,
"Run action with verbose logs",
NULL
@@ -237,14 +242,26 @@
g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MASK, log_handler, NULL);
/* Setup output */
+ if (output_keyvalue_flag && output_json_flag) {
+ g_printerr ("error: only one output type supported at the same time\n");
+ exit (EXIT_FAILURE);
+ }
if (output_keyvalue_flag) {
if (verbose_flag) {
g_printerr ("error: cannot set verbose output in keyvalue output type\n");
exit (EXIT_FAILURE);
}
mmcli_output_set (MMC_OUTPUT_TYPE_KEYVALUE);
- } else
+ }
+ else if (output_json_flag) {
+ if (verbose_flag) {
+ g_printerr ("error: cannot set verbose output in JSON output type\n");
+ exit (EXIT_FAILURE);
+ }
+ mmcli_output_set (MMC_OUTPUT_TYPE_JSON);
+ } else {
mmcli_output_set (MMC_OUTPUT_TYPE_HUMAN);
+ }
/* Setup signals */
signal (SIGINT, signals_handler);
diff --git a/configure.ac b/configure.ac
index a6ebfd6..d3c9beb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -314,6 +314,23 @@
AM_CONDITIONAL(WITH_POLKIT, [test "x$with_polkit" != "xno"])
dnl-----------------------------------------------------------------------------
+dnl AT command via DBus support (disabled by default unless running in --debug)
+dnl
+dnl It is suggested that this option is only enabled in custom built systems and
+dnl only if truly required.
+dnl
+
+AC_ARG_WITH(at_command_via_dbus,
+ AS_HELP_STRING([--with-at-command-via-dbus],
+ [Build with Modem.Command() interface enabled always]),
+ [],
+ [with_at_command_via_dbus=no])
+
+if test "x$with_at_command_via_dbus" = "xyes"; then
+ AC_DEFINE(WITH_AT_COMMAND_VIA_DBUS, 1, [Define if you want to enable AT commands via DBus])
+fi
+
+dnl-----------------------------------------------------------------------------
dnl MBIM support (enabled by default)
dnl
@@ -444,6 +461,7 @@
qmi: ${with_qmi}
systemd suspend/resume: ${with_systemd_suspend_resume}
systemd journal: ${with_systemd_journal}
+ at command via dbus: ${with_at_command_via_dbus}
Miscellaneous:
gobject introspection: ${found_introspection}
diff --git a/decode/packet.py b/decode/packet.py
index e28b62f..97daee1 100644
--- a/decode/packet.py
+++ b/decode/packet.py
@@ -109,7 +109,7 @@
def add_line(self, line):
line = line.strip()
if not len(line):
- return
+ return
self.lines.append(line)
if line[0] == '[':
diff --git a/docs/man/mmcli.1 b/docs/man/mmcli.1
index cb00f32..4a08792 100644
--- a/docs/man/mmcli.1
+++ b/docs/man/mmcli.1
@@ -390,16 +390,26 @@
.B \-\-location\-disable\-3gpp
Disable location discovery using the 3GPP network.
.TP
-.B \-\-location\-enable\-agps
+.B \-\-location\-enable\-agps-msa
Enable A-GPS (MSA) support. This command does not implicitly start the GPS
engine, it just specifies that A-GPS should be enabled when the engine is
started. Therefore, the user should request enabling A-GPS before the raw
or NMEA outputs are enabled with \fB\-\-location\-enable\-gps\-raw\fR or
\fB\-\-location\-enable\-gps\-nmea\fR.
.TP
-.B \-\-location\-disable\-agps
+.B \-\-location\-disable\-agps-msa
Disable A-GPS (MSA) support.
.TP
+.B \-\-location\-enable\-agps-msb
+Enable A-GPS (MSB) support. This command does not implicitly start the GPS
+engine, it just specifies that A-GPS should be enabled when the engine is
+started. Therefore, the user should request enabling A-GPS before the raw
+or NMEA outputs are enabled with \fB\-\-location\-enable\-gps\-raw\fR or
+\fB\-\-location\-enable\-gps\-nmea\fR.
+.TP
+.B \-\-location\-disable\-agps-msb
+Disable A-GPS (MSB) support.
+.TP
.B \-\-location\-enable\-gps\-nmea
Enable location discovery using GPS and reported with NMEA traces.
@@ -893,14 +903,14 @@
.Bd -literal -compact
$ mmcli -m 0 --location-status
--------------------------------
- Location | capabilities: 3gpp-lac-ci, gps-raw, gps-nmea, agps
+ Location | capabilities: 3gpp-lac-ci, gps-raw, gps-nmea, agps-msa, agps-msb
| enabled: 3gpp-lac-ci
| signals: no
-----------------------------
GPS | refresh rate: 30 seconds
| a-gps supl server: supl.google.com:7276
- $ sudo mmcli -m 0 --location-enable-agps
+ $ sudo mmcli -m 0 --location-enable-agps-msa
successfully setup location gathering
$ sudo mmcli -m 0 --location-enable-gps-nmea
@@ -924,7 +934,7 @@
.Bd -literal -compact
$ mmcli -m 0 --location-status
--------------------------------
- Location | capabilities: 3gpp-lac-ci, gps-raw, gps-nmea, agps
+ Location | capabilities: 3gpp-lac-ci, gps-raw, gps-nmea, agps-msa, agps-msb
| enabled: 3gpp-lac-ci
| signals: no
--------------------------------
diff --git a/docs/reference/api/ModemManager-docs.xml b/docs/reference/api/ModemManager-docs.xml
index 93d6e95..2918af6 100644
--- a/docs/reference/api/ModemManager-docs.xml
+++ b/docs/reference/api/ModemManager-docs.xml
@@ -19,22 +19,22 @@
<authorgroup>
<author>
- <firstname>Dan</firstname>
- <surname>Williams</surname>
- <affiliation>
- <address>
- <email>dcbw@redhat.com</email>
- </address>
- </affiliation>
+ <firstname>Dan</firstname>
+ <surname>Williams</surname>
+ <affiliation>
+ <address>
+ <email>dcbw@redhat.com</email>
+ </address>
+ </affiliation>
</author>
<author>
- <firstname>Aleksander</firstname>
- <surname>Morgado</surname>
- <affiliation>
- <address>
- <email>aleksander@aleksander.es</email>
- </address>
- </affiliation>
+ <firstname>Aleksander</firstname>
+ <surname>Morgado</surname>
+ <affiliation>
+ <address>
+ <email>aleksander@aleksander.es</email>
+ </address>
+ </affiliation>
</author>
</authorgroup>
diff --git a/docs/reference/api/ModemManager-overview.xml b/docs/reference/api/ModemManager-overview.xml
index 963c0df..80c1805 100644
--- a/docs/reference/api/ModemManager-overview.xml
+++ b/docs/reference/api/ModemManager-overview.xml
@@ -125,6 +125,24 @@
</programlisting>
</listitem>
<listitem>
+ <para><emphasis>MM_FILTER_RULE_EXPLICIT_BLACKLIST</emphasis></para>
+ <para>
+ This filter allows users to manually tag devices and/or device ports with the
+ <emphasis>ID_MM_DEVICE_IGNORE</emphasis> udev tag. If the filter finds this tag,
+ the device and/or device ports will be automatically ignored and port probing
+ will be never run on them.
+ </para>
+ <programlisting>
+$ sudo vim /lib/udev/rules.d/78-mm-blacklist-internal-modem.rules
+ ACTION!="add|change|move", GOTO="mm_blacklist_internal_modem_end"
+ ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{ID_MM_DEVICE_IGNORE}="1"
+ LABEL="mm_blacklist_internal_modem_end"
+// Apply new rules without reboot
+$ sudo udevadm control --reload
+$ sudo udevadm trigger
+ </programlisting>
+ </listitem>
+ <listitem>
<para><emphasis>MM_FILTER_RULE_VIRTUAL</emphasis></para>
<para>
This filter will automatically flag as forbidden all ports exposed by virtual
@@ -167,7 +185,7 @@
<para><emphasis>MM_FILTER_RULE_TTY_BLACKLIST</emphasis></para>
<para>
This filter will not allow probing any of the devices flagged as
- <emphasis>ID_MM_DEVICE_IGNORE</emphasis>, like the ones in the default blacklist
+ <emphasis>ID_MM_TTY_BLACKLIST</emphasis>, like the ones in the default blacklist
shipped by ModemManager.
</para>
</listitem>
@@ -175,7 +193,7 @@
<para><emphasis>MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY</emphasis></para>
<para>
This filter will not allow automatic probing any of the devices flagged as
- <emphasis>ID_MM_DEVICE_MANUAL_SCAN_ONLY</emphasis>, like the ones in the default
+ <emphasis>ID_MM_TTY_MANUAL_SCAN_ONLY</emphasis>, like the ones in the default
USB serial adapters greylist shipped by ModemManager. Devices flagged like
this will only be probed when a manual scan is requested via the
<link linkend="gdbus-method-org-freedesktop-ModemManager1.ScanDevices">ScanDevices</link>
@@ -185,13 +203,7 @@
<listitem>
<para><emphasis>MM_FILTER_RULE_TTY_PLATFORM_DRIVER</emphasis></para>
<para>
- If this filter is enabled, all platform TTY ports not explicitly flagged with the
- <emphasis>ID_MM_PLATFORM_DRIVER_PROBE</emphasis> will be forbidden. If the flag
- is found in a platform TTY port, port probing will be allowed directly.
- </para>
- <para>
- Note that this filter is obsoleted by the more generic MM_FILTER_RULE_EXPLICIT_WHITELIST
- filter. It is maintained for backwards compatibility with older ModemManager versions.
+ If this filter is enabled, all platform TTY ports will be forbidden automatically.
</para>
</listitem>
<listitem>
@@ -239,6 +251,7 @@
This is a policy where the following rules are enabled:
<itemizedlist>
<listitem>MM_FILTER_RULE_EXPLICIT_WHITELIST</listitem>
+ <listitem>MM_FILTER_RULE_EXPLICIT_BLACKLIST</listitem>
<listitem>MM_FILTER_RULE_VIRTUAL</listitem>
<listitem>MM_FILTER_RULE_NET</listitem>
<listitem>MM_FILTER_RULE_CDC_WDM</listitem>
@@ -264,6 +277,7 @@
This is a policy where the following rules are enabled:
<itemizedlist>
<listitem>MM_FILTER_RULE_EXPLICIT_WHITELIST</listitem>
+ <listitem>MM_FILTER_RULE_EXPLICIT_BLACKLIST</listitem>
<listitem>MM_FILTER_RULE_VIRTUAL</listitem>
<listitem>MM_FILTER_RULE_NET</listitem>
<listitem>MM_FILTER_RULE_CDC_WDM</listitem>
@@ -290,6 +304,7 @@
This policy is a mix of the Default and Strict ones:
<itemizedlist>
<listitem>MM_FILTER_RULE_EXPLICIT_WHITELIST</listitem>
+ <listitem>MM_FILTER_RULE_EXPLICIT_BLACKLIST</listitem>
<listitem>MM_FILTER_RULE_VIRTUAL</listitem>
<listitem>MM_FILTER_RULE_NET</listitem>
<listitem>MM_FILTER_RULE_CDC_WDM</listitem>
diff --git a/docs/reference/api/ModemManager-sections.txt b/docs/reference/api/ModemManager-sections.txt
index 5fab5c6..15d976c 100644
--- a/docs/reference/api/ModemManager-sections.txt
+++ b/docs/reference/api/ModemManager-sections.txt
@@ -139,8 +139,10 @@
MM_MODEM_BAND_CDMA_BC17_US_FLO_2500
MM_MODEM_BAND_CDMA_BC18_US_PS_700
MM_MODEM_BAND_CDMA_BC19_US_LOWER_700
+MM_MODEM_LOCATION_SOURCE_AGPS
<SUBSECTION Private>
MMModemBandDeprecated
+MMModemLocationSourceDeprecated
MM_DEPRECATED
</SECTION>
@@ -149,11 +151,11 @@
<TITLE>Common udev tags</TITLE>
ID_MM_CANDIDATE
ID_MM_PHYSDEV_UID
-ID_MM_PORT_IGNORE
ID_MM_DEVICE_PROCESS
ID_MM_DEVICE_IGNORE
-ID_MM_DEVICE_MANUAL_SCAN_ONLY
-ID_MM_PLATFORM_DRIVER_PROBE
+ID_MM_PORT_IGNORE
+ID_MM_TTY_BLACKLIST
+ID_MM_TTY_MANUAL_SCAN_ONLY
ID_MM_PORT_TYPE_AT_PPP
ID_MM_PORT_TYPE_AT_PRIMARY
ID_MM_PORT_TYPE_AT_SECONDARY
diff --git a/docs/reference/libmm-glib/libmm-glib-docs.xml b/docs/reference/libmm-glib/libmm-glib-docs.xml
index 5778936..ec4d4c9 100644
--- a/docs/reference/libmm-glib/libmm-glib-docs.xml
+++ b/docs/reference/libmm-glib/libmm-glib-docs.xml
@@ -19,13 +19,13 @@
<authorgroup>
<author>
- <firstname>Aleksander</firstname>
- <surname>Morgado</surname>
- <affiliation>
- <address>
- <email>aleksander@aleksander.es</email>
- </address>
- </affiliation>
+ <firstname>Aleksander</firstname>
+ <surname>Morgado</surname>
+ <affiliation>
+ <address>
+ <email>aleksander@aleksander.es</email>
+ </address>
+ </affiliation>
</author>
</authorgroup>
diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt
index 583b494..690db8b 100644
--- a/docs/reference/libmm-glib/libmm-glib-sections.txt
+++ b/docs/reference/libmm-glib/libmm-glib-sections.txt
@@ -1012,6 +1012,18 @@
mm_modem_voice_list_calls
mm_modem_voice_list_calls_finish
mm_modem_voice_list_calls_sync
+mm_modem_voice_hangup_and_accept
+mm_modem_voice_hangup_and_accept_finish
+mm_modem_voice_hangup_and_accept_sync
+mm_modem_voice_hold_and_accept
+mm_modem_voice_hold_and_accept_finish
+mm_modem_voice_hold_and_accept_sync
+mm_modem_voice_hangup_all
+mm_modem_voice_hangup_all_finish
+mm_modem_voice_hangup_all_sync
+mm_modem_voice_transfer
+mm_modem_voice_transfer_finish
+mm_modem_voice_transfer_sync
<SUBSECTION Standard>
MMModemVoiceClass
MMModemVoicePrivate
@@ -1320,6 +1332,7 @@
mm_call_dup_audio_port
mm_call_get_audio_format
mm_call_peek_audio_format
+mm_call_get_multiparty
<SUBSECTION Methods>
mm_call_start
mm_call_start_finish
@@ -1333,6 +1346,15 @@
mm_call_send_dtmf
mm_call_send_dtmf_finish
mm_call_send_dtmf_sync
+mm_call_deflect
+mm_call_deflect_finish
+mm_call_deflect_sync
+mm_call_join_multiparty
+mm_call_join_multiparty_finish
+mm_call_join_multiparty_sync
+mm_call_leave_multiparty
+mm_call_leave_multiparty_finish
+mm_call_leave_multiparty_sync
<SUBSECTION Standard>
MMCallClass
MMCallPrivate
@@ -2835,6 +2857,18 @@
mm_gdbus_modem_voice_call_list_calls
mm_gdbus_modem_voice_call_list_calls_finish
mm_gdbus_modem_voice_call_list_calls_sync
+mm_gdbus_modem_voice_call_hangup_and_accept
+mm_gdbus_modem_voice_call_hangup_and_accept_finish
+mm_gdbus_modem_voice_call_hangup_and_accept_sync
+mm_gdbus_modem_voice_call_hold_and_accept
+mm_gdbus_modem_voice_call_hold_and_accept_finish
+mm_gdbus_modem_voice_call_hold_and_accept_sync
+mm_gdbus_modem_voice_call_hangup_all
+mm_gdbus_modem_voice_call_hangup_all_finish
+mm_gdbus_modem_voice_call_hangup_all_sync
+mm_gdbus_modem_voice_call_transfer
+mm_gdbus_modem_voice_call_transfer_finish
+mm_gdbus_modem_voice_call_transfer_sync
<SUBSECTION Private>
mm_gdbus_modem_voice_set_calls
mm_gdbus_modem_voice_emit_call_added
@@ -2842,6 +2876,10 @@
mm_gdbus_modem_voice_complete_create_call
mm_gdbus_modem_voice_complete_delete_call
mm_gdbus_modem_voice_complete_list_calls
+mm_gdbus_modem_voice_complete_hangup_and_accept
+mm_gdbus_modem_voice_complete_hold_and_accept
+mm_gdbus_modem_voice_complete_hangup_all
+mm_gdbus_modem_voice_complete_transfer
mm_gdbus_modem_voice_interface_info
mm_gdbus_modem_voice_override_properties
<SUBSECTION Standard>
@@ -3215,6 +3253,7 @@
mm_gdbus_call_dup_audio_port
mm_gdbus_call_get_audio_format
mm_gdbus_call_get_audio_port
+mm_gdbus_call_get_multiparty
<SUBSECTION Methods>
mm_gdbus_call_call_accept
mm_gdbus_call_call_accept_finish
@@ -3228,6 +3267,15 @@
mm_gdbus_call_call_send_dtmf
mm_gdbus_call_call_send_dtmf_finish
mm_gdbus_call_call_send_dtmf_sync
+mm_gdbus_call_call_deflect
+mm_gdbus_call_call_deflect_finish
+mm_gdbus_call_call_deflect_sync
+mm_gdbus_call_call_join_multiparty
+mm_gdbus_call_call_join_multiparty_finish
+mm_gdbus_call_call_join_multiparty_sync
+mm_gdbus_call_call_leave_multiparty
+mm_gdbus_call_call_leave_multiparty_finish
+mm_gdbus_call_call_leave_multiparty_sync
<SUBSECTION Private>
mm_gdbus_call_set_direction
mm_gdbus_call_set_number
@@ -3235,10 +3283,14 @@
mm_gdbus_call_set_state_reason
mm_gdbus_call_set_audio_format
mm_gdbus_call_set_audio_port
+mm_gdbus_call_set_multiparty
mm_gdbus_call_complete_accept
mm_gdbus_call_complete_hangup
mm_gdbus_call_complete_send_dtmf
mm_gdbus_call_complete_start
+mm_gdbus_call_complete_deflect
+mm_gdbus_call_complete_join_multiparty
+mm_gdbus_call_complete_leave_multiparty
mm_gdbus_call_interface_info
mm_gdbus_call_override_properties
mm_gdbus_call_emit_dtmf_received
diff --git a/include/ModemManager-compat.h b/include/ModemManager-compat.h
index 974b69b..6eb687e 100644
--- a/include/ModemManager-compat.h
+++ b/include/ModemManager-compat.h
@@ -27,6 +27,7 @@
/**
* SECTION:mm-compat
* @title: API break replacements
+ * @short_description: List of deprecated methods, types and symbols.
*
* These compatibility types and methods are flagged as deprecated and
* therefore shouldn't be used in newly written code. They are provided to
@@ -694,6 +695,20 @@
*/
#define MM_MODEM_BAND_CDMA_BC19_US_LOWER_700 ((MMModemBandDeprecated)MM_MODEM_BAND_CDMA_BC19)
+/* The following type exists just so that we can get deprecation warnings */
+MM_DEPRECATED
+typedef int MMModemLocationSourceDeprecated;
+
+/**
+ * MM_MODEM_LOCATION_SOURCE_AGPS:
+ *
+ * A-GPS location requested.
+ *
+ * Since: 1.0
+ * Deprecated: 1.12.0: Use #MM_MODEM_LOCATION_SOURCE_AGPS_MSA instead.
+ */
+#define MM_MODEM_LOCATION_SOURCE_AGPS ((MMModemLocationSourceDeprecated)MM_MODEM_LOCATION_SOURCE_AGPS_MSA)
+
#endif /* MM_DISABLE_DEPRECATED */
#endif /* _MODEMMANAGER_COMPAT_H_ */
diff --git a/include/ModemManager-enums.h b/include/ModemManager-enums.h
index 9e75e2f..f84eafc 100644
--- a/include/ModemManager-enums.h
+++ b/include/ModemManager-enums.h
@@ -928,7 +928,8 @@
* @MM_MODEM_LOCATION_SOURCE_GPS_NMEA: GPS location given as NMEA traces.
* @MM_MODEM_LOCATION_SOURCE_CDMA_BS: CDMA base station position.
* @MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED: No location given, just GPS module setup.
- * @MM_MODEM_LOCATION_SOURCE_AGPS: A-GPS location requested.
+ * @MM_MODEM_LOCATION_SOURCE_AGPS_MSA: Mobile Station Assisted A-GPS location requested. Since 1.12.
+ * @MM_MODEM_LOCATION_SOURCE_AGPS_MSB: Mobile Station Based A-GPS location requested. Since 1.12.
*
* Sources of location information supported by the modem.
*/
@@ -939,7 +940,13 @@
MM_MODEM_LOCATION_SOURCE_GPS_NMEA = 1 << 2,
MM_MODEM_LOCATION_SOURCE_CDMA_BS = 1 << 3,
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED = 1 << 4,
- MM_MODEM_LOCATION_SOURCE_AGPS = 1 << 5,
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA = 1 << 5,
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB = 1 << 6,
+#if defined (MM_COMPILATION)
+ /* MM internal methods, not part of the API */
+ MM_MODEM_LOCATION_SOURCE_FIRST = MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI, /*< skip >*/
+ MM_MODEM_LOCATION_SOURCE_LAST = MM_MODEM_LOCATION_SOURCE_AGPS_MSB, /*< skip >*/
+#endif
} MMModemLocationSource;
/**
@@ -1379,6 +1386,10 @@
* @MM_CALL_STATE_REASON_REFUSED_OR_BUSY: Remote peer is busy or refused call.
* @MM_CALL_STATE_REASON_ERROR: Wrong number or generic network error.
* @MM_CALL_STATE_REASON_AUDIO_SETUP_FAILED: Error setting up audio channel.
+ * @MM_CALL_STATE_REASON_TRANSFERRED: Call has been transferred. Since 1.12.
+ * @MM_CALL_STATE_REASON_DEFLECTED: Call has been deflected to a new number. Since 1.12.
+ *
+ * Reason for the state change in the call.
*/
typedef enum { /*< underscore_name=mm_call_state_reason >*/
MM_CALL_STATE_REASON_UNKNOWN = 0,
@@ -1388,7 +1399,9 @@
MM_CALL_STATE_REASON_TERMINATED = 4,
MM_CALL_STATE_REASON_REFUSED_OR_BUSY = 5,
MM_CALL_STATE_REASON_ERROR = 6,
- MM_CALL_STATE_REASON_AUDIO_SETUP_FAILED = 7
+ MM_CALL_STATE_REASON_AUDIO_SETUP_FAILED = 7,
+ MM_CALL_STATE_REASON_TRANSFERRED = 8,
+ MM_CALL_STATE_REASON_DEFLECTED = 9,
} MMCallStateReason;
/**
@@ -1396,6 +1409,8 @@
* @MM_CALL_DIRECTION_UNKNOWN: unknown.
* @MM_CALL_DIRECTION_INCOMING: call from network.
* @MM_CALL_DIRECTION_OUTGOING: call to network.
+ *
+ * Direction of the call.
*/
typedef enum { /*< underscore_name=mm_call_direction >*/
MM_CALL_DIRECTION_UNKNOWN = 0,
diff --git a/include/ModemManager-tags.h b/include/ModemManager-tags.h
index 4be1e79..ed65af5 100644
--- a/include/ModemManager-tags.h
+++ b/include/ModemManager-tags.h
@@ -73,6 +73,20 @@
#define ID_MM_DEVICE_PROCESS "ID_MM_DEVICE_PROCESS"
/**
+ * ID_MM_DEVICE_IGNORE:
+ *
+ * This is a device-specific tag that allows explicitly requesting to
+ * ignore all ports exposed by the device.
+ *
+ * This tag was originally applicable to TTY ports and only when running
+ * in DEFAULT or PARANOID filter policy types. Since 1.12, this tag
+ * applies to all filter types (including STRICT), and to all port types
+ * (not only TTYs), and is associated to the
+ * MM_FILTER_RULE_EXPLICIT_BLACKLIST rule.
+ */
+#define ID_MM_DEVICE_IGNORE "ID_MM_DEVICE_IGNORE"
+
+/**
* ID_MM_PORT_IGNORE:
*
* This is a port-specific tag that allows explicitly ignoring a given port
@@ -82,8 +96,8 @@
*/
#define ID_MM_PORT_IGNORE "ID_MM_PORT_IGNORE"
-/**
- * ID_MM_DEVICE_IGNORE:
+ /**
+ * ID_MM_TTY_BLACKLIST:
*
* This is a device-specific tag that allows explicitly blacklisting
* devices that expose TTY devices so that they are never probed.
@@ -94,10 +108,10 @@
*
* This tag is ignored when the STRICT filter policy is used.
*/
-#define ID_MM_DEVICE_IGNORE "ID_MM_DEVICE_IGNORE"
+#define ID_MM_TTY_BLACKLIST "ID_MM_TTY_BLACKLIST"
/**
- * ID_MM_DEVICE_MANUAL_SCAN_ONLY:
+ * ID_MM_TTY_MANUAL_SCAN_ONLY:
*
* This is a device-specific tag that allows explicitly greylisting
* devices that expose TTY devices so that they are never probed
@@ -111,23 +125,7 @@
*
* This tag is ignored when the STRICT filter policy is used.
*/
-#define ID_MM_DEVICE_MANUAL_SCAN_ONLY "ID_MM_DEVICE_MANUAL_SCAN_ONLY"
-
-/**
- * ID_MM_PLATFORM_DRIVER_PROBE:
- *
- * This is a port-specific tag applied to platform ports so that they
- * are probed automatically by the daemon. Platform ports that don't
- * have this tag will never probed. This tag is a bit redundant, as
- * the user could also use ID_MM_DEVICE_PROCESS for the same purpose.
- *
- * This tag is associated to the MM_FILTER_RULE_TTY_PLATFORM_DRIVER
- * rule, which is only meaningful when the daemon runs with the
- * DEFAULT filter policy type, as that is the only one that would
- * allow probing all ports not explicitly forbidden before the last
- * MM_FILTER_RULE_TTY_DEFAULT_ALLOWED rule.
- */
-#define ID_MM_PLATFORM_DRIVER_PROBE "ID_MM_PLATFORM_DRIVER_PROBE"
+#define ID_MM_TTY_MANUAL_SCAN_ONLY "ID_MM_TTY_MANUAL_SCAN_ONLY"
/**
* ID_MM_PORT_TYPE_AT_PRIMARY:
diff --git a/include/ModemManager-version.h.in b/include/ModemManager-version.h.in
index 0cb4341..695a1b0 100644
--- a/include/ModemManager-version.h.in
+++ b/include/ModemManager-version.h.in
@@ -58,6 +58,8 @@
* @minor: minor version (e.g. 2 for version 1.2.5)
* @micro: micro version (e.g. 5 for version 1.2.5)
*
+ * Checks the version of ModemManager at compile time.
+ *
* Returns: %TRUE if the version of the ModemManager header files
* is the same as or newer than the passed-in version.
*/
diff --git a/introspection/org.freedesktop.ModemManager1.Call.xml b/introspection/org.freedesktop.ModemManager1.Call.xml
index f53f5b7b..659601b 100644
--- a/introspection/org.freedesktop.ModemManager1.Call.xml
+++ b/introspection/org.freedesktop.ModemManager1.Call.xml
@@ -5,6 +5,7 @@
Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
Copyright (C) 2015 Riccardo Vangelisti <riccardo.vangelisti@sadel.it>
+ Copyright (C) 2019 Purism SPC
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
@@ -38,6 +39,48 @@
<method name="Accept" />
<!--
+ Deflect:
+ @number: new number where the call will be deflected.
+
+ Deflect an incoming or waiting call to a new number. This call will be
+ considered terminated once the deflection is performed.
+
+ Applicable only if state is <link linkend="MM-CALL-STATE-RINGING-IN:CAPS"><constant>MM_CALL_STATE_RINGING_IN</constant></link> or
+ <link linkend="MM-CALL-STATE-WAITING:CAPS"><constant>MM_CALL_STATE_WAITING</constant></link> and direction is
+ <link linkend="MM-CALL-DIRECTION-INCOMING:CAPS"><constant>MM_CALL_DIRECTION_INCOMING</constant></link>.
+ -->
+ <method name="Deflect">
+ <arg name="number" type="s" />
+ </method>
+
+ <!--
+ JoinMultiparty:
+
+ Join the currently held call into a single multiparty call with another
+ already active call.
+
+ The calls will be flagged with the
+ '<link linkend="gdbus-property-org-freedesktop-ModemManager1-Call.Multiparty">Multiparty</link>'
+ property while they are part of the multiparty call.
+
+ Applicable only if state is <link linkend="MM-CALL-STATE-HELD:CAPS"><constant>MM_CALL_STATE_HELD</constant></link>.
+ -->
+ <method name="JoinMultiparty" />
+
+ <!--
+ LeaveMultiparty:
+
+ If this call is part of an ongoing multiparty call, detach it from the multiparty call,
+ put the multiparty call on hold, and activate this one alone. This operation makes this
+ call private again between both ends of the call.
+
+ Applicable only if state is <link linkend="MM-CALL-STATE-ACTIVE:CAPS"><constant>MM_CALL_STATE_ACTIVE</constant></link> or
+ <link linkend="MM-CALL-STATE-HELD:CAPS"><constant>MM_CALL_STATE_HELD</constant></link> and
+ the call is a multiparty call.
+ -->
+ <method name="LeaveMultiparty"/>
+
+ <!--
Hangup:
Hangup the active call.
@@ -113,6 +156,13 @@
<property name="Number" type="s" access="read" />
<!--
+ Multiparty:
+
+ Whether the call is currently part of a multiparty conference call.
+ -->
+ <property name="Multiparty" type="b" access="read" />
+
+ <!--
AudioPort:
If call audio is routed via the host, the name of the kernel device that
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.Location.xml b/introspection/org.freedesktop.ModemManager1.Modem.Location.xml
index 5c49abb..d785a24 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.Location.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.Location.xml
@@ -47,12 +47,20 @@
location information.
The optional
- <link linkend="MM-MODEM-LOCATION-SOURCE-AGPS:CAPS">MM_MODEM_LOCATION_SOURCE_AGPS</link>
- allows to request A-GPS operation, and it must be given along with either
+ <link linkend="MM-MODEM-LOCATION-SOURCE-AGPS-MSA:CAPS">MM_MODEM_LOCATION_SOURCE_AGPS_MSA</link>
+ and
+ <link linkend="MM-MODEM-LOCATION-SOURCE-AGPS-MSB:CAPS">MM_MODEM_LOCATION_SOURCE_AGPS_MSB</link>
+ allow to request MSA/MSB A-GPS operation, and they must be given along with either
<link linkend="MM-MODEM-LOCATION-SOURCE-GPS-RAW:CAPS">MM_MODEM_LOCATION_SOURCE_GPS_RAW</link>
or
<link linkend="MM-MODEM-LOCATION-SOURCE-GPS-NMEA:CAPS">MM_MODEM_LOCATION_SOURCE_GPS_NMEA</link>.
+ Both
+ <link linkend="MM-MODEM-LOCATION-SOURCE-AGPS-MSA:CAPS">MM_MODEM_LOCATION_SOURCE_AGPS_MSA</link>
+ and
+ <link linkend="MM-MODEM-LOCATION-SOURCE-AGPS-MSB:CAPS">MM_MODEM_LOCATION_SOURCE_AGPS_MSB</link>
+ cannot be given at the same time, and if none given, standalone GPS is assumed.
+
-->
<method name="Setup">
<arg name="sources" type="u" direction="in" />
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.Voice.xml b/introspection/org.freedesktop.ModemManager1.Modem.Voice.xml
index 4912ba5..59f11e9 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.Voice.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.Voice.xml
@@ -5,6 +5,7 @@
Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
Copyright (C) 2015 Riccardo Vangelisti <riccardo.vangelisti@sadel.it>
+ Copyright (C) 2019 Purism SPC
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
@@ -64,10 +65,95 @@
</method>
<!--
+ HoldAndAccept:
+
+ Place all active calls on hold, if any, and accept the next
+ call.
+
+ Waiting calls have preference over held calls, so the next
+ call being active will be any waiting call, or otherwise,
+ any held call.
+
+ The user should monitor the state of all available ongoing
+ calls to be reported of which one becomes active.
+
+ No error is returned if there are no waiting or held calls.
+ -->
+ <method name="HoldAndAccept" />
+
+ <!--
+ HangupAndAccept:
+
+ Hangup all active calls, if any, and accept the next call.
+
+ Waiting calls have preference over held calls, so the next
+ call being active will be any waiting call, or otherwise,
+ any held call.
+
+ The user should monitor the state of all available ongoing
+ calls to be reported of which one becomes active.
+
+ No error is returned if there are no waiting or held calls.
+ In this case, this method would be equivalent to calling
+ <link linkend="gdbus-method-org-freedesktop-ModemManager1-Call.Hangup">Hangup()</link>
+ on the active call.
+ -->
+ <method name="HangupAndAccept" />
+
+ <!--
+ HangupAll:
+
+ Hangup all active calls.
+
+ Depending on how the device implements the action, calls on
+ hold or in waiting state may also be terminated.
+
+ No error is returned if there are no ongoing calls.
+ -->
+ <method name="HangupAll" />
+
+ <!--
+ Transfer:
+
+ Join the currently active and held calls together into a single
+ multiparty call, but disconnects from them.
+
+ The affected calls will be considered terminated from the point of
+ view of the subscriber.
+ -->
+ <method name="Transfer" />
+
+ <!--
+ CallWaitingSetup:
+
+ Activates or deactivates the call waiting network service, as per
+ 3GPP TS 22.083.
+
+ This operation requires communication with the network in order to
+ complete, so the modem must be successfully registered.
+ -->
+ <method name="CallWaitingSetup">
+ <arg name="enable" type="b" direction="in" />
+ </method>
+
+ <!--
+ CallWaitingQuery:
+
+ Queries the status of the call waiting network service, as per
+ 3GPP TS 22.083.
+
+ This operation requires communication with the network in order to
+ complete, so the modem must be successfully registered.
+ -->
+ <method name="CallWaitingQuery">
+ <arg name="status" type="b" direction="out" />
+ </method>
+
+ <!--
CallAdded:
@path: Object path of the new call.
- Emitted when any part of a Call has been received or added.
+ Emitted when a call has been added.
-->
<signal name="CallAdded">
<arg name="path" type="o" />
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.xml b/introspection/org.freedesktop.ModemManager1.Modem.xml
index 0fc5741..eb4debd 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.xml
@@ -132,11 +132,11 @@
<!--
SetPowerState:
- @state: A <link linkend="MMModemPowerState">MMModemPowerState</link> value, to specify the desired power state.
+ @state: A <link linkend="MMModemPowerState">MMModemPowerState</link> value, to specify the desired power state.
Set the power state of the modem. This action can only be run when the
- modem is in <link linkend="MM-MODEM-STATE-DISABLED:CAPS"><constant>MM_MODEM_STATE_DISABLED</constant></link>
- state.
+ modem is in <link linkend="MM-MODEM-STATE-DISABLED:CAPS"><constant>MM_MODEM_STATE_DISABLED</constant></link>
+ state.
-->
<method name="SetPowerState">
<arg name="state" type="u" direction="in" />
@@ -424,9 +424,9 @@
StateFailedReason:
Error specifying why the modem is in
- <link linkend="MM-MODEM-STATE-FAILED:CAPS"><constant>MM_MODEM_STATE_FAILED</constant></link>
- state, given as a
- <link linkend="MMModemStateFailedReason">MMModemStateFailedReason</link> value.
+ <link linkend="MM-MODEM-STATE-FAILED:CAPS"><constant>MM_MODEM_STATE_FAILED</constant></link>
+ state, given as a
+ <link linkend="MMModemStateFailedReason">MMModemStateFailedReason</link> value.
-->
<property name="StateFailedReason" type="u" access="read" />
@@ -466,8 +466,8 @@
<!--
PowerState:
- A <link linkend="MMModemPowerState">MMModemPowerState</link> value
- specifying the current power state of the modem.
+ A <link linkend="MMModemPowerState">MMModemPowerState</link> value
+ specifying the current power state of the modem.
-->
<property name="PowerState" type="u" access="read" />
diff --git a/introspection/org.freedesktop.ModemManager1.Sms.xml b/introspection/org.freedesktop.ModemManager1.Sms.xml
index 1bd2150..91df019 100644
--- a/introspection/org.freedesktop.ModemManager1.Sms.xml
+++ b/introspection/org.freedesktop.ModemManager1.Sms.xml
@@ -109,8 +109,8 @@
<varlistentry><term><link linkend="MM-SMS-VALIDITY-TYPE-RELATIVE:CAPS">MM_SMS_VALIDITY_TYPE_RELATIVE</link></term>
<listitem>
<para>
- The value is the length of the validity period in minutes, given
- as an unsigned integer (D-Bus signature <literal>'u'</literal>).
+ The value is the length of the validity period in minutes, given
+ as an unsigned integer (D-Bus signature <literal>'u'</literal>).
</para>
</listitem>
</varlistentry>
diff --git a/libmm-glib/mm-call.c b/libmm-glib/mm-call.c
index af1d8f1..91439f5 100644
--- a/libmm-glib/mm-call.c
+++ b/libmm-glib/mm-call.c
@@ -148,6 +148,24 @@
/*****************************************************************************/
/**
+ * mm_call_get_multiparty:
+ * @self: A #MMCall.
+ *
+ * Gets whether the call is part of a multiparty call.
+ *
+ * Returns: %TRUE if the call is part of a multiparty call, %FALSE otherwise..
+ */
+gboolean
+mm_call_get_multiparty (MMCall *self)
+{
+ g_return_val_if_fail (MM_IS_CALL (self), FALSE);
+
+ return mm_gdbus_call_get_multiparty (MM_GDBUS_CALL (self));
+}
+
+/*****************************************************************************/
+
+/**
* mm_call_get_state:
* @self: A #MMCall.
*
@@ -492,6 +510,241 @@
/*****************************************************************************/
/**
+ * mm_call_deflect_finish:
+ * @self: A #MMCall.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_call_deflect().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_call_deflect().
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+mm_call_deflect_finish (MMCall *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_CALL (self), FALSE);
+
+ return mm_gdbus_call_call_deflect_finish (MM_GDBUS_CALL (self), res, error);
+}
+
+/**
+ * mm_call_deflect:
+ * @self: A #MMCall.
+ * @number: new number where the call will be deflected.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously requests to deflect the incoming call.
+ *
+ * This call will be considered terminated once the deflection is performed.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_call_deflect_finish() to get the result of the operation.
+ *
+ * See mm_call_deflect_sync() for the synchronous, blocking version of this method.
+ */
+void
+mm_call_deflect (MMCall *self,
+ const gchar *number,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_CALL (self));
+
+ mm_gdbus_call_call_deflect (MM_GDBUS_CALL (self),
+ number,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_call_deflect_sync:
+ * @self: A #MMCall.
+ * @number: new number where the call will be deflected.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously requests to deflect the incoming call.
+ *
+ * This call will be considered terminated once the deflection is performed.
+ *
+ * The calling thread is blocked until an incoming call is ready.
+ * See mm_call_deflect() for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+mm_call_deflect_sync (MMCall *self,
+ const gchar *number,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_CALL (self), FALSE);
+
+ return mm_gdbus_call_call_deflect_sync (MM_GDBUS_CALL (self),
+ number,
+ cancellable,
+ error);
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_call_join_multiparty_finish:
+ * @self: A #MMCall.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_call_join_multiparty().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_call_join_multiparty().
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+mm_call_join_multiparty_finish (MMCall *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_CALL (self), FALSE);
+
+ return mm_gdbus_call_call_join_multiparty_finish (MM_GDBUS_CALL (self), res, error);
+}
+
+/**
+ * mm_call_join_multiparty:
+ * @self: A #MMCall.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Synchronously requests to join this call into a multiparty call.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_call_join_multiparty_finish() to get the result of the operation.
+ *
+ * See mm_call_join_multiparty_sync() for the synchronous, blocking version of this method.
+ */
+void
+mm_call_join_multiparty (MMCall *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_CALL (self));
+
+ mm_gdbus_call_call_join_multiparty (MM_GDBUS_CALL (self),
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_call_join_multiparty_sync:
+ * @self: A #MMCall.g
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously requests to join this call into a multiparty call.
+ *
+ * The calling thread is blocked until an incoming call is ready.
+ * See mm_call_join_multiparty() for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+mm_call_join_multiparty_sync (MMCall *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_CALL (self), FALSE);
+
+ return mm_gdbus_call_call_join_multiparty_sync (MM_GDBUS_CALL (self),
+ cancellable,
+ error);
+}
+
+/**
+ * mm_call_leave_multiparty_finish:
+ * @self: A #MMCall.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_call_leave_multiparty().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_call_leave_multiparty().
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+mm_call_leave_multiparty_finish (MMCall *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_CALL (self), FALSE);
+
+ return mm_gdbus_call_call_leave_multiparty_finish (MM_GDBUS_CALL (self), res, error);
+}
+
+/**
+ * mm_call_leave_multiparty:
+ * @self: A #MMCall.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Synchronously requests to make this call private again by leaving the
+ * multiparty call.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_call_leave_multiparty_finish() to get the result of the operation.
+ *
+ * See mm_call_leave_multiparty_sync() for the synchronous, blocking version of this method.
+ */
+void
+mm_call_leave_multiparty (MMCall *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_CALL (self));
+
+ mm_gdbus_call_call_leave_multiparty (MM_GDBUS_CALL (self),
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_call_leave_multiparty_sync:
+ * @self: A #MMCall.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously requests to make this call private again by leaving the
+ * multiparty call.
+ *
+ * The calling thread is blocked until an incoming call is ready.
+ * See mm_call_leave_multiparty() for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+mm_call_leave_multiparty_sync (MMCall *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_CALL (self), FALSE);
+
+ return mm_gdbus_call_call_leave_multiparty_sync (MM_GDBUS_CALL (self),
+ cancellable,
+ error);
+}
+
+ /*****************************************************************************/
+
+/**
* mm_call_hangup_finish:
* @self: A #MMCall.
* @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_call_hangup().
diff --git a/libmm-glib/mm-call.h b/libmm-glib/mm-call.h
index e6d8957..3ae5a77 100644
--- a/libmm-glib/mm-call.h
+++ b/libmm-glib/mm-call.h
@@ -76,6 +76,8 @@
MMCallDirection mm_call_get_direction (MMCall *self);
+gboolean mm_call_get_multiparty (MMCall *self);
+
const gchar *mm_call_get_audio_port (MMCall *self);
gchar *mm_call_dup_audio_port (MMCall *self);
@@ -110,6 +112,46 @@
GCancellable *cancellable,
GError **error);
+void mm_call_deflect (MMCall *self,
+ const gchar *number,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean mm_call_deflect_finish (MMCall *self,
+ GAsyncResult *res,
+ GError **error);
+
+gboolean mm_call_deflect_sync (MMCall *self,
+ const gchar *number,
+ GCancellable *cancellable,
+ GError **error);
+
+void mm_call_join_multiparty (MMCall *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean mm_call_join_multiparty_finish (MMCall *self,
+ GAsyncResult *res,
+ GError **error);
+
+gboolean mm_call_join_multiparty_sync (MMCall *self,
+ GCancellable *cancellable,
+ GError **error);
+
+void mm_call_leave_multiparty (MMCall *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean mm_call_leave_multiparty_finish (MMCall *self,
+ GAsyncResult *res,
+ GError **error);
+
+gboolean mm_call_leave_multiparty_sync (MMCall *self,
+ GCancellable *cancellable,
+ GError **error);
void mm_call_hangup (MMCall *self,
GCancellable *cancellable,
diff --git a/libmm-glib/mm-common-helpers.c b/libmm-glib/mm-common-helpers.c
index a3cd16a..8673421 100644
--- a/libmm-glib/mm-common-helpers.c
+++ b/libmm-glib/mm-common-helpers.c
@@ -722,6 +722,20 @@
return !different;
}
+gboolean
+mm_common_bands_garray_lookup (GArray *array,
+ MMModemBand value)
+{
+ guint i;
+
+ for (i = 0; i < array->len; i++) {
+ if (value == g_array_index (array, MMModemBand, i))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
void
mm_common_bands_garray_sort (GArray *array)
{
diff --git a/libmm-glib/mm-common-helpers.h b/libmm-glib/mm-common-helpers.h
index 71ef978..5af5842 100644
--- a/libmm-glib/mm-common-helpers.h
+++ b/libmm-glib/mm-common-helpers.h
@@ -103,8 +103,9 @@
GVariant *mm_common_build_bands_any (void);
GVariant *mm_common_build_bands_unknown (void);
-gboolean mm_common_bands_garray_cmp (GArray *a, GArray *b);
-void mm_common_bands_garray_sort (GArray *array);
+gboolean mm_common_bands_garray_cmp (GArray *a, GArray *b);
+void mm_common_bands_garray_sort (GArray *array);
+gboolean mm_common_bands_garray_lookup (GArray *array, MMModemBand value);
gboolean mm_common_band_is_gsm (MMModemBand band);
gboolean mm_common_band_is_utran (MMModemBand band);
diff --git a/libmm-glib/mm-firmware-update-settings.c b/libmm-glib/mm-firmware-update-settings.c
index 37c8381..fc8a45b 100644
--- a/libmm-glib/mm-firmware-update-settings.c
+++ b/libmm-glib/mm-firmware-update-settings.c
@@ -174,7 +174,7 @@
g_variant_builder_add (&builder, "u", method);
g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
- {
+ if (self) {
g_variant_builder_add (&builder,
"{sv}",
PROPERTY_DEVICE_IDS,
diff --git a/libmm-glib/mm-modem-voice.c b/libmm-glib/mm-modem-voice.c
index db70355..e23a730 100644
--- a/libmm-glib/mm-modem-voice.c
+++ b/libmm-glib/mm-modem-voice.c
@@ -532,6 +532,491 @@
/*****************************************************************************/
+/**
+ * mm_modem_voice_hold_and_accept_finish:
+ * @self: A #MMModemVoice.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_modem_voice_hold_and_accept().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_modem_voice_hold_and_accept().
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_hold_and_accept_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_hold_and_accept_finish (MM_GDBUS_MODEM_VOICE (self), res, error);
+}
+
+/**
+ * mm_modem_voice_hold_and_accept:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously puts all active calls on hold and accepts the next waiting or held call.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_modem_voice_hold_and_accept_finish() to get the result of the operation.
+ *
+ * See mm_modem_voice_hold_and_accept_sync() for the synchronous, blocking version of this method.
+ *
+ * Since: 1.12
+ */
+void
+mm_modem_voice_hold_and_accept (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_VOICE (self));
+
+ mm_gdbus_modem_voice_call_hold_and_accept (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_modem_voice_hold_and_accept_sync:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously puts all active calls on hold and accepts the next waiting or held call.
+ *
+ * The calling thread is blocked until a reply is received. See mm_modem_voice_hold_and_accept()
+ * for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_hold_and_accept_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_hold_and_accept_sync (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ error);
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_modem_voice_hangup_and_accept_finish:
+ * @self: A #MMModemVoice.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_modem_voice_hangup_and_accept().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_modem_voice_hangup_and_accept().
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_hangup_and_accept_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_hangup_and_accept_finish (MM_GDBUS_MODEM_VOICE (self), res, error);
+}
+
+/**
+ * mm_modem_voice_hangup_and_accept:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously hangs up all active calls and accepts the next waiting or held call.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_modem_voice_hangup_and_accept_finish() to get the result of the operation.
+ *
+ * See mm_modem_voice_hangup_and_accept_sync() for the synchronous, blocking version of this method.
+ *
+ * Since: 1.12
+ */
+void
+mm_modem_voice_hangup_and_accept (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_VOICE (self));
+
+ mm_gdbus_modem_voice_call_hangup_and_accept (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_modem_voice_hangup_and_accept_sync:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously hangs up all active calls and accepts the next waiting or held call.
+ *
+ * The calling thread is blocked until a reply is received. See mm_modem_voice_hangup_and_accept()
+ * for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_hangup_and_accept_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_hangup_and_accept_sync (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ error);
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_modem_voice_hangup_all_finish:
+ * @self: A #MMModemVoice.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_modem_voice_hangup_all().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_modem_voice_hangup_all().
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_hangup_all_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_hangup_all_finish (MM_GDBUS_MODEM_VOICE (self), res, error);
+}
+
+/**
+ * mm_modem_voice_hangup_all:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously hangs up all ongoing (active, waiting, held) calls.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_modem_voice_hangup_all_finish() to get the result of the operation.
+ *
+ * See mm_modem_voice_hangup_all_sync() for the synchronous, blocking version of this method.
+ *
+ * Since: 1.12
+ */
+void
+mm_modem_voice_hangup_all (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_VOICE (self));
+
+ mm_gdbus_modem_voice_call_hangup_all (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_modem_voice_hangup_all_sync:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously hangs up all ongoing (active, waiting, held) calls.
+ *
+ * The calling thread is blocked until a reply is received. See mm_modem_voice_hangup_all()
+ * for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_hangup_all_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_hangup_all_sync (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ error);
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_modem_voice_transfer_finish:
+ * @self: A #MMModemVoice.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_modem_voice_transfer().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_modem_voice_transfer().
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_transfer_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_transfer_finish (MM_GDBUS_MODEM_VOICE (self), res, error);
+}
+
+/**
+ * mm_modem_voice_transfer:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously joins all active and held calls, and disconnects from them.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_modem_voice_transfer_finish() to get the result of the operation.
+ *
+ * See mm_modem_voice_transfer_sync() for the synchronous, blocking version of this method.
+ *
+ * Since: 1.12
+ */
+void
+mm_modem_voice_transfer (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_VOICE (self));
+
+ mm_gdbus_modem_voice_call_transfer (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_modem_voice_transfer_sync:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously joins all active and held calls, and disconnects from them.
+ *
+ * The calling thread is blocked until a reply is received. See mm_modem_voice_transfer()
+ * for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation was successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_transfer_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_transfer_sync (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ error);
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_modem_voice_call_waiting_setup_finish:
+ * @self: A #MMModemVoice.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_modem_voice_call_waiting_setup().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_modem_voice_call_waiting_setup().
+ *
+ * Returns: %TRUE if @status is set, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_call_waiting_setup_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_call_waiting_setup_finish (MM_GDBUS_MODEM_VOICE (self), res, error);
+}
+
+/**
+ * mm_modem_voice_call_waiting_setup:
+ * @self: A #MMModemVoice.
+ * @enable: Whether the call waiting service should be enabled.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously enables or disables the call waiting network service.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_modem_voice_call_waiting_setup_finish() to get the result of the operation.
+ *
+ * See mm_modem_voice_call_waiting_setup_sync() for the synchronous, blocking version of this method.
+ *
+ * Since: 1.12
+ */
+void
+mm_modem_voice_call_waiting_setup (MMModemVoice *self,
+ gboolean enable,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_VOICE (self));
+
+ mm_gdbus_modem_voice_call_call_waiting_setup (MM_GDBUS_MODEM_VOICE (self),
+ enable,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_modem_voice_call_waiting_setup_sync:
+ * @self: A #MMModemVoice.
+ * @enable: Whether the call waiting service should be enabled.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously enables or disables the call waiting network service.
+ *
+ * The calling thread is blocked until a reply is received. See mm_modem_voice_call_waiting_setup()
+ * for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation is successful, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_call_waiting_setup_sync (MMModemVoice *self,
+ gboolean enable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_call_waiting_setup_sync (MM_GDBUS_MODEM_VOICE (self),
+ enable,
+ cancellable,
+ error);
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_modem_voice_call_waiting_query_finish:
+ * @self: A #MMModemVoice.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_modem_voice_call_waiting_query().
+ * @status: Output location where to store the status.
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_modem_voice_call_waiting_query().
+ *
+ * Returns: %TRUE if @status is set, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_call_waiting_query_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ gboolean *status,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_call_waiting_query_finish (MM_GDBUS_MODEM_VOICE (self), status, res, error);
+}
+
+/**
+ * mm_modem_voice_call_waiting_query:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously queries the status of the call waiting network service.
+ *
+ * When the operation is finished, @callback will be invoked in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link> of the thread you are calling this method from.
+ * You can then call mm_modem_voice_call_waiting_query_finish() to get the result of the operation.
+ *
+ * See mm_modem_voice_call_waiting_query_sync() for the synchronous, blocking version of this method.
+ *
+ * Since: 1.12
+ */
+void
+mm_modem_voice_call_waiting_query (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (MM_IS_MODEM_VOICE (self));
+
+ mm_gdbus_modem_voice_call_call_waiting_query (MM_GDBUS_MODEM_VOICE (self),
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * mm_modem_voice_call_waiting_query_sync:
+ * @self: A #MMModemVoice.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @status: Output location where to store the status.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously queries the status of the call waiting network service.
+ *
+ * The calling thread is blocked until a reply is received. See mm_modem_voice_call_waiting_query()
+ * for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if @status is set, %FALSE if @error is set.
+ * Since: 1.12
+ */
+gboolean
+mm_modem_voice_call_waiting_query_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ gboolean *status,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_MODEM_VOICE (self), FALSE);
+
+ return mm_gdbus_modem_voice_call_call_waiting_query_sync (MM_GDBUS_MODEM_VOICE (self),
+ status,
+ cancellable,
+ error);
+}
+
+/*****************************************************************************/
+
static void
mm_modem_voice_init (MMModemVoice *self)
{
diff --git a/libmm-glib/mm-modem-voice.h b/libmm-glib/mm-modem-voice.h
index 2f33447..f43a60a 100644
--- a/libmm-glib/mm-modem-voice.h
+++ b/libmm-glib/mm-modem-voice.h
@@ -110,6 +110,76 @@
GCancellable *cancellable,
GError **error);
+void mm_modem_voice_hold_and_accept (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_modem_voice_hold_and_accept_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+gboolean mm_modem_voice_hold_and_accept_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ GError **error);
+
+void mm_modem_voice_hangup_and_accept (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_modem_voice_hangup_and_accept_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+gboolean mm_modem_voice_hangup_and_accept_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ GError **error);
+
+void mm_modem_voice_hangup_all (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_modem_voice_hangup_all_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+gboolean mm_modem_voice_hangup_all_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ GError **error);
+
+void mm_modem_voice_transfer (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_modem_voice_transfer_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+gboolean mm_modem_voice_transfer_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ GError **error);
+
+void mm_modem_voice_call_waiting_setup (MMModemVoice *self,
+ gboolean enable,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_modem_voice_call_waiting_setup_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+gboolean mm_modem_voice_call_waiting_setup_sync (MMModemVoice *self,
+ gboolean enable,
+ GCancellable *cancellable,
+ GError **error);
+
+void mm_modem_voice_call_waiting_query (MMModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_modem_voice_call_waiting_query_finish (MMModemVoice *self,
+ GAsyncResult *res,
+ gboolean *status,
+ GError **error);
+gboolean mm_modem_voice_call_waiting_query_sync (MMModemVoice *self,
+ GCancellable *cancellable,
+ gboolean *status,
+ GError **error);
+
G_END_DECLS
#endif /* _MM_MODEM_VOICE_H_ */
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 016ef55..dd66d2f 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -78,6 +78,8 @@
tests/test-fixture.c \
tests/test-port-context.h \
tests/test-port-context.c \
+ tests/test-helpers.h \
+ tests/test-helpers.c \
$(NULL)
libmm_test_common_la_CPPFLAGS = \
-I$(top_builddir)/libmm-glib/generated/tests \
@@ -98,11 +100,12 @@
-I$(top_srcdir)/libmm-glib/generated \
-I$(top_builddir)/libmm-glib/generated \
-I$(top_builddir)/libmm-glib/generated/tests \
- -DCOMMON_GSM_PORT_CONF=\""$(abs_top_srcdir)/plugins/tests/gsm-port.conf"\"
+ -DCOMMON_GSM_PORT_CONF=\""$(abs_top_srcdir)/plugins/tests/gsm-port.conf"\" \
+ $(NULL)
TEST_COMMON_LIBADD_FLAGS = \
$(builddir)/libmm-test-common.la \
- $(top_builddir)/libmm-glib/libmm-glib.la
+ $(NULL)
################################################################################
# common icera support
@@ -310,7 +313,10 @@
noinst_PROGRAMS += test-service-generic
test_service_generic_SOURCES = generic/tests/test-service-generic.c
test_service_generic_CPPFLAGS = $(TEST_COMMON_COMPILER_FLAGS)
-test_service_generic_LDADD = $(TEST_COMMON_LIBADD_FLAGS)
+test_service_generic_LDADD = \
+ $(top_builddir)/libmm-glib/libmm-glib.la \
+ $(TEST_COMMON_LIBADD_FLAGS) \
+ $(NULL)
################################################################################
# plugin: motorola
@@ -355,8 +361,6 @@
huawei/mm-plugin-huawei.h \
huawei/mm-sim-huawei.c \
huawei/mm-sim-huawei.h \
- huawei/mm-call-huawei.c \
- huawei/mm-call-huawei.h \
huawei/mm-broadband-modem-huawei.c \
huawei/mm-broadband-modem-huawei.h \
huawei/mm-broadband-bearer-huawei.c \
@@ -897,8 +901,12 @@
test_modem_helpers_telit_SOURCES = \
telit/tests/test-mm-modem-helpers-telit.c \
$(NULL)
-test_modem_helpers_telit_CPPFLAGS = $(PLUGIN_TELIT_COMPILER_FLAGS)
+test_modem_helpers_telit_CPPFLAGS = \
+ $(PLUGIN_TELIT_COMPILER_FLAGS) \
+ $(TEST_COMMON_COMPILER_FLAGS) \
+ $(NULL)
test_modem_helpers_telit_LDADD = \
+ $(TEST_COMMON_LIBADD_FLAGS) \
$(builddir)/libhelpers-telit.la \
$(top_builddir)/src/libhelpers.la \
$(top_builddir)/libmm-glib/libmm-glib.la \
@@ -1018,8 +1026,12 @@
test_modem_helpers_ublox_SOURCES = \
ublox/tests/test-modem-helpers-ublox.c \
$(NULL)
-test_modem_helpers_ublox_CPPFLAGS = $(PLUGIN_UBLOX_COMPILER_FLAGS)
+test_modem_helpers_ublox_CPPFLAGS = \
+ $(PLUGIN_UBLOX_COMPILER_FLAGS) \
+ $(TEST_COMMON_COMPILER_FLAGS) \
+ $(NULL)
test_modem_helpers_ublox_LDADD = \
+ $(TEST_COMMON_LIBADD_FLAGS) \
$(builddir)/libhelpers-ublox.la \
$(top_builddir)/src/libhelpers.la \
$(top_builddir)/libmm-glib/libmm-glib.la \
@@ -1029,8 +1041,6 @@
libmm_plugin_ublox_la_SOURCES = \
ublox/mm-plugin-ublox.c \
ublox/mm-plugin-ublox.h \
- ublox/mm-call-ublox.c \
- ublox/mm-call-ublox.h \
ublox/mm-broadband-bearer-ublox.h \
ublox/mm-broadband-bearer-ublox.c \
ublox/mm-broadband-modem-ublox.h \
@@ -1089,6 +1099,8 @@
# plugin: quectel
################################################################################
+dist_udevrules_DATA += quectel/77-mm-quectel-port-types.rules
+
pkglib_LTLIBRARIES += libmm-plugin-quectel.la
libmm_plugin_quectel_la_SOURCES = \
quectel/mm-plugin-quectel.c \
@@ -1125,6 +1137,38 @@
AM_CFLAGS += -DTESTUDEVRULESDIR_FIBOCOM=\"${srcdir}/fibocom\"
################################################################################
+# plugin: dlink
+################################################################################
+
+pkglib_LTLIBRARIES += libmm-plugin-dlink.la
+libmm_plugin_dlink_la_SOURCES = \
+ dlink/mm-plugin-dlink.c \
+ dlink/mm-plugin-dlink.h \
+ $(NULL)
+libmm_plugin_dlink_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
+libmm_plugin_dlink_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
+
+dist_udevrules_DATA += dlink/77-mm-dlink-port-types.rules
+
+AM_CFLAGS += -DTESTUDEVRULESDIR_DLINK=\"${srcdir}/dlink\"
+
+################################################################################
+# plugin: tplink
+################################################################################
+
+pkglib_LTLIBRARIES += libmm-plugin-tplink.la
+libmm_plugin_tplink_la_SOURCES = \
+ tplink/mm-plugin-tplink.c \
+ tplink/mm-plugin-tplink.h \
+ $(NULL)
+libmm_plugin_tplink_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
+libmm_plugin_tplink_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
+
+dist_udevrules_DATA += tplink/77-mm-tplink-port-types.rules
+
+AM_CFLAGS += -DTESTUDEVRULESDIR_TPLINK=\"${srcdir}/tplink\"
+
+################################################################################
# udev rules tester
################################################################################
diff --git a/plugins/altair/mm-broadband-bearer-altair-lte.c b/plugins/altair/mm-broadband-bearer-altair-lte.c
index bde06e8..d8e1a0f 100644
--- a/plugins/altair/mm-broadband-bearer-altair-lte.c
+++ b/plugins/altair/mm-broadband-bearer-altair-lte.c
@@ -219,11 +219,9 @@
/* 3GPP Disconnect sequence */
typedef struct {
- MMBroadbandBearer *self;
MMBaseModem *modem;
MMPortSerialAt *primary;
MMPort *data;
- GSimpleAsyncResult *result;
} DetailedDisconnectContext;
static gboolean
diff --git a/plugins/cinterion/77-mm-cinterion-port-types.rules b/plugins/cinterion/77-mm-cinterion-port-types.rules
index 89a792c..e21a1b1 100644
--- a/plugins/cinterion/77-mm-cinterion-port-types.rules
+++ b/plugins/cinterion/77-mm-cinterion-port-types.rules
@@ -10,7 +10,14 @@
# PHS8
ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0053", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_GPS}="1"
-# PLS8
+# PLS8 port types
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): GPS data port
+# ttyACM3 (if #6): unknown
+# ttyACM4 (if #8): unknown
ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1"
LABEL="mm_cinterion_port_types_end"
diff --git a/plugins/cinterion/mm-broadband-modem-cinterion.c b/plugins/cinterion/mm-broadband-modem-cinterion.c
index 14ff248..a99db32 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,23 @@
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 iface_modem_time_init (MMIfaceModemTime *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;
+static MMIfaceModemTime *iface_modem_time_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_IFACE_MODEM_TIME, iface_modem_time_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
typedef enum {
@@ -80,8 +87,8 @@
GArray *cnmi_supported_ds;
GArray *cnmi_supported_bfr;
- /* +CIEV 'psinfo' indications */
- GRegex *ciev_psinfo_regex;
+ /* +CIEV indications as configured via AT^SIND */
+ GRegex *ciev_regex;
/* Flags for feature support checks */
FeatureSupport swwan_support;
@@ -290,6 +297,30 @@
}
/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+modem_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=1,1",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
/* Power down */
static gboolean
@@ -814,20 +845,25 @@
/* Setup/Cleanup unsolicited events (3GPP interface) */
static void
-sind_psinfo_received (MMPortSerialAt *port,
- GMatchInfo *match_info,
- MMBroadbandModemCinterion *self)
+sind_ciev_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemCinterion *self)
{
- guint val;
+ guint val = 0;
+ gchar *indicator;
- if (!mm_get_uint_from_match_info (match_info, 1, &val)) {
- mm_dbg ("Failed to convert psinfo value");
- return;
+ indicator = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (!mm_get_uint_from_match_info (match_info, 2, &val))
+ mm_dbg ("couldn't parse indicator '%s' value", indicator);
+ else {
+ mm_dbg ("received indicator '%s' update: %u", indicator, val);
+ if (g_strcmp0 (indicator, "psinfo") == 0) {
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ mm_cinterion_get_access_technology_from_sind_psinfo (val),
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+ }
}
-
- mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
- mm_cinterion_get_access_technology_from_sind_psinfo (val),
- MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+ g_free (indicator);
}
static void
@@ -847,8 +883,8 @@
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
- self->priv->ciev_psinfo_regex,
- enable ? (MMPortSerialAtUnsolicitedMsgFn)sind_psinfo_received : NULL,
+ self->priv->ciev_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)sind_ciev_received : NULL,
enable ? self : NULL,
NULL);
}
@@ -1819,8 +1855,8 @@
self->priv->sind_psinfo_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->swwan_support = FEATURE_SUPPORT_UNKNOWN;
- self->priv->ciev_psinfo_regex = g_regex_new ("\\r\\n\\+CIEV: psinfo,(\\d+)\\r\\n",
- G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:\\s*([a-z]+),(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
}
static void
@@ -1842,7 +1878,7 @@
if (self->priv->cnmi_supported_bfr)
g_array_unref (self->priv->cnmi_supported_bfr);
- g_regex_unref (self->priv->ciev_psinfo_regex);
+ g_regex_unref (self->priv->ciev_regex);
G_OBJECT_CLASS (mm_broadband_modem_cinterion_parent_class)->finalize (object);
}
@@ -1872,6 +1908,8 @@
iface->modem_after_sim_unlock_finish = after_sim_unlock_finish;
iface->load_unlock_retries = load_unlock_retries;
iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->reset = modem_reset;
+ iface->reset_finish = modem_reset_finish;
iface->modem_power_down = modem_power_down;
iface->modem_power_down_finish = modem_power_down_finish;
iface->modem_power_off = modem_power_off;
@@ -1926,9 +1964,53 @@
}
static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_call = mm_shared_cinterion_create_call;
+
+ iface->check_support = mm_shared_cinterion_voice_check_support;
+ iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
+ 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
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface_modem_time_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemTime *
+peek_parent_time_interface (MMSharedCinterion *self)
+{
+ return iface_modem_time_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;
+ iface->peek_parent_time_interface = peek_parent_time_interface;
}
static void
diff --git a/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c b/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c
index 6048cca..c14fb23 100644
--- a/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c
+++ b/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c
@@ -26,16 +26,23 @@
#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 iface_modem_time_init (MMIfaceModemTime *iface);
static void shared_cinterion_init (MMSharedCinterion *iface);
static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+static MMIfaceModemTime *iface_modem_time_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_IFACE_MODEM_TIME, iface_modem_time_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
/*****************************************************************************/
@@ -81,9 +88,53 @@
}
static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_call = mm_shared_cinterion_create_call;
+
+ iface->check_support = mm_shared_cinterion_voice_check_support;
+ iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
+ 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
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface_modem_time_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemTime *
+peek_parent_time_interface (MMSharedCinterion *self)
+{
+ return iface_modem_time_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;
+ iface->peek_parent_time_interface = peek_parent_time_interface;
}
static void
diff --git a/plugins/cinterion/mm-modem-helpers-cinterion.c b/plugins/cinterion/mm-modem-helpers-cinterion.c
index 2afd11a..0eac912 100644
--- a/plugins/cinterion/mm-modem-helpers-cinterion.c
+++ b/plugins/cinterion/mm-modem-helpers-cinterion.c
@@ -10,9 +10,10 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
- * Copyright (C) 2016 Trimble Navigation Limited
* Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es>
- * Contributor: Matthew Stanger <matthew_stanger@trimble.com>
+ * Copyright (C) 2016 Trimble Navigation Limited
+ * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com>
+ * Copyright (C) 2019 Purism SPC
*/
#include <config.h>
@@ -201,7 +202,7 @@
r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\s*\"?([0-9a-fA-F]*)\"?", 0, 0, NULL);
g_assert (r != NULL);
- if (g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) {
+ if (g_regex_match (r, response, 0, &match_info)) {
gchar *currentstr;
guint current = 0;
@@ -441,7 +442,7 @@
r = g_regex_new ("\\^SIND:\\s*(.*),(\\d+),(\\d+)(\\r\\n)?", 0, 0, NULL);
g_assert (r != NULL);
- if (g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) {
+ if (g_regex_match (r, response, 0, &match_info)) {
if (description) {
*description = mm_get_string_unquoted_from_match_info (match_info, 1);
if (*description == NULL)
@@ -667,3 +668,201 @@
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);
+}
+
+/*****************************************************************************/
+/* +CTZU URC helpers */
+
+GRegex *
+mm_cinterion_get_ctzu_regex (void)
+{
+ /*
+ * From PLS-8 AT command spec:
+ * +CTZU:<nitzUT>, <nitzTZ>[, <nitzDST>]
+ * E.g.:
+ * +CTZU: "19/07/09,10:19:15",+08,1
+ */
+
+ return g_regex_new ("\\r\\n\\+CTZU:\\s*\"(\\d+)\\/(\\d+)\\/(\\d+),(\\d+):(\\d+):(\\d+)\",([\\-\\+\\d]+)(?:,(\\d+))?(?:\\r\\n)?",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error)
+{
+ guint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, dst = 0;
+ gint tz = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &year) ||
+ !mm_get_uint_from_match_info (match_info, 2, &month) ||
+ !mm_get_uint_from_match_info (match_info, 3, &day) ||
+ !mm_get_uint_from_match_info (match_info, 4, &hour) ||
+ !mm_get_uint_from_match_info (match_info, 5, &minute) ||
+ !mm_get_uint_from_match_info (match_info, 6, &second) ||
+ !mm_get_int_from_match_info (match_info, 7, &tz)) {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse +CTZU URC");
+ return FALSE;
+ }
+
+ /* adjust year */
+ if (year < 100)
+ year += 2000;
+
+ /*
+ * tz = timezone offset in 15 minute intervals
+ */
+ if (iso8601p) {
+ /* Return ISO-8601 format date/time string */
+ *iso8601p = mm_new_iso8601_time (year, month, day, hour,
+ minute, second,
+ TRUE, tz * 15);
+ }
+
+ if (tzp) {
+ *tzp = mm_network_timezone_new ();
+ mm_network_timezone_set_offset (*tzp, tz * 15);
+ }
+
+ /* dst flag is optional in the URC
+ *
+ * tz = timezone offset in 15 minute intervals
+ * dst = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours
+ */
+ if (tzp && mm_get_uint_from_match_info (match_info, 8, &dst))
+ mm_network_timezone_set_dst_offset (*tzp, dst * 60);
+
+ return TRUE;
+}
diff --git a/plugins/cinterion/mm-modem-helpers-cinterion.h b/plugins/cinterion/mm-modem-helpers-cinterion.h
index 2ec0515..a318b8d 100644
--- a/plugins/cinterion/mm-modem-helpers-cinterion.h
+++ b/plugins/cinterion/mm-modem-helpers-cinterion.h
@@ -10,9 +10,10 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
- * Copyright (C) 2016 Trimble Navigation Limited
* Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es>
- * Contributor: Matthew Stanger <matthew_stanger@trimble.com>
+ * Copyright (C) 2016 Trimble Navigation Limited
+ * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com>
+ * Copyright (C) 2019 Purism SPC
*/
#ifndef MM_MODEM_HELPERS_CINTERION_H
@@ -22,6 +23,8 @@
#include <ModemManager.h>
#include <mm-base-bearer.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
/*****************************************************************************/
/* ^SCFG test parser */
@@ -87,4 +90,24 @@
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);
+
+/*****************************************************************************/
+/* +CTZU URC helpers */
+
+GRegex *mm_cinterion_get_ctzu_regex (void);
+gboolean mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error);
+
#endif /* MM_MODEM_HELPERS_CINTERION_H */
diff --git a/plugins/cinterion/mm-shared-cinterion.c b/plugins/cinterion/mm-shared-cinterion.c
index ab95140..5cb35a8 100644
--- a/plugins/cinterion/mm-shared-cinterion.c
+++ b/plugins/cinterion/mm-shared-cinterion.c
@@ -12,6 +12,7 @@
*
* Copyright (C) 2014 Ammonit Measurement GmbH
* Copyright (C) 2014 - 2018 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2019 Purism SPC
*/
#include <config.h>
@@ -28,6 +29,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 +44,26 @@
} 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;
+ /* time */
+ MMIfaceModemTime *iface_modem_time_parent;
+ GRegex *ctzu_regex;
} Private;
static void
private_free (Private *ctx)
{
+ g_regex_unref (ctx->ctzu_regex);
+ g_regex_unref (ctx->slcc_regex);
g_slice_free (Private, ctx);
}
@@ -71,11 +83,21 @@
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 ();
+ priv->ctzu_regex = mm_cinterion_get_ctzu_regex ();
- /* Setup parent class' MMIfaceModemLocation */
+ /* Setup parent class' MMIfaceModemLocation, MMIfaceModemVoice and MMIfaceModemTime */
+
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_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_time_interface);
+ priv->iface_modem_time_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_time_interface (self);
+
g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
}
@@ -787,6 +809,726 @@
/*****************************************************************************/
+MMBaseCall *
+mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
+ MMCallDirection direction,
+ const gchar *number)
+{
+ Private *priv;
+
+ /* If ^SLCC is supported create a cinterion call object */
+ priv = get_private (MM_SHARED_CINTERION (self));
+ if (priv->slcc_support == FEATURE_SUPPORTED) {
+ mm_dbg ("Created new call with ^SLCC support");
+ return mm_base_call_new (MM_BASE_MODEM (self),
+ direction,
+ number,
+ /* When SLCC is supported we have support for detailed
+ * call list events via call list report URCs */
+ TRUE, /* incoming timeout not required */
+ TRUE, /* dialing->ringing supported */
+ TRUE); /* ringing->active supported */
+ }
+
+ /* 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);
+}
+
+/*****************************************************************************/
+/* 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)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_dbg ("Couldn't %s ^SLCC reporting: '%s'",
+ ctx->enable ? "enable" : "disable",
+ error->message);
+ g_error_free (error);
+ }
+
+ /* 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);
+}
+
+/*****************************************************************************/
+/* Check if Voice supported (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+slcc_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ /* ^SLCC supported unless we got any error response */
+ priv->slcc_support = (!!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ?
+ FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED);
+
+ /* If ^SLCC supported we won't need polling in the parent */
+ g_object_set (self,
+ MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->slcc_support == FEATURE_SUPPORTED),
+ NULL);
+
+ /* ^SLCC command is supported; assume we have full voice capabilities */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_voice_check_support_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+ GError *error = NULL;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ if (!priv->iface_modem_voice_parent->check_support_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* voice is supported, check if ^SLCC is available */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SLCC=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) slcc_format_check_ready,
+ task);
+}
+
+void
+mm_shared_cinterion_voice_check_support (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->check_support);
+ g_assert (priv->iface_modem_voice_parent->check_support_finish);
+
+ /* chain up parent's setup first */
+ priv->iface_modem_voice_parent->check_support (
+ self,
+ (GAsyncReadyCallback)parent_voice_check_support_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup time unsolicited events */
+
+static void
+ctzu_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedCinterion *self)
+{
+ gchar *iso8601 = NULL;
+ MMNetworkTimezone *tz = NULL;
+ GError *error = NULL;
+
+ if (!mm_cinterion_parse_ctzu_urc (match_info, &iso8601, &tz, &error)) {
+ mm_dbg ("Couldn't process +CTZU URC: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_assert (iso8601);
+ mm_dbg ("+CTZU URC received: %s", iso8601);
+ mm_iface_modem_time_update_network_time (MM_IFACE_MODEM_TIME (self), iso8601);
+ g_free (iso8601);
+
+ g_assert (tz);
+ mm_iface_modem_time_update_network_timezone (MM_IFACE_MODEM_TIME (self), tz);
+ g_object_unref (tz);
+}
+
+static void
+common_time_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));
+
+ mm_dbg ("%s up time unsolicited events...",
+ enable ? "Setting" : "Cleaning");
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->ctzu_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)ctzu_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Time interface) */
+
+gboolean
+mm_shared_cinterion_time_cleanup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_time_cleanup_unsolicited_events_ready (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_time_parent->cleanup_unsolicited_events_finish (self, res, &error)) {
+ mm_warn ("Couldn't cleanup parent time unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_time_cleanup_unsolicited_events (MMIfaceModemTime *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_time_parent);
+
+ /* our own cleanup first */
+ common_time_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), FALSE);
+
+ if (priv->iface_modem_time_parent->cleanup_unsolicited_events &&
+ priv->iface_modem_time_parent->cleanup_unsolicited_events_finish) {
+ /* Chain up parent's cleanup */
+ priv->iface_modem_time_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_time_cleanup_unsolicited_events_ready,
+ task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Time interface) */
+
+gboolean
+mm_shared_cinterion_time_setup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_time_setup_unsolicited_events (GTask *task)
+{
+ MMSharedCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ /* our own setup next */
+ common_time_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_time_setup_unsolicited_events_ready (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_time_parent->cleanup_unsolicited_events_finish (self, res, &error)) {
+ mm_warn ("Couldn't cleanup parent time unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ own_time_setup_unsolicited_events (task);
+}
+
+void
+mm_shared_cinterion_time_setup_unsolicited_events (MMIfaceModemTime *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_time_parent);
+
+ if (priv->iface_modem_time_parent->setup_unsolicited_events &&
+ priv->iface_modem_time_parent->setup_unsolicited_events_finish) {
+ /* chain up parent's setup first */
+ priv->iface_modem_time_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_time_setup_unsolicited_events_ready,
+ task);
+ return;
+ }
+
+ own_time_setup_unsolicited_events (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 310a538..6500dd4 100644
--- a/plugins/cinterion/mm-shared-cinterion.h
+++ b/plugins/cinterion/mm-shared-cinterion.h
@@ -12,6 +12,7 @@
*
* Copyright (C) 2014 Ammonit Measurement GmbH
* Copyright (C) 2014 - 2018 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2019 Purism SPC
*/
#ifndef MM_SHARED_CINTERION_H
@@ -26,6 +27,8 @@
#include "mm-broadband-modem.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-iface-modem-time.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,10 +42,19 @@
/* 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);
+
+ /* Peek time interface of the parent class of the object */
+ MMIfaceModemTime * (* peek_parent_time_interface) (MMSharedCinterion *self);
};
GType mm_shared_cinterion_get_type (void);
+/*****************************************************************************/
+/* Location interface */
+
void mm_shared_cinterion_location_load_capabilities (MMIfaceModemLocation *self,
GAsyncReadyCallback callback,
gpointer user_data);
@@ -66,4 +78,63 @@
GAsyncResult *res,
GError **error);
+/*****************************************************************************/
+/* Voice interface */
+
+MMBaseCall *mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
+ MMCallDirection direction,
+ const gchar *number);
+
+void mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_check_support_finish (MMIfaceModemVoice *self,
+ 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);
+
+/*****************************************************************************/
+/* Time interface */
+
+void mm_shared_cinterion_time_setup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_time_setup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_time_cleanup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_time_cleanup_unsolicited_events_finish (MMIfaceModemTime *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 2578eb0..eef0a70 100644
--- a/plugins/cinterion/tests/test-modem-helpers-cinterion.c
+++ b/plugins/cinterion/tests/test-modem-helpers-cinterion.c
@@ -668,6 +668,195 @@
}
/*****************************************************************************/
+/* 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));
+}
+
+/*****************************************************************************/
+/* Test +CTZU URCs */
+
+static void
+common_test_ctzu_urc (const gchar *urc,
+ const gchar *expected_iso8601,
+ gint expected_offset,
+ gint expected_dst_offset)
+{
+ GError *error = NULL;
+ GRegex *ctzu_regex = NULL;
+ gboolean result;
+ GMatchInfo *match_info = NULL;
+ gchar *iso8601;
+ MMNetworkTimezone *tz = NULL;
+
+ ctzu_regex = mm_cinterion_get_ctzu_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (ctzu_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ result = mm_cinterion_parse_ctzu_urc (match_info, &iso8601, &tz, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ g_assert (iso8601);
+ g_assert_cmpstr (expected_iso8601, ==, iso8601);
+ g_free (iso8601);
+
+ g_assert (tz);
+ g_assert_cmpint (expected_offset, ==, mm_network_timezone_get_offset (tz));
+
+ if (expected_dst_offset >= 0)
+ g_assert_cmpuint ((guint)expected_dst_offset, ==, mm_network_timezone_get_dst_offset (tz));
+
+ g_object_unref (tz);
+ g_match_info_free (match_info);
+ g_regex_unref (ctzu_regex);
+}
+
+static void
+test_ctzu_urc_simple (void)
+{
+ const gchar *urc = "\r\n+CTZU: \"19/07/09,11:15:40\",+08\r\n";
+ const gchar *expected_iso8601 = "2019-07-09T11:15:40+02:00";
+ gint expected_offset = 120;
+ gint expected_dst_offset = -1; /* not given */
+
+ common_test_ctzu_urc (urc, expected_iso8601, expected_offset, expected_dst_offset);
+}
+
+static void
+test_ctzu_urc_full (void)
+{
+ const gchar *urc = "\r\n+CTZU: \"19/07/09,11:15:40\",+08,1\r\n";
+ const gchar *expected_iso8601 = "2019-07-09T11:15:40+02:00";
+ gint expected_offset = 120;
+ gint expected_dst_offset = 60;
+
+ common_test_ctzu_urc (urc, expected_iso8601, expected_offset, expected_dst_offset);
+}
+
+/*****************************************************************************/
void
_mm_log (const char *loc,
@@ -706,6 +895,12 @@
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);
+ g_test_add_func ("/MM/cinterion/ctzu/urc/simple", test_ctzu_urc_simple);
+ g_test_add_func ("/MM/cinterion/ctzu/urc/full", test_ctzu_urc_full);
return g_test_run ();
}
diff --git a/plugins/dell/77-mm-dell-port-types.rules b/plugins/dell/77-mm-dell-port-types.rules
index c092261..8efc2fc 100644
--- a/plugins/dell/77-mm-dell-port-types.rules
+++ b/plugins/dell/77-mm-dell-port-types.rules
@@ -18,5 +18,11 @@
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_TYPE_GPS}="1"
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+# Dell DW5820e
+# if 02: AT port
+# if 04: debug port (ignore)
+# if 06: AT port
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d9", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+
GOTO="mm_dell_port_types_end"
LABEL="mm_dell_port_types_end"
diff --git a/plugins/dlink/77-mm-dlink-port-types.rules b/plugins/dlink/77-mm-dlink-port-types.rules
new file mode 100644
index 0000000..6489b7c
--- /dev/null
+++ b/plugins/dlink/77-mm-dlink-port-types.rules
@@ -0,0 +1,16 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_dlink_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2001", GOTO="mm_dlink_port_types"
+GOTO="mm_dlink_port_types_end"
+
+LABEL="mm_dlink_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# D-Link DWM-222
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+
+LABEL="mm_dlink_port_types_end"
diff --git a/plugins/dlink/mm-plugin-dlink.c b/plugins/dlink/mm-plugin-dlink.c
new file mode 100644
index 0000000..e7c6816
--- /dev/null
+++ b/plugins/dlink/mm-plugin-dlink.c
@@ -0,0 +1,94 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-port-enums-types.h"
+#include "mm-log.h"
+#include "mm-plugin-dlink.h"
+#include "mm-broadband-modem.h"
+
+#if defined WITH_QMI
+# include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginDlink, mm_plugin_dlink, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ GList *probes,
+ GError **error)
+{
+#if defined WITH_QMI
+ if (mm_port_probe_list_has_qmi_port (probes)) {
+ mm_dbg ("QMI-powered D-Link modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usb", NULL };
+ static const guint16 vendor_ids[] = { 0x2001, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_DLINK,
+ MM_PLUGIN_NAME, "D-Link",
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_dlink_init (MMPluginDlink *self)
+{
+}
+
+static void
+mm_plugin_dlink_class_init (MMPluginDlinkClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/plugins/dlink/mm-plugin-dlink.h b/plugins/dlink/mm-plugin-dlink.h
new file mode 100644
index 0000000..13453cb
--- /dev/null
+++ b/plugins/dlink/mm-plugin-dlink.h
@@ -0,0 +1,40 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_DLINK_H
+#define MM_PLUGIN_DLINK_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_DLINK (mm_plugin_dlink_get_type ())
+#define MM_PLUGIN_DLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_DLINK, MMPluginDlink))
+#define MM_PLUGIN_DLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_DLINK, MMPluginDlinkClass))
+#define MM_IS_PLUGIN_DLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_DLINK))
+#define MM_IS_PLUGIN_DLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_DLINK))
+#define MM_PLUGIN_DLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_DLINK, MMPluginDlinkClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginDlink;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginDlinkClass;
+
+GType mm_plugin_dlink_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_DLINK_H */
diff --git a/plugins/generic/tests/test-service-generic.c b/plugins/generic/tests/test-service-generic.c
index c6e4277..ea29e9e 100644
--- a/plugins/generic/tests/test-service-generic.c
+++ b/plugins/generic/tests/test-service-generic.c
@@ -37,7 +37,7 @@
/* Create port name, and add process ID so that multiple runs of this test
* in the same system don't clash with each other */
- ports[0] = g_strdup_printf ("abstract:port0:%ld", (glong) getpid ());
+ ports[0] = g_strdup_printf ("abstract:port0:%ld", (glong) getpid ());
g_debug ("test service generic: using abstract port at '%s'", ports[0]);
/* Setup new port context */
diff --git a/plugins/huawei/mm-broadband-modem-huawei.c b/plugins/huawei/mm-broadband-modem-huawei.c
index d4c0e83..76490a9 100644
--- a/plugins/huawei/mm-broadband-modem-huawei.c
+++ b/plugins/huawei/mm-broadband-modem-huawei.c
@@ -14,8 +14,8 @@
* Copyright (C) 2009 - 2012 Red Hat, Inc.
* Copyright (C) 2011 - 2012 Google Inc.
* Copyright (C) 2012 Huawei Technologies Co., Ltd
- * Copyright (C) 2012 - 2013 Aleksander Morgado <aleksander@gnu.org>
* Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
+ * Copyright (C) 2012 - 2019 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
@@ -49,7 +49,6 @@
#include "mm-broadband-bearer.h"
#include "mm-bearer-list.h"
#include "mm-sim-huawei.h"
-#include "mm-call-huawei.h"
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
@@ -103,6 +102,13 @@
GRegex *dsflowrpt_regex;
GRegex *ndisstat_regex;
+ /* Regex for voice management notifications */
+ GRegex *orig_regex;
+ GRegex *conf_regex;
+ GRegex *conn_regex;
+ GRegex *cend_regex;
+ GRegex *ddtmf_regex;
+
/* Regex to ignore */
GRegex *boot_regex;
GRegex *connect_regex;
@@ -123,7 +129,6 @@
GRegex *ltersrp_regex;
GRegex *cschannelinfo_regex;
GRegex *eons_regex;
- GRegex *orig_regex;
FeatureSupport ndisdup_support;
FeatureSupport rfswitch_support;
@@ -2934,20 +2939,363 @@
}
/*****************************************************************************/
-/* Enabling unsolicited events (Voice interface) */
+/* In-call audio channel setup/cleanup */
static gboolean
-modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
- GAsyncResult *res,
- GError **error)
+modem_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *_self,
+ GAsyncResult *res,
+ MMPort **audio_port,
+ MMCallAudioFormat **audio_format,
+ GError **error)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ if (self->priv->cvoice_support == FEATURE_SUPPORTED) {
+ /* Setup audio format */
+ if (audio_format) {
+ gchar *resolution_str;
+
+ resolution_str = g_strdup_printf ("s%ule", self->priv->audio_bits);
+ *audio_format = mm_call_audio_format_new ();
+ mm_call_audio_format_set_encoding (*audio_format, "pcm");
+ mm_call_audio_format_set_resolution (*audio_format, resolution_str);
+ mm_call_audio_format_set_rate (*audio_format, self->priv->audio_hz);
+ g_free (resolution_str);
+ }
+
+ /* The QCDM port, if present, switches from QCDM to voice while
+ * a voice call is active. */
+ if (audio_port)
+ *audio_port = MM_PORT (mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self)));
+ } else {
+ if (audio_format)
+ *audio_format = NULL;
+ if (audio_port)
+ *audio_port = NULL;
+ }
+
+ return TRUE;
+}
+
+static void
+ddsetex_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_setup_in_call_audio_channel (MMIfaceModemVoice *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* If there is no CVOICE support, no custom audio setup required
+ * (i.e. audio path is externally managed) */
+ if (self->priv->cvoice_support != FEATURE_SUPPORTED) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Enable audio streaming on the audio port */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^DDSETEX=2",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)ddsetex_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup voice unsolicited events */
+
+typedef enum {
+ HUAWEI_CALL_TYPE_VOICE = 0,
+ HUAWEI_CALL_TYPE_CS_DATA = 1,
+ HUAWEI_CALL_TYPE_PS_DATA = 2,
+ HUAWEI_CALL_TYPE_CDMA_SMS = 3,
+ HUAWEI_CALL_TYPE_OTA_STANDARD_OTASP = 7,
+ HUAWEI_CALL_TYPE_OTA_NON_STANDARD_OTASP = 8,
+ HUAWEI_CALL_TYPE_EMERGENCY = 9,
+} HuaweiCallType;
+
+static void
+orig_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMCallInfo call_info = { 0 };
+ guint aux = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &aux)) {
+ mm_warn ("couldn't parse call type from ^ORIG");
+ return;
+ }
+ if (aux != HUAWEI_CALL_TYPE_VOICE && aux != HUAWEI_CALL_TYPE_EMERGENCY) {
+ mm_dbg ("ignored ^ORIG for non-voice call");
+ return;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_warn ("couldn't parse call index from ^ORIG");
+ return;
+ }
+ call_info.index = aux;
+ call_info.state = MM_CALL_STATE_DIALING;
+ call_info.direction = MM_CALL_DIRECTION_OUTGOING;
+
+ mm_dbg ("call %u state updated: dialing", call_info.index);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+conf_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMCallInfo call_info = { 0 };
+ guint aux = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_warn ("couldn't parse call index from ^CONF");
+ return;
+ }
+ call_info.index = aux;
+ call_info.state = MM_CALL_STATE_RINGING_OUT;
+ call_info.direction = MM_CALL_DIRECTION_OUTGOING;
+
+ mm_dbg ("call %u state updated: ringing-out", call_info.index);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+conn_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMCallInfo call_info = { 0 };
+ guint aux = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_warn ("couldn't parse call index from ^CONN");
+ return;
+ }
+ call_info.index = aux;
+ call_info.state = MM_CALL_STATE_ACTIVE;
+ call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
+
+ mm_dbg ("call %u state updated: active", aux);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+cend_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMCallInfo call_info = { 0 };
+ guint aux = 0;
+
+ /* only index is mandatory */
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_warn ("couldn't parse call index from ^CEND");
+ return;
+ }
+ call_info.index = aux;
+ call_info.state = MM_CALL_STATE_TERMINATED;
+ call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
+
+ mm_dbg ("call %u state updated: terminated", call_info.index);
+ if (mm_get_uint_from_match_info (match_info, 2, &aux))
+ mm_dbg (" call duration: %u seconds", aux);
+ if (mm_get_uint_from_match_info (match_info, 3, &aux))
+ mm_dbg (" end status code: %u", aux);
+ if (mm_get_uint_from_match_info (match_info, 4, &aux))
+ mm_dbg (" call control cause: %u", aux);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+ddtmf_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ gchar *dtmf;
+
+ dtmf = g_match_info_fetch (match_info, 1);
+ mm_dbg ("received DTMF: %s", dtmf);
+ /* call index unknown */
+ mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf);
+ g_free (dtmf);
+}
+
+static void
+common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemHuawei *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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],
+ self->priv->orig_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)orig_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->conf_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)conf_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->conn_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)conn_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->cend_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)cend_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->ddtmf_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)ddtmf_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
-own_voice_enable_unsolicited_events_ready (MMBaseModem *self,
+parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Our own setup now */
+ common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_voice_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Voice interface) */
+
+static gboolean
+modem_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;
+
+ if (!iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* cleanup our own */
+ common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
+
+ /* Chain up parent's cleanup */
+ iface_modem_voice_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_voice_enable_unsolicited_events_ready (MMBaseModem *self,
GAsyncResult *res,
- GTask *task)
+ GTask *task)
{
GError *error = NULL;
@@ -2967,8 +3315,8 @@
static void
parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
- GAsyncResult *res,
- GTask *task)
+ GAsyncResult *res,
+ GTask *task)
{
GError *error = NULL;
@@ -2991,9 +3339,9 @@
}
static void
-modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
+modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
GTask *task;
@@ -3010,17 +3358,17 @@
/* Disabling unsolicited events (Voice interface) */
static gboolean
-modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
- GAsyncResult *res,
- GError **error)
+modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
-own_voice_disable_unsolicited_events_ready (MMBaseModem *self,
+own_voice_disable_unsolicited_events_ready (MMBaseModem *self,
GAsyncResult *res,
- GTask *task)
+ GTask *task)
{
GError *error = NULL;
@@ -3039,15 +3387,19 @@
};
static void
-modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
+parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
{
- GTask *task;
+ GError *error = NULL;
- task = g_task_new (self, NULL, callback, user_data);
+ if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
- /* No unsolicited events disabling in parent */
+ /* our own disable now */
mm_base_modem_at_sequence_full (
MM_BASE_MODEM (self),
@@ -3060,25 +3412,36 @@
task);
}
+static void
+modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's disable */
+ iface_modem_voice_parent->disable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
+ task);
+}
+
/*****************************************************************************/
/* Create call (Voice interface) */
static MMBaseCall *
-create_call (MMIfaceModemVoice *_self,
+create_call (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number)
{
- MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
-
- /* If CVOICE is supported we must have audio settings */
- g_assert (self->priv->cvoice_support == FEATURE_NOT_SUPPORTED ||
- (self->priv->cvoice_support == FEATURE_SUPPORTED && self->priv->audio_hz && self->priv->audio_bits));
-
- return mm_call_huawei_new (MM_BASE_MODEM (self),
- direction,
- number,
- self->priv->audio_hz,
- self->priv->audio_bits);
+ return mm_base_call_new (MM_BASE_MODEM (self),
+ direction,
+ number,
+ TRUE, /* skip_incoming_timeout */
+ TRUE, /* supports_dialing_to_ringing */
+ TRUE); /* supports_ringing_to_active) */
}
/*****************************************************************************/
@@ -4019,10 +4382,6 @@
port,
self->priv->eons_regex,
NULL, NULL, NULL);
- mm_port_serial_at_add_unsolicited_msg_handler (
- port,
- self->priv->orig_regex,
- NULL, NULL, NULL);
}
g_list_free_full (ports, g_object_unref);
@@ -4108,6 +4467,18 @@
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->ndisstat_regex = g_regex_new ("\\r\\n(\\^NDISSTAT:.+)\\r+\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->orig_regex = g_regex_new ("\\r\\n\\^ORIG:\\s*(\\d+),\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->conf_regex = g_regex_new ("\\r\\n\\^CONF:\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->conn_regex = g_regex_new ("\\r\\n\\^CONN:\\s*(\\d+),\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->cend_regex = g_regex_new ("\\r\\n\\^CEND:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d*))?\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ddtmf_regex = g_regex_new ("\\r\\n\\^DDTMF:\\s*([0-9A-D\\*\\#])\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
self->priv->boot_regex = g_regex_new ("\\r\\n\\^BOOT:.+\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->connect_regex = g_regex_new ("\\r\\n\\^CONNECT .+\\r\\n",
@@ -4146,8 +4517,6 @@
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->eons_regex = g_regex_new ("\\r\\n\\^EONS:.+\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
- self->priv->orig_regex = g_regex_new ("\\r\\n\\^ORIG:.+\\r\\n",
- G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->ndisdup_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->rfswitch_support = FEATURE_SUPPORT_UNKNOWN;
@@ -4181,6 +4550,12 @@
g_regex_unref (self->priv->mode_regex);
g_regex_unref (self->priv->dsflowrpt_regex);
g_regex_unref (self->priv->ndisstat_regex);
+ g_regex_unref (self->priv->orig_regex);
+ g_regex_unref (self->priv->conf_regex);
+ g_regex_unref (self->priv->conn_regex);
+ g_regex_unref (self->priv->cend_regex);
+ g_regex_unref (self->priv->ddtmf_regex);
+
g_regex_unref (self->priv->boot_regex);
g_regex_unref (self->priv->connect_regex);
g_regex_unref (self->priv->csnr_regex);
@@ -4200,7 +4575,6 @@
g_regex_unref (self->priv->ltersrp_regex);
g_regex_unref (self->priv->cschannelinfo_regex);
g_regex_unref (self->priv->eons_regex);
- g_regex_unref (self->priv->orig_regex);
if (self->priv->syscfg_supported_modes)
g_array_unref (self->priv->syscfg_supported_modes);
@@ -4317,10 +4691,16 @@
iface->check_support = modem_voice_check_support;
iface->check_support_finish = modem_voice_check_support_finish;
+ iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish;
iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish;
+ iface->setup_in_call_audio_channel = modem_voice_setup_in_call_audio_channel;
+ iface->setup_in_call_audio_channel_finish = modem_voice_setup_in_call_audio_channel_finish;
iface->create_call = create_call;
}
diff --git a/plugins/huawei/mm-call-huawei.c b/plugins/huawei/mm-call-huawei.c
deleted file mode 100644
index 6c7a885..0000000
--- a/plugins/huawei/mm-call-huawei.c
+++ /dev/null
@@ -1,445 +0,0 @@
-/* -*- 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>
- */
-
-#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-log.h"
-#include "mm-base-modem-at.h"
-#include "mm-broadband-modem-huawei.h"
-#include "mm-call-huawei.h"
-
-G_DEFINE_TYPE (MMCallHuawei, mm_call_huawei, MM_TYPE_BASE_CALL)
-
-enum {
- PROP_0,
- PROP_AUDIO_HZ,
- PROP_AUDIO_BITS,
- PROP_LAST
-};
-
-static GParamSpec *properties[PROP_LAST];
-
-struct _MMCallHuaweiPrivate {
- GRegex *conf_regex;
- GRegex *conn_regex;
- GRegex *cend_regex;
- GRegex *ddtmf_regex;
- guint audio_hz;
- guint audio_bits;
-};
-
-/*****************************************************************************/
-/* Audio channel setup */
-
-typedef struct {
- MMBaseModem *modem;
- MMPort *audio_port;
- MMCallAudioFormat *audio_format;
-} SetupAudioChannelContext;
-
-static void
-setup_audio_channel_context_free (SetupAudioChannelContext *ctx)
-{
- g_clear_object (&ctx->audio_port);
- g_clear_object (&ctx->audio_format);
- g_clear_object (&ctx->modem);
- g_slice_free (SetupAudioChannelContext, ctx);
-}
-
-static gboolean
-setup_audio_channel_finish (MMBaseCall *self,
- GAsyncResult *res,
- MMPort **audio_port,
- MMCallAudioFormat **audio_format,
- GError **error)
-{
- SetupAudioChannelContext *ctx;
-
- if (!g_task_propagate_boolean (G_TASK (res), error))
- return FALSE;
-
- ctx = g_task_get_task_data (G_TASK (res));
-
- if (audio_port && ctx->audio_port)
- *audio_port = g_object_ref (ctx->audio_port);
- if (audio_format && ctx->audio_format)
- *audio_format = g_object_ref (ctx->audio_format);
-
- return TRUE;
-}
-
-static void
-ddsetex_ready (MMBaseModem *modem,
- GAsyncResult *res,
- GTask *task)
-{
- MMCallHuawei *self;
- SetupAudioChannelContext *ctx;
- GError *error = NULL;
- const gchar *response = NULL;
- gchar *resolution_str;
-
- response = mm_base_modem_at_command_finish (modem, res, &error);
- if (!response) {
- mm_dbg ("Error enabling audio streaming: '%s'", error->message);
- g_task_return_error (task, error);
- g_object_unref (task);
- return;
- }
-
- self = g_task_get_source_object (task);
- ctx = g_task_get_task_data (task);
-
- /* Setup audio format */
- g_assert (self->priv->audio_hz && self->priv->audio_bits);
- resolution_str = g_strdup_printf ("s%ule", self->priv->audio_bits);
- ctx->audio_format = mm_call_audio_format_new ();
- mm_call_audio_format_set_encoding (ctx->audio_format, "pcm");
- mm_call_audio_format_set_resolution (ctx->audio_format, resolution_str);
- mm_call_audio_format_set_rate (ctx->audio_format, self->priv->audio_hz);
-
- /* The QCDM port, if present, switches from QCDM to voice while
- * a voice call is active. */
- ctx->audio_port = MM_PORT (mm_base_modem_get_port_qcdm (modem));
-
- g_task_return_boolean (task, TRUE);
- g_object_unref (task);
-}
-
-static void
-setup_audio_channel (MMBaseCall *_self,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- SetupAudioChannelContext *ctx;
- MMCallHuawei *self;
- GTask *task;
- MMBaseModem *modem = NULL;
-
- self = MM_CALL_HUAWEI (_self);
-
- task = g_task_new (self, NULL, callback, user_data);
-
- /* If there is no CVOICE support, no custom audio setup required
- * (i.e. audio path is externally managed) */
- if (!self->priv->audio_hz && !self->priv->audio_bits) {
- g_task_return_boolean (task, TRUE);
- g_object_unref (task);
- return;
- }
-
- ctx = g_slice_new0 (SetupAudioChannelContext);
- g_object_get (self,
- MM_BASE_CALL_MODEM, &ctx->modem,
- NULL);
- g_task_set_task_data (task, ctx, (GDestroyNotify) setup_audio_channel_context_free);
-
- /* Enable audio streaming on the audio port */
- mm_base_modem_at_command (modem,
- "AT^DDSETEX=2",
- 5,
- FALSE,
- (GAsyncReadyCallback)ddsetex_ready,
- task);
-}
-
-/*****************************************************************************/
-/* In-call unsolicited events */
-
-static void
-huawei_voice_ringback_tone (MMPortSerialAt *port,
- GMatchInfo *match_info,
- MMCallHuawei *self)
-{
- guint call_x = 0;
-
- if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
- return;
-
- mm_dbg ("Ringback tone from call id '%u'", call_x);
-
- if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_DIALING)
- mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_RINGING_OUT, MM_CALL_STATE_REASON_OUTGOING_STARTED);
-}
-
-static void
-huawei_voice_call_connection (MMPortSerialAt *port,
- GMatchInfo *match_info,
- MMCallHuawei *self)
-{
- guint call_x = 0;
- guint call_type = 0;
-
- if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
- return;
-
- if (!mm_get_uint_from_match_info (match_info, 2, &call_type))
- return;
-
- mm_dbg ("Call id '%u' of type '%u' connected", call_x, call_type);
-
- if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_RINGING_OUT)
- mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
-}
-
-static void
-huawei_voice_call_end (MMPortSerialAt *port,
- GMatchInfo *match_info,
- MMCallHuawei *self)
-{
- guint call_x = 0;
- guint duration = 0;
- guint cc_cause = 0;
- guint end_status = 0;
-
- if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
- return;
-
- if (!mm_get_uint_from_match_info (match_info, 2, &duration))
- return;
-
- if (!mm_get_uint_from_match_info (match_info, 3, &end_status))
- return;
-
- /* This is optional */
- mm_get_uint_from_match_info (match_info, 4, &cc_cause);
-
- mm_dbg ("Call id '%u' terminated with status '%u' and cause '%u'. Duration of call '%d'",
- call_x, end_status, cc_cause, duration);
-
- mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED);
-}
-
-static void
-huawei_voice_received_dtmf (MMPortSerialAt *port,
- GMatchInfo *match_info,
- MMCallHuawei *self)
-{
- gchar *key;
-
- key = g_match_info_fetch (match_info, 1);
-
- if (!key)
- return;
-
- mm_dbg ("Received DTMF '%s'", key);
- mm_base_call_received_dtmf (MM_BASE_CALL (self), key);
- g_free (key);
-}
-
-static gboolean
-common_setup_cleanup_unsolicited_events (MMCallHuawei *self,
- gboolean enable,
- GError **error)
-{
- MMBaseModem *modem = NULL;
- GList *ports, *l;
-
- if (G_UNLIKELY (!self->priv->conf_regex))
- self->priv->conf_regex = g_regex_new ("\\r\\n\\^CONF:\\s*(\\d+)\\r\\n",
- G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
- if (G_UNLIKELY (!self->priv->conn_regex))
- self->priv->conn_regex = g_regex_new ("\\r\\n\\^CONN:\\s*(\\d+),(\\d+)\\r\\n",
- G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
- if (G_UNLIKELY (!self->priv->cend_regex))
- self->priv->cend_regex = g_regex_new ("\\r\\n\\^CEND:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),?\\s*(\\d*)\\r\\n",
- G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
- if (G_UNLIKELY (!self->priv->ddtmf_regex))
- self->priv->ddtmf_regex = g_regex_new ("\\r\\n\\^DDTMF:\\s*([0-9A-D\\*\\#])\\r\\n",
- G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
-
- g_object_get (self,
- MM_BASE_CALL_MODEM, &modem,
- NULL);
-
- ports = mm_broadband_modem_huawei_get_at_port_list (MM_BROADBAND_MODEM_HUAWEI (modem));
-
- for (l = ports; l; l = g_list_next (l)) {
- MMPortSerialAt *port;
-
- port = MM_PORT_SERIAL_AT (l->data);
- mm_port_serial_at_add_unsolicited_msg_handler (
- port,
- self->priv->conf_regex,
- enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_ringback_tone : NULL,
- enable ? self : NULL,
- NULL);
- mm_port_serial_at_add_unsolicited_msg_handler (
- port,
- self->priv->conn_regex,
- enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_call_connection : NULL,
- enable ? self : NULL,
- NULL);
- mm_port_serial_at_add_unsolicited_msg_handler (
- port,
- self->priv->cend_regex,
- enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_call_end : NULL,
- enable ? self : NULL,
- NULL);
- mm_port_serial_at_add_unsolicited_msg_handler (
- port,
- self->priv->ddtmf_regex,
- enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_received_dtmf: NULL,
- enable ? self : NULL,
- NULL);
- }
-
- g_list_free_full (ports, g_object_unref);
- g_object_unref (modem);
- return TRUE;
-}
-
-static gboolean
-setup_unsolicited_events (MMBaseCall *self,
- GError **error)
-{
- return common_setup_cleanup_unsolicited_events (MM_CALL_HUAWEI (self), TRUE, error);
-}
-
-static gboolean
-cleanup_unsolicited_events (MMBaseCall *self,
- GError **error)
-{
- return common_setup_cleanup_unsolicited_events (MM_CALL_HUAWEI (self), FALSE, error);
-}
-
-/*****************************************************************************/
-
-MMBaseCall *
-mm_call_huawei_new (MMBaseModem *modem,
- MMCallDirection direction,
- const gchar *number,
- guint audio_hz,
- guint audio_bits)
-{
- return MM_BASE_CALL (g_object_new (MM_TYPE_CALL_HUAWEI,
- MM_BASE_CALL_MODEM, modem,
- "direction", direction,
- "number", number,
- MM_CALL_HUAWEI_AUDIO_HZ, audio_hz,
- MM_CALL_HUAWEI_AUDIO_BITS, audio_bits,
- MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE,
- MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE,
- NULL));
-}
-
-static void
-mm_call_huawei_init (MMCallHuawei *self)
-{
- /* Initialize private data */
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_HUAWEI, MMCallHuaweiPrivate);
-}
-
-static void
-set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- MMCallHuawei *self = MM_CALL_HUAWEI (object);
-
- switch (prop_id) {
- case PROP_AUDIO_HZ:
- self->priv->audio_hz = g_value_get_uint (value);
- break;
- case PROP_AUDIO_BITS:
- self->priv->audio_bits = g_value_get_uint (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)
-{
- MMCallHuawei *self = MM_CALL_HUAWEI (object);
-
- switch (prop_id) {
- case PROP_AUDIO_HZ:
- g_value_set_uint (value, self->priv->audio_hz);
- break;
- case PROP_AUDIO_BITS:
- g_value_set_uint (value, self->priv->audio_bits);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-finalize (GObject *object)
-{
- MMCallHuawei *self = MM_CALL_HUAWEI (object);
-
- if (self->priv->conf_regex)
- g_regex_unref (self->priv->conf_regex);
- if (self->priv->conn_regex)
- g_regex_unref (self->priv->conn_regex);
- if (self->priv->cend_regex)
- g_regex_unref (self->priv->cend_regex);
- if (self->priv->ddtmf_regex)
- g_regex_unref (self->priv->ddtmf_regex);
-
- G_OBJECT_CLASS (mm_call_huawei_parent_class)->finalize (object);
-}
-
-static void
-mm_call_huawei_class_init (MMCallHuaweiClass *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 (MMCallHuaweiPrivate));
-
- object_class->get_property = get_property;
- object_class->set_property = set_property;
- object_class->finalize = finalize;
-
- base_call_class->setup_unsolicited_events = setup_unsolicited_events;
- base_call_class->cleanup_unsolicited_events = cleanup_unsolicited_events;
- base_call_class->setup_audio_channel = setup_audio_channel;
- base_call_class->setup_audio_channel_finish = setup_audio_channel_finish;
-
- properties[PROP_AUDIO_HZ] =
- g_param_spec_uint (MM_CALL_HUAWEI_AUDIO_HZ,
- "Audio Hz",
- "Voice call audio hz if call audio is routed via the host",
- 0, 24000, 0,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
- g_object_class_install_property (object_class, PROP_AUDIO_HZ, properties[PROP_AUDIO_HZ]);
-
- properties[PROP_AUDIO_BITS] =
- g_param_spec_uint (MM_CALL_HUAWEI_AUDIO_BITS,
- "Audio Bits",
- "Voice call audio bits if call audio is routed via the host",
- 0, 24, 0,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
- g_object_class_install_property (object_class, PROP_AUDIO_BITS, properties[PROP_AUDIO_BITS]);
-}
diff --git a/plugins/huawei/mm-call-huawei.h b/plugins/huawei/mm-call-huawei.h
deleted file mode 100644
index 1b70e81..0000000
--- a/plugins/huawei/mm-call-huawei.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- 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>
- */
-
-#ifndef MM_CALL_HUAWEI_H
-#define MM_CALL_HUAWEI_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-#include "mm-base-call.h"
-
-#define MM_TYPE_CALL_HUAWEI (mm_call_huawei_get_type ())
-#define MM_CALL_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CALL_HUAWEI, MMCallHuawei))
-#define MM_CALL_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_CALL_HUAWEI, MMCallHuaweiClass))
-#define MM_IS_CALL_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CALL_HUAWEI))
-#define MM_IS_CALL_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CALL_HUAWEI))
-#define MM_CALL_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CALL_HUAWEI, MMCallHuaweiClass))
-
-#define MM_CALL_HUAWEI_AUDIO_HZ "call-huawei-audio-hz"
-#define MM_CALL_HUAWEI_AUDIO_BITS "call-huawei-audio-bits"
-
-typedef struct _MMCallHuawei MMCallHuawei;
-typedef struct _MMCallHuaweiClass MMCallHuaweiClass;
-typedef struct _MMCallHuaweiPrivate MMCallHuaweiPrivate;
-
-struct _MMCallHuawei {
- MMBaseCall parent;
- MMCallHuaweiPrivate *priv;
-};
-
-struct _MMCallHuaweiClass {
- MMBaseCallClass parent;
-};
-
-GType mm_call_huawei_get_type (void);
-
-MMBaseCall *mm_call_huawei_new (MMBaseModem *modem,
- MMCallDirection direction,
- const gchar *number,
- guint audio_hz,
- guint audio_bits);
-
-#endif /* MM_CALL_HUAWEI_H */
diff --git a/plugins/mbm/mm-broadband-modem-mbm.c b/plugins/mbm/mm-broadband-modem-mbm.c
index 06bc9ac..a4b89fd 100644
--- a/plugins/mbm/mm-broadband-modem-mbm.c
+++ b/plugins/mbm/mm-broadband-modem-mbm.c
@@ -1262,7 +1262,8 @@
mm_port_serial_command (MM_PORT_SERIAL (gps_port),
buf,
3,
- FALSE,
+ FALSE, /* never cached */
+ FALSE, /* always queued last */
NULL,
NULL,
NULL);
diff --git a/plugins/quectel/77-mm-quectel-port-types.rules b/plugins/quectel/77-mm-quectel-port-types.rules
new file mode 100644
index 0000000..eb6c0a7
--- /dev/null
+++ b/plugins/quectel/77-mm-quectel-port-types.rules
@@ -0,0 +1,30 @@
+# do not edit this file, it will be overwritten on update
+ACTION!="add|change|move|bind", GOTO="mm_quectel_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c7c", GOTO="mm_quectel_port_types"
+GOTO="mm_quectel_port_types_end"
+
+LABEL="mm_quectel_port_types"
+
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Quectel EG06
+# ttyUSB0 (if #0): QCDM/DIAG port
+# ttyUSB1 (if #1): GPS data port
+# ttyUSB2 (if #2): AT primary port
+# ttyUSB3 (if #3): AT secondary port
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# Quectel EG91
+# ttyUSB0 (if #0): QCDM/DIAG port
+# ttyUSB1 (if #1): GPS data port
+# ttyUSB2 (if #2): AT primary port
+# ttyUSB3 (if #3): AT secondary port
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+LABEL="mm_quectel_port_types_end"
diff --git a/plugins/quectel/mm-plugin-quectel.c b/plugins/quectel/mm-plugin-quectel.c
index 85d9314..383b08c 100644
--- a/plugins/quectel/mm-plugin-quectel.c
+++ b/plugins/quectel/mm-plugin-quectel.c
@@ -67,18 +67,18 @@
mm_plugin_create (void)
{
static const gchar *subsystems[] = { "tty", "net", "usb", NULL };
+ static const gchar *vendor_strings[] = { "quectel", NULL };
static const guint16 vendor_ids[] = { 0x2c7c, 0 };
- static const gchar *drivers[] = { "qmi_wwan", NULL };
return MM_PLUGIN (
g_object_new (MM_TYPE_PLUGIN_QUECTEL,
- MM_PLUGIN_NAME, "Quectel",
- MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
- MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
- MM_PLUGIN_ALLOWED_DRIVERS, drivers,
- MM_PLUGIN_ALLOWED_AT, TRUE,
- MM_PLUGIN_ALLOWED_QCDM, TRUE,
- MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_NAME, "Quectel",
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
NULL));
}
diff --git a/plugins/sierra/77-mm-sierra.rules b/plugins/sierra/77-mm-sierra.rules
index d3da313..a976e60 100644
--- a/plugins/sierra/77-mm-sierra.rules
+++ b/plugins/sierra/77-mm-sierra.rules
@@ -5,8 +5,17 @@
GOTO="mm_sierra_end"
LABEL="mm_sierra"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
# Netgear AC341U: enable connection status polling explicitly
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9057", ENV{ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE}="1"
-LABEL="mm_sierra_end"
\ No newline at end of file
+# MC74XX: Add port hints
+# if 03: primary port
+# if 02: raw NMEA port
+# if 00: diag/qcdm port
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+LABEL="mm_sierra_end"
diff --git a/plugins/simtech/mm-broadband-modem-simtech.c b/plugins/simtech/mm-broadband-modem-simtech.c
index c511629..b200114 100644
--- a/plugins/simtech/mm-broadband-modem-simtech.c
+++ b/plugins/simtech/mm-broadband-modem-simtech.c
@@ -209,7 +209,7 @@
static const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
/* Autoreport access technology changes */
- { "+CNSMOD=1", 5, FALSE, NULL },
+ { "+CNSMOD=1", 20, FALSE, NULL },
/* Autoreport CSQ (first arg), and only report when it changes (second arg) */
{ "+AUTOCSQ=1,1", 5, FALSE, NULL },
{ NULL }
diff --git a/plugins/telit/77-mm-telit-port-types.rules b/plugins/telit/77-mm-telit-port-types.rules
index f8f9f08..6ec5a7e 100644
--- a/plugins/telit/77-mm-telit-port-types.rules
+++ b/plugins/telit/77-mm-telit-port-types.rules
@@ -33,4 +33,22 @@
# CE910-DUAL
ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1011", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+# LE922
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# ME910
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# LM940/960 use alternate settings for 3G band management
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="0x1040", ENV{ID_MM_TELIT_BND_ALTERNATE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="0x1041", ENV{ID_MM_TELIT_BND_ALTERNATE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="0x1042", ENV{ID_MM_TELIT_BND_ALTERNATE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="0x1043", ENV{ID_MM_TELIT_BND_ALTERNATE}="1"
+
LABEL="mm_telit_port_types_end"
diff --git a/plugins/telit/mm-broadband-modem-mbim-telit.c b/plugins/telit/mm-broadband-modem-mbim-telit.c
index d4a8095..43bce7f 100644
--- a/plugins/telit/mm-broadband-modem-mbim-telit.c
+++ b/plugins/telit/mm-broadband-modem-mbim-telit.c
@@ -159,9 +159,9 @@
iface->set_current_bands = mm_shared_telit_modem_set_current_bands;
iface->set_current_bands_finish = mm_shared_telit_modem_set_current_bands_finish;
iface->load_current_bands = mm_shared_telit_modem_load_current_bands;
- iface->load_current_bands_finish = mm_shared_telit_modem_load_bands_finish;
+ iface->load_current_bands_finish = mm_shared_telit_modem_load_current_bands_finish;
iface->load_supported_bands = mm_shared_telit_modem_load_supported_bands;
- iface->load_supported_bands_finish = mm_shared_telit_modem_load_bands_finish;
+ iface->load_supported_bands_finish = mm_shared_telit_modem_load_supported_bands_finish;
iface->load_supported_modes = load_supported_modes;
iface->load_supported_modes_finish = load_supported_modes_finish;
iface->load_current_modes = mm_shared_telit_load_current_modes;
diff --git a/plugins/telit/mm-broadband-modem-telit.c b/plugins/telit/mm-broadband-modem-telit.c
index dab0b7a..d6b1ea2 100644
--- a/plugins/telit/mm-broadband-modem-telit.c
+++ b/plugins/telit/mm-broadband-modem-telit.c
@@ -844,8 +844,8 @@
}
static const MMBaseModemAtCommand access_tech_commands[] = {
- { "#PSNT?", 3, TRUE, response_processor_psnt_ignore_at_errors },
- { "+SERVICE?", 3, TRUE, response_processor_service_ignore_at_errors },
+ { "#PSNT?", 3, FALSE, response_processor_psnt_ignore_at_errors },
+ { "+SERVICE?", 3, FALSE, response_processor_service_ignore_at_errors },
{ NULL }
};
@@ -1031,9 +1031,9 @@
iface->set_current_bands = mm_shared_telit_modem_set_current_bands;
iface->set_current_bands_finish = mm_shared_telit_modem_set_current_bands_finish;
iface->load_current_bands = mm_shared_telit_modem_load_current_bands;
- iface->load_current_bands_finish = mm_shared_telit_modem_load_bands_finish;
+ iface->load_current_bands_finish = mm_shared_telit_modem_load_current_bands_finish;
iface->load_supported_bands = mm_shared_telit_modem_load_supported_bands;
- iface->load_supported_bands_finish = mm_shared_telit_modem_load_bands_finish;
+ iface->load_supported_bands_finish = mm_shared_telit_modem_load_supported_bands_finish;
iface->load_unlock_retries_finish = modem_load_unlock_retries_finish;
iface->load_unlock_retries = modem_load_unlock_retries;
iface->reset = modem_reset;
diff --git a/plugins/telit/mm-modem-helpers-telit.c b/plugins/telit/mm-modem-helpers-telit.c
index c162964..e4bacc5 100644
--- a/plugins/telit/mm-modem-helpers-telit.c
+++ b/plugins/telit/mm-modem-helpers-telit.c
@@ -10,8 +10,8 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
- * Copyright (C) 2015 Telit.
- *
+ * Copyright (C) 2015-2019 Telit.
+ * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <stdlib.h>
@@ -27,115 +27,299 @@
#include "mm-modem-helpers.h"
#include "mm-modem-helpers-telit.h"
+/*****************************************************************************/
+/* AT#BND 2G values */
+
+#define MM_MODEM_BAND_TELIT_2G_FIRST MM_MODEM_BAND_EGSM
+#define MM_MODEM_BAND_TELIT_2G_LAST MM_MODEM_BAND_G850
+
+#define B2G_FLAG(band) (1 << (band - MM_MODEM_BAND_TELIT_2G_FIRST))
+
+/* Index of the array is the telit 2G band value [0-5]
+ * The bitmask value here is built from the 2G MMModemBand values right away. */
+static const guint32 telit_2g_to_mm_band_mask[] = {
+ [0] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS),
+ [1] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_PCS),
+ [2] = B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_G850),
+ [3] = B2G_FLAG (MM_MODEM_BAND_PCS) + B2G_FLAG (MM_MODEM_BAND_G850),
+ [4] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_PCS),
+ [5] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_PCS) + B2G_FLAG (MM_MODEM_BAND_G850),
+};
+
+/*****************************************************************************/
+/* AT#BND 3G values */
+
+/* NOTE: UTRAN_1 to UTRAN_9 enum values are NOT IN ORDER!
+ * E.g. numerically UTRAN_7 is after UTRAN_9...
+ *
+ * This array allows us to iterate them in a way which is a bit
+ * more friendly.
+ */
+static const guint band_utran_index[] = {
+ [MM_MODEM_BAND_UTRAN_1] = 1,
+ [MM_MODEM_BAND_UTRAN_2] = 2,
+ [MM_MODEM_BAND_UTRAN_3] = 3,
+ [MM_MODEM_BAND_UTRAN_4] = 4,
+ [MM_MODEM_BAND_UTRAN_5] = 5,
+ [MM_MODEM_BAND_UTRAN_6] = 6,
+ [MM_MODEM_BAND_UTRAN_7] = 7,
+ [MM_MODEM_BAND_UTRAN_8] = 8,
+ [MM_MODEM_BAND_UTRAN_9] = 9,
+ [MM_MODEM_BAND_UTRAN_10] = 10,
+ [MM_MODEM_BAND_UTRAN_11] = 11,
+ [MM_MODEM_BAND_UTRAN_12] = 12,
+ [MM_MODEM_BAND_UTRAN_13] = 13,
+ [MM_MODEM_BAND_UTRAN_14] = 14,
+ [MM_MODEM_BAND_UTRAN_19] = 19,
+ [MM_MODEM_BAND_UTRAN_20] = 20,
+ [MM_MODEM_BAND_UTRAN_21] = 21,
+ [MM_MODEM_BAND_UTRAN_22] = 22,
+ [MM_MODEM_BAND_UTRAN_25] = 25,
+ [MM_MODEM_BAND_UTRAN_26] = 26,
+ [MM_MODEM_BAND_UTRAN_32] = 32,
+};
+
+#define MM_MODEM_BAND_TELIT_3G_FIRST MM_MODEM_BAND_UTRAN_1
+#define MM_MODEM_BAND_TELIT_3G_LAST MM_MODEM_BAND_UTRAN_19
+
+#define B3G_NUM(band) band_utran_index[band]
+#define B3G_FLAG(band) ((B3G_NUM (band) > 0) ? (1 << (B3G_NUM (band) - B3G_NUM (MM_MODEM_BAND_TELIT_3G_FIRST))) : 0)
+
+/* Index of the arrays is the telit 3G band value.
+ * The bitmask value here is built from the 3G MMModemBand values right away.
+ *
+ * We have 2 different sets of bands that are different for values >=12, because
+ * the LM940/960 models support a different set of 3G bands.
+ */
+
+#define TELIT_3G_TO_MM_BAND_MASK_DEFAULT_N_ELEMENTS 27
+static guint32 telit_3g_to_mm_band_mask_default[TELIT_3G_TO_MM_BAND_MASK_DEFAULT_N_ELEMENTS];
+
+#define TELIT_3G_TO_MM_BAND_MASK_ALTERNATE_N_ELEMENTS 20
+static guint32 telit_3g_to_mm_band_mask_alternate[TELIT_3G_TO_MM_BAND_MASK_ALTERNATE_N_ELEMENTS];
+
+static void
+initialize_telit_3g_to_mm_band_masks (void)
+{
+ static gboolean initialized = FALSE;
+
+ /* We need to initialize the arrays in runtime because gcc < 8 doesn't like initializing
+ * with operations that are using the band_utran_index constant array elements */
+
+ if (initialized)
+ return;
+
+ telit_3g_to_mm_band_mask_default[0] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1);
+ telit_3g_to_mm_band_mask_default[1] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2);
+ telit_3g_to_mm_band_mask_default[2] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[3] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[4] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[5] = B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[6] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[7] = B3G_FLAG (MM_MODEM_BAND_UTRAN_4);
+ telit_3g_to_mm_band_mask_default[8] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[9] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[10] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[11] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[12] = B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[13] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3);
+ telit_3g_to_mm_band_mask_default[14] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[15] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[16] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[17] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[18] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[19] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[20] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[21] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[22] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[23] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3);
+ telit_3g_to_mm_band_mask_default[24] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[25] = B3G_FLAG (MM_MODEM_BAND_UTRAN_19);
+ telit_3g_to_mm_band_mask_default[26] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_19);
+
+ /* Initialize alternate 3G band set */
+ telit_3g_to_mm_band_mask_alternate[0] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1);
+ telit_3g_to_mm_band_mask_alternate[1] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2);
+ telit_3g_to_mm_band_mask_alternate[2] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[3] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[4] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[5] = B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[6] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[7] = B3G_FLAG (MM_MODEM_BAND_UTRAN_4);
+ telit_3g_to_mm_band_mask_alternate[8] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[9] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[10] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[11] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[12] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[13] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3);
+ telit_3g_to_mm_band_mask_alternate[14] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[15] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[16] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[17] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[18] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_9) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_19);
+ telit_3g_to_mm_band_mask_alternate[19] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_9) + B3G_FLAG (MM_MODEM_BAND_UTRAN_19);
+}
+
+/*****************************************************************************/
+/* AT#BND 4G values
+ *
+ * The Telit-specific value for 4G bands is a bitmask of the band values, given
+ * in hexadecimal or decimal format.
+ */
+
+#define MM_MODEM_BAND_TELIT_4G_FIRST MM_MODEM_BAND_EUTRAN_1
+#define MM_MODEM_BAND_TELIT_4G_LAST MM_MODEM_BAND_EUTRAN_44
+
+#define B4G_FLAG(band) (((guint64) 1) << (band - MM_MODEM_BAND_TELIT_4G_FIRST))
/*****************************************************************************/
/* Set current bands helpers */
-void
-mm_telit_get_band_flag (GArray *bands_array,
- gint *flag2g,
- gint *flag3g,
- gint *flag4g)
+gchar *
+mm_telit_build_bnd_request (GArray *bands_array,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ GError **error)
{
- guint mask2g = 0;
- guint mask3g = 0;
- guint mask4g = 0;
- guint found4g = FALSE;
- guint i;
+ guint32 mask2g = 0;
+ guint32 mask3g = 0;
+ guint64 mask4g = 0;
+ guint i;
+ gint flag2g = -1;
+ gint flag3g = -1;
+ gint flag4g = -1;
+ gchar *cmd;
+ const guint32 *telit_3g_to_mm_band_mask;
+ guint telit_3g_to_mm_band_mask_n_elements;
+
+ initialize_telit_3g_to_mm_band_masks ();
+
+ /* Select correct 3G band mask */
+ if (modem_alternate_3g_bands) {
+ telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_alternate;
+ telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_alternate);
+ } else {
+ telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_default;
+ telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_default);
+ }
for (i = 0; i < bands_array->len; i++) {
- MMModemBand band = g_array_index (bands_array, MMModemBand, i);
+ MMModemBand band;
- if (flag2g != NULL) {
- switch (band) {
- case MM_MODEM_BAND_EGSM:
- case MM_MODEM_BAND_DCS:
- case MM_MODEM_BAND_PCS:
- case MM_MODEM_BAND_G850:
- mask2g += 1 << band;
- break;
- default:
- break;
+ band = g_array_index (bands_array, MMModemBand, i);
+
+ /* Convert 2G bands into a bitmask, to match against telit_2g_to_mm_band_mask. */
+ if (flag2g && mm_common_band_is_gsm (band) &&
+ (band >= MM_MODEM_BAND_TELIT_2G_FIRST) && (band <= MM_MODEM_BAND_TELIT_2G_LAST))
+ mask2g += B2G_FLAG (band);
+
+ /* Convert 3G bands into a bitmask, to match against telit_3g_to_mm_band_mask. */
+ if (flag3g && mm_common_band_is_utran (band) &&
+ (B3G_NUM (band) >= B3G_NUM (MM_MODEM_BAND_TELIT_3G_FIRST)) && (B3G_NUM (band) <= B3G_NUM (MM_MODEM_BAND_TELIT_3G_LAST)))
+ mask3g += B3G_FLAG (band);
+
+ /* Convert 4G bands into a bitmask. We use a 64bit explicit bitmask so that
+ * all values fit correctly. */
+ if (flag4g && mm_common_band_is_eutran (band) &&
+ (band >= MM_MODEM_BAND_TELIT_4G_FIRST && band <= MM_MODEM_BAND_TELIT_4G_LAST))
+ mask4g += B4G_FLAG (band);
+ }
+
+ /* Get 2G-specific telit value */
+ if (mask2g) {
+ for (i = 0; i < G_N_ELEMENTS (telit_2g_to_mm_band_mask); i++) {
+ if (mask2g == telit_2g_to_mm_band_mask[i]) {
+ flag2g = i;
+ break;
}
}
+ if (flag2g == -1) {
+ gchar *bands_str;
- if (flag3g != NULL) {
- switch (band) {
- case MM_MODEM_BAND_UTRAN_1:
- case MM_MODEM_BAND_UTRAN_2:
- case MM_MODEM_BAND_UTRAN_3:
- case MM_MODEM_BAND_UTRAN_4:
- case MM_MODEM_BAND_UTRAN_5:
- case MM_MODEM_BAND_UTRAN_6:
- case MM_MODEM_BAND_UTRAN_7:
- case MM_MODEM_BAND_UTRAN_8:
- case MM_MODEM_BAND_UTRAN_9:
- mask3g += 1 << band;
- break;
- default:
- break;
+ bands_str = mm_common_build_bands_string ((const MMModemBand *)(bands_array->data), bands_array->len);
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't find matching 2G bands Telit value for band combination: '%s'", bands_str);
+ g_free (bands_str);
+ return NULL;
+ }
+ }
+
+ /* Get 3G-specific telit value */
+ if (mask3g) {
+ for (i = 0; i < telit_3g_to_mm_band_mask_n_elements; i++) {
+ if (mask3g == telit_3g_to_mm_band_mask[i]) {
+ flag3g = i;
+ break;
}
}
+ if (flag3g == -1) {
+ gchar *bands_str;
- if (flag4g != NULL &&
- band >= MM_MODEM_BAND_EUTRAN_1 && band <= MM_MODEM_BAND_EUTRAN_44) {
- mask4g += 1 << (band - MM_MODEM_BAND_EUTRAN_1);
- found4g = TRUE;
+ bands_str = mm_common_build_bands_string ((const MMModemBand *)(bands_array->data), bands_array->len);
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't find matching 3G bands Telit value for band combination: '%s'", bands_str);
+ g_free (bands_str);
+ return NULL;
}
}
- /* Get 2G flag */
- if (flag2g != NULL) {
- if (mask2g == ((1 << MM_MODEM_BAND_EGSM) + (1 << MM_MODEM_BAND_DCS)))
- *flag2g = 0;
- else if (mask2g == ((1 << MM_MODEM_BAND_EGSM) + (1 << MM_MODEM_BAND_PCS)))
- *flag2g = 1;
- else if (mask2g == ((1 << MM_MODEM_BAND_G850) + (1 << MM_MODEM_BAND_DCS)))
- *flag2g = 2;
- else if (mask2g == ((1 << MM_MODEM_BAND_G850) + (1 << MM_MODEM_BAND_PCS)))
- *flag2g = 3;
- else
- *flag2g = -1;
+ /* Get 4G-specific telit band bitmask */
+ flag4g = (mask4g != 0) ? mask4g : -1;
+
+ /* If the modem supports a given access tech, we must always give band settings
+ * for the specific tech */
+ if (modem_is_2g && flag2g == -1) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "None or invalid 2G bands combination in the provided list");
+ return NULL;
+ }
+ if (modem_is_3g && flag3g == -1) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "None or invalid 3G bands combination in the provided list");
+ return NULL;
+ }
+ if (modem_is_4g && flag4g == -1) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "None or invalid 4G bands combination in the provided list");
+ return NULL;
}
- /* Get 3G flag */
- if (flag3g != NULL) {
- if (mask3g == (1 << MM_MODEM_BAND_UTRAN_1))
- *flag3g = 0;
- else if (mask3g == (1 << MM_MODEM_BAND_UTRAN_2))
- *flag3g = 1;
- else if (mask3g == (1 << MM_MODEM_BAND_UTRAN_5))
- *flag3g = 2;
- else if (mask3g == ((1 << MM_MODEM_BAND_UTRAN_1) +
- (1 << MM_MODEM_BAND_UTRAN_2) +
- (1 << MM_MODEM_BAND_UTRAN_5)))
- *flag3g = 3;
- else if (mask3g == ((1 << MM_MODEM_BAND_UTRAN_2) +
- (1 << MM_MODEM_BAND_UTRAN_5)))
- *flag3g = 4;
- else if (mask3g == (1 << MM_MODEM_BAND_UTRAN_8))
- *flag3g = 5;
- else if (mask3g == ((1 << MM_MODEM_BAND_UTRAN_1) +
- (1 << MM_MODEM_BAND_UTRAN_8)))
- *flag3g = 6;
- else if (mask3g == (1 << MM_MODEM_BAND_UTRAN_4))
- *flag3g = 7;
- else
- *flag3g = -1;
- }
+ if (modem_is_2g && !modem_is_3g && !modem_is_4g)
+ cmd = g_strdup_printf ("#BND=%d", flag2g);
+ else if (!modem_is_2g && modem_is_3g && !modem_is_4g)
+ cmd = g_strdup_printf ("#BND=0,%d", flag3g);
+ else if (!modem_is_2g && !modem_is_3g && modem_is_4g)
+ cmd = g_strdup_printf ("#BND=0,0,%d", flag4g);
+ else if (modem_is_2g && modem_is_3g && !modem_is_4g)
+ cmd = g_strdup_printf ("#BND=%d,%d", flag2g, flag3g);
+ else if (!modem_is_2g && modem_is_3g && modem_is_4g)
+ cmd = g_strdup_printf ("#BND=0,%d,%d", flag3g, flag4g);
+ else if (modem_is_2g && !modem_is_3g && modem_is_4g)
+ cmd = g_strdup_printf ("#BND=%d,0,%d", flag2g, flag4g);
+ else if (modem_is_2g && modem_is_3g && modem_is_4g)
+ cmd = g_strdup_printf ("#BND=%d,%d,%d", flag2g, flag3g, flag4g);
+ else
+ g_assert_not_reached ();
- /* 4G flag correspond to the mask */
- if (flag4g != NULL) {
- if (found4g)
- *flag4g = mask4g;
- else
- *flag4g = -1;
- }
+ return cmd;
}
-#define SUPP_BAND_RESPONSE_REGEX "#BND:\\s*\\((?P<Bands2G>[0-9\\-,]*)\\)(,\\s*\\((?P<Bands3G>[0-9\\-,]*)\\))?(,\\s*\\((?P<Bands4G>[0-9\\-,]*)\\))?"
-#define CURR_BAND_RESPONSE_REGEX "#BND:\\s*(?P<Bands2G>\\d+)(,\\s*(?P<Bands3G>\\d+))?(,\\s*(?P<Bands4G>\\d+))?"
-
/*****************************************************************************/
/* #BND response parser
*
@@ -187,333 +371,255 @@
* 4 = 3G band flag 4 is U1900 + U850
*
*/
-gboolean
-mm_telit_parse_bnd_response (const gchar *response,
- gboolean modem_is_2g,
- gboolean modem_is_3g,
- gboolean modem_is_4g,
- MMTelitLoadBandsType band_type,
- GArray **supported_bands,
- GError **error)
-{
- GArray *bands = NULL;
- GMatchInfo *match_info = NULL;
- GRegex *r = NULL;
- gboolean ret = FALSE;
- switch (band_type) {
- case LOAD_SUPPORTED_BANDS:
- /* Parse #BND=? response */
- r = g_regex_new (SUPP_BAND_RESPONSE_REGEX, G_REGEX_RAW, 0, NULL);
- break;
- case LOAD_CURRENT_BANDS:
- /* Parse #BND? response */
- r = g_regex_new (CURR_BAND_RESPONSE_REGEX, G_REGEX_RAW, 0, NULL);
- default:
- break;
+static gboolean
+telit_get_2g_mm_bands (GMatchInfo *match_info,
+ GArray **bands,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GArray *values = NULL;
+ gchar *match_str = NULL;
+ guint i;
+
+ match_str = g_match_info_fetch_named (match_info, "Bands2G");
+ if (!match_str || match_str[0] == '\0') {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not find 2G band values from response");
+ goto out;
}
+ values = mm_parse_uint_list (match_str, &inner_error);
+ if (!values)
+ goto out;
+
+ for (i = 0; i < values->len; i++) {
+ guint value;
+
+ value = g_array_index (values, guint, i);
+ if (value < G_N_ELEMENTS (telit_2g_to_mm_band_mask)) {
+ guint j;
+
+ for (j = MM_MODEM_BAND_TELIT_2G_FIRST; j <= MM_MODEM_BAND_TELIT_2G_LAST; j++) {
+ if ((telit_2g_to_mm_band_mask[value] & B2G_FLAG (j)) && !mm_common_bands_garray_lookup (*bands, j))
+ *bands = g_array_append_val (*bands, j);
+ }
+ } else
+ mm_dbg ("unhandled telit 2G band value configuration: %u", value);
+ }
+
+out:
+ g_free (match_str);
+ g_clear_pointer (&values, g_array_unref);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+telit_get_3g_mm_bands (GMatchInfo *match_info,
+ GArray **bands,
+ gboolean modem_alternate_3g_bands,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GArray *values = NULL;
+ gchar *match_str = NULL;
+ guint i;
+ const guint32 *telit_3g_to_mm_band_mask;
+ guint telit_3g_to_mm_band_mask_n_elements;
+
+ initialize_telit_3g_to_mm_band_masks ();
+
+ /* Select correct 3G band mask */
+ if (modem_alternate_3g_bands) {
+ telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_alternate;
+ telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_alternate);
+ } else {
+ telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_default;
+ telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_default);
+ }
+
+ match_str = g_match_info_fetch_named (match_info, "Bands3G");
+ if (!match_str || match_str[0] == '\0') {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not find 3G band values from response");
+ goto out;
+ }
+
+ values = mm_parse_uint_list (match_str, &inner_error);
+ if (!values)
+ goto out;
+
+ for (i = 0; i < values->len; i++) {
+ guint value;
+
+ value = g_array_index (values, guint, i);
+
+ if (value < telit_3g_to_mm_band_mask_n_elements) {
+ guint j;
+
+ for (j = 0; j < G_N_ELEMENTS (band_utran_index); j++) {
+ /* ignore non-3G bands */
+ if (band_utran_index[j] == 0)
+ continue;
+
+ if ((telit_3g_to_mm_band_mask[value] & B3G_FLAG (j)) && !mm_common_bands_garray_lookup (*bands, j))
+ *bands = g_array_append_val (*bands, j);
+ }
+ } else
+ mm_dbg ("unhandled telit 3G band value configuration: %u", value);
+ }
+
+out:
+ g_free (match_str);
+ g_clear_pointer (&values, g_array_unref);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+telit_get_4g_mm_bands (GMatchInfo *match_info,
+ GArray **bands,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ MMModemBand band;
+ gchar *match_str = NULL;
+ guint64 value;
+ gchar **tokens = NULL;
+
+ match_str = g_match_info_fetch_named (match_info, "Bands4G");
+ if (!match_str || match_str[0] == '\0') {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not find 4G band flags from response");
+ goto out;
+ }
+
+ /* splitting will never return NULL as string is not empty */
+ tokens = g_strsplit (match_str, "-", -1);
+
+ /* If this is a range, get upper threshold, which contains the total supported mask */
+ if (!mm_get_u64_from_str (tokens[1] ? tokens[1] : tokens[0], &value)) {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not parse 4G band mask from string: '%s'", match_str);
+ goto out;
+ }
+
+ for (band = MM_MODEM_BAND_TELIT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_4G_LAST; band++) {
+ if ((value & B4G_FLAG (band)) && !mm_common_bands_garray_lookup (*bands, band))
+ g_array_append_val (*bands, band);
+ }
+
+out:
+ g_strfreev (tokens);
+ g_free (match_str);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+typedef enum {
+ LOAD_BANDS_TYPE_SUPPORTED,
+ LOAD_BANDS_TYPE_CURRENT,
+} LoadBandsType;
+
+static GArray *
+common_parse_bnd_response (const gchar *response,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ LoadBandsType load_type,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GArray *bands = NULL;
+ GMatchInfo *match_info = NULL;
+ GRegex *r;
+
+ static const gchar *load_bands_regex[] = {
+ [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:\\s*\\((?P<Bands2G>[0-9\\-,]*)\\)(,\\s*\\((?P<Bands3G>[0-9\\-,]*)\\))?(,\\s*\\((?P<Bands4G>[0-9\\-,]*)\\))?",
+ [LOAD_BANDS_TYPE_CURRENT] = "#BND:\\s*(?P<Bands2G>\\d+)(,\\s*(?P<Bands3G>\\d+))?(,\\s*(?P<Bands4G>\\d+))?",
+ };
+
+ r = g_regex_new (load_bands_regex[load_type], G_REGEX_RAW, 0, NULL);
+ g_assert (r);
if (!g_regex_match (r, response, 0, &match_info)) {
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse response '%s'", response);
- goto end;
+ goto out;
}
if (!g_match_info_matches (match_info)) {
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not find matches in response '%s'", response);
- goto end;
+ goto out;
}
bands = g_array_new (TRUE, TRUE, sizeof (MMModemBand));
- if (modem_is_2g && !mm_telit_get_2g_mm_bands (match_info, &bands, error))
- goto end;
+ if (modem_is_2g && !telit_get_2g_mm_bands (match_info, &bands, &inner_error))
+ goto out;
- if (modem_is_3g && !mm_telit_get_3g_mm_bands (match_info, &bands, error))
- goto end;
+ if (modem_is_3g && !telit_get_3g_mm_bands (match_info, &bands, modem_alternate_3g_bands, &inner_error))
+ goto out;
- if (modem_is_4g && !mm_telit_get_4g_mm_bands (match_info, &bands, error))
- goto end;
+ if (modem_is_4g && !telit_get_4g_mm_bands (match_info, &bands, &inner_error))
+ goto out;
- *supported_bands = bands;
- ret = TRUE;
-
-end:
- if (!ret && bands != NULL)
- g_array_free (bands, TRUE);
-
+out:
g_match_info_free (match_info);
g_regex_unref (r);
- return ret;
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ g_clear_pointer (&bands, g_array_unref);
+ return NULL;
+ }
+
+ return bands;
}
-gboolean
-mm_telit_get_2g_mm_bands (GMatchInfo *match_info,
- GArray **bands,
- GError **error)
+GArray *
+mm_telit_parse_bnd_query_response (const gchar *response,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ GError **error)
{
- GArray *flags = NULL;
- gchar *match_str = NULL;
- guint i;
- gboolean ret = TRUE;
-
- TelitToMMBandMap map [5] = {
- { BND_FLAG_GSM900_DCS1800, {MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_UNKNOWN} }, /* 0 */
- { BND_FLAG_GSM900_PCS1900, {MM_MODEM_BAND_EGSM, MM_MODEM_BAND_PCS, MM_MODEM_BAND_UNKNOWN} }, /* 1 */
- { BND_FLAG_GSM850_DCS1800, {MM_MODEM_BAND_DCS, MM_MODEM_BAND_G850, MM_MODEM_BAND_UNKNOWN} }, /* 2 */
- { BND_FLAG_GSM850_PCS1900, {MM_MODEM_BAND_PCS, MM_MODEM_BAND_G850, MM_MODEM_BAND_UNKNOWN} }, /* 3 */
- { BND_FLAG_UNKNOWN, {}},
- };
-
- match_str = g_match_info_fetch_named (match_info, "Bands2G");
-
- if (match_str == NULL || match_str[0] == '\0') {
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
- "Could not find 2G band flags from response");
- ret = FALSE;
- goto end;
- }
-
- flags = g_array_new (FALSE, FALSE, sizeof (guint));
-
- if (!mm_telit_get_band_flags_from_string (match_str, &flags, error)) {
- ret = FALSE;
- goto end;
- }
-
- for (i = 0; i < flags->len; i++) {
- guint flag;
-
- flag = g_array_index (flags, guint, i);
- if (!mm_telit_update_band_array (flag, map, bands, error)) {
- ret = FALSE;
- goto end;
- }
- }
-
-end:
- g_free (match_str);
-
- if (flags != NULL)
- g_array_free (flags, TRUE);
-
- return ret;
+ return common_parse_bnd_response (response,
+ modem_is_2g, modem_is_3g, modem_is_4g,
+ modem_alternate_3g_bands,
+ LOAD_BANDS_TYPE_CURRENT,
+ error);
}
-gboolean
-mm_telit_get_3g_mm_bands (GMatchInfo *match_info,
- GArray **bands,
- GError **error)
+GArray *
+mm_telit_parse_bnd_test_response (const gchar *response,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ GError **error)
{
- GArray *flags = NULL;
- gchar *match_str = NULL;
- guint i;
- gboolean ret = TRUE;
-
- TelitToMMBandMap map [] = {
- { BND_FLAG_0, { MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UNKNOWN} },
- { BND_FLAG_1, { MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UNKNOWN} },
- { BND_FLAG_2, { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UNKNOWN} },
- { BND_FLAG_3, { MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UNKNOWN} },
- { BND_FLAG_4, { MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UNKNOWN} },
- { BND_FLAG_5, { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UNKNOWN} },
- { BND_FLAG_6, { MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UNKNOWN} },
- { BND_FLAG_7, { MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UNKNOWN} },
- { BND_FLAG_8, { MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_9, { MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_10, { MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_12, { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UNKNOWN}},
- { BND_FLAG_13, { MM_MODEM_BAND_UTRAN_3, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_14, { MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_15, { MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_3, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_16, { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_17, { MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_18, { MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UNKNOWN}},
- { BND_FLAG_19, { MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UNKNOWN }},
- { BND_FLAG_20, { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UNKNOWN}},
- { BND_FLAG_21, { MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UNKNOWN}},
- { BND_FLAG_UNKNOWN, {}},
- };
-
- match_str = g_match_info_fetch_named (match_info, "Bands3G");
-
- if (match_str == NULL || match_str[0] == '\0') {
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
- "Could not find 3G band flags from response");
- ret = FALSE;
- goto end;
- }
-
- flags = g_array_new (FALSE, FALSE, sizeof (guint));
-
- if (!mm_telit_get_band_flags_from_string (match_str, &flags, error)) {
- ret = FALSE;
- goto end;
- }
-
- for (i = 0; i < flags->len; i++) {
- guint flag;
-
- flag = g_array_index (flags, guint, i);
- if (!mm_telit_update_band_array (flag, map, bands, error)) {
- ret = FALSE;
- goto end;
- }
- }
-
-end:
- g_free (match_str);
-
- if (flags != NULL)
- g_array_free (flags, TRUE);
-
- return ret;
-}
-
-gboolean
-mm_telit_get_4g_mm_bands (GMatchInfo *match_info,
- GArray **bands,
- GError **error)
-{
- MMModemBand band;
- gboolean ret = TRUE;
- gchar *match_str = NULL;
- guint i;
- guint value;
- gchar **tokens;
-
- match_str = g_match_info_fetch_named (match_info, "Bands4G");
-
- if (match_str == NULL || match_str[0] == '\0') {
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
- "Could not find 4G band flags from response");
- ret = FALSE;
- goto end;
- }
-
- if (strstr (match_str, "-")) {
- tokens = g_strsplit (match_str, "-", -1);
- if (tokens == NULL) {
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
- "Could not get 4G band ranges from string '%s'",
- match_str);
- ret = FALSE;
- goto end;
- }
- sscanf (tokens[1], "%d", &value);
- g_strfreev (tokens);
- } else {
- sscanf (match_str, "%d", &value);
- }
-
- for (i = 0; value > 0; i++) {
- if (value % 2 != 0) {
- band = MM_MODEM_BAND_EUTRAN_1 + i;
- g_array_append_val (*bands, band);
- }
- value = value >> 1;
- }
-
-end:
- g_free (match_str);
-
- return ret;
-}
-
-gboolean
-mm_telit_bands_contains (GArray *mm_bands, const MMModemBand mm_band)
-{
- guint i;
-
- for (i = 0; i < mm_bands->len; i++) {
- if (mm_band == g_array_index (mm_bands, MMModemBand, i))
- return TRUE;
- }
-
- return FALSE;
-}
-
-gboolean
-mm_telit_update_band_array (const gint bands_flag,
- const TelitToMMBandMap *map,
- GArray **bands,
- GError **error)
-{
- guint i;
- guint j;
-
- for (i = 0; map[i].flag != BND_FLAG_UNKNOWN; i++) {
- if (bands_flag == map[i].flag) {
- for (j = 0; map[i].mm_bands[j] != MM_MODEM_BAND_UNKNOWN; j++) {
- if (!mm_telit_bands_contains (*bands, map[i].mm_bands[j])) {
- g_array_append_val (*bands, map[i].mm_bands[j]);
- }
- }
-
- return TRUE;
- }
- }
-
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
- "No MM band found for Telit #BND flag '%d'",
- bands_flag);
-
- return FALSE;
-}
-
-
-gboolean
-mm_telit_get_band_flags_from_string (const gchar *flag_str,
- GArray **band_flags,
- GError **error)
-{
- gchar **range;
- gchar **tokens;
- guint flag;
- guint i;
-
- if (flag_str == NULL || flag_str[0] == '\0') {
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
- "String is empty, no band flags to parse");
- return FALSE;
- }
-
- tokens = g_strsplit (flag_str, ",", -1);
- if (!tokens) {
- g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
- "Could not get the list of flags");
- return FALSE;
- }
-
- for (i = 0; tokens[i]; i++) {
- /* check whether tokens[i] defines a
- * single band value or a range of bands */
- if (!strstr (tokens[i], "-")) {
- sscanf (tokens[i], "%d", &flag);
- g_array_append_val (*band_flags, flag);
- } else {
- gint range_start;
- gint range_end;
-
- range = g_strsplit (tokens[i], "-", 2);
-
- sscanf (range[0], "%d", &range_start);
- sscanf (range[1], "%d", &range_end);
-
- for (flag = range_start; flag <= range_end; flag++) {
- g_array_append_val (*band_flags, flag);
- }
-
- g_strfreev (range);
- }
- }
-
- g_strfreev (tokens);
-
- return TRUE;
+ return common_parse_bnd_response (response,
+ modem_is_2g, modem_is_3g, modem_is_4g,
+ modem_alternate_3g_bands,
+ LOAD_BANDS_TYPE_SUPPORTED,
+ error);
}
/*****************************************************************************/
diff --git a/plugins/telit/mm-modem-helpers-telit.h b/plugins/telit/mm-modem-helpers-telit.h
index 0b82c42..491e8fa 100644
--- a/plugins/telit/mm-modem-helpers-telit.h
+++ b/plugins/telit/mm-modem-helpers-telit.h
@@ -10,8 +10,8 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
- * Copyright (C) 2015 Telit.
- *
+ * Copyright (C) 2015-2019 Telit.
+ * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
*/
#ifndef MM_MODEM_HELPERS_TELIT_H
#define MM_MODEM_HELPERS_TELIT_H
@@ -19,81 +19,25 @@
#include <glib.h>
#include "ModemManager.h"
-#define MAX_BANDS_LIST_LEN 20
-
-#define BND_FLAG_UNKNOWN -1
-
-/* AT#BND 2G flags */
-typedef enum {
- BND_FLAG_GSM900_DCS1800,
- BND_FLAG_GSM900_PCS1900,
- BND_FLAG_GSM850_DCS1800,
- BND_FLAG_GSM850_PCS1900,
-} BndFlag2G;
-
-/* AT#BND 3G flags */
-typedef enum {
- BND_FLAG_0, /* B1 (2100 MHz) */
- BND_FLAG_1, /* B2 (1900 MHz) */
- BND_FLAG_2, /* B5 (850 MHz) */
- BND_FLAG_3, /* B1 (2100 MHz) + B2 (1900 MHz) + B5 (850 MHz) */
- BND_FLAG_4, /* B2 (1900 MHz) + B5 (850 MHz) */
- BND_FLAG_5, /* B8 (900 MHz) */
- BND_FLAG_6, /* B1 (2100 MHz) + B8 (900 MHz) */
- BND_FLAG_7, /* B4 (1700 MHz) */
- BND_FLAG_8, /* B1 (2100 MHz) + B5 (850 MHz) */
- BND_FLAG_9, /* B1 (2100 MHz) + B8 (900 MHz) + B5 (850 MHz) */
- BND_FLAG_10, /* B2 (1900 MHz) + B4 (1700 MHz) + B5 (850 MHz) */
- BND_FLAG_12, /* B6 (800 MHz) */
- BND_FLAG_13, /* B3 (1800 MHz) */
- BND_FLAG_14, /* B1 (2100 MHz) + B2 (1900 MHz) + B4 (1700 MHz) + B5 (850 MHz) + B6 (800MHz) */
- BND_FLAG_15, /* B1 (2100 MHz) + B8 (900 MHz) + B3 (1800 MHz) */
- BND_FLAG_16, /* B8 (900 MHz) + B5 (850 MHz) */
- BND_FLAG_17, /* B2 (1900 MHz) + B4 (1700 MHz) + B5 (850 MHz) + B6 (800 MHz) */
- BND_FLAG_18, /* B1 (2100 MHz) + B2 (1900 MHz) + B5 (850 MHz) + B6 (800 MHz) */
- BND_FLAG_19, /* B2 (1900 MHz) + B6 (800 MHz) */
- BND_FLAG_20, /* B5 (850 MHz) + B6 (800 MHz) */
- BND_FLAG_21, /* B2 (1900 MHz) + B5 (850 MHz) + B6 (800 MHz) */
-} BndFlag3G;
-
-typedef struct {
- gint flag;
- MMModemBand mm_bands[MAX_BANDS_LIST_LEN];
-} TelitToMMBandMap;
-
-typedef enum {
- LOAD_SUPPORTED_BANDS,
- LOAD_CURRENT_BANDS
-} MMTelitLoadBandsType;
-
-/* #BND response parser */
-gboolean
-mm_telit_parse_bnd_response (const gchar *response,
- gboolean modem_is_2g,
- gboolean modem_is_3g,
- gboolean modem_is_4g,
- MMTelitLoadBandsType band_type,
- GArray **supported_bands,
- GError **error);
-
-
-gboolean mm_telit_bands_contains (GArray *mm_bands, const MMModemBand mm_band);
-
-gboolean mm_telit_update_band_array (const gint bands_flag,
- const TelitToMMBandMap *map,
- GArray **bands,
- GError **error);
-
-gboolean mm_telit_get_band_flags_from_string (const gchar *flag_str, GArray **band_flags, GError **error);
-gboolean mm_telit_get_2g_mm_bands(GMatchInfo *match_info, GArray **bands, GError **error);
-gboolean mm_telit_get_3g_mm_bands(GMatchInfo *match_info, GArray **bands, GError **error);
-gboolean mm_telit_get_4g_mm_bands(GMatchInfo *match_info, GArray **bands, GError **error);
-
-gboolean mm_telit_update_2g_bands(gchar *band_list, GMatchInfo **match_info, GArray **bands, GError **error);
-gboolean mm_telit_update_3g_bands(gchar *band_list, GMatchInfo **match_info, GArray **bands, GError **error);
-gboolean mm_telit_update_4g_bands(GArray** bands, GMatchInfo *match_info, GError **error);
-
-void mm_telit_get_band_flag (GArray *bands_array, gint *flag_2g, gint *flag_3g, gint *flag_4g);
+/* #BND response parsers and request builder */
+GArray *mm_telit_parse_bnd_query_response (const gchar *response,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ GError **error);
+GArray *mm_telit_parse_bnd_test_response (const gchar *response,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ GError **error);
+gchar *mm_telit_build_bnd_request (GArray *bands_array,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ GError **error);
/* #QSS? response parser */
typedef enum { /*< underscore_name=mm_telit_qss_status >*/
diff --git a/plugins/telit/mm-shared-telit.c b/plugins/telit/mm-shared-telit.c
index c79f7ff..f1eb051 100644
--- a/plugins/telit/mm-shared-telit.c
+++ b/plugins/telit/mm-shared-telit.c
@@ -32,6 +32,59 @@
#include "mm-shared-telit.h"
/*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "shared-telit-private-tag"
+static GQuark private_quark;
+
+typedef struct {
+ gboolean alternate_3g_bands;
+ GArray *supported_bands;
+} Private;
+
+static void
+private_free (Private *priv)
+{
+ if (priv->supported_bands)
+ g_array_unref (priv->supported_bands);
+ g_slice_free (Private, priv);
+}
+
+static void
+initialize_alternate_3g_band (MMSharedTelit *self,
+ Private *priv)
+{
+ MMPort *primary;
+ MMKernelDevice *port;
+
+ primary = MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)));
+ port = mm_port_peek_kernel_device (primary);
+
+ /* Lookup for the tag specifying that we're using the alternate 3G band mapping */
+ priv->alternate_3g_bands = mm_kernel_device_get_global_property_as_boolean (port, "ID_MM_TELIT_BND_ALTERNATE");
+ if (priv->alternate_3g_bands)
+ mm_dbg ("Telit modem using alternate 3G band mask setup");
+}
+
+static Private *
+get_private (MMSharedTelit *self)
+{
+ Private *priv;
+
+ if (G_UNLIKELY (!private_quark))
+ private_quark = g_quark_from_static_string (PRIVATE_TAG);
+
+ priv = g_object_get_qdata (G_OBJECT (self), private_quark);
+ if (!priv) {
+ priv = g_slice_new0 (Private);
+ initialize_alternate_3g_band (self, priv);
+ g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+ }
+
+ return priv;
+}
+
+/*****************************************************************************/
/* Load current mode (Modem interface) */
gboolean
@@ -114,126 +167,133 @@
/*****************************************************************************/
/* Load supported bands (Modem interface) */
-typedef struct {
- gboolean mm_modem_is_2g;
- gboolean mm_modem_is_3g;
- gboolean mm_modem_is_4g;
- MMTelitLoadBandsType band_type;
-} LoadBandsContext;
-
-static void
-mm_shared_telit_load_bands_context_free (LoadBandsContext *ctx)
+GArray *
+mm_shared_telit_modem_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
{
- g_slice_free (LoadBandsContext, ctx);
+ return g_task_propagate_pointer (G_TASK (res), error);
}
static void
-mm_shared_telit_load_bands_ready (MMBaseModem *self,
- GAsyncResult *res,
- GTask *task)
+mm_shared_telit_load_supported_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
{
const gchar *response;
- GError *error = NULL;
- GArray *bands = NULL;
- LoadBandsContext *ctx;
+ GError *error = NULL;
+ Private *priv;
- ctx = g_task_get_task_data (task);
+ priv = get_private (MM_SHARED_TELIT (self));
+
response = mm_base_modem_at_command_finish (self, res, &error);
-
if (!response)
g_task_return_error (task, error);
- else if (!mm_telit_parse_bnd_response (response,
- ctx->mm_modem_is_2g,
- ctx->mm_modem_is_3g,
- ctx->mm_modem_is_4g,
- ctx->band_type,
- &bands,
- &error))
- g_task_return_error (task, error);
- else
- g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ else {
+ GArray *bands;
+
+ bands = mm_telit_parse_bnd_test_response (response,
+ mm_iface_modem_is_2g (MM_IFACE_MODEM (self)),
+ mm_iface_modem_is_3g (MM_IFACE_MODEM (self)),
+ mm_iface_modem_is_4g (MM_IFACE_MODEM (self)),
+ priv->alternate_3g_bands,
+ &error);
+ if (!bands)
+ g_task_return_error (task, error);
+ else {
+ /* Store supported bands to be able to build ANY when setting */
+ priv->supported_bands = g_array_ref (bands);
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ }
+ }
g_object_unref (task);
}
void
-mm_shared_telit_modem_load_supported_bands (MMIfaceModem *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
+mm_shared_telit_modem_load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
- GTask *task;
- LoadBandsContext *ctx;
-
- ctx = g_slice_new0 (LoadBandsContext);
-
- ctx->mm_modem_is_2g = mm_iface_modem_is_2g (self);
- ctx->mm_modem_is_3g = mm_iface_modem_is_3g (self);
- ctx->mm_modem_is_4g = mm_iface_modem_is_4g (self);
- ctx->band_type = LOAD_SUPPORTED_BANDS;
-
- task = g_task_new (self, NULL, callback, user_data);
- g_task_set_task_data (task, ctx, (GDestroyNotify)mm_shared_telit_load_bands_context_free);
-
mm_base_modem_at_command (MM_BASE_MODEM (self),
"#BND=?",
3,
- FALSE,
- (GAsyncReadyCallback) mm_shared_telit_load_bands_ready,
- task);
+ TRUE,
+ (GAsyncReadyCallback) mm_shared_telit_load_supported_bands_ready,
+ g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Load current bands (Modem interface) */
GArray *
-mm_shared_telit_modem_load_bands_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error)
+mm_shared_telit_modem_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
{
- return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+mm_shared_telit_load_current_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response)
+ g_task_return_error (task, error);
+ else {
+ GArray *bands;
+
+ bands = mm_telit_parse_bnd_query_response (response,
+ mm_iface_modem_is_2g (MM_IFACE_MODEM (self)),
+ mm_iface_modem_is_3g (MM_IFACE_MODEM (self)),
+ mm_iface_modem_is_4g (MM_IFACE_MODEM (self)),
+ priv->alternate_3g_bands,
+ &error);
+ if (!bands)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ }
+
+ g_object_unref (task);
}
void
-mm_shared_telit_modem_load_current_bands (MMIfaceModem *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
+mm_shared_telit_modem_load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
- GTask *task;
- LoadBandsContext *ctx;
-
- ctx = g_slice_new0 (LoadBandsContext);
-
- ctx->mm_modem_is_2g = mm_iface_modem_is_2g (self);
- ctx->mm_modem_is_3g = mm_iface_modem_is_3g (self);
- ctx->mm_modem_is_4g = mm_iface_modem_is_4g (self);
- ctx->band_type = LOAD_CURRENT_BANDS;
-
- task = g_task_new (self, NULL, callback, user_data);
- g_task_set_task_data (task, ctx, (GDestroyNotify)mm_shared_telit_load_bands_context_free);
-
mm_base_modem_at_command (MM_BASE_MODEM (self),
"#BND?",
3,
FALSE,
- (GAsyncReadyCallback) mm_shared_telit_load_bands_ready,
- task);
+ (GAsyncReadyCallback) mm_shared_telit_load_current_bands_ready,
+ g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Set current bands (Modem interface) */
gboolean
-mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error)
+mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
-mm_shared_telit_modem_set_current_bands_ready (MMBaseModem *self,
- GAsyncResult *res,
- GTask *task)
+set_current_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
{
GError *error = NULL;
@@ -247,88 +307,47 @@
}
void
-mm_shared_telit_modem_set_current_bands (MMIfaceModem *self,
- GArray *bands_array,
- GAsyncReadyCallback callback,
- gpointer user_data)
+mm_shared_telit_modem_set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
- gchar *cmd;
- gint flag2g;
- gint flag3g;
- gint flag4g;
- gboolean is_2g;
- gboolean is_3g;
- gboolean is_4g;
- GTask *task;
+ GTask *task;
+ GError *error = NULL;
+ gchar *cmd;
+ Private *priv;
- mm_telit_get_band_flag (bands_array, &flag2g, &flag3g, &flag4g);
+ priv = get_private (MM_SHARED_TELIT (self));
- is_2g = mm_iface_modem_is_2g (self);
- is_3g = mm_iface_modem_is_3g (self);
- is_4g = mm_iface_modem_is_4g (self);
-
- if (is_2g && flag2g == -1) {
- g_task_report_new_error (self,
- callback,
- user_data,
- mm_shared_telit_modem_set_current_bands,
- MM_CORE_ERROR,
- MM_CORE_ERROR_NOT_FOUND,
- "None or invalid 2G bands combination in the provided list");
- return;
- }
-
- if (is_3g && flag3g == -1) {
- g_task_report_new_error (self,
- callback,
- user_data,
- mm_shared_telit_modem_set_current_bands,
- MM_CORE_ERROR,
- MM_CORE_ERROR_NOT_FOUND,
- "None or invalid 3G bands combination in the provided list");
- return;
- }
-
- if (is_4g && flag4g == -1) {
- g_task_report_new_error (self,
- callback,
- user_data,
- mm_shared_telit_modem_set_current_bands,
- MM_CORE_ERROR,
- MM_CORE_ERROR_NOT_FOUND,
- "None or invalid 4G bands combination in the provided list");
- return;
- }
-
- cmd = NULL;
- if (is_2g && !is_3g && !is_4g)
- cmd = g_strdup_printf ("AT#BND=%d", flag2g);
- else if (is_2g && is_3g && !is_4g)
- cmd = g_strdup_printf ("AT#BND=%d,%d", flag2g, flag3g);
- else if (is_2g && is_3g && is_4g)
- cmd = g_strdup_printf ("AT#BND=%d,%d,%d", flag2g, flag3g, flag4g);
- else if (!is_2g && !is_3g && is_4g)
- cmd = g_strdup_printf ("AT#BND=0,0,%d", flag4g);
- else if (!is_2g && is_3g && is_4g)
- cmd = g_strdup_printf ("AT#BND=0,%d,%d", flag3g, flag4g);
- else if (is_2g && !is_3g && is_4g)
- cmd = g_strdup_printf ("AT#BND=%d,0,%d", flag2g, flag4g);
- else {
- g_task_report_new_error (self,
- callback,
- user_data,
- mm_shared_telit_modem_set_current_bands,
- MM_CORE_ERROR,
- MM_CORE_ERROR_FAILED,
- "Unexpected error: could not compose AT#BND command");
- return;
- }
task = g_task_new (self, NULL, callback, user_data);
+
+ if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ if (!priv->supported_bands) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't build ANY band settings: unknown supported bands");
+ g_object_unref (task);
+ return;
+ }
+ bands_array = priv->supported_bands;
+ }
+
+ cmd = mm_telit_build_bnd_request (bands_array,
+ mm_iface_modem_is_2g (self),
+ mm_iface_modem_is_3g (self),
+ mm_iface_modem_is_4g (self),
+ priv->alternate_3g_bands,
+ &error);
+ if (!cmd) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
mm_base_modem_at_command (MM_BASE_MODEM (self),
cmd,
20,
FALSE,
- (GAsyncReadyCallback)mm_shared_telit_modem_set_current_bands_ready,
+ (GAsyncReadyCallback)set_current_bands_ready,
task);
g_free (cmd);
}
diff --git a/plugins/telit/mm-shared-telit.h b/plugins/telit/mm-shared-telit.h
index 2eb6d51..4cfaeb5 100644
--- a/plugins/telit/mm-shared-telit.h
+++ b/plugins/telit/mm-shared-telit.h
@@ -66,14 +66,18 @@
GAsyncReadyCallback callback,
gpointer user_data);
-GArray * mm_shared_telit_modem_load_bands_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error);
+GArray * mm_shared_telit_modem_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
void mm_shared_telit_modem_load_current_bands (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data);
+GArray * mm_shared_telit_modem_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
gboolean mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error);
diff --git a/plugins/telit/tests/test-mm-modem-helpers-telit.c b/plugins/telit/tests/test-mm-modem-helpers-telit.c
index 6103a2c..f95a97d 100644
--- a/plugins/telit/tests/test-mm-modem-helpers-telit.c
+++ b/plugins/telit/tests/test-mm-modem-helpers-telit.c
@@ -10,7 +10,8 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
- * Copyright (C) 2015 Telit
+ * Copyright (C) 2015-2019 Telit
+ * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
*
*/
#include <stdio.h>
@@ -22,429 +23,489 @@
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
+#include "mm-log.h"
#include "mm-modem-helpers.h"
#include "mm-modem-helpers-telit.h"
-static void
-test_mm_bands_contains (void) {
- GArray* mm_bands;
- guint i = 1;
+#include "test-helpers.h"
- mm_bands = g_array_sized_new (FALSE, TRUE, sizeof (MMModemBand), 3);
+/******************************************************************************/
- for (i = 0; i < 3; i++)
- g_array_append_val (mm_bands, i);
-
- g_assert (mm_telit_bands_contains (mm_bands, 2));
- g_assert (mm_telit_bands_contains (mm_bands, 2));
- g_assert (!mm_telit_bands_contains (mm_bands, 3));
-
- g_array_free (mm_bands, TRUE);
-}
+#define MAX_BANDS_LIST_LEN 17
typedef struct {
- gchar* band_flag_str;
- guint band_flags_len;
- guint band_flags [MAX_BANDS_LIST_LEN];
-} BNDFlagsTest;
+ gchar *response;
+ gboolean modem_is_2g;
+ gboolean modem_is_3g;
+ gboolean modem_is_4g;
+ gboolean modem_alternate_3g_bands;
+ guint mm_bands_len;
+ MMModemBand mm_bands [MAX_BANDS_LIST_LEN];
+} BndResponseTest;
-static BNDFlagsTest band_flag_test[] = {
- {"0-3", 4, {0, 1, 2, 3} },
- {"0,3", 2, {0, 3} },
- {"0,2-3,5-7,9", 7, {0, 2, 3, 5, 6, 7, 9} },
- { NULL, 0, {}},
+static BndResponseTest supported_band_mapping_tests [] = {
+ {
+ "#BND: (0-3)", TRUE, FALSE, FALSE, FALSE, 4,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850 }
+ },
+ {
+ "#BND: (0-3),(0,2,5,6)", TRUE, TRUE, FALSE, FALSE, 7,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: (0,3),(0,2,5,6)", TRUE, TRUE, FALSE, FALSE, 7,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: (0,2),(0,2,5,6)", TRUE, TRUE, FALSE, FALSE, 6,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: (0,2),(0-4,5,6)", TRUE, TRUE, FALSE, FALSE, 7,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: (0-3),(0,2,5,6),(1-1)", TRUE, TRUE, TRUE, FALSE, 8,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_EUTRAN_1 }
+ },
+ {
+ "#BND: (0),(0),(1-3)", TRUE, TRUE, TRUE, FALSE, 5,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2 }
+ },
+ {
+ "#BND: (0),(0),(1-3)", FALSE, FALSE, TRUE, FALSE, 2,
+ { MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2 }
+ },
+ /* 3G alternate band settings: default */
+ {
+ "#BND: (0),(0,2,5,6,12,25)", FALSE, TRUE, FALSE, FALSE, 5,
+ { MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_6,
+ MM_MODEM_BAND_UTRAN_19 }
+ },
+ /* 3G alternate band settings: alternate */
+ {
+ "#BND: (0),(0,2,5,6,12,13)", FALSE, TRUE, FALSE, TRUE, 4,
+ { MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_3,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ /* ME910 (2G+4G device)
+ * 168695967: 0xA0E189F: 0000 1010 0000 1110 0001 1000 1001 1111
+ */
+ {
+ "#BND: (0-5),(0),(1-168695967)", TRUE, FALSE, TRUE, FALSE, 17,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2,
+ MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4,
+ MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12,
+ MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_18,
+ MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20,
+ MM_MODEM_BAND_EUTRAN_26,
+ MM_MODEM_BAND_EUTRAN_28 }
+ }
};
static void
-test_parse_band_flag_str (void) {
- GError *error = NULL;
- gboolean res = FALSE;
- GArray *band_flags = NULL;
- guint i, j;
+test_parse_supported_bands_response (void)
+{
+ guint i;
- for (i = 0; band_flag_test[i].band_flag_str != NULL; i++) {
- band_flags = g_array_new (FALSE, FALSE, sizeof (guint));
- res = mm_telit_get_band_flags_from_string (band_flag_test[i].band_flag_str,
- &band_flags,
+ for (i = 0; i < G_N_ELEMENTS (supported_band_mapping_tests); i++) {
+ GError *error = NULL;
+ GArray *bands = NULL;
+
+ bands = mm_telit_parse_bnd_test_response (supported_band_mapping_tests[i].response,
+ supported_band_mapping_tests[i].modem_is_2g,
+ supported_band_mapping_tests[i].modem_is_3g,
+ supported_band_mapping_tests[i].modem_is_4g,
+ supported_band_mapping_tests[i].modem_alternate_3g_bands,
+ &error);
+ g_assert_no_error (error);
+ g_assert (bands);
+
+ mm_test_helpers_compare_bands (bands,
+ supported_band_mapping_tests[i].mm_bands,
+ supported_band_mapping_tests[i].mm_bands_len);
+ g_array_unref (bands);
+ }
+}
+
+static BndResponseTest current_band_mapping_tests [] = {
+ {
+ "#BND: 0", TRUE, FALSE, FALSE, FALSE, 2,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS }
+ },
+ {
+ "#BND: 0,5", TRUE, TRUE, FALSE, FALSE, 3,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: 1,3", TRUE, TRUE, FALSE, FALSE, 5,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_5 }
+ },
+ {
+ "#BND: 2,7", TRUE, TRUE, FALSE, FALSE, 3,
+ { MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_4 }
+ },
+ {
+ "#BND: 3,0,1", TRUE, TRUE, TRUE, FALSE, 4,
+ { MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_EUTRAN_1 }
+ },
+ {
+ "#BND: 0,0,3", TRUE, FALSE, TRUE, FALSE, 4,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2 }
+ },
+ {
+ "#BND: 0,0,3", FALSE, FALSE, TRUE, FALSE, 2,
+ { MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2 }
+ },
+ /* 3G alternate band settings: default */
+ {
+ "#BND: 0,12", FALSE, TRUE, FALSE, FALSE, 1,
+ { MM_MODEM_BAND_UTRAN_6 }
+ },
+ /* 3G alternate band settings: alternate */
+ {
+ "#BND: 0,12", FALSE, TRUE, FALSE, TRUE, 4,
+ { MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_3,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ /* ME910 (2G+4G device)
+ * 168695967: 0xA0E189F: 0000 1010 0000 1110 0001 1000 1001 1111
+ */
+ {
+ "#BND: 5,0,168695967", TRUE, FALSE, TRUE, FALSE, 17,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2,
+ MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4,
+ MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12,
+ MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_18,
+ MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20,
+ MM_MODEM_BAND_EUTRAN_26,
+ MM_MODEM_BAND_EUTRAN_28 }
+ }
+};
+
+static void
+test_parse_current_bands_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (current_band_mapping_tests); i++) {
+ GError *error = NULL;
+ GArray *bands = NULL;
+
+ bands = mm_telit_parse_bnd_query_response (current_band_mapping_tests[i].response,
+ current_band_mapping_tests[i].modem_is_2g,
+ current_band_mapping_tests[i].modem_is_3g,
+ current_band_mapping_tests[i].modem_is_4g,
+ current_band_mapping_tests[i].modem_alternate_3g_bands,
&error);
g_assert_no_error (error);
- g_assert (res);
+ g_assert (bands);
- for (j = 0; j < band_flag_test[i].band_flags_len; j++) {
- guint ref;
- guint cur;
-
- ref = band_flag_test[i].band_flags[j];
- cur = g_array_index (band_flags, guint, j);
-
- g_assert (ref == cur);
- }
-
- g_array_free (band_flags, TRUE);
+ mm_test_helpers_compare_bands (bands,
+ current_band_mapping_tests[i].mm_bands,
+ current_band_mapping_tests[i].mm_bands_len);
+ g_array_unref (bands);
}
}
-typedef struct {
- gchar* response;
- gboolean modem_is_2g;
- gboolean modem_is_3g;
- gboolean modem_is_4g;
- guint mm_bands_len;
- MMModemBand mm_bands [MAX_BANDS_LIST_LEN];
-} BNDResponseTest;
-
-static BNDResponseTest supported_band_mapping_tests [] = {
- { "#BND: (0-3)", TRUE, FALSE, FALSE, 4, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_PCS,
- MM_MODEM_BAND_G850} },
- { "#BND: (0-3),(0,2,5,6)", TRUE, TRUE, FALSE, 7, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_PCS,
- MM_MODEM_BAND_G850,
- MM_MODEM_BAND_UTRAN_1,
- MM_MODEM_BAND_UTRAN_5,
- MM_MODEM_BAND_UTRAN_8 } },
- { "#BND: (0,3),(0,2,5,6)", TRUE, TRUE, FALSE, 7, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_PCS,
- MM_MODEM_BAND_G850,
- MM_MODEM_BAND_UTRAN_1,
- MM_MODEM_BAND_UTRAN_5,
- MM_MODEM_BAND_UTRAN_8} },
- { "#BND: (0,2),(0,2,5,6)", TRUE, TRUE, FALSE, 6, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_G850,
- MM_MODEM_BAND_UTRAN_1,
- MM_MODEM_BAND_UTRAN_5,
- MM_MODEM_BAND_UTRAN_8} },
- { "#BND: (0,2),(0-4,5,6)", TRUE, TRUE, FALSE, 7, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_G850,
- MM_MODEM_BAND_UTRAN_1,
- MM_MODEM_BAND_UTRAN_2,
- MM_MODEM_BAND_UTRAN_5,
- MM_MODEM_BAND_UTRAN_8} },
- { "#BND: (0-3),(0,2,5,6),(1-1)", TRUE, TRUE, TRUE, 8, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_PCS,
- MM_MODEM_BAND_G850,
- MM_MODEM_BAND_UTRAN_1,
- MM_MODEM_BAND_UTRAN_5,
- MM_MODEM_BAND_UTRAN_8,
- MM_MODEM_BAND_EUTRAN_1} },
- { "#BND: (0),(0),(1-3)", TRUE, TRUE, TRUE, 5, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_UTRAN_1,
- MM_MODEM_BAND_EUTRAN_1,
- MM_MODEM_BAND_EUTRAN_2} },
- { "#BND: (0),(0),(1-3)", FALSE, FALSE, TRUE, 2, { MM_MODEM_BAND_EUTRAN_1,
- MM_MODEM_BAND_EUTRAN_2} },
- { NULL, FALSE, FALSE, FALSE, 0, {}},
-};
+/******************************************************************************/
static void
-test_parse_supported_bands_response (void) {
- GError* error = NULL;
- gboolean res = FALSE;
- guint i, j;
- GArray* bands = NULL;
+test_common_bnd_cmd (const gchar *expected_cmd,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ GArray *bands_array)
+{
+ gchar *cmd;
+ GError *error = NULL;
- for (i = 0; supported_band_mapping_tests[i].response != NULL; i++) {
- res = mm_telit_parse_bnd_response (supported_band_mapping_tests[i].response,
- supported_band_mapping_tests[i].modem_is_2g,
- supported_band_mapping_tests[i].modem_is_3g,
- supported_band_mapping_tests[i].modem_is_4g,
- LOAD_SUPPORTED_BANDS,
- &bands,
- &error);
- g_assert_no_error (error);
- g_assert (res);
-
-
- for (j = 0; j < supported_band_mapping_tests[i].mm_bands_len; j++) {
- MMModemBand ref;
- MMModemBand cur;
-
- ref = supported_band_mapping_tests[i].mm_bands[j];
- cur = g_array_index (bands, MMModemBand, j);
- g_assert_cmpint (cur, ==, ref);
- }
-
- g_assert_cmpint (bands->len, ==, supported_band_mapping_tests[i].mm_bands_len);
-
- g_array_free (bands, TRUE);
- bands = NULL;
- }
+ cmd = mm_telit_build_bnd_request (bands_array,
+ modem_is_2g, modem_is_3g, modem_is_4g,
+ modem_alternate_3g_bands,
+ &error);
+ g_assert_no_error (error);
+ g_assert_cmpstr (cmd, ==, expected_cmd);
+ g_free (cmd);
}
-
-static BNDResponseTest current_band_mapping_tests [] = {
- { "#BND: 0", TRUE, FALSE, FALSE, 2, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS
- }
- },
- { "#BND: 0,5", TRUE, TRUE, FALSE, 3, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_UTRAN_8
- }
- },
- { "#BND: 1,3", TRUE, TRUE, FALSE, 5, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_PCS,
- MM_MODEM_BAND_UTRAN_1,
- MM_MODEM_BAND_UTRAN_2,
- MM_MODEM_BAND_UTRAN_5,
- }
- },
- { "#BND: 2,7", TRUE, TRUE, FALSE, 3, { MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_G850,
- MM_MODEM_BAND_UTRAN_4
- }
- },
- { "#BND: 3,0,1", TRUE, TRUE, TRUE, 4, { MM_MODEM_BAND_PCS,
- MM_MODEM_BAND_G850,
- MM_MODEM_BAND_UTRAN_1,
- MM_MODEM_BAND_EUTRAN_1
- }
- },
- { "#BND: 0,0,3", TRUE, FALSE, TRUE, 4, { MM_MODEM_BAND_EGSM,
- MM_MODEM_BAND_DCS,
- MM_MODEM_BAND_EUTRAN_1,
- MM_MODEM_BAND_EUTRAN_2
- }
- },
- { "#BND: 0,0,3", FALSE, FALSE, TRUE, 2, { MM_MODEM_BAND_EUTRAN_1,
- MM_MODEM_BAND_EUTRAN_2
- }
- },
- { NULL, FALSE, FALSE, FALSE, 0, {}},
-};
+#define test_common_bnd_cmd_2g(EXPECTED_CMD, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, TRUE, FALSE, FALSE, FALSE, BANDS_ARRAY)
+#define test_common_bnd_cmd_3g(EXPECTED_CMD, ALTERNATE, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, FALSE, TRUE, FALSE, ALTERNATE, BANDS_ARRAY)
+#define test_common_bnd_cmd_4g(EXPECTED_CMD, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, FALSE, FALSE, TRUE, FALSE, BANDS_ARRAY)
static void
-test_parse_current_bands_response (void) {
- GError* error = NULL;
- gboolean res = FALSE;
- guint i, j;
- GArray* bands = NULL;
+test_common_bnd_cmd_invalid (gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ GArray *bands_array)
+{
+ gchar *cmd;
+ GError *error = NULL;
- for (i = 0; current_band_mapping_tests[i].response != NULL; i++) {
- res = mm_telit_parse_bnd_response (current_band_mapping_tests[i].response,
- current_band_mapping_tests[i].modem_is_2g,
- current_band_mapping_tests[i].modem_is_3g,
- current_band_mapping_tests[i].modem_is_4g,
- LOAD_CURRENT_BANDS,
- &bands,
- &error);
- g_assert_no_error (error);
- g_assert (res);
-
-
- for (j = 0; j < current_band_mapping_tests[i].mm_bands_len; j++) {
- MMModemBand ref;
- MMModemBand cur;
-
- ref = current_band_mapping_tests[i].mm_bands[j];
- cur = g_array_index (bands, MMModemBand, j);
- g_assert_cmpint (cur, ==, ref);
- }
-
- g_assert_cmpint (bands->len, ==, current_band_mapping_tests[i].mm_bands_len);
-
- g_array_free (bands, TRUE);
- bands = NULL;
- }
+ cmd = mm_telit_build_bnd_request (bands_array,
+ modem_is_2g, modem_is_3g, modem_is_4g,
+ FALSE,
+ &error);
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
+ g_assert (!cmd);
}
+#define test_common_bnd_cmd_2g_invalid(BANDS_ARRAY) test_common_bnd_cmd_invalid (TRUE, FALSE, FALSE, BANDS_ARRAY)
+#define test_common_bnd_cmd_3g_invalid(BANDS_ARRAY) test_common_bnd_cmd_invalid (FALSE, TRUE, FALSE, BANDS_ARRAY)
+#define test_common_bnd_cmd_4g_invalid(BANDS_ARRAY) test_common_bnd_cmd_invalid (FALSE, FALSE, TRUE, BANDS_ARRAY)
+
static void
test_telit_get_2g_bnd_flag (void)
{
- GArray *bands_array;
- gint flag2g;
- MMModemBand egsm = MM_MODEM_BAND_EGSM;
- MMModemBand dcs = MM_MODEM_BAND_DCS;
- MMModemBand pcs = MM_MODEM_BAND_PCS;
- MMModemBand g850 = MM_MODEM_BAND_G850;
+ GArray *bands_array;
+ MMModemBand egsm = MM_MODEM_BAND_EGSM;
+ MMModemBand dcs = MM_MODEM_BAND_DCS;
+ MMModemBand pcs = MM_MODEM_BAND_PCS;
+ MMModemBand g850 = MM_MODEM_BAND_G850;
/* Test Flag 0 */
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
g_array_append_val (bands_array, egsm);
g_array_append_val (bands_array, dcs);
-
- mm_telit_get_band_flag (bands_array, &flag2g, NULL, NULL);
- g_assert_cmpuint (flag2g, ==, 0);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_2g ("#BND=0", bands_array);
+ g_array_unref (bands_array);
/* Test flag 1 */
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
g_array_append_val (bands_array, egsm);
g_array_append_val (bands_array, pcs);
-
- mm_telit_get_band_flag (bands_array, &flag2g, NULL, NULL);
- g_assert_cmpuint (flag2g, ==, 1);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_2g ("#BND=1", bands_array);
+ g_array_unref (bands_array);
/* Test flag 2 */
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
g_array_append_val (bands_array, g850);
g_array_append_val (bands_array, dcs);
-
- mm_telit_get_band_flag (bands_array, &flag2g, NULL, NULL);
- g_assert_cmpuint (flag2g, ==, 2);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_2g ("#BND=2", bands_array);
+ g_array_unref (bands_array);
/* Test flag 3 */
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
g_array_append_val (bands_array, g850);
g_array_append_val (bands_array, pcs);
-
- mm_telit_get_band_flag (bands_array, &flag2g, NULL, NULL);
- g_assert_cmpuint (flag2g, ==, 3);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_2g ("#BND=3", bands_array);
+ g_array_unref (bands_array);
/* Test invalid band array */
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
g_array_append_val (bands_array, g850);
g_array_append_val (bands_array, egsm);
-
- mm_telit_get_band_flag (bands_array, &flag2g, NULL, NULL);
- g_assert_cmpuint (flag2g, ==, -1);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_2g_invalid (bands_array);
+ g_array_unref (bands_array);
}
-
static void
test_telit_get_3g_bnd_flag (void)
{
- GArray *bands_array;
- MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1;
- MMModemBand u1900 = MM_MODEM_BAND_UTRAN_2;
- MMModemBand u850 = MM_MODEM_BAND_UTRAN_5;
- MMModemBand u900 = MM_MODEM_BAND_UTRAN_8;
- MMModemBand u17iv = MM_MODEM_BAND_UTRAN_4;
- MMModemBand u17ix = MM_MODEM_BAND_UTRAN_9;
- gint flag;
+ GArray *bands_array;
+ MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1;
+ MMModemBand u1900 = MM_MODEM_BAND_UTRAN_2;
+ MMModemBand u1800 = MM_MODEM_BAND_UTRAN_3;
+ MMModemBand u850 = MM_MODEM_BAND_UTRAN_5;
+ MMModemBand u800 = MM_MODEM_BAND_UTRAN_6;
+ MMModemBand u900 = MM_MODEM_BAND_UTRAN_8;
+ MMModemBand u17iv = MM_MODEM_BAND_UTRAN_4;
+ MMModemBand u17ix = MM_MODEM_BAND_UTRAN_9;
/* Test flag 0 */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
g_array_append_val (bands_array, u2100);
-
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, 0);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_3g ("#BND=0,0", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,0", TRUE, bands_array);
+ g_array_unref (bands_array);
/* Test flag 1 */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
g_array_append_val (bands_array, u1900);
-
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, 1);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_3g ("#BND=0,1", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,1", TRUE, bands_array);
+ g_array_unref (bands_array);
/* Test flag 2 */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
g_array_append_val (bands_array, u850);
-
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, 2);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_3g ("#BND=0,2", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,2", TRUE, bands_array);
+ g_array_unref (bands_array);
/* Test flag 3 */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 3);
g_array_append_val (bands_array, u2100);
g_array_append_val (bands_array, u1900);
g_array_append_val (bands_array, u850);
-
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, 3);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_3g ("#BND=0,3", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,3", TRUE, bands_array);
+ g_array_unref (bands_array);
/* Test flag 4 */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
g_array_append_val (bands_array, u1900);
g_array_append_val (bands_array, u850);
-
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, 4);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_3g ("#BND=0,4", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,4", TRUE, bands_array);
+ g_array_unref (bands_array);
/* Test flag 5 */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
g_array_append_val (bands_array, u900);
-
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, 5);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_3g ("#BND=0,5", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,5", TRUE, bands_array);
+ g_array_unref (bands_array);
/* Test flag 6 */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
g_array_append_val (bands_array, u2100);
g_array_append_val (bands_array, u900);
-
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, 6);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_3g ("#BND=0,6", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,6", TRUE, bands_array);
+ g_array_unref (bands_array);
/* Test flag 7 */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
g_array_append_val (bands_array, u17iv);
+ test_common_bnd_cmd_3g ("#BND=0,7", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,7", TRUE, bands_array);
+ g_array_unref (bands_array);
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, 7);
- g_array_free (bands_array, TRUE);
+ /* Test flag 12 in default */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, u800);
+ test_common_bnd_cmd_3g ("#BND=0,12", FALSE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 12 in alternate */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4);
+ g_array_append_val (bands_array, u2100);
+ g_array_append_val (bands_array, u1800);
+ g_array_append_val (bands_array, u850);
+ g_array_append_val (bands_array, u900);
+ test_common_bnd_cmd_3g ("#BND=0,12", TRUE, bands_array);
+ g_array_unref (bands_array);
/* Test invalid band array */
- flag = -1;
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
g_array_append_val (bands_array, u17ix);
-
- mm_telit_get_band_flag (bands_array, NULL, &flag, NULL);
- g_assert_cmpint (flag, ==, -1);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_3g_invalid (bands_array);
+ g_array_unref (bands_array);
}
static void
test_telit_get_4g_bnd_flag (void)
{
- GArray *bands_array;
- MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1;
- MMModemBand eutran_ii = MM_MODEM_BAND_EUTRAN_2;
- MMModemBand egsm = MM_MODEM_BAND_EGSM;
- gint flag = -1;
+ GArray *bands_array;
+ MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1;
+ MMModemBand eutran_ii = MM_MODEM_BAND_EUTRAN_2;
+ MMModemBand egsm = MM_MODEM_BAND_EGSM;
/* Test flag 1 */
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
g_array_append_val (bands_array, eutran_i);
-
- mm_telit_get_band_flag (bands_array, NULL, NULL, &flag);
- g_assert_cmpint (flag, ==, 1);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_4g ("#BND=0,0,1", bands_array);
+ g_array_unref (bands_array);
/* Test flag 3 */
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
g_array_append_val (bands_array, eutran_i);
g_array_append_val (bands_array, eutran_ii);
-
- mm_telit_get_band_flag (bands_array, NULL, NULL, &flag);
- g_assert_cmpint (flag, ==, 3);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_4g ("#BND=0,0,3", bands_array);
+ g_array_unref (bands_array);
/* Test invalid bands array */
bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
g_array_append_val (bands_array, egsm);
-
- mm_telit_get_band_flag (bands_array, NULL, NULL, &flag);
- g_assert_cmpint (flag, ==, -1);
- g_array_free (bands_array, TRUE);
+ test_common_bnd_cmd_4g_invalid (bands_array);
+ g_array_unref (bands_array);
}
+/******************************************************************************/
+
typedef struct {
const char* response;
MMTelitQssStatus expected_qss;
@@ -483,14 +544,34 @@
}
}
+/******************************************************************************/
+
+void
+_mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...)
+{
+#if defined ENABLE_TEST_MESSAGE_TRACES
+ /* Dummy log function */
+ va_list args;
+ gchar *msg;
+
+ va_start (args, fmt);
+ msg = g_strdup_vprintf (fmt, args);
+ va_end (args);
+ g_print ("%s\n", msg);
+ g_free (msg);
+#endif
+}
+
int main (int argc, char **argv)
{
setlocale (LC_ALL, "");
g_test_init (&argc, &argv, NULL);
- g_test_add_func ("/MM/telit/bands/supported/bands_contains", test_mm_bands_contains);
- g_test_add_func ("/MM/telit/bands/supported/parse_band_flag", test_parse_band_flag_str);
g_test_add_func ("/MM/telit/bands/supported/parse_bands_response", test_parse_supported_bands_response);
g_test_add_func ("/MM/telit/bands/current/parse_bands_response", test_parse_current_bands_response);
g_test_add_func ("/MM/telit/bands/current/set_bands/2g", test_telit_get_2g_bnd_flag);
diff --git a/plugins/tests/test-helpers.c b/plugins/tests/test-helpers.c
new file mode 100644
index 0000000..5620c04
--- /dev/null
+++ b/plugins/tests/test-helpers.c
@@ -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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+
+#include "test-helpers.h"
+
+void
+mm_test_helpers_compare_bands (GArray *bands,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands)
+{
+ gchar *bands_str;
+ GArray *expected_bands_array;
+ gchar *expected_bands_str;
+
+ if (!expected_bands || !n_expected_bands) {
+ g_assert (!bands);
+ return;
+ }
+
+ g_assert (bands);
+ mm_common_bands_garray_sort (bands);
+ bands_str = mm_common_build_bands_string ((MMModemBand *)(bands->data), bands->len);
+
+ expected_bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_expected_bands);
+ g_array_append_vals (expected_bands_array, expected_bands, n_expected_bands);
+ mm_common_bands_garray_sort (expected_bands_array);
+ expected_bands_str = mm_common_build_bands_string ((MMModemBand *)(expected_bands_array->data), expected_bands_array->len);
+ g_array_unref (expected_bands_array);
+
+ g_assert_cmpstr (bands_str, ==, expected_bands_str);
+ g_free (bands_str);
+ g_free (expected_bands_str);
+}
diff --git a/plugins/tests/test-helpers.h b/plugins/tests/test-helpers.h
new file mode 100644
index 0000000..8362754
--- /dev/null
+++ b/plugins/tests/test-helpers.h
@@ -0,0 +1,26 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef TEST_HELPERS_H
+#define TEST_HELPERS_H
+
+#include <glib.h>
+#include <libmm-glib.h>
+
+void mm_test_helpers_compare_bands (GArray *bands,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands);
+
+#endif /* TEST_HELPERS_H */
diff --git a/plugins/thuraya/mm-modem-helpers-thuraya.c b/plugins/thuraya/mm-modem-helpers-thuraya.c
index b940e63..1dad59f 100644
--- a/plugins/thuraya/mm-modem-helpers-thuraya.c
+++ b/plugins/thuraya/mm-modem-helpers-thuraya.c
@@ -99,7 +99,7 @@
array = g_array_new (FALSE, FALSE, sizeof (MMSmsStorage));
/* Got a range group to match */
- if (g_regex_match_full (r, splita[i], strlen (splita[i]), 0, 0, &match_info, NULL)) {
+ if (g_regex_match (r, splita[i], 0, &match_info)) {
while (g_match_info_matches (match_info)) {
gchar *str;
diff --git a/plugins/tplink/77-mm-tplink-port-types.rules b/plugins/tplink/77-mm-tplink-port-types.rules
new file mode 100644
index 0000000..34c9638
--- /dev/null
+++ b/plugins/tplink/77-mm-tplink-port-types.rules
@@ -0,0 +1,15 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_tplink_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2357", GOTO="mm_tplink_port_types"
+GOTO="mm_tplink_port_types_end"
+
+LABEL="mm_tplink_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# D-Link DWM-222
+ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+LABEL="mm_tplink_port_types_end"
diff --git a/plugins/tplink/mm-plugin-tplink.c b/plugins/tplink/mm-plugin-tplink.c
new file mode 100644
index 0000000..20cf9db
--- /dev/null
+++ b/plugins/tplink/mm-plugin-tplink.c
@@ -0,0 +1,94 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-port-enums-types.h"
+#include "mm-log.h"
+#include "mm-plugin-tplink.h"
+#include "mm-broadband-modem.h"
+
+#if defined WITH_QMI
+# include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginTplink, mm_plugin_tplink, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ GList *probes,
+ GError **error)
+{
+#if defined WITH_QMI
+ if (mm_port_probe_list_has_qmi_port (probes)) {
+ mm_dbg ("QMI-powered TP-Link modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usb", NULL };
+ static const guint16 vendor_ids[] = { 0x2357, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_TPLINK,
+ MM_PLUGIN_NAME, "TP-Link",
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_tplink_init (MMPluginTplink *self)
+{
+}
+
+static void
+mm_plugin_tplink_class_init (MMPluginTplinkClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/plugins/tplink/mm-plugin-tplink.h b/plugins/tplink/mm-plugin-tplink.h
new file mode 100644
index 0000000..16dc5f5
--- /dev/null
+++ b/plugins/tplink/mm-plugin-tplink.h
@@ -0,0 +1,40 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_TPLINK_H
+#define MM_PLUGIN_TPLINK_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_TPLINK (mm_plugin_tplink_get_type ())
+#define MM_PLUGIN_TPLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_TPLINK, MMPluginTplink))
+#define MM_PLUGIN_TPLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_TPLINK, MMPluginTplinkClass))
+#define MM_IS_PLUGIN_TPLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_TPLINK))
+#define MM_IS_PLUGIN_TPLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_TPLINK))
+#define MM_PLUGIN_TPLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_TPLINK, MMPluginTplinkClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginTplink;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginTplinkClass;
+
+GType mm_plugin_tplink_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_TPLINK_H */
diff --git a/plugins/ublox/mm-broadband-modem-ublox.c b/plugins/ublox/mm-broadband-modem-ublox.c
index fe1c319..509c4c6 100644
--- a/plugins/ublox/mm-broadband-modem-ublox.c
+++ b/plugins/ublox/mm-broadband-modem-ublox.c
@@ -10,7 +10,7 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
- * Copyright (C) 2016-2018 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2016-2019 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
@@ -33,7 +33,6 @@
#include "mm-sim-ublox.h"
#include "mm-modem-helpers-ublox.h"
#include "mm-ublox-enums-types.h"
-#include "mm-call-ublox.h"
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
@@ -67,6 +66,9 @@
/* Operator ID for manual registration */
gchar *operator_id;
+ /* Voice +UCALLSTAT support */
+ GRegex *ucallstat_regex;
+
/* Regex to ignore */
GRegex *pbready_regex;
};
@@ -1005,6 +1007,23 @@
}
static void
+parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
own_voice_disable_unsolicited_events_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
@@ -1012,11 +1031,17 @@
GError *error = NULL;
mm_base_modem_at_command_full_finish (self, res, &error);
- if (error)
+ if (error) {
g_task_return_error (task, error);
- else
- g_task_return_boolean (task, TRUE);
- g_object_unref (task);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Chain up parent's disable */
+ iface_modem_voice_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
+ task);
}
static void
@@ -1028,8 +1053,6 @@
task = g_task_new (self, NULL, callback, user_data);
- /* Note: no parent disable method */
-
mm_base_modem_at_command_full (
MM_BASE_MODEM (self),
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
@@ -1043,6 +1066,177 @@
}
/*****************************************************************************/
+/* Common setup/cleanup voice unsolicited events */
+
+static void
+ucallstat_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemUblox *self)
+{
+ static const MMCallState ublox_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) */
+ [6] = MM_CALL_STATE_TERMINATED,
+ [7] = MM_CALL_STATE_ACTIVE, /* Treated same way as ACTIVE */
+ };
+
+ MMCallInfo call_info = { 0 };
+ guint aux;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_warn ("couldn't parse call index from +UCALLSTAT");
+ return;
+ }
+ call_info.index = aux;
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &aux) ||
+ (aux >= G_N_ELEMENTS (ublox_call_state))) {
+ mm_warn ("couldn't parse call state from +UCALLSTAT");
+ return;
+ }
+ call_info.state = ublox_call_state[aux];
+
+ /* guess direction for some of the states */
+ switch (call_info.state) {
+ case MM_CALL_STATE_DIALING:
+ case MM_CALL_STATE_RINGING_OUT:
+ call_info.direction = MM_CALL_DIRECTION_OUTGOING;
+ break;
+ case MM_CALL_STATE_RINGING_IN:
+ case MM_CALL_STATE_WAITING:
+ call_info.direction = MM_CALL_DIRECTION_INCOMING;
+ break;
+ default:
+ call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
+ break;
+ }
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemUblox *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ if (G_UNLIKELY (!self->priv->ucallstat_regex))
+ self->priv->ucallstat_regex = g_regex_new ("\\r\\n\\+UCALLSTAT:\\s*(\\d+),(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ 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],
+ self->priv->ucallstat_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)ucallstat_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Voice interface) */
+
+static gboolean
+modem_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;
+
+ if (!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);
+}
+
+static void
+modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* our own cleanup first */
+ common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), FALSE);
+
+ /* Chain up parent's cleanup */
+ iface_modem_voice_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Voice interface) */
+
+static gboolean
+modem_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;
+
+ if (!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_BROADBAND_MODEM_UBLOX (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* chain up parent's setup first */
+ iface_modem_voice_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
/* Create call (Voice interface) */
static MMBaseCall *
@@ -1050,8 +1244,12 @@
MMCallDirection direction,
const gchar *number)
{
- /* New u-blox Call */
- return mm_call_ublox_new (MM_BASE_MODEM (self), direction, number);
+ return mm_base_call_new (MM_BASE_MODEM (self),
+ direction,
+ number,
+ TRUE, /* skip_incoming_timeout */
+ TRUE, /* supports_dialing_to_ringing */
+ TRUE); /* supports_ringing_to_active */
}
/*****************************************************************************/
@@ -1445,6 +1643,10 @@
{
iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+ iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish;
iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
@@ -1460,6 +1662,8 @@
g_regex_unref (self->priv->pbready_regex);
+ if (self->priv->ucallstat_regex)
+ g_regex_unref (self->priv->ucallstat_regex);
g_free (self->priv->operator_id);
G_OBJECT_CLASS (mm_broadband_modem_ublox_parent_class)->finalize (object);
diff --git a/plugins/ublox/mm-call-ublox.c b/plugins/ublox/mm-call-ublox.c
deleted file mode 100644
index f9011fa..0000000
--- a/plugins/ublox/mm-call-ublox.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
- */
-
-#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-log.h"
-#include "mm-base-modem-at.h"
-#include "mm-broadband-modem-ublox.h"
-#include "mm-call-ublox.h"
-
-G_DEFINE_TYPE (MMCallUblox, mm_call_ublox, MM_TYPE_BASE_CALL)
-
-struct _MMCallUbloxPrivate {
- GRegex *ucallstat_regex;
-};
-
-/*****************************************************************************/
-/* In-call unsolicited events */
-
-static void
-ublox_ucallstat_received (MMPortSerialAt *port,
- GMatchInfo *match_info,
- MMCallUblox *self)
-{
- static const gchar *call_stat_names[] = {
- [0] = "active",
- [1] = "hold",
- [2] = "dialling (MO)",
- [3] = "alerting (MO)",
- [4] = "ringing (MT)",
- [5] = "waiting (MT)",
- [6] = "disconnected",
- [7] = "connected",
- };
- guint id;
- guint stat;
-
- if (!mm_get_uint_from_match_info (match_info, 1, &id))
- return;
-
- if (!mm_get_uint_from_match_info (match_info, 2, &stat))
- return;
-
- if (stat < G_N_ELEMENTS (call_stat_names))
- mm_dbg ("u-blox call id '%u' state: '%s'", id, call_stat_names[stat]);
- else
- mm_dbg ("u-blox call id '%u' state unknown: '%u'", id, stat);
-
- switch (stat) {
- case 0:
- /* ringing to active */
- if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_RINGING_OUT)
- mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
- break;
- case 1:
- /* ignore hold state, we don't support this yet. */
- break;
- case 2:
- /* ignore dialing state, we already handle this in the parent */
- break;
- case 3:
- /* dialing to ringing */
- if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_DIALING)
- mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_RINGING_OUT, MM_CALL_STATE_REASON_OUTGOING_STARTED);
- break;
- case 4:
- /* ignore MT ringing state, we already handle this in the parent */
- break;
- case 5:
- /* ignore MT waiting state */
- break;
- case 6:
- mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED);
- break;
- case 7:
- /* ringing to active */
- if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_RINGING_OUT)
- mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
- break;
- }
-}
-
-static gboolean
-common_setup_cleanup_unsolicited_events (MMCallUblox *self,
- gboolean enable,
- GError **error)
-{
- MMBaseModem *modem = NULL;
- MMPortSerialAt *port;
-
- if (G_UNLIKELY (!self->priv->ucallstat_regex))
- self->priv->ucallstat_regex = g_regex_new ("\\r\\n\\+UCALLSTAT:\\s*(\\d+),(\\d+)\\r\\n",
- G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
-
- g_object_get (self,
- MM_BASE_CALL_MODEM, &modem,
- NULL);
-
- port = mm_base_modem_peek_port_primary (MM_BASE_MODEM (modem));
- if (port) {
- mm_dbg ("%s +UCALLSTAT URC handler", enable ? "adding" : "removing");
- mm_port_serial_at_add_unsolicited_msg_handler (
- port,
- self->priv->ucallstat_regex,
- enable ? (MMPortSerialAtUnsolicitedMsgFn)ublox_ucallstat_received : NULL,
- enable ? self : NULL,
- NULL);
- }
-
- g_object_unref (modem);
- return TRUE;
-}
-
-static gboolean
-setup_unsolicited_events (MMBaseCall *self,
- GError **error)
-{
- return common_setup_cleanup_unsolicited_events (MM_CALL_UBLOX (self), TRUE, error);
-}
-
-static gboolean
-cleanup_unsolicited_events (MMBaseCall *self,
- GError **error)
-{
- return common_setup_cleanup_unsolicited_events (MM_CALL_UBLOX (self), FALSE, error);
-}
-
-/*****************************************************************************/
-
-MMBaseCall *
-mm_call_ublox_new (MMBaseModem *modem,
- MMCallDirection direction,
- const gchar *number)
-{
- return MM_BASE_CALL (g_object_new (MM_TYPE_CALL_UBLOX,
- MM_BASE_CALL_MODEM, modem,
- "direction", direction,
- "number", number,
- MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE,
- MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE,
- NULL));
-}
-
-static void
-mm_call_ublox_init (MMCallUblox *self)
-{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_UBLOX, MMCallUbloxPrivate);
-}
-
-static void
-finalize (GObject *object)
-{
- MMCallUblox *self = MM_CALL_UBLOX (object);
-
- if (self->priv->ucallstat_regex)
- g_regex_unref (self->priv->ucallstat_regex);
-
- G_OBJECT_CLASS (mm_call_ublox_parent_class)->finalize (object);
-}
-
-static void
-mm_call_ublox_class_init (MMCallUbloxClass *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 (MMCallUbloxPrivate));
-
- object_class->finalize = finalize;
-
- base_call_class->setup_unsolicited_events = setup_unsolicited_events;
- base_call_class->cleanup_unsolicited_events = cleanup_unsolicited_events;
-}
diff --git a/plugins/ublox/mm-call-ublox.h b/plugins/ublox/mm-call-ublox.h
deleted file mode 100644
index abacb50..0000000
--- a/plugins/ublox/mm-call-ublox.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
- */
-
-#ifndef MM_CALL_UBLOX_H
-#define MM_CALL_UBLOX_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-#include "mm-base-call.h"
-
-#define MM_TYPE_CALL_UBLOX (mm_call_ublox_get_type ())
-#define MM_CALL_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CALL_UBLOX, MMCallUblox))
-#define MM_CALL_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_CALL_UBLOX, MMCallUbloxClass))
-#define MM_IS_CALL_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CALL_UBLOX))
-#define MM_IS_CALL_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CALL_UBLOX))
-#define MM_CALL_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CALL_UBLOX, MMCallUbloxClass))
-
-typedef struct _MMCallUblox MMCallUblox;
-typedef struct _MMCallUbloxClass MMCallUbloxClass;
-typedef struct _MMCallUbloxPrivate MMCallUbloxPrivate;
-
-struct _MMCallUblox {
- MMBaseCall parent;
- MMCallUbloxPrivate *priv;
-};
-
-struct _MMCallUbloxClass {
- MMBaseCallClass parent;
-};
-
-GType mm_call_ublox_get_type (void);
-
-MMBaseCall *mm_call_ublox_new (MMBaseModem *modem,
- MMCallDirection direction,
- const gchar *number);
-
-#endif /* MM_CALL_UBLOX_H */
diff --git a/plugins/ublox/mm-modem-helpers-ublox.c b/plugins/ublox/mm-modem-helpers-ublox.c
index 8608960..df6b6a2 100644
--- a/plugins/ublox/mm-modem-helpers-ublox.c
+++ b/plugins/ublox/mm-modem-helpers-ublox.c
@@ -1578,6 +1578,10 @@
g_array_unref (nums);
}
+ if (!bands)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No known band selection values matched in +UACT response: '%s'", response);
+
return bands;
}
diff --git a/plugins/ublox/tests/test-modem-helpers-ublox.c b/plugins/ublox/tests/test-modem-helpers-ublox.c
index e0a7d9a..5fad8a7 100644
--- a/plugins/ublox/tests/test-modem-helpers-ublox.c
+++ b/plugins/ublox/tests/test-modem-helpers-ublox.c
@@ -26,6 +26,8 @@
#include "mm-modem-helpers.h"
#include "mm-modem-helpers-ublox.h"
+#include "test-helpers.h"
+
/*****************************************************************************/
/* Test +UPINCNT responses */
@@ -488,36 +490,6 @@
/* Test +UBANDSEL? response parser */
static void
-common_compare_bands (GArray *bands,
- const MMModemBand *expected_bands,
- guint n_expected_bands)
-{
- gchar *bands_str;
- GArray *expected_bands_array;
- gchar *expected_bands_str;
-
- if (!expected_bands || !n_expected_bands) {
- g_assert (!bands);
- return;
- }
-
- g_assert (bands);
- mm_common_bands_garray_sort (bands);
- bands_str = mm_common_build_bands_string ((MMModemBand *)(bands->data), bands->len);
- g_array_unref (bands);
-
- expected_bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_expected_bands);
- g_array_append_vals (expected_bands_array, expected_bands, n_expected_bands);
- mm_common_bands_garray_sort (expected_bands_array);
- expected_bands_str = mm_common_build_bands_string ((MMModemBand *)(expected_bands_array->data), expected_bands_array->len);
- g_array_unref (expected_bands_array);
-
- g_assert_cmpstr (bands_str, ==, expected_bands_str);
- g_free (bands_str);
- g_free (expected_bands_str);
-}
-
-static void
common_validate_ubandsel_response (const gchar *str,
const MMModemBand *expected_bands,
const gchar *model,
@@ -530,7 +502,8 @@
g_assert_no_error (error);
g_assert (bands);
- common_compare_bands (bands, expected_bands, n_expected_bands);
+ mm_test_helpers_compare_bands (bands, expected_bands, n_expected_bands);
+ g_array_unref (bands);
}
static void
@@ -651,14 +624,23 @@
GArray *bands;
bands = mm_ublox_parse_uact_response (str, &error);
- g_assert_no_error (error);
- common_compare_bands (bands, expected_bands, n_expected_bands);
+ if (n_expected_bands > 0) {
+ g_assert (bands);
+ g_assert_no_error (error);
+ mm_test_helpers_compare_bands (bands, expected_bands, n_expected_bands);
+ g_array_unref (bands);
+ } else {
+ g_assert (!bands);
+ g_assert (error);
+ g_error_free (error);
+ }
}
static void
test_uact_response_empty_list (void)
{
+ common_validate_uact_response ("", NULL, 0);
common_validate_uact_response ("+UACT: ,,,\r\n", NULL, 0);
}
@@ -723,9 +705,15 @@
g_assert_no_error (error);
g_assert (result);
- common_compare_bands (bands_2g, expected_bands_2g, n_expected_bands_2g);
- common_compare_bands (bands_3g, expected_bands_3g, n_expected_bands_3g);
- common_compare_bands (bands_4g, expected_bands_4g, n_expected_bands_4g);
+ mm_test_helpers_compare_bands (bands_2g, expected_bands_2g, n_expected_bands_2g);
+ if (bands_2g)
+ g_array_unref (bands_2g);
+ mm_test_helpers_compare_bands (bands_3g, expected_bands_3g, n_expected_bands_3g);
+ if (bands_3g)
+ g_array_unref (bands_3g);
+ mm_test_helpers_compare_bands (bands_4g, expected_bands_4g, n_expected_bands_4g);
+ if (bands_4g)
+ g_array_unref (bands_4g);
}
static void
diff --git a/plugins/wavecom/mm-broadband-modem-wavecom.c b/plugins/wavecom/mm-broadband-modem-wavecom.c
index 51e5045..47980d8 100644
--- a/plugins/wavecom/mm-broadband-modem-wavecom.c
+++ b/plugins/wavecom/mm-broadband-modem-wavecom.c
@@ -261,7 +261,7 @@
r = g_regex_new ("\\r\\n\\+WWSM: ([0-2])(,([0-2]))?.*$", 0, 0, NULL);
g_assert (r != NULL);
- if (g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) {
+ if (g_regex_match (r, response, 0, &match_info)) {
guint allowed = 0;
if (mm_get_uint_from_match_info (match_info, 1, &allowed)) {
diff --git a/plugins/xmm/mm-shared-xmm.c b/plugins/xmm/mm-shared-xmm.c
index cbc7c7b..d23a657 100644
--- a/plugins/xmm/mm-shared-xmm.c
+++ b/plugins/xmm/mm-shared-xmm.c
@@ -40,7 +40,8 @@
typedef enum {
GPS_ENGINE_STATE_OFF,
GPS_ENGINE_STATE_STANDALONE,
- GPS_ENGINE_STATE_ASSISTED,
+ GPS_ENGINE_STATE_AGPS_MSA,
+ GPS_ENGINE_STATE_AGPS_MSB,
} GpsEngineState;
typedef struct {
@@ -855,7 +856,7 @@
if (transport_protocol_supl_supported && ms_assisted_based_position_mode_supported) {
mm_dbg ("XLCSLSR based A-GPS control supported");
- priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_AGPS;
+ priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_AGPS_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
} else {
mm_dbg ("XLCSLSR based A-GPS control unsupported: protocol supl %s, ms assisted/based %s",
transport_protocol_supl_supported ? "supported" : "unsupported",
@@ -870,6 +871,18 @@
}
static void
+run_xlcslsr_test (GTask *task)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (g_task_get_source_object (task)),
+ "+XLCSLSR=?",
+ 3,
+ TRUE, /* allow caching */
+ (GAsyncReadyCallback)xlcslsr_test_ready,
+ task);
+}
+
+static void
parent_load_capabilities_ready (MMIfaceModemLocation *self,
GAsyncResult *res,
GTask *task)
@@ -897,14 +910,7 @@
/* Cache sources supported by the parent */
g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
-
- mm_base_modem_at_command (
- MM_BASE_MODEM (g_task_get_source_object (task)),
- "+XLCSLSR=?",
- 3,
- TRUE, /* allow caching */
- (GAsyncReadyCallback)xlcslsr_test_ready,
- task);
+ run_xlcslsr_test (task);
}
void
@@ -919,8 +925,14 @@
task = g_task_new (self, NULL, callback, user_data);
g_assert (priv->iface_modem_location_parent);
- g_assert (priv->iface_modem_location_parent->load_capabilities);
- g_assert (priv->iface_modem_location_parent->load_capabilities_finish);
+
+ if (!priv->iface_modem_location_parent->load_capabilities ||
+ !priv->iface_modem_location_parent->load_capabilities_finish) {
+ /* no parent capabilities */
+ g_task_set_task_data (task, GUINT_TO_POINTER (MM_MODEM_LOCATION_SOURCE_NONE), NULL);
+ run_xlcslsr_test (task);
+ return;
+ }
priv->iface_modem_location_parent->load_capabilities (self,
(GAsyncReadyCallback)parent_load_capabilities_ready,
@@ -1058,7 +1070,11 @@
transport_protocol = 2;
pos_mode = 3;
break;
- case GPS_ENGINE_STATE_ASSISTED:
+ case GPS_ENGINE_STATE_AGPS_MSB:
+ transport_protocol = 1;
+ pos_mode = 1;
+ break;
+ case GPS_ENGINE_STATE_AGPS_MSA:
transport_protocol = 1;
pos_mode = 2;
break;
@@ -1069,8 +1085,8 @@
/*
* AT+XLCSLSR
- * transport_protocol: 2 (invalid) or 1 (supl)
- * pos_mode: 3 (standalone) or 2 (ms assisted+based)
+ * transport_protocol: 2 (invalid) or 1 (supl)
+ * pos_mode: 3 (standalone), 1 (msb) or 2 (msa)
* client_id: <empty>
* client_id_type: <empty>
* mlc_number: <empty>
@@ -1191,9 +1207,12 @@
{
/* If at lease one of GPS nmea/raw sources enabled, engine started */
if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
- /* If A-GPS is enabled, ASSISTED mode */
- if (sources & MM_MODEM_LOCATION_SOURCE_AGPS)
- return GPS_ENGINE_STATE_ASSISTED;
+ /* If MSA A-GPS is enabled, MSA mode */
+ if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSA)
+ return GPS_ENGINE_STATE_AGPS_MSA;
+ /* If MSB A-GPS is enabled, MSB mode */
+ if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSB)
+ return GPS_ENGINE_STATE_AGPS_MSB;
/* Otherwise, STANDALONE */
return GPS_ENGINE_STATE_STANDALONE;
}
@@ -1290,7 +1309,8 @@
/* We only expect GPS sources here */
g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
- MM_MODEM_LOCATION_SOURCE_AGPS));
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB));
/* Update engine based on the expected sources */
gps_engine_state_select (MM_SHARED_XMM (self),
@@ -1366,12 +1386,12 @@
priv = get_private (MM_SHARED_XMM (self));
g_assert (priv->iface_modem_location_parent);
- g_assert (priv->iface_modem_location_parent->enable_location_gathering);
- g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish);
/* Only consider request if it applies to one of the sources we are
* supporting, otherwise run parent enable */
- if (!(priv->supported_sources & source)) {
+ if (priv->iface_modem_location_parent->enable_location_gathering &&
+ priv->iface_modem_location_parent->enable_location_gathering_finish &&
+ !(priv->supported_sources & source)) {
priv->iface_modem_location_parent->enable_location_gathering (self,
source,
(GAsyncReadyCallback)parent_enable_location_gathering_ready,
@@ -1382,7 +1402,8 @@
/* We only expect GPS sources here */
g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
- MM_MODEM_LOCATION_SOURCE_AGPS));
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB));
/* Update engine based on the expected sources */
gps_engine_state_select (MM_SHARED_XMM (self),
diff --git a/po/LINGUAS b/po/LINGUAS
index 27395f0..68225ab 100644
--- a/po/LINGUAS
+++ b/po/LINGUAS
@@ -6,9 +6,11 @@
hu
id
it
+lt
pl
pt_BR
sk
sv
tr
uk
+zh_CN
diff --git a/po/lt.po b/po/lt.po
new file mode 100644
index 0000000..ded731e
--- /dev/null
+++ b/po/lt.po
@@ -0,0 +1,110 @@
+# Lithuanian translation for ModemManager.
+# Copyright (C) 2019 ModemManager's COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Moo, 2019.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
+"POT-Creation-Date: 2019-04-25 10:07+0200\n"
+"PO-Revision-Date: 2019-04-13 22:08+0300\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: lt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.1\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && (n%100<11 || n%100>19) ? 0 : n"
+"%10>=2 && n%10<=9 && (n%100<11 || n%100>19) ? 1 : 2);\n"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:13
+msgid "Control the Modem Manager daemon"
+msgstr "Valdyti modemo tvarkytuvės tarnybą"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:14
+msgid "System policy prevents controlling the Modem Manager."
+msgstr "Sistemos politika neleidžia valdyti modemo tvarkytuvę."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:22
+msgid "Unlock and control a mobile broadband device"
+msgstr "Atrakinti ir valdyti mobiliojo plačiajuosčio ryšio įrenginį"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:23
+msgid ""
+"System policy prevents unlocking or controlling the mobile broadband device."
+msgstr ""
+"Sistemos politika neleidžia atrakinti ir valdyti mobiliojo plačiajuosčio "
+"ryšio įrenginį."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:31
+msgid "Add, modify, and delete mobile broadband contacts"
+msgstr ""
+"Pridėti, modifikuoti bei ištrinti mobiliojo plačiajuosčio ryšio kontaktus"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:32
+msgid ""
+"System policy prevents adding, modifying, or deleting this device's contacts."
+msgstr ""
+"Sistemos politika neleidžia pridėti, modifikuoti ar ištrinti šio įrenginio "
+"kontaktus."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:40
+msgid "Send, save, modify, and delete text messages"
+msgstr "Siųsti, įrašyti, modifikuoti bei ištrinti tekstines žinutes"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:41
+msgid ""
+"System policy prevents sending or manipulating this device's text messages."
+msgstr ""
+"Sistemos politika neleidžia siųsti ar valdyti šio įrenginio tekstines "
+"žinutes."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:49
+msgid "Accept incoming voice calls or start outgoing voice calls."
+msgstr ""
+"Atsiliepti į gaunamus balso skambučius ar inicijuoti išsiunčiamuosius balso "
+"skambučius."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:50
+msgid "System policy prevents voice calls."
+msgstr "Sistemos politika neleidžia balso skambučių."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Enable and view geographic location and positioning information"
+msgstr "Įjungti ir rodyti geografinės vietos bei pozicionavimo informaciją"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+msgid ""
+"System policy prevents enabling or viewing geographic location information."
+msgstr ""
+"Sistemos politika neleidžia įjungti ar rodyti geografinės vietos informaciją."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
+msgid "Query and utilize network information and services"
+msgstr "Užklausti ir panaudoti tinklo informacija bei paslaugas"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
+msgid ""
+"System policy prevents querying or utilizing network information and "
+"services."
+msgstr ""
+"Sistemos politika neleidžia užklausti ar panaudoti tinklo informacija ir "
+"paslaugas."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
+msgid "Query and manage firmware on a mobile broadband device"
+msgstr ""
+"Užklausti ir tvarkyti programinę aparatinę įrangą mobiliojo plačiajuosčio "
+"ryšio įrenginyje"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
+msgid "System policy prevents querying or managing this device's firmware."
+msgstr ""
+"Sistemos politika neleidžia užklausti ar tvarkyti šio įrenginio programinę "
+"aparatinę įrangą."
+
+#: src/mm-sleep-monitor.c:114
+msgid "ModemManager needs to reset devices"
+msgstr "ModemManager turi atstatyti įrenginius"
diff --git a/po/zh_CN.po b/po/zh_CN.po
new file mode 100644
index 0000000..4a998d2
--- /dev/null
+++ b/po/zh_CN.po
@@ -0,0 +1,93 @@
+# Chinese (China) translation for ModemManager.
+# Copyright (C) 2019 ModemManager's COPYRIGHT HOLDER
+# This file is distributed under the same license as the ModemManager package.
+# Estel Zhang <callmebedrockdigger@gmail.com>, 2018.
+# 王滋涵 Zephyr Waitzman <i@wi24rd.ml>, 2019.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: ModemManager master\n"
+"Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
+"POT-Creation-Date: 2019-07-02 09:46+0200\n"
+"PO-Revision-Date: 2019-05-03 00:10+0800\n"
+"Last-Translator: 王滋涵 <i@wi24rd.ml>\n"
+"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.1\n"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:13
+msgid "Control the Modem Manager daemon"
+msgstr "控制调制解调器管理器守护进程"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:14
+msgid "System policy prevents controlling the Modem Manager."
+msgstr "系统策略禁止控制调制解调器管理器。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:22
+msgid "Unlock and control a mobile broadband device"
+msgstr "解锁并控制移动宽带设备"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:23
+msgid ""
+"System policy prevents unlocking or controlling the mobile broadband device."
+msgstr "系统策略禁止解锁或控制移动宽带设备。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:31
+msgid "Add, modify, and delete mobile broadband contacts"
+msgstr "添加、修改或删除移动宽带联系人"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:32
+msgid ""
+"System policy prevents adding, modifying, or deleting this device's contacts."
+msgstr "系统策略禁止添加、修改或删除移动宽带联系人。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:40
+msgid "Send, save, modify, and delete text messages"
+msgstr "发送、保存修改或删除文本消息"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:41
+msgid ""
+"System policy prevents sending or manipulating this device's text messages."
+msgstr "系统策略禁止发送或操作此设备的文本消息。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:49
+msgid "Accept incoming voice calls or start outgoing voice calls."
+msgstr "接听传入的语音呼叫或者开始传出语音呼叫。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:50
+msgid "System policy prevents voice calls."
+msgstr "系统策略禁止语音呼叫。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Enable and view geographic location and positioning information"
+msgstr "启用和查看地理位置和定位信息"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+msgid ""
+"System policy prevents enabling or viewing geographic location information."
+msgstr "系统策略禁止启用和查看地理位置和定位信息。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
+msgid "Query and utilize network information and services"
+msgstr "查询和利用网络信息和服务"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
+msgid ""
+"System policy prevents querying or utilizing network information and "
+"services."
+msgstr "系统策略禁止查询和利用网络信息和服务。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
+msgid "Query and manage firmware on a mobile broadband device"
+msgstr "在移动宽带设备上查询和管理固件"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
+msgid "System policy prevents querying or managing this device's firmware."
+msgstr "系统策略禁止在移动宽带设备上查询和管理固件。"
+
+#: src/mm-sleep-monitor.c:114
+msgid "ModemManager needs to reset devices"
+msgstr "调制解调器管理器需要重置设备"
diff --git a/src/77-mm-pcmcia-device-blacklist.rules b/src/77-mm-pcmcia-device-blacklist.rules
index b496a8e..7c82fa9 100644
--- a/src/77-mm-pcmcia-device-blacklist.rules
+++ b/src/77-mm-pcmcia-device-blacklist.rules
@@ -4,6 +4,6 @@
SUBSYSTEM!="pcmcia", GOTO="mm_pcmcia_device_blacklist_end"
# Gemplus Serial Port smartcard adapter
-ATTRS{prod_id1}=="Gemplus", ATTRS{prod_id2}=="SerialPort", ATTRS{prod_id3}=="GemPC Card", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{prod_id1}=="Gemplus", ATTRS{prod_id2}=="SerialPort", ATTRS{prod_id3}=="GemPC Card", ENV{ID_MM_TTY_BLACKLIST}="1"
LABEL="mm_pcmcia_device_blacklist_end"
diff --git a/src/77-mm-usb-device-blacklist.rules b/src/77-mm-usb-device-blacklist.rules
index e8d7a4c..983fe43 100644
--- a/src/77-mm-usb-device-blacklist.rules
+++ b/src/77-mm-usb-device-blacklist.rules
@@ -4,208 +4,208 @@
SUBSYSTEM!="usb", GOTO="mm_usb_device_blacklist_end"
# APC UPS devices
-ATTRS{idVendor}=="051d", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="051d", ENV{ID_MM_TTY_BLACKLIST}="1"
# Sweex 1000VA
-ATTRS{idVendor}=="0925", ATTRS{idProduct}=="1234", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0925", ATTRS{idProduct}=="1234", ENV{ID_MM_TTY_BLACKLIST}="1"
# Agiler UPS
-ATTRS{idVendor}=="05b8", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="05b8", ATTRS{idProduct}=="0000", ENV{ID_MM_TTY_BLACKLIST}="1"
# Krauler UP-M500VA
-ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0000", ENV{ID_MM_TTY_BLACKLIST}="1"
# Ablerex 625L USB
-ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="0000", ENV{ID_MM_TTY_BLACKLIST}="1"
# Belkin F6C1200-UNV
-ATTRS{idVendor}=="0665", ATTRS{idProduct}=="5161", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0665", ATTRS{idProduct}=="5161", ENV{ID_MM_TTY_BLACKLIST}="1"
# Various Liebert and Phoenixtec Power devices
-ATTRS{idVendor}=="06da", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="06da", ENV{ID_MM_TTY_BLACKLIST}="1"
# Unitek Alpha 1200Sx
-ATTRS{idVendor}=="0f03", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0f03", ATTRS{idProduct}=="0001", ENV{ID_MM_TTY_BLACKLIST}="1"
# Various Tripplite devices
-ATTRS{idVendor}=="09ae", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="09ae", ENV{ID_MM_TTY_BLACKLIST}="1"
# Various MGE Office Protection Systems devices
-ATTRS{idVendor}=="0463", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="0463", ATTRS{idProduct}=="ffff", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0463", ATTRS{idProduct}=="0001", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="0463", ATTRS{idProduct}=="ffff", ENV{ID_MM_TTY_BLACKLIST}="1"
# CyberPower 900AVR/BC900D
-ATTRS{idVendor}=="0764", ATTRS{idProduct}=="0005", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0764", ATTRS{idProduct}=="0005", ENV{ID_MM_TTY_BLACKLIST}="1"
# CyberPower CP1200AVR/BC1200D
-ATTRS{idVendor}=="0764", ATTRS{idProduct}=="0501", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0764", ATTRS{idProduct}=="0501", ENV{ID_MM_TTY_BLACKLIST}="1"
# Various Belkin devices
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0980", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0900", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0910", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0912", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0551", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0751", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0375", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="1100", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0980", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0900", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0910", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0912", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0551", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0751", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0375", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="1100", ENV{ID_MM_TTY_BLACKLIST}="1"
# HP R/T 2200 INTL (like SMART2200RMXL2U)
-ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="1f0a", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="1f0a", ENV{ID_MM_TTY_BLACKLIST}="1"
# Powerware devices
-ATTRS{idVendor}=="0592", ATTRS{idProduct}=="0002", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0592", ATTRS{idProduct}=="0002", ENV{ID_MM_TTY_BLACKLIST}="1"
# Palm Treo 700/900/etc
# Shouldn't be probed themselves, but you can install programs like
# "MobileStream USB Modem" which changes the USB PID of the device to something
# that isn't blacklisted.
-ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0061", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0061", ENV{ID_MM_TTY_BLACKLIST}="1"
# GlobalScaleTechnologies SheevaPlug
-ATTRS{idVendor}=="9e88", ATTRS{idProduct}=="9e8f", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="9e88", ATTRS{idProduct}=="9e8f", ENV{ID_MM_TTY_BLACKLIST}="1"
# Atmel Corp at91sam SAMBA bootloader
-ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="6124", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="6124", ENV{ID_MM_TTY_BLACKLIST}="1"
# All devices from the Swiss Federal Institute of Technology
-ATTRS{idVendor}=="0617", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0617", ENV{ID_MM_TTY_BLACKLIST}="1"
# West Mountain Radio devices
-ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="814a", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="814b", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="2405", ATTRS{idProduct}=="0003", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="814a", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="814b", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="2405", ATTRS{idProduct}=="0003", ENV{ID_MM_TTY_BLACKLIST}="1"
# Arduinos
-ATTRS{idVendor}=="2341", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="2a03", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9207", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9208", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="2341", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="2a03", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9207", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9208", ENV{ID_MM_TTY_BLACKLIST}="1"
# Adafruit Flora
-ATTRS{idVendor}=="239a", ATTRS{idProduct}=="0004", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="239a", ATTRS{idProduct}=="8004", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="239a", ATTRS{idProduct}=="0004", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="239a", ATTRS{idProduct}=="8004", ENV{ID_MM_TTY_BLACKLIST}="1"
# All devices from Pololu Corporation
# except some possible future products.
-ATTRS{idVendor}=="1ffb", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="1ffb", ATTRS{idProduct}=="00ad", ENV{ID_MM_DEVICE_IGNORE}="0"
-ATTRS{idVendor}=="1ffb", ATTRS{idProduct}=="00ae", ENV{ID_MM_DEVICE_IGNORE}="0"
+ATTRS{idVendor}=="1ffb", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="1ffb", ATTRS{idProduct}=="00ad", ENV{ID_MM_TTY_BLACKLIST}="0"
+ATTRS{idVendor}=="1ffb", ATTRS{idProduct}=="00ae", ENV{ID_MM_TTY_BLACKLIST}="0"
# Altair U-Boot device
-ATTRS{idVendor}=="0216", ATTRS{idProduct}=="0051", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0216", ATTRS{idProduct}=="0051", ENV{ID_MM_TTY_BLACKLIST}="1"
# Bluegiga BLE112B
-ATTRS{idVendor}=="2458", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="2458", ATTRS{idProduct}=="0001", ENV{ID_MM_TTY_BLACKLIST}="1"
# MediaTek GPS chip (HOLUX M-1200E, GlobalTop Gms-d1, etc)
-ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", ENV{ID_MM_TTY_BLACKLIST}="1"
# MediaTek MT65xx preloader
-ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="2000", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="2000", ENV{ID_MM_TTY_BLACKLIST}="1"
# PS-360 OEM (GPS sold with MS Street and Trips 2005)
-ATTRS{idVendor}=="067b", ATTRS{idProduct}=="aaa0", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="067b", ATTRS{idProduct}=="aaa0", ENV{ID_MM_TTY_BLACKLIST}="1"
# u-blox AG, u-blox 5 GPS chips
-ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", ENV{ID_MM_TTY_BLACKLIST}="1"
# Garmin GPS devices
-DRIVERS=="garmin_gps", ENV{ID_MM_DEVICE_IGNORE}="1"
+DRIVERS=="garmin_gps", ENV{ID_MM_TTY_BLACKLIST}="1"
# Garmin ANT+ stick
-ATTRS{idVendor}=="0fcf", ATTRS{idProduct}=="1009", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0fcf", ATTRS{idProduct}=="1009", ENV{ID_MM_TTY_BLACKLIST}="1"
# Cypress M8-based GPS devices, UPSes, and serial converters
-DRIVERS=="cypress_m8", ENV{ID_MM_DEVICE_IGNORE}="1"
+DRIVERS=="cypress_m8", ENV{ID_MM_TTY_BLACKLIST}="1"
# All devices in the Openmoko vendor ID, except usb hubs
-ATTRS{idVendor}=="1d50", ATTRS{bDeviceClass}!="09", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1d50", ATTRS{bDeviceClass}!="09", ENV{ID_MM_TTY_BLACKLIST}="1"
# All devices from 3D Robotics
-ATTRS{idVendor}=="26ac", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="26ac", ENV{ID_MM_TTY_BLACKLIST}="1"
# empiriKit science lab controller device
-ATTRS{idVendor}=="0425", ATTRS{idProduct}=="0408", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0425", ATTRS{idProduct}=="0408", ENV{ID_MM_TTY_BLACKLIST}="1"
# Infineon Flashloader used by Intel XMM modem bootloader
-ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0716", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0801", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0716", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0801", ENV{ID_MM_TTY_BLACKLIST}="1"
# Intel coredump downloader device
-ATTRS{idVendor}=="1519", ATTRS{idProduct}=="f000", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1519", ATTRS{idProduct}=="f000", ENV{ID_MM_TTY_BLACKLIST}="1"
# GW Instek AFG-2225 arbitrary function generator
-ATTRS{idVendor}=="2184", ATTRS{idProduct}=="001c", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="2184", ATTRS{idProduct}=="001c", ENV{ID_MM_TTY_BLACKLIST}="1"
# PalmOS devices - even though some are phones, they are so old they most
# likely are not being used anymore
-DRIVERS=="visor", ENV{ID_MM_DEVICE_IGNORE}="1"
+DRIVERS=="visor", ENV{ID_MM_TTY_BLACKLIST}="1"
# Palmconnect
-ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0080", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0080", ENV{ID_MM_TTY_BLACKLIST}="1"
# IMC flashing device
-ATTRS{idVendor}=="058b", ATTRS{idProduct}=="0041", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="058b", ATTRS{idProduct}=="0041", ENV{ID_MM_TTY_BLACKLIST}="1"
# All devices from the Access Interfacing Solutions (Access Ltd)
# Access IS do not produce modems and are unlikely to do so in future
-ATTRS{idVendor}=="0db5", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0db5", ENV{ID_MM_TTY_BLACKLIST}="1"
# Palm M500
-ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0001", ENV{ID_MM_TTY_BLACKLIST}="1"
# Palm M505
-ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0002", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0002", ENV{ID_MM_TTY_BLACKLIST}="1"
# Palm M515
-ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0003", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0003", ENV{ID_MM_TTY_BLACKLIST}="1"
# All devices from POSNET POLSKA S.A.
# POSNET POLSKA S.A. do not produce modems and are unlikely to do so in future
-ATTRS{idVendor}=="1424", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1424", ENV{ID_MM_TTY_BLACKLIST}="1"
# proxmark3
-ATTRS{manufacturer}=="proxmark.org", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{manufacturer}=="proxmark.org", ENV{ID_MM_TTY_BLACKLIST}="1"
# Sigma Sport Docking Station TOPLINE 2009
-ATTRS{idVendor}=="1d9d", ATTRS{idProduct}=="1010", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1d9d", ATTRS{idProduct}=="1010", ENV{ID_MM_TTY_BLACKLIST}="1"
# Sigma Sport Docking Station TOPLINE 2012
-ATTRS{idVendor}=="1d9d", ATTRS{idProduct}=="1011", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1d9d", ATTRS{idProduct}=="1011", ENV{ID_MM_TTY_BLACKLIST}="1"
# Telit LE866 flashing device
-ATTRS{idVendor}=="216f", ATTRS{idProduct}=="0051", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="216f", ATTRS{idProduct}=="0051", ENV{ID_MM_TTY_BLACKLIST}="1"
# Analog Devices ADALM-PLUTO (PlutoSDR)
-ATTRS{idVendor}=="0456", ATTRS{idProduct}=="b673", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0456", ATTRS{idProduct}=="b673", ENV{ID_MM_TTY_BLACKLIST}="1"
# Renesas development and promotion boards
-ATTRS{idVendor}=="045B", ATTRS{idProduct}=="0212", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="0409", ATTRS{idProduct}=="0063", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="045B", ATTRS{idProduct}=="0212", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="0409", ATTRS{idProduct}=="0063", ENV{ID_MM_TTY_BLACKLIST}="1"
# Analog Devices EVAL-ADXL362Z-DB
-ATTRS{idVendor}=="064B", ATTRS{idProduct}=="7825", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="064B", ATTRS{idProduct}=="7825", ENV{ID_MM_TTY_BLACKLIST}="1"
# keyboard.io devices
-ATTRS{idVendor}=="1209", ATTRS{idProduct}=="2300", ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{idVendor}=="1209", ATTRS{idProduct}=="2301", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1209", ATTRS{idProduct}=="2300", ENV{ID_MM_TTY_BLACKLIST}="1"
+ATTRS{idVendor}=="1209", ATTRS{idProduct}=="2301", ENV{ID_MM_TTY_BLACKLIST}="1"
# Netchip Technology, Inc. Linux-USB Serial Gadget (CDC ACM mode)
-ATTRS{idVendor}=="0525", ATTRS{idProduct}=="a4a7", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="0525", ATTRS{idProduct}=="a4a7", ENV{ID_MM_TTY_BLACKLIST}="1"
# Silicon Labs Telegesis ETRX USB Zigbee dongle
-ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="000f", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="000f", ENV{ID_MM_TTY_BLACKLIST}="1"
# Devices using Microchip's VID
# Dangerous Prototypes Bus Pirate v4
-ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="fb00", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="fb00", ENV{ID_MM_TTY_BLACKLIST}="1"
# Pycom Pysense
-ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="f012", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="f012", ENV{ID_MM_TTY_BLACKLIST}="1"
# Pycom Pytrack
-ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="f013", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="f013", ENV{ID_MM_TTY_BLACKLIST}="1"
# All devices from Prusa Research
-ATTRS{idVendor}=="2c99", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="2c99", ENV{ID_MM_TTY_BLACKLIST}="1"
LABEL="mm_usb_device_blacklist_end"
diff --git a/src/77-mm-usb-serial-adapters-greylist.rules b/src/77-mm-usb-serial-adapters-greylist.rules
index 2dc6f7d..935275d 100644
--- a/src/77-mm-usb-serial-adapters-greylist.rules
+++ b/src/77-mm-usb-serial-adapters-greylist.rules
@@ -4,47 +4,47 @@
SUBSYSTEM!="usb", GOTO="mm_usb_serial_adapters_greylist_end"
# Belkin F5U183 Serial Adapter
-ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0103", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0103", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# FTDI-based serial adapters
# FTDI does USB to serial converter ICs; and it's very likely that they'll
# never do modems themselves, so it should be safe to add a rule only based
# on the vendor Id.
-ATTRS{idVendor}=="0403", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="0403", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# Devices using Microchip's VID
-ATTRS{idVendor}=="04d8", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04d8", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# ATEN Intl UC-232A (Prolific)
-ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# Prolific USB to Serial adapter
-ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# Magic Control Technology Corp adapters
-ATTRS{idVendor}=="0711", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="0711", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# Cygnal Integrated Products, Inc. CP210x
-ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
-ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea71", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea71", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# QinHeng Electronics HL-340
-ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# Atmel Corp. LUFA USB to Serial Adapter Project (Arduino)
-ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="204b", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="204b", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# Netchip Technology, Inc. Linux-USB Serial Gadget (CDC ACM mode)
-ATTRS{idVendor}=="0525", ATTRS{idProduct}=="a4a7", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="0525", ATTRS{idProduct}=="a4a7", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
# Cypress Serial-USB devices
-ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0002", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
-ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0003", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
-ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0004", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
-ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0005", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
-ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0006", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
-ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0007", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
-ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0009", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
-ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="000A", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0002", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0003", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0004", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0005", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0006", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0007", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0009", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="000A", ENV{ID_MM_TTY_MANUAL_SCAN_ONLY}="1"
LABEL="mm_usb_serial_adapters_greylist_end"
diff --git a/src/Makefile.am b/src/Makefile.am
index 7d7bc2e..8f46d2c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -267,6 +267,7 @@
ModemManager_CPPFLAGS = \
-DPLUGINDIR=\"$(pkglibdir)\" \
+ -DMM_COMPILATION \
$(NULL)
ModemManager_LDADD = \
diff --git a/src/mm-base-bearer.c b/src/mm-base-bearer.c
index 6b7e1e5..9808eb4 100644
--- a/src/mm-base-bearer.c
+++ b/src/mm-base-bearer.c
@@ -82,6 +82,9 @@
gchar *path;
/* Status of this bearer */
MMBearerStatus status;
+ /* Whether we must ignore all disconnection updates if they're
+ * detected by ModemManager itself. */
+ gboolean ignore_disconnection_reports;
/* Configuration of the bearer */
MMBearerProperties *config;
/* Default IP family of this bearer */
@@ -373,6 +376,8 @@
* interface when going into disconnected state. */
if (self->priv->status == MM_BEARER_STATUS_DISCONNECTED) {
bearer_reset_interface_status (self);
+ /* Cleanup flag to ignore disconnection reports */
+ self->priv->ignore_disconnection_reports = FALSE;
/* Stop statistics */
bearer_stats_stop (self);
/* Stop connection monitoring */
@@ -396,6 +401,16 @@
MM_GDBUS_BEARER (self),
mm_bearer_ip_config_get_dictionary (ipv6_config));
+ /* If PPP is involved in the requested IP config, we must ignore
+ * all disconnection reports found via CGACT? polling or CGEV URCs.
+ * In this case, upper layers should always explicitly disconnect
+ * the bearer when ownership of the TTY is given back to MM. */
+ if ((ipv4_config && mm_bearer_ip_config_get_method (ipv4_config) == MM_BEARER_IP_METHOD_PPP) ||
+ (ipv6_config && mm_bearer_ip_config_get_method (ipv6_config) == MM_BEARER_IP_METHOD_PPP)) {
+ mm_dbg ("PPP is required for connection, will ignore disconnection reports");
+ self->priv->ignore_disconnection_reports = TRUE;
+ }
+
/* Start statistics */
bearer_stats_start (self);
@@ -709,9 +724,8 @@
mm_dbg ("Couldn't connect bearer '%s': '%s'",
self->priv->path,
error->message);
- if (g_error_matches (error,
- MM_CORE_ERROR,
- MM_CORE_ERROR_CANCELLED)) {
+ if ( g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED)
+ || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
/* Will launch disconnection */
launch_disconnect = TRUE;
} else
@@ -943,7 +957,7 @@
GError *error = NULL;
if (!MM_BASE_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
- mm_dbg ("Couldn't disconnect bearer '%s'", self->priv->path);
+ mm_dbg ("Couldn't disconnect bearer '%s': %s", self->priv->path, error->message);
bearer_update_status (self, MM_BEARER_STATUS_CONNECTED);
g_task_return_error (task, error);
}
@@ -1256,13 +1270,46 @@
bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
}
+/*
+ * This method is used exclusively in two different scenarios:
+ * a) to report disconnections detected by ModemManager itself (e.g. based on
+ * CGACT polling or CGEV URCs), applicable to bearers using both NET and
+ * PPP data ports.
+ * b) to report failed or successful connection attempts by plugins using NET
+ * data ports that rely on vendor-specific URCs (e.g. Icera, MBM, Option
+ * HSO).
+ *
+ * The method is also subclass-able because plugins may require specific
+ * cleanup operations to be done when a bearer is reported as disconnected.
+ * (e.g. the QMI or MBIM implementations require removing signal handlers).
+ *
+ * For all the scenarios involving a) the plugins are required to call the
+ * parent report_connection_status() implementation to report the
+ * DISCONNECTED state. For scenarios involving b) the parent reporting is not
+ * expected at all. In other words, the parent report_connection_status()
+ * is exclusively used in processing disconnections detected by ModemManager
+ * itself.
+ *
+ * If the bearer has been connected and it has required PPP method, we will
+ * ignore all disconnection reports because we cannot disconnect a PPP-based
+ * bearer before the upper layers have stopped using the TTY. In this case,
+ * we must wait for upper layers to detect the disconnection themselves (e.g.
+ * pppd should detect it) and disconnect the bearer through DBus.
+ */
void
-mm_base_bearer_report_connection_status (MMBaseBearer *self,
- MMBearerConnectionStatus status)
+mm_base_bearer_report_connection_status (MMBaseBearer *self,
+ MMBearerConnectionStatus status)
{
+ if ((status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) && self->priv->ignore_disconnection_reports) {
+ mm_dbg ("ignoring disconnection report for bearer '%s'", self->priv->path);
+ return;
+ }
+
return MM_BASE_BEARER_GET_CLASS (self)->report_connection_status (self, status);
}
+/*****************************************************************************/
+
static void
set_property (GObject *object,
guint prop_id,
diff --git a/src/mm-base-call.c b/src/mm-base-call.c
index 60795fc..0d09a09 100644
--- a/src/mm-base-call.c
+++ b/src/mm-base-call.c
@@ -11,6 +11,8 @@
* 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>
@@ -40,6 +42,7 @@
PROP_PATH,
PROP_CONNECTION,
PROP_MODEM,
+ PROP_SKIP_INCOMING_TIMEOUT,
PROP_SUPPORTS_DIALING_TO_RINGING,
PROP_SUPPORTS_RINGING_TO_ACTIVE,
PROP_LAST
@@ -55,90 +58,20 @@
/* The path where the call object is exported */
gchar *path;
/* Features */
+ gboolean skip_incoming_timeout;
gboolean supports_dialing_to_ringing;
gboolean supports_ringing_to_active;
guint incoming_timeout;
- GRegex *in_call_events;
/* The port used for audio while call is ongoing, if known */
MMPort *audio_port;
+
+ /* Ongoing call index */
+ guint index;
};
/*****************************************************************************/
-/* In-call unsolicited events
- * Once a call is started, we may need to detect special URCs to trigger call
- * state changes, e.g. "NO CARRIER" when the remote party hangs up. */
-
-static void
-in_call_event_received (MMPortSerialAt *port,
- GMatchInfo *info,
- MMBaseCall *self)
-{
- gchar *str;
-
- str = g_match_info_fetch (info, 1);
- if (!str)
- return;
-
- if (!strcmp (str, "NO DIALTONE"))
- mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_ERROR);
- else if (!strcmp (str, "NO CARRIER") || !strcmp (str, "BUSY") || !strcmp (str, "NO ANSWER"))
- mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_REFUSED_OR_BUSY);
-
- g_free (str);
-}
-
-static gboolean
-common_setup_cleanup_unsolicited_events (MMBaseCall *self,
- gboolean enable,
- GError **error)
-{
- MMBaseModem *modem = NULL;
- MMPortSerialAt *ports[2];
- gint i;
-
- if (G_UNLIKELY (!self->priv->in_call_events))
- self->priv->in_call_events = g_regex_new ("\\r\\n(NO CARRIER|BUSY|NO ANSWER|NO DIALTONE)\\r\\n$",
- G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
-
- g_object_get (self,
- MM_BASE_CALL_MODEM, &modem,
- NULL);
-
- ports[0] = mm_base_modem_peek_port_primary (modem);
- ports[1] = mm_base_modem_peek_port_secondary (modem);
-
- for (i = 0; i < G_N_ELEMENTS (ports); i++) {
- if (!ports[i])
- continue;
-
- mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
- self->priv->in_call_events,
- enable ? (MMPortSerialAtUnsolicitedMsgFn) in_call_event_received : NULL,
- enable ? self : NULL,
- NULL);
- }
-
- g_object_unref (modem);
- return TRUE;
-}
-
-static gboolean
-setup_unsolicited_events (MMBaseCall *self,
- GError **error)
-{
- return common_setup_cleanup_unsolicited_events (self, TRUE, error);
-}
-
-static gboolean
-cleanup_unsolicited_events (MMBaseCall *self,
- GError **error)
-{
- return common_setup_cleanup_unsolicited_events (self, FALSE, error);
-}
-
-/*****************************************************************************/
/* Incoming calls are reported via RING URCs. If the caller stops the call
* attempt before it has been answered, the only thing we would see is that the
* URCs are no longer received. So, we will start a timeout whenever a new RING
@@ -161,6 +94,9 @@
void
mm_base_call_incoming_refresh (MMBaseCall *self)
{
+ if (self->priv->skip_incoming_timeout)
+ return;
+
if (self->priv->incoming_timeout)
g_source_remove (self->priv->incoming_timeout);
self->priv->incoming_timeout = g_timeout_add_seconds (INCOMING_TIMEOUT_SECS, (GSourceFunc)incoming_timeout_cb, self);
@@ -169,10 +105,10 @@
/*****************************************************************************/
/* Update audio settings */
-static void
-update_audio_settings (MMBaseCall *self,
- MMPort *audio_port,
- MMCallAudioFormat *audio_format)
+void
+mm_base_call_change_audio_settings (MMBaseCall *self,
+ MMPort *audio_port,
+ MMCallAudioFormat *audio_format)
{
if (!audio_port && self->priv->audio_port && mm_port_get_connected (self->priv->audio_port))
mm_port_set_connected (self->priv->audio_port, FALSE);
@@ -206,50 +142,6 @@
}
static void
-call_started (HandleStartContext *ctx)
-{
- mm_info ("call is started");
-
- /* If dialing to ringing supported, leave it dialing */
- if (!ctx->self->priv->supports_dialing_to_ringing) {
- /* If ringing to active supported, set it ringing */
- if (ctx->self->priv->supports_ringing_to_active)
- mm_base_call_change_state (ctx->self, MM_CALL_STATE_RINGING_OUT, MM_CALL_STATE_REASON_OUTGOING_STARTED);
- else
- /* Otherwise, active right away */
- mm_base_call_change_state (ctx->self, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_OUTGOING_STARTED);
- }
- mm_gdbus_call_complete_start (MM_GDBUS_CALL (ctx->self), ctx->invocation);
- handle_start_context_free (ctx);
-}
-
-static void
-start_setup_audio_channel_ready (MMBaseCall *self,
- GAsyncResult *res,
- HandleStartContext *ctx)
-{
- MMPort *audio_port = NULL;
- MMCallAudioFormat *audio_format = NULL;
- GError *error = NULL;
-
- if (!MM_BASE_CALL_GET_CLASS (self)->setup_audio_channel_finish (self, res, &audio_port, &audio_format, &error)) {
- mm_warn ("Couldn't setup audio channel: '%s'", error->message);
- mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_AUDIO_SETUP_FAILED);
- g_dbus_method_invocation_take_error (ctx->invocation, error);
- handle_start_context_free (ctx);
- return;
- }
-
- if (audio_port || audio_format) {
- update_audio_settings (self, audio_port, audio_format);
- g_clear_object (&audio_port);
- g_clear_object (&audio_format);
- }
-
- call_started (ctx);
-}
-
-static void
handle_start_ready (MMBaseCall *self,
GAsyncResult *res,
HandleStartContext *ctx)
@@ -272,17 +164,19 @@
return;
}
- /* If there is an audio setup method, run it now */
- if (MM_BASE_CALL_GET_CLASS (self)->setup_audio_channel) {
- mm_info ("setting up audio channel...");
- MM_BASE_CALL_GET_CLASS (self)->setup_audio_channel (self,
- (GAsyncReadyCallback) start_setup_audio_channel_ready,
- ctx);
- return;
- }
+ mm_info ("call is started");
- /* Otherwise, we're done */
- call_started (ctx);
+ /* If dialing to ringing supported, leave it dialing */
+ if (!ctx->self->priv->supports_dialing_to_ringing) {
+ /* If ringing to active supported, set it ringing */
+ if (ctx->self->priv->supports_ringing_to_active)
+ mm_base_call_change_state (ctx->self, MM_CALL_STATE_RINGING_OUT, MM_CALL_STATE_REASON_OUTGOING_STARTED);
+ else
+ /* Otherwise, active right away */
+ mm_base_call_change_state (ctx->self, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_OUTGOING_STARTED);
+ }
+ mm_gdbus_call_complete_start (MM_GDBUS_CALL (ctx->self), ctx->invocation);
+ handle_start_context_free (ctx);
}
static void
@@ -371,46 +265,6 @@
}
static void
-call_accepted (HandleAcceptContext *ctx)
-{
- mm_info ("call is accepted");
-
- if (ctx->self->priv->incoming_timeout) {
- g_source_remove (ctx->self->priv->incoming_timeout);
- ctx->self->priv->incoming_timeout = 0;
- }
- mm_base_call_change_state (ctx->self, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
- mm_gdbus_call_complete_accept (MM_GDBUS_CALL (ctx->self), ctx->invocation);
- handle_accept_context_free (ctx);
-}
-
-static void
-accept_setup_audio_channel_ready (MMBaseCall *self,
- GAsyncResult *res,
- HandleAcceptContext *ctx)
-{
- MMPort *audio_port = NULL;
- MMCallAudioFormat *audio_format = NULL;
- GError *error = NULL;
-
- if (!MM_BASE_CALL_GET_CLASS (self)->setup_audio_channel_finish (self, res, &audio_port, &audio_format, &error)) {
- mm_warn ("Couldn't setup audio channel: '%s'", error->message);
- mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_AUDIO_SETUP_FAILED);
- g_dbus_method_invocation_take_error (ctx->invocation, error);
- handle_accept_context_free (ctx);
- return;
- }
-
- if (audio_port || audio_format) {
- update_audio_settings (self, audio_port, audio_format);
- g_clear_object (&audio_port);
- g_clear_object (&audio_format);
- }
-
- call_accepted (ctx);
-}
-
-static void
handle_accept_ready (MMBaseCall *self,
GAsyncResult *res,
HandleAcceptContext *ctx)
@@ -424,17 +278,15 @@
return;
}
- /* If there is an audio setup method, run it now */
- if (MM_BASE_CALL_GET_CLASS (self)->setup_audio_channel) {
- mm_info ("setting up audio channel...");
- MM_BASE_CALL_GET_CLASS (self)->setup_audio_channel (self,
- (GAsyncReadyCallback) accept_setup_audio_channel_ready,
- ctx);
- return;
- }
+ mm_info ("call is accepted");
- /* Otherwise, we're done */
- call_accepted (ctx);
+ if (ctx->self->priv->incoming_timeout) {
+ g_source_remove (ctx->self->priv->incoming_timeout);
+ ctx->self->priv->incoming_timeout = 0;
+ }
+ mm_base_call_change_state (ctx->self, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
+ mm_gdbus_call_complete_accept (MM_GDBUS_CALL (ctx->self), ctx->invocation);
+ handle_accept_context_free (ctx);
}
static void
@@ -503,7 +355,265 @@
}
/*****************************************************************************/
+/* Deflect call (DBus call handling) */
+typedef struct {
+ MMBaseCall *self;
+ MMBaseModem *modem;
+ GDBusMethodInvocation *invocation;
+ gchar *number;
+} HandleDeflectContext;
+
+static void
+handle_deflect_context_free (HandleDeflectContext *ctx)
+{
+ g_free (ctx->number);
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_slice_free (HandleDeflectContext, ctx);
+}
+
+static void
+handle_deflect_ready (MMBaseCall *self,
+ GAsyncResult *res,
+ HandleDeflectContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!MM_BASE_CALL_GET_CLASS (self)->deflect_finish (self, res, &error)) {
+ mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_ERROR);
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_deflect_context_free (ctx);
+ return;
+ }
+
+ mm_info ("call is deflected to '%s'", ctx->number);
+ mm_base_call_change_state (ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_DEFLECTED);
+ mm_gdbus_call_complete_deflect (MM_GDBUS_CALL (ctx->self), ctx->invocation);
+ handle_deflect_context_free (ctx);
+}
+
+static void
+handle_deflect_auth_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ HandleDeflectContext *ctx)
+{
+ MMCallState state;
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (modem, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_deflect_context_free (ctx);
+ return;
+ }
+
+ state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self));
+
+ /* We can only deflect incoming call in ringing or waiting state */
+ if (state != MM_CALL_STATE_RINGING_IN && state != MM_CALL_STATE_WAITING) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "This call was not ringing/waiting, cannot deflect");
+ handle_deflect_context_free (ctx);
+ return;
+ }
+
+ mm_info ("user request to deflect call");
+
+ /* Check if we do support doing it */
+ if (!MM_BASE_CALL_GET_CLASS (ctx->self)->deflect ||
+ !MM_BASE_CALL_GET_CLASS (ctx->self)->deflect_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Deflecting call is not supported by this modem");
+ handle_deflect_context_free (ctx);
+ return;
+ }
+
+ MM_BASE_CALL_GET_CLASS (ctx->self)->deflect (ctx->self,
+ ctx->number,
+ (GAsyncReadyCallback)handle_deflect_ready,
+ ctx);
+}
+
+static gboolean
+handle_deflect (MMBaseCall *self,
+ GDBusMethodInvocation *invocation,
+ const gchar *number)
+{
+ HandleDeflectContext *ctx;
+
+ ctx = g_slice_new0 (HandleDeflectContext);
+ ctx->self = g_object_ref (self);
+ ctx->invocation = g_object_ref (invocation);
+ ctx->number = g_strdup (number);
+ g_object_get (self,
+ MM_BASE_CALL_MODEM, &ctx->modem,
+ NULL);
+
+ mm_base_modem_authorize (ctx->modem,
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_deflect_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Join multiparty call (DBus call handling) */
+
+typedef struct {
+ MMBaseCall *self;
+ MMBaseModem *modem;
+ GDBusMethodInvocation *invocation;
+} HandleJoinMultipartyContext;
+
+static void
+handle_join_multiparty_context_free (HandleJoinMultipartyContext *ctx)
+{
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static void
+modem_voice_join_multiparty_ready (MMIfaceModemVoice *modem,
+ GAsyncResult *res,
+ HandleJoinMultipartyContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_iface_modem_voice_join_multiparty_finish (modem, res, &error))
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ else
+ mm_gdbus_call_complete_join_multiparty (MM_GDBUS_CALL (ctx->self), ctx->invocation);
+ handle_join_multiparty_context_free (ctx);
+}
+
+static void
+handle_join_multiparty_auth_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ HandleJoinMultipartyContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (modem, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_join_multiparty_context_free (ctx);
+ return;
+ }
+
+ /* This action is provided in the Call API, but implemented in the Modem.Voice interface
+ * logic, because the action affects not only one call object, but all call objects that
+ * are part of the multiparty call. */
+ mm_iface_modem_voice_join_multiparty (MM_IFACE_MODEM_VOICE (ctx->modem),
+ ctx->self,
+ (GAsyncReadyCallback)modem_voice_join_multiparty_ready,
+ ctx);
+}
+
+static gboolean
+handle_join_multiparty (MMBaseCall *self,
+ GDBusMethodInvocation *invocation)
+{
+ HandleJoinMultipartyContext *ctx;
+
+ ctx = g_new0 (HandleJoinMultipartyContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->invocation = g_object_ref (invocation);
+ g_object_get (self,
+ MM_BASE_CALL_MODEM, &ctx->modem,
+ NULL);
+
+ mm_base_modem_authorize (ctx->modem,
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_join_multiparty_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Leave multiparty call (DBus call handling) */
+
+typedef struct {
+ MMBaseCall *self;
+ MMBaseModem *modem;
+ GDBusMethodInvocation *invocation;
+} HandleLeaveMultipartyContext;
+
+static void
+handle_leave_multiparty_context_free (HandleLeaveMultipartyContext *ctx)
+{
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static void
+modem_voice_leave_multiparty_ready (MMIfaceModemVoice *modem,
+ GAsyncResult *res,
+ HandleLeaveMultipartyContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_iface_modem_voice_leave_multiparty_finish (modem, res, &error))
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ else
+ mm_gdbus_call_complete_leave_multiparty (MM_GDBUS_CALL (ctx->self), ctx->invocation);
+
+ handle_leave_multiparty_context_free (ctx);
+}
+
+static void
+handle_leave_multiparty_auth_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ HandleLeaveMultipartyContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (modem, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_leave_multiparty_context_free (ctx);
+ return;
+ }
+
+ /* This action is provided in the Call API, but implemented in the Modem.Voice interface
+ * logic, because the action affects not only one call object, but all call objects that
+ * are part of the multiparty call. */
+ mm_iface_modem_voice_leave_multiparty (MM_IFACE_MODEM_VOICE (ctx->modem),
+ ctx->self,
+ (GAsyncReadyCallback)modem_voice_leave_multiparty_ready,
+ ctx);
+}
+
+static gboolean
+handle_leave_multiparty (MMBaseCall *self,
+ GDBusMethodInvocation *invocation)
+{
+ HandleLeaveMultipartyContext *ctx;
+
+ ctx = g_new0 (HandleLeaveMultipartyContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->invocation = g_object_ref (invocation);
+ g_object_get (self,
+ MM_BASE_CALL_MODEM, &ctx->modem,
+ NULL);
+
+ mm_base_modem_authorize (ctx->modem,
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_leave_multiparty_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
/* Hangup call (DBus call handling) */
typedef struct {
@@ -534,10 +644,7 @@
if (!MM_BASE_CALL_GET_CLASS (self)->hangup_finish (self, res, &error))
g_dbus_method_invocation_take_error (ctx->invocation, error);
else {
- if (ctx->self->priv->incoming_timeout) {
- g_source_remove (ctx->self->priv->incoming_timeout);
- ctx->self->priv->incoming_timeout = 0;
- }
+ /* note: timeouts are already removed when setting state as TERMINATED */
mm_gdbus_call_complete_hangup (MM_GDBUS_CALL (ctx->self), ctx->invocation);
}
@@ -742,21 +849,14 @@
GError *error = NULL;
/* Handle method invocations */
- g_signal_connect (self,
- "handle-start",
- G_CALLBACK (handle_start),
- NULL);
- g_signal_connect (self,
- "handle-accept",
- G_CALLBACK (handle_accept),
- NULL);
- g_signal_connect (self,
- "handle-hangup",
- G_CALLBACK (handle_hangup),
- NULL);
- g_signal_connect (self,
- "handle-send-dtmf",
- G_CALLBACK (handle_send_dtmf),
+ g_object_connect (self,
+ "signal::handle-start", G_CALLBACK (handle_start), NULL,
+ "signal::handle-accept", G_CALLBACK (handle_accept), NULL,
+ "signal::handle-deflect", G_CALLBACK (handle_deflect), NULL,
+ "signal::handle-join-multiparty", G_CALLBACK (handle_join_multiparty), NULL,
+ "signal::handle-leave-multiparty", G_CALLBACK (handle_leave_multiparty), NULL,
+ "signal::handle-hangup", G_CALLBACK (handle_hangup), NULL,
+ "signal::handle-send-dtmf", G_CALLBACK (handle_send_dtmf), NULL,
NULL);
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self),
@@ -786,32 +886,70 @@
return self->priv->path;
}
-/* Define the states in which we want to handle in-call events */
-#define MM_CALL_STATE_IS_IN_CALL(state) \
- (state == MM_CALL_STATE_DIALING || \
- state == MM_CALL_STATE_RINGING_IN || \
- state == MM_CALL_STATE_RINGING_OUT || \
- state == MM_CALL_STATE_ACTIVE)
-
-static void
-cleanup_audio_channel_ready (MMBaseCall *self,
- GAsyncResult *res)
+const gchar *
+mm_base_call_get_number (MMBaseCall *self)
{
- GError *error = NULL;
-
- if (!MM_BASE_CALL_GET_CLASS (self)->cleanup_audio_channel_finish (self, res, &error)) {
- mm_warn ("audio channel cleanup failed: %s", error->message);
- g_error_free (error);
- }
+ return mm_gdbus_call_get_number (MM_GDBUS_CALL (self));
}
void
+mm_base_call_set_number (MMBaseCall *self,
+ const gchar *number)
+{
+ return mm_gdbus_call_set_number (MM_GDBUS_CALL (self), number);
+}
+
+MMCallDirection
+mm_base_call_get_direction (MMBaseCall *self)
+{
+ return (MMCallDirection) mm_gdbus_call_get_direction (MM_GDBUS_CALL (self));
+}
+
+MMCallState
+mm_base_call_get_state (MMBaseCall *self)
+{
+ return (MMCallState) mm_gdbus_call_get_state (MM_GDBUS_CALL (self));
+}
+
+gboolean
+mm_base_call_get_multiparty (MMBaseCall *self)
+{
+ return mm_gdbus_call_get_multiparty (MM_GDBUS_CALL (self));
+}
+
+void
+mm_base_call_set_multiparty (MMBaseCall *self,
+ gboolean multiparty)
+{
+ return mm_gdbus_call_set_multiparty (MM_GDBUS_CALL (self), multiparty);
+}
+
+/*****************************************************************************/
+/* Current call index, only applicable while the call is ongoing
+ * See 3GPP TS 22.030 [27], subclause 6.5.5.1.
+ */
+
+guint
+mm_base_call_get_index (MMBaseCall *self)
+{
+ return self->priv->index;
+}
+
+void
+mm_base_call_set_index (MMBaseCall *self,
+ guint index)
+{
+ self->priv->index = index;
+}
+
+/*****************************************************************************/
+
+void
mm_base_call_change_state (MMBaseCall *self,
MMCallState new_state,
MMCallStateReason reason)
{
- MMCallState old_state;
- GError *error = NULL;
+ MMCallState old_state;
old_state = mm_gdbus_call_get_state (MM_GDBUS_CALL (self));
@@ -824,26 +962,13 @@
mm_call_state_reason_get_string (reason));
/* Setup/cleanup unsolicited events based on state transitions to/from ACTIVE */
- if (!MM_CALL_STATE_IS_IN_CALL (old_state) && MM_CALL_STATE_IS_IN_CALL (new_state)) {
- mm_dbg ("Setting up in-call unsolicited events...");
- if (MM_BASE_CALL_GET_CLASS (self)->setup_unsolicited_events &&
- !MM_BASE_CALL_GET_CLASS (self)->setup_unsolicited_events (self, &error)) {
- mm_warn ("Couldn't setup in-call unsolicited events: %s", error->message);
- g_error_free (error);
- }
- } else if (MM_CALL_STATE_IS_IN_CALL (old_state) && !MM_CALL_STATE_IS_IN_CALL (new_state)) {
- mm_dbg ("Cleaning up in-call unsolicited events...");
- if (MM_BASE_CALL_GET_CLASS (self)->cleanup_unsolicited_events &&
- !MM_BASE_CALL_GET_CLASS (self)->cleanup_unsolicited_events (self, &error)) {
- mm_warn ("Couldn't cleanup in-call unsolicited events: %s", error->message);
- g_error_free (error);
- }
- if (MM_BASE_CALL_GET_CLASS (self)->cleanup_audio_channel) {
- mm_info ("cleaning up audio channel...");
- update_audio_settings (self, NULL, NULL);
- MM_BASE_CALL_GET_CLASS (self)->cleanup_audio_channel (self,
- (GAsyncReadyCallback) cleanup_audio_channel_ready,
- NULL);
+ if (new_state == MM_CALL_STATE_TERMINATED) {
+ /* reset index */
+ self->priv->index = 0;
+ /* cleanup incoming timeout, if any */
+ if (self->priv->incoming_timeout) {
+ g_source_remove (self->priv->incoming_timeout);
+ self->priv->incoming_timeout = 0;
}
}
@@ -852,6 +977,8 @@
mm_gdbus_call_emit_state_changed (MM_GDBUS_CALL (self), old_state, new_state, reason);
}
+/*****************************************************************************/
+
void
mm_base_call_received_dtmf (MMBaseCall *self,
const gchar *dtmf)
@@ -962,6 +1089,53 @@
}
/*****************************************************************************/
+/* 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
@@ -973,14 +1147,13 @@
}
static void
-call_hangup_ready (MMBaseModem *modem,
- GAsyncResult *res,
- GTask *task)
+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
@@ -989,6 +1162,43 @@
}
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_warn ("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)
@@ -996,12 +1206,24 @@
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
- mm_base_modem_at_command (self->priv->modem,
- "+CHUP",
- 2,
- FALSE,
- (GAsyncReadyCallback)call_hangup_ready,
- task);
+
+ /* 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);
}
/*****************************************************************************/
@@ -1061,12 +1283,18 @@
MMBaseCall *
mm_base_call_new (MMBaseModem *modem,
MMCallDirection direction,
- const gchar *number)
+ 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,
"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));
}
@@ -1112,6 +1340,9 @@
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
}
break;
+ case PROP_SKIP_INCOMING_TIMEOUT:
+ self->priv->skip_incoming_timeout = g_value_get_boolean (value);
+ break;
case PROP_SUPPORTS_DIALING_TO_RINGING:
self->priv->supports_dialing_to_ringing = g_value_get_boolean (value);
break;
@@ -1142,6 +1373,9 @@
case PROP_MODEM:
g_value_set_object (value, self->priv->modem);
break;
+ case PROP_SKIP_INCOMING_TIMEOUT:
+ g_value_set_boolean (value, self->priv->skip_incoming_timeout);
+ break;
case PROP_SUPPORTS_DIALING_TO_RINGING:
g_value_set_boolean (value, self->priv->supports_dialing_to_ringing);
break;
@@ -1166,8 +1400,6 @@
{
MMBaseCall *self = MM_BASE_CALL (object);
- if (self->priv->in_call_events)
- g_regex_unref (self->priv->in_call_events);
g_free (self->priv->path);
G_OBJECT_CLASS (mm_base_call_parent_class)->finalize (object);
@@ -1214,13 +1446,12 @@
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;
- klass->setup_unsolicited_events = setup_unsolicited_events;
- klass->cleanup_unsolicited_events = cleanup_unsolicited_events;
-
properties[PROP_CONNECTION] =
g_param_spec_object (MM_BASE_CALL_CONNECTION,
@@ -1246,6 +1477,14 @@
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]);
+ properties[PROP_SKIP_INCOMING_TIMEOUT] =
+ g_param_spec_boolean (MM_BASE_CALL_SKIP_INCOMING_TIMEOUT,
+ "Skip incoming timeout",
+ "There is no need to setup a timeout for incoming calls",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_SKIP_INCOMING_TIMEOUT, properties[PROP_SKIP_INCOMING_TIMEOUT]);
+
properties[PROP_SUPPORTS_DIALING_TO_RINGING] =
g_param_spec_boolean (MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING,
"Dialing to ringing",
diff --git a/src/mm-base-call.h b/src/mm-base-call.h
index edb4aae..973bcaf 100644
--- a/src/mm-base-call.h
+++ b/src/mm-base-call.h
@@ -11,6 +11,7 @@
* GNU General Public License for more details:
*
* Copyright (C) 2015 Riccardo Vangelisti <riccardo.vangelisti@sadel.it>
+ * Copyright (C) 2019 Purism SPC
*/
#ifndef MM_BASE_CALL_H
@@ -39,6 +40,7 @@
#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_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"
@@ -66,6 +68,15 @@
GAsyncResult *res,
GError **error);
+ /* Deflect the call */
+ void (* deflect) (MMBaseCall *self,
+ const gchar *number,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* deflect_finish) (MMBaseCall *self,
+ GAsyncResult *res,
+ GError **error);
+
/* Hangup the call */
void (* hangup) (MMBaseCall *self,
GAsyncReadyCallback callback,
@@ -82,28 +93,6 @@
gboolean (* send_dtmf_finish) (MMBaseCall *self,
GAsyncResult *res,
GError **error);
-
- /* Setup/cleanup in-call unsolicited events */
- gboolean (* setup_unsolicited_events) (MMBaseCall *self,
- GError **error);
- gboolean (* cleanup_unsolicited_events) (MMBaseCall *self,
- GError **error);
-
- /* Setup/cleanup audio channel */
- void (* setup_audio_channel) (MMBaseCall *self,
- GAsyncReadyCallback callback,
- gpointer user_data);
- gboolean (* setup_audio_channel_finish) (MMBaseCall *self,
- GAsyncResult *res,
- MMPort **audio_port,
- MMCallAudioFormat **audio_format,
- GError **error);
- void (* cleanup_audio_channel) (MMBaseCall *self,
- GAsyncReadyCallback callback,
- gpointer user_data);
- gboolean (* cleanup_audio_channel_finish) (MMBaseCall *self,
- GAsyncResult *res,
- GError **error);
};
GType mm_base_call_get_type (void);
@@ -111,16 +100,36 @@
/* This one can be overriden by plugins */
MMBaseCall *mm_base_call_new (MMBaseModem *modem,
MMCallDirection direction,
- const gchar *number);
+ 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);
-const gchar *mm_base_call_get_path (MMBaseCall *self);
+void mm_base_call_export (MMBaseCall *self);
+void mm_base_call_unexport (MMBaseCall *self);
+
+const gchar *mm_base_call_get_path (MMBaseCall *self);
+const gchar *mm_base_call_get_number (MMBaseCall *self);
+MMCallDirection mm_base_call_get_direction (MMBaseCall *self);
+MMCallState mm_base_call_get_state (MMBaseCall *self);
+guint mm_base_call_get_index (MMBaseCall *self);
+gboolean mm_base_call_get_multiparty (MMBaseCall *self);
+
+void mm_base_call_set_number (MMBaseCall *self,
+ const gchar *number);
+void mm_base_call_set_index (MMBaseCall *self,
+ guint index);
+void mm_base_call_set_multiparty (MMBaseCall *self,
+ gboolean multiparty);
void mm_base_call_change_state (MMBaseCall *self,
MMCallState new_state,
MMCallStateReason reason);
+void mm_base_call_change_audio_settings (MMBaseCall *self,
+ MMPort *audio_port,
+ MMCallAudioFormat *audio_format);
+
void mm_base_call_received_dtmf (MMBaseCall *self,
const gchar *dtmf);
diff --git a/src/mm-base-modem-at.c b/src/mm-base-modem-at.c
index 05b3be4..b4e0660 100644
--- a/src/mm-base-modem-at.c
+++ b/src/mm-base-modem-at.c
@@ -301,7 +301,7 @@
ctx->current->command,
ctx->current->timeout,
FALSE,
- FALSE,
+ ctx->current->allow_cached,
ctx->cancellable,
(GAsyncReadyCallback)at_sequence_parse_response,
ctx);
diff --git a/src/mm-base-sim.c b/src/mm-base-sim.c
index 72ad2bb..1cb2459 100644
--- a/src/mm-base-sim.c
+++ b/src/mm-base-sim.c
@@ -122,7 +122,7 @@
command = g_strdup_printf ("+CPWD=\"SC\",\"%s\",\"%s\"",
old_pin,
new_pin);
- mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
+ mm_base_modem_at_command (self->priv->modem,
command,
3,
FALSE,
@@ -291,7 +291,7 @@
command = g_strdup_printf ("+CLCK=\"SC\",%d,\"%s\"",
enabled ? 1 : 0,
pin);
- mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
+ mm_base_modem_at_command (self->priv->modem,
command,
3,
FALSE,
@@ -461,7 +461,7 @@
g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin) :
g_strdup_printf ("+CPIN=\"%s\"", pin));
- mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
+ mm_base_modem_at_command (self->priv->modem,
command,
3,
FALSE,
@@ -1041,7 +1041,7 @@
/* READ BINARY of EFiccid (ICC Identification) ETSI TS 102.221 section 13.2 */
mm_base_modem_at_command (
- MM_BASE_MODEM (self->priv->modem),
+ self->priv->modem,
"+CRSM=176,12258,0,0,10",
20,
FALSE,
@@ -1108,7 +1108,7 @@
mm_dbg ("loading IMSI...");
mm_base_modem_at_command (
- MM_BASE_MODEM (self->priv->modem),
+ self->priv->modem,
"+CIMI",
3,
FALSE,
@@ -1227,7 +1227,7 @@
/* READ BINARY of EFad (Administrative Data) ETSI 51.011 section 10.3.18 */
mm_base_modem_at_command (
- MM_BASE_MODEM (self->priv->modem),
+ self->priv->modem,
"+CRSM=176,28589,0,0,4",
10,
FALSE,
@@ -1322,7 +1322,7 @@
/* READ BINARY of EFspn (Service Provider Name) ETSI 51.011 section 10.3.11 */
mm_base_modem_at_command (
- MM_BASE_MODEM (self->priv->modem),
+ self->priv->modem,
"+CRSM=176,28486,0,0,17",
10,
FALSE,
diff --git a/src/mm-base-sms.c b/src/mm-base-sms.c
index 25a0261..6aeabf1 100644
--- a/src/mm-base-sms.c
+++ b/src/mm-base-sms.c
@@ -1,3 +1,4 @@
+
/* -*- 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
@@ -914,7 +915,11 @@
ctx = g_task_get_task_data (task);
- /* Send the actual message data */
+ /* Send the actual message data.
+ * We send the data as 'raw' data because we do NOT want it to
+ * be treated as an AT command (i.e. we don't want it prefixed
+ * with AT+ and suffixed with <CR><LF>), plus, we want it to be
+ * sent right away (not queued after other AT commands). */
mm_base_modem_at_command_raw (ctx->modem,
ctx->msg_data,
10,
@@ -1131,10 +1136,14 @@
ctx = g_task_get_task_data (task);
- /* Send the actual message data */
+ /* Send the actual message data.
+ * We send the data as 'raw' data because we do NOT want it to
+ * be treated as an AT command (i.e. we don't want it prefixed
+ * with AT+ and suffixed with <CR><LF>), plus, we want it to be
+ * sent right away (not queued after other AT commands). */
mm_base_modem_at_command_raw (ctx->modem,
ctx->msg_data,
- 10,
+ 60,
FALSE,
(GAsyncReadyCallback)send_generic_msg_data_ready,
task);
@@ -1205,7 +1214,7 @@
mm_sms_part_get_index ((MMSmsPart *)ctx->current->data));
mm_base_modem_at_command (ctx->modem,
cmd,
- 30,
+ 60,
FALSE,
(GAsyncReadyCallback)send_from_storage_ready,
task);
@@ -1232,7 +1241,7 @@
g_assert (ctx->msg_data != NULL);
mm_base_modem_at_command (ctx->modem,
cmd,
- 30,
+ 60,
FALSE,
(GAsyncReadyCallback)send_generic_ready,
task);
@@ -1498,16 +1507,45 @@
/*****************************************************************************/
-static gboolean
-assemble_sms (MMBaseSms *self,
- GError **error)
+static void
+initialize_sms (MMBaseSms *self)
{
- GList *l;
- guint idx;
+ MMSmsPart *part;
+ guint validity_relative;
+
+ /* Some of the fields of the SMS object may be initialized as soon as we have
+ * one part already available, even if it's not exactly the first one */
+ g_assert (self->priv->parts);
+ part = (MMSmsPart *)(self->priv->parts->data);
+
+ /* Prepare for validity tuple */
+ validity_relative = mm_sms_part_get_validity_relative (part);
+
+ g_object_set (self,
+ "pdu-type", mm_sms_part_get_pdu_type (part),
+ "smsc", mm_sms_part_get_smsc (part),
+ "class", mm_sms_part_get_class (part),
+ "teleservice-id", mm_sms_part_get_cdma_teleservice_id (part),
+ "service-category", mm_sms_part_get_cdma_service_category (part),
+ "number", mm_sms_part_get_number (part),
+ "validity", (validity_relative ?
+ g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_RELATIVE, g_variant_new_uint32 (validity_relative)) :
+ g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_UNKNOWN, g_variant_new_boolean (FALSE))),
+ "timestamp", mm_sms_part_get_timestamp (part),
+ "discharge-timestamp", mm_sms_part_get_discharge_timestamp (part),
+ "delivery-state", mm_sms_part_get_delivery_state (part),
+ NULL);
+}
+
+static gboolean
+assemble_sms (MMBaseSms *self,
+ GError **error)
+{
+ GList *l;
+ guint idx;
MMSmsPart **sorted_parts;
- GString *fulltext;
+ GString *fulltext;
GByteArray *fulldata;
- guint validity_relative;
sorted_parts = g_new0 (MMSmsPart *, self->priv->max_parts);
@@ -1595,30 +1633,15 @@
/* If we got all parts, we also have the first one always */
g_assert (sorted_parts[0] != NULL);
- /* Prepare for validity tuple */
- validity_relative = mm_sms_part_get_validity_relative (sorted_parts[0]);
-
/* If we got everything, assemble the text! */
g_object_set (self,
- "pdu-type", mm_sms_part_get_pdu_type (sorted_parts[0]),
- "text", fulltext->str,
- "data", g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
- fulldata->data,
- fulldata->len * sizeof (guint8),
- TRUE,
- (GDestroyNotify) g_byte_array_unref,
- g_byte_array_ref (fulldata)),
- "smsc", mm_sms_part_get_smsc (sorted_parts[0]),
- "class", mm_sms_part_get_class (sorted_parts[0]),
- "teleservice-id", mm_sms_part_get_cdma_teleservice_id (sorted_parts[0]),
- "service-category", mm_sms_part_get_cdma_service_category (sorted_parts[0]),
- "number", mm_sms_part_get_number (sorted_parts[0]),
- "validity", (validity_relative ?
- g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_RELATIVE, g_variant_new_uint32 (validity_relative)) :
- g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_UNKNOWN, g_variant_new_boolean (FALSE))),
- "timestamp", mm_sms_part_get_timestamp (sorted_parts[0]),
- "discharge-timestamp", mm_sms_part_get_discharge_timestamp (sorted_parts[0]),
- "delivery-state", mm_sms_part_get_delivery_state (sorted_parts[0]),
+ "text", fulltext->str,
+ "data", g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
+ fulldata->data,
+ fulldata->len * sizeof (guint8),
+ TRUE,
+ (GDestroyNotify) g_byte_array_unref,
+ g_byte_array_ref (fulldata)),
/* delivery report request and message reference taken always from the last part */
"message-reference", mm_sms_part_get_message_reference (sorted_parts[self->priv->max_parts - 1]),
"delivery-report-request", mm_sms_part_get_delivery_report_request (sorted_parts[self->priv->max_parts - 1]),
@@ -1690,6 +1713,10 @@
part,
(GCompareFunc)cmp_sms_part_sequence);
+ /* If this is the first part we take, initialize common SMS fields */
+ if (g_list_length (self->priv->parts) == 1)
+ initialize_sms (self);
+
/* We only populate contents when the multipart SMS is complete */
if (mm_base_sms_multipart_is_complete (self)) {
GError *inner_error = NULL;
@@ -1740,6 +1767,9 @@
/* Keep the single part in the list */
self->priv->parts = g_list_prepend (self->priv->parts, part);
+ /* Initialize common SMS fields */
+ initialize_sms (self);
+
if (!assemble_sms (self, error)) {
/* Note: we need to remove the part from the list, as we really didn't
* take it, and therefore the caller is responsible for freeing it. */
diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c
index 1e37666..8815122 100644
--- a/src/mm-bearer-qmi.c
+++ b/src/mm-bearer-qmi.c
@@ -1698,7 +1698,6 @@
} DisconnectStep;
typedef struct {
- MMPort *data;
DisconnectStep step;
gboolean running_ipv4;
@@ -1723,7 +1722,6 @@
g_object_unref (ctx->client_ipv4);
if (ctx->client_ipv6)
g_object_unref (ctx->client_ipv6);
- g_object_unref (ctx->data);
g_slice_free (DisconnectContext, ctx);
}
@@ -1851,9 +1849,10 @@
ctx->client_ipv4,
FALSE,
&self->priv->packet_service_status_ipv4_indication_id);
- cleanup_event_report_unsolicited_events (self,
- ctx->client_ipv4,
- &self->priv->event_report_ipv4_indication_id);
+ if (self->priv->event_report_ipv4_indication_id)
+ cleanup_event_report_unsolicited_events (self,
+ ctx->client_ipv4,
+ &self->priv->event_report_ipv4_indication_id);
input = qmi_message_wds_stop_network_input_new ();
qmi_message_wds_stop_network_input_set_packet_data_handle (input, ctx->packet_data_handle_ipv4, NULL);
@@ -1880,9 +1879,10 @@
ctx->client_ipv6,
FALSE,
&self->priv->packet_service_status_ipv6_indication_id);
- cleanup_event_report_unsolicited_events (self,
- ctx->client_ipv6,
- &self->priv->event_report_ipv6_indication_id);
+ if (self->priv->event_report_ipv6_indication_id)
+ cleanup_event_report_unsolicited_events (self,
+ ctx->client_ipv6,
+ &self->priv->event_report_ipv6_indication_id);
input = qmi_message_wds_stop_network_input_new ();
qmi_message_wds_stop_network_input_set_packet_data_handle (input, ctx->packet_data_handle_ipv6, NULL);
@@ -1948,7 +1948,6 @@
}
ctx = g_slice_new0 (DisconnectContext);
- ctx->data = g_object_ref (self->priv->data);
ctx->client_ipv4 = self->priv->client_ipv4 ? g_object_ref (self->priv->client_ipv4) : NULL;
ctx->packet_data_handle_ipv4 = self->priv->packet_data_handle_ipv4;
ctx->client_ipv6 = self->priv->client_ipv6 ? g_object_ref (self->priv->client_ipv6) : NULL;
diff --git a/src/mm-broadband-bearer.c b/src/mm-broadband-bearer.c
index aa6654e..cce1fb4 100644
--- a/src/mm-broadband-bearer.c
+++ b/src/mm-broadband-bearer.c
@@ -600,21 +600,34 @@
/*****************************************************************************/
/* 3GPP cid selection (sub-step of the 3GPP Connection sequence) */
+typedef enum {
+ CID_SELECTION_3GPP_STEP_FIRST,
+ CID_SELECTION_3GPP_STEP_FORMAT,
+ CID_SELECTION_3GPP_STEP_CURRENT,
+ CID_SELECTION_3GPP_STEP_SELECT_CONTEXT,
+ CID_SELECTION_3GPP_STEP_INITIALIZE_CONTEXT,
+ CID_SELECTION_3GPP_STEP_LAST,
+} CidSelection3gppStep;
+
typedef struct {
- MMBroadbandBearer *self;
- MMBaseModem *modem;
- MMPortSerialAt *primary;
- GCancellable *cancellable;
- guint cid;
- guint max_cid;
- gboolean use_existing_cid;
- MMBearerIpFamily ip_family;
+ CidSelection3gppStep step;
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ GCancellable *cancellable;
+ GList *context_list;
+ GList *context_format_list;
+ guint cid;
+ gboolean cid_reused;
+ gboolean cid_overwritten;
+ MMBearerIpFamily ip_family;
+ const gchar *pdp_type;
} CidSelection3gppContext;
static void
cid_selection_3gpp_context_free (CidSelection3gppContext *ctx)
{
- g_object_unref (ctx->self);
+ mm_3gpp_pdp_context_format_list_free (ctx->context_format_list);
+ mm_3gpp_pdp_context_list_free (ctx->context_list);
g_object_unref (ctx->modem);
g_object_unref (ctx->primary);
g_object_unref (ctx->cancellable);
@@ -633,211 +646,129 @@
return (guint) (cid < 0 ? 0 : cid);
}
+static void cid_selection_3gpp_context_step (GTask *task);
+
static void
-initialize_pdp_context_ready (MMBaseModem *modem,
- GAsyncResult *res,
- GTask *task)
+cgdcont_set_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
{
CidSelection3gppContext *ctx;
GError *error = NULL;
- ctx = (CidSelection3gppContext *) g_task_get_task_data (task);
+ ctx = g_task_get_task_data (task);
+
mm_base_modem_at_command_full_finish (modem, res, &error);
if (error) {
- mm_warn ("Couldn't initialize PDP context with our APN: '%s'", error->message);
+ mm_warn ("Couldn't initialize context: '%s'", error->message);
g_task_return_error (task, error);
- } else
- g_task_return_int (task, (gssize) ctx->cid);
- g_object_unref (task);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->step++;
+ cid_selection_3gpp_context_step (task);
}
static void
-find_cid_ready (MMBaseModem *modem,
- GAsyncResult *res,
- GTask *task)
+cid_selection_3gpp_initialize_context (GTask *task)
{
- gchar *apn;
- gchar *command;
- GError *error = NULL;
- const gchar *pdp_type;
+ MMBroadbandBearer *self;
CidSelection3gppContext *ctx;
+ const gchar *apn;
+ gchar *quoted_apn;
+ gchar *cmd;
- ctx = (CidSelection3gppContext *) g_task_get_task_data (task);
-
- mm_base_modem_at_sequence_full_finish (modem, res, NULL, &error);
- if (error) {
- mm_warn ("Couldn't find best CID to use: '%s'", error->message);
- g_task_return_error (task, error);
- g_object_unref (task);
- return;
- }
-
- /* Validate requested PDP type */
- pdp_type = mm_3gpp_get_pdp_type_from_ip_family (ctx->ip_family);
- if (!pdp_type) {
- gchar * str;
-
- str = mm_bearer_ip_family_build_string_from_mask (ctx->ip_family);
- g_task_return_new_error (task,
- MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
- "Unsupported IP type requested: '%s'", str);
- g_object_unref (task);
- g_free (str);
- return;
- }
-
- /* If no error reported, we must have a valid CID to be used */
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
g_assert (ctx->cid != 0);
+ g_assert (!ctx->cid_reused);
- /* If there's already a PDP context defined, just use it */
- if (ctx->use_existing_cid) {
- g_task_return_int (task, (gssize) ctx->cid);
- g_object_unref (task);
- return;
- }
+ /* Initialize a PDP context with our APN and PDP type */
+ apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ mm_dbg ("%s context with APN '%s' and PDP type '%s'",
+ ctx->cid_overwritten ? "Overwriting" : "Initializing",
+ apn, ctx->pdp_type);
+ quoted_apn = mm_port_serial_at_quote_string (apn);
+ cmd = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s", ctx->cid, ctx->pdp_type, quoted_apn);
+ g_free (quoted_apn);
- /* Otherwise, initialize a new PDP context with our APN */
- apn = mm_port_serial_at_quote_string (mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self))));
- command = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s", ctx->cid, pdp_type, apn);
- g_free (apn);
mm_base_modem_at_command_full (ctx->modem,
ctx->primary,
- command,
+ cmd,
3,
FALSE,
FALSE, /* raw */
NULL, /* cancellable */
- (GAsyncReadyCallback) initialize_pdp_context_ready,
+ (GAsyncReadyCallback) cgdcont_set_ready,
task);
- g_free (command);
+ g_free (cmd);
}
-static gboolean
-parse_cid_range (MMBaseModem *modem,
- CidSelection3gppContext *ctx,
- const gchar *command,
- const gchar *response,
- gboolean last_command,
- const GError *error,
- GVariant **result,
- GError **result_error)
+static void
+cid_selection_3gpp_select_context (GTask *task)
{
- GError *inner_error = NULL;
- GList *formats, *l;
- guint cid;
+ MMBroadbandBearer *self;
+ CidSelection3gppContext *ctx;
- /* If cancelled, set result error */
- if (g_cancellable_is_cancelled (ctx->cancellable)) {
- g_set_error (result_error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED,
- "Connection setup operation has been cancelled");
- return FALSE;
- }
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
- if (error) {
- mm_dbg ("Unexpected +CGDCONT error: '%s'", error->message);
- mm_dbg ("Defaulting to CID=1");
- ctx->cid = 1;
- return TRUE;
- }
+ ctx->cid = mm_3gpp_select_best_cid (mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (self))),
+ ctx->ip_family,
+ ctx->context_list,
+ ctx->context_format_list,
+ &ctx->cid_reused,
+ &ctx->cid_overwritten);
- formats = mm_3gpp_parse_cgdcont_test_response (response, &inner_error);
- if (inner_error) {
- mm_dbg ("Error parsing +CGDCONT test response: '%s'", inner_error->message);
- mm_dbg ("Defaulting to CID=1");
- g_error_free (inner_error);
- ctx->cid = 1;
- return TRUE;
- }
+ /* At this point, CID must ALWAYS be set */
+ g_assert (ctx->cid);
- cid = 0;
- for (l = formats; l; l = g_list_next (l)) {
- MM3gppPdpContextFormat *format = l->data;
-
- /* Found exact PDP type? */
- if (format->pdp_type == ctx->ip_family) {
- gchar *ip_family_str;
-
- ip_family_str = mm_bearer_ip_family_build_string_from_mask (format->pdp_type);
- if (ctx->max_cid < format->max_cid) {
- cid = ctx->max_cid + 1;
- mm_dbg ("Using empty CID %u with PDP type '%s'", cid, ip_family_str);
- } else {
- cid = ctx->max_cid;
- mm_dbg ("Re-using CID %u (max) with PDP type '%s'", cid, ip_family_str);
- }
- g_free (ip_family_str);
- break;
- }
- }
-
- mm_3gpp_pdp_context_format_list_free (formats);
-
- if (cid == 0) {
- mm_dbg ("Defaulting to CID=1");
- cid = 1;
- }
-
- ctx->cid = cid;
- return TRUE;
+ /* If we're reusing an existing one, no need to initialize it, so we're done */
+ if (ctx->cid_reused) {
+ g_assert (!ctx->cid_overwritten);
+ ctx->step = CID_SELECTION_3GPP_STEP_LAST;
+ } else
+ ctx->step++;
+ cid_selection_3gpp_context_step (task);
}
-static gboolean
-parse_pdp_list (MMBaseModem *modem,
- CidSelection3gppContext *ctx,
- const gchar *command,
- const gchar *response,
- gboolean last_command,
- const GError *error,
- GVariant **result,
- GError **result_error)
+static void
+cgdcont_query_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
{
- GError *inner_error = NULL;
- GList *pdp_list;
- GList *l;
- guint cid;
+ CidSelection3gppContext *ctx;
+ GError *error = NULL;
+ const gchar *response;
+ GList *l;
- /* If cancelled, set result error */
- if (g_cancellable_is_cancelled (ctx->cancellable)) {
- g_set_error (result_error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED,
- "Connection setup operation has been cancelled");
- return FALSE;
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response) {
+ /* Ignore errors */
+ mm_dbg ("Failed checking currently defined contexts: %s", error->message);
+ g_clear_error (&error);
+ goto out;
}
- /* Some Android phones don't support querying existing PDP contexts,
- * but will accept setting the APN. So if CGDCONT? isn't supported,
- * just ignore that error and hope for the best. (bgo #637327)
- */
- if (g_error_matches (error,
- MM_MOBILE_EQUIPMENT_ERROR,
- MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED)) {
- mm_dbg ("Querying PDP context list is unsupported");
- return FALSE;
+ /* Build context list */
+ ctx->context_list = mm_3gpp_parse_cgdcont_read_response (response, &error);
+ if (!ctx->context_list) {
+ if (error) {
+ mm_dbg ("Failed parsing currently defined contexts: %s", error->message);
+ g_clear_error (&error);
+ } else
+ mm_dbg ("No contexts currently defined");
+ goto out;
}
- if (error) {
- mm_dbg ("Unexpected +CGDCONT? error: '%s'", error->message);
- return FALSE;
- }
-
- pdp_list = mm_3gpp_parse_cgdcont_read_response (response, &inner_error);
- if (!pdp_list) {
- if (inner_error) {
- mm_dbg ("%s", inner_error->message);
- g_error_free (inner_error);
- } else {
- /* No predefined PDP contexts found */
- mm_dbg ("No PDP contexts found");
- }
- return FALSE;
- }
-
- cid = 0;
-
/* Show all found PDP contexts in debug log */
- mm_dbg ("Found '%u' PDP contexts", g_list_length (pdp_list));
- for (l = pdp_list; l; l = g_list_next (l)) {
+ mm_dbg ("Found '%u' PDP contexts", g_list_length (ctx->context_list));
+ for (l = ctx->context_list; l; l = g_list_next (l)) {
MM3gppPdpContext *pdp = l->data;
- gchar *ip_family_str;
+ gchar *ip_family_str;
ip_family_str = mm_bearer_ip_family_build_string_from_mask (pdp->pdp_type);
mm_dbg (" PDP context [cid=%u] [type='%s'] [apn='%s']",
@@ -847,56 +778,121 @@
g_free (ip_family_str);
}
- /* Look for the exact PDP context we want */
- for (l = pdp_list; l; l = g_list_next (l)) {
- MM3gppPdpContext *pdp = l->data;
-
- if (pdp->pdp_type == ctx->ip_family) {
- const gchar *apn;
-
- apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)));
-
- /* First requested, then existing */
- if (mm_3gpp_cmp_apn_name (apn, pdp->apn)) {
- gchar *ip_family_str;
-
- /* Found a PDP context with the same APN and PDP type, we'll use it. */
- ip_family_str = mm_bearer_ip_family_build_string_from_mask (pdp->pdp_type);
- mm_dbg ("Found PDP context with CID %u and PDP type %s for APN '%s'",
- pdp->cid, ip_family_str, apn ? apn : "");
- cid = pdp->cid;
- ctx->use_existing_cid = TRUE;
- g_free (ip_family_str);
- /* In this case, stop searching */
- break;
- }
-
- /* PDP with no APN set? we may use that one if not exact match found */
- if (!pdp->apn || !pdp->apn[0]) {
- mm_dbg ("Found PDP context with CID %u and no APN",
- pdp->cid);
- cid = pdp->cid;
- }
- }
-
- if (ctx->max_cid < pdp->cid)
- ctx->max_cid = pdp->cid;
- }
- mm_3gpp_pdp_context_list_free (pdp_list);
-
- if (cid > 0) {
- ctx->cid = cid;
- return TRUE;
- }
-
- return FALSE;
+out:
+ ctx->step++;
+ cid_selection_3gpp_context_step (task);
}
-static const MMBaseModemAtCommand find_cid_sequence[] = {
- { "+CGDCONT?", 3, FALSE, (MMBaseModemAtResponseProcessor) parse_pdp_list },
- { "+CGDCONT=?", 3, TRUE, (MMBaseModemAtResponseProcessor) parse_cid_range },
- { NULL }
-};
+static void
+cid_selection_3gpp_query_current (GTask *task)
+{
+ CidSelection3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_dbg ("Checking currently defined contexts...");
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "+CGDCONT?",
+ 3,
+ FALSE, /* cached */
+ FALSE, /* raw */
+ ctx->cancellable,
+ (GAsyncReadyCallback)cgdcont_query_ready,
+ task);
+}
+
+static void
+cgdcont_test_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ CidSelection3gppContext *ctx;
+ GError *error = NULL;
+ const gchar *response;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response) {
+ /* Ignore errors */
+ mm_dbg ("Failed checking context definition format: %s", error->message);
+ g_clear_error (&error);
+ goto out;
+ }
+
+ ctx->context_format_list = mm_3gpp_parse_cgdcont_test_response (response, &error);
+ if (error) {
+ mm_dbg ("Error parsing +CGDCONT test response: '%s'", error->message);
+ g_clear_error (&error);
+ goto out;
+ }
+
+out:
+ ctx->step++;
+ cid_selection_3gpp_context_step (task);
+}
+
+static void
+cid_selection_3gpp_query_format (GTask *task)
+{
+ CidSelection3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_dbg ("Checking context definition format...");
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "+CGDCONT=?",
+ 3,
+ TRUE, /* cached */
+ FALSE, /* raw */
+ ctx->cancellable,
+ (GAsyncReadyCallback)cgdcont_test_ready,
+ task);
+}
+
+static void
+cid_selection_3gpp_context_step (GTask *task)
+{
+ CidSelection3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ /* Abort if we've been cancelled */
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ switch (ctx->step) {
+ case CID_SELECTION_3GPP_STEP_FIRST:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case CID_SELECTION_3GPP_STEP_FORMAT:
+ cid_selection_3gpp_query_format (task);
+ return;
+
+ case CID_SELECTION_3GPP_STEP_CURRENT:
+ cid_selection_3gpp_query_current (task);
+ return;
+
+ case CID_SELECTION_3GPP_STEP_SELECT_CONTEXT:
+ cid_selection_3gpp_select_context (task);
+ return;
+
+ case CID_SELECTION_3GPP_STEP_INITIALIZE_CONTEXT:
+ cid_selection_3gpp_initialize_context (task);
+ return;
+
+ case CID_SELECTION_3GPP_STEP_LAST:
+ g_assert (ctx->cid != 0);
+ g_task_return_int (task, (gssize) ctx->cid);
+ g_object_unref (task);
+ return;
+ }
+}
static void
cid_selection_3gpp (MMBroadbandBearer *self,
@@ -909,25 +905,30 @@
GTask *task;
CidSelection3gppContext *ctx;
+ task = g_task_new (self, cancellable, callback, user_data);
+
ctx = g_slice_new0 (CidSelection3gppContext);
- ctx->self = g_object_ref (self);
+ ctx->step = CID_SELECTION_3GPP_STEP_FIRST;
ctx->modem = g_object_ref (modem);
ctx->primary = g_object_ref (primary);
ctx->cancellable = g_object_ref (cancellable);
ctx->ip_family = select_bearer_ip_family (self);
-
- task = g_task_new (self, cancellable, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify) cid_selection_3gpp_context_free);
- mm_dbg ("Looking for best CID...");
- mm_base_modem_at_sequence_full (ctx->modem,
- ctx->primary,
- find_cid_sequence,
- ctx, /* also passed as response processor context */
- NULL, /* response_processor_context_free */
- NULL, /* cancellable */
- (GAsyncReadyCallback) find_cid_ready,
- task);
+ /* Validate PDP type */
+ ctx->pdp_type = mm_3gpp_get_pdp_type_from_ip_family (ctx->ip_family);
+ if (!ctx->pdp_type) {
+ gchar * str;
+
+ str = mm_bearer_ip_family_build_string_from_mask (ctx->ip_family);
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Unsupported IP type requested: '%s'", str);
+ g_object_unref (task);
+ g_free (str);
+ return;
+ }
+
+ cid_selection_3gpp_context_step (task);
}
/*****************************************************************************/
@@ -1692,7 +1693,7 @@
mm_base_modem_at_command_full (ctx->modem,
ctx->primary,
ctx->cgact_command,
- 10,
+ 45,
FALSE,
FALSE, /* raw */
NULL, /* cancellable */
@@ -1711,7 +1712,7 @@
mm_base_modem_at_command_full (ctx->modem,
ctx->secondary,
ctx->cgact_command,
- 10,
+ 45,
FALSE,
FALSE, /* raw */
NULL, /* cancellable */
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index 1cc4ec2..10d831a 100644
--- a/src/mm-broadband-modem-mbim.c
+++ b/src/mm-broadband-modem-mbim.c
@@ -884,8 +884,8 @@
/* Unlock required loading (Modem interface) */
typedef struct {
- guint n_ready_status_checks;
MbimDevice *device;
+ gboolean last_attempt;
} LoadUnlockRequiredContext;
static void
@@ -1008,29 +1008,28 @@
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
+ goto out;
}
+
/* Need to retry? */
- else if (ready_state == MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED ||
- ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED) {
- if (--ctx->n_ready_status_checks == 0) {
- /* All retries consumed, issue error */
+ if (ready_state == MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED ||
+ ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED) {
+ /* All retries consumed? issue error */
+ if (ctx->last_attempt) {
if (ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED)
- g_task_return_error (
- task,
- mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED));
+ g_task_return_error (task, mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED));
else
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_FAILED,
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Error waiting for SIM to get initialized");
- g_object_unref (task);
- } else {
- /* Retry */
- g_timeout_add_seconds (1, (GSourceFunc)wait_for_sim_ready, task);
- }
+ } else
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_RETRY,
+ "SIM not ready yet (retry)");
+ g_object_unref (task);
+ goto out;
}
+
/* Initialized but locked? */
- else if (ready_state == MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED) {
+ if (ready_state == MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED) {
MbimMessage *message;
/* Query which lock is to unlock */
@@ -1042,14 +1041,19 @@
(GAsyncReadyCallback)pin_query_ready,
task);
mbim_message_unref (message);
+ goto out;
}
- /* Initialized but locked? */
- else if (ready_state == MBIM_SUBSCRIBER_READY_STATE_INITIALIZED) {
+
+ /* Initialized! */
+ if (ready_state == MBIM_SUBSCRIBER_READY_STATE_INITIALIZED) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
- } else
- g_assert_not_reached ();
+ goto out;
+ }
+ g_assert_not_reached ();
+
+out:
if (response)
mbim_message_unref (response);
}
@@ -1074,6 +1078,7 @@
static void
modem_load_unlock_required (MMIfaceModem *self,
+ gboolean last_attempt,
GAsyncReadyCallback callback,
gpointer user_data)
{
@@ -1086,7 +1091,7 @@
ctx = g_slice_new (LoadUnlockRequiredContext);
ctx->device = g_object_ref (device);
- ctx->n_ready_status_checks = 10;
+ ctx->last_attempt = last_attempt;
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_required_context_free);
@@ -3938,24 +3943,41 @@
{
MbimMessage *response;
GError *error = NULL;
- MbimNwError nw_error;
response = mbim_device_command_finish (device, res, &error);
+ /* According to Mobile Broadband Interface Model specification 1.0,
+ * Errata 1, table 10.5.9.8: Status codes for MBIM_CID_REGISTER_STATE,
+ * NwError field of MBIM_REGISTRATION_STATE_INFO structure is valid
+ * if and only if MBIM_SET_REGISTRATION_STATE response status code equals
+ * MBIM_STATUS_FAILURE.
+ * Therefore it only makes sense to parse this value if MBIM_STATUS_FAILURE
+ * result is returned in response, contrary to usual "success" code.
+ * However, some modems do not set this value to 0 when registered,
+ * causing ModemManager to drop to idle state, while modem itself is
+ * registered.
+ * Also NwError "0" is defined in 3GPP TS 24.008 as "Unknown error",
+ * not "No error", making it unsuitable as condition for registration check.
+ */
if (response &&
- mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
- mbim_message_register_state_response_parse (
- response,
- &nw_error,
- NULL, /* ®ister_state */
- NULL, /* register_mode */
- NULL, /* available_data_classes */
- NULL, /* current_cellular_class */
- NULL, /* provider_id */
- NULL, /* provider_name */
- NULL, /* roaming_text */
- NULL, /* registration_flag */
- NULL)) {
- if (nw_error)
+ !mbim_message_response_get_result (response,
+ MBIM_MESSAGE_TYPE_COMMAND_DONE,
+ &error) &&
+ g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FAILURE)) {
+ MbimNwError nw_error;
+
+ g_clear_error (&error);
+ if (mbim_message_register_state_response_parse (
+ response,
+ &nw_error,
+ NULL, /* ®ister_state */
+ NULL, /* register_mode */
+ NULL, /* available_data_classes */
+ NULL, /* current_cellular_class */
+ NULL, /* provider_id */
+ NULL, /* provider_name */
+ NULL, /* roaming_text */
+ NULL, /* registration_flag */
+ &error))
error = mm_mobile_equipment_error_from_mbim_nw_error (nw_error);
}
@@ -4452,7 +4474,7 @@
{
GTask *task = NULL;
MMModem3gppUssdSessionState ussd_state = MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE;
- GByteArray *bytearray = NULL;
+ GByteArray *bytearray;
gchar *converted = NULL;
GError *error = NULL;
@@ -4462,8 +4484,9 @@
self->priv->pending_ussd_action = NULL;
}
- if (data_size)
- bytearray = g_byte_array_append (g_byte_array_new (), data, data_size);
+ bytearray = g_byte_array_new ();
+ if (data && data_size)
+ bytearray = g_byte_array_append (bytearray, data, data_size);
switch (ussd_response) {
case MBIM_USSD_RESPONSE_NO_ACTION_REQUIRED:
@@ -4520,8 +4543,7 @@
mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self), ussd_state);
- if (bytearray)
- g_byte_array_unref (bytearray);
+ g_byte_array_unref (bytearray);
/* Complete the pending action */
if (task) {
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index 7356a5e..6329c21 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -648,6 +648,7 @@
LoadUnlockRequiredStep step;
QmiClient *dms;
QmiClient *uim;
+ gboolean last_attempt;
} LoadUnlockRequiredContext;
static MMModemLock
@@ -903,10 +904,13 @@
GAsyncResult *res,
GTask *task)
{
+ LoadUnlockRequiredContext *ctx;
QmiMessageUimGetCardStatusOutput *output;
GError *error = NULL;
MMModemLock lock = MM_MODEM_LOCK_UNKNOWN;
+ ctx = g_task_get_task_data (task);
+
output = qmi_client_uim_get_card_status_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
@@ -919,6 +923,15 @@
&lock,
NULL, NULL, NULL, NULL,
&error)) {
+ /* The device may report a SIM NOT INSERTED error if we're querying the
+ * card status soon after power on. We'll let the Modem interface generic
+ * logic retry loading the info a bit later if that's the case. This will
+ * make device detection slower when there's really no SIM card, but there's
+ * no clear better way to handle it :/ */
+ if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) && !ctx->last_attempt) {
+ g_clear_error (&error);
+ g_set_error (&error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY, "No card found (retry)");
+ }
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else
@@ -1101,6 +1114,7 @@
static void
modem_load_unlock_required (MMIfaceModem *self,
+ gboolean last_attempt,
GAsyncReadyCallback callback,
gpointer user_data)
{
@@ -1109,6 +1123,7 @@
ctx = g_new0 (LoadUnlockRequiredContext, 1);
ctx->step = LOAD_UNLOCK_REQUIRED_STEP_FIRST;
+ ctx->last_attempt = last_attempt;
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, g_free);
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 0a51b4b..3cbf513 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -13,7 +13,8 @@
* Copyright (C) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2012 Red Hat, Inc.
* Copyright (C) 2011 - 2012 Google, Inc.
- * Copyright (C) 2015 - Marco Bascetta <marco.bascetta@sadel.it>
+ * Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
+ * Copyright (C) 2019 Purism SPC
*/
#include <config.h>
@@ -120,6 +121,7 @@
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
PROP_MODEM_SIM_HOT_SWAP_CONFIGURED,
PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
+ PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
PROP_MODEM_CARRIER_CONFIG_MAPPING,
PROP_FLOW_CONTROL,
PROP_LAST
@@ -223,8 +225,10 @@
/*<--- Modem Voice interface --->*/
/* Properties */
- GObject *modem_voice_dbus_skeleton;
+ GObject *modem_voice_dbus_skeleton;
MMCallList *modem_voice_call_list;
+ gboolean periodic_call_list_check_disabled;
+ gboolean clcc_supported;
/*<--- Modem Time interface --->*/
/* Properties */
@@ -1316,6 +1320,7 @@
static void
modem_load_unlock_required (MMIfaceModem *self,
+ gboolean last_attempt,
GAsyncReadyCallback callback,
gpointer user_data)
{
@@ -1334,7 +1339,7 @@
mm_dbg ("checking if unlock required...");
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CPIN?",
- 3,
+ 10,
FALSE,
(GAsyncReadyCallback)cpin_query_ready,
task);
@@ -1957,8 +1962,8 @@
* try the other command if the first one fails.
*/
static const MMBaseModemAtCommand signal_quality_csq_sequence[] = {
- { "+CSQ", 3, TRUE, response_processor_string_ignore_at_errors },
- { "+CSQ?", 3, TRUE, response_processor_string_ignore_at_errors },
+ { "+CSQ", 3, FALSE, response_processor_string_ignore_at_errors },
+ { "+CSQ?", 3, FALSE, response_processor_string_ignore_at_errors },
{ NULL }
};
@@ -3312,28 +3317,28 @@
/* CMER on primary port */
if (!ctx->cmer_primary_done && ctx->cmer_command && ctx->primary) {
- mm_dbg ("Enabling +CIND event reporting in primary port...");
+ mm_dbg ("%s +CIND event reporting in primary port...", ctx->enable ? "Enabling" : "Disabling");
ctx->cmer_primary_done = TRUE;
command = ctx->cmer_command;
port = ctx->primary;
}
/* CMER on secondary port */
else if (!ctx->cmer_secondary_done && ctx->cmer_command && ctx->secondary) {
- mm_dbg ("Enabling +CIND event reporting in secondary port...");
+ mm_dbg ("%s +CIND event reporting in secondary port...", ctx->enable ? "Enabling" : "Disabling");
ctx->cmer_secondary_done = TRUE;
command = ctx->cmer_command;
port = ctx->secondary;
}
/* CGEREP on primary port */
else if (!ctx->cgerep_primary_done && ctx->cgerep_command && ctx->primary) {
- mm_dbg ("Enabling +CGEV event reporting in primary port...");
+ mm_dbg ("%s +CGEV event reporting in primary port...", ctx->enable ? "Enabling" : "Disabling");
ctx->cgerep_primary_done = TRUE;
command = ctx->cgerep_command;
port = ctx->primary;
}
/* CGEREP on secondary port */
else if (!ctx->cgerep_secondary_done && ctx->cgerep_command && ctx->secondary) {
- mm_dbg ("Enabling +CGEV event reporting in secondary port...");
+ mm_dbg ("%s +CGEV event reporting in secondary port...", ctx->enable ? "Enabling" : "Disabling");
ctx->cgerep_secondary_done = TRUE;
port = ctx->secondary;
command = ctx->cgerep_command;
@@ -6959,7 +6964,7 @@
0, 0, NULL);
g_assert (r);
- if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) {
+ if (!g_regex_match (r, response, 0, &match_info)) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
@@ -7200,6 +7205,24 @@
}
static void
+clcc_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ /* +CLCC supported unless we got any error response */
+ self->priv->clcc_supported = !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+
+ /* If +CLCC unsupported we disable polling in the parent directly */
+ g_object_set (self,
+ MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, !self->priv->clcc_supported,
+ NULL);
+
+ /* ATH command is supported; assume we have full voice capabilities */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
ath_format_check_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
@@ -7213,9 +7236,13 @@
return;
}
- /* ATH command is supported; assume we have full voice capabilities */
- g_task_return_boolean (task, TRUE);
- g_object_unref (task);
+ /* Also check if +CLCC is supported */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CLCC=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)clcc_format_check_ready,
+ task);
}
static void
@@ -7239,6 +7266,149 @@
}
/*****************************************************************************/
+/* Load full list of calls (Voice interface) */
+
+static gboolean
+modem_voice_load_call_list_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GList **out_call_info_list,
+ GError **error)
+{
+ GList *call_info_list;
+ GError *inner_error = NULL;
+
+ call_info_list = g_task_propagate_pointer (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_assert (!call_info_list);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *out_call_info_list = call_info_list;
+ return TRUE;
+}
+
+static void
+clcc_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ GList *call_info_list = NULL;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response || !mm_3gpp_parse_clcc_response (response, &call_info_list, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, call_info_list, (GDestroyNotify)mm_3gpp_call_info_list_free);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_load_call_list (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CLCC",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)clcc_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup/cleanup voice related in-call unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_setup_cleanup_in_call_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+in_call_event_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModem *self)
+{
+ MMCallInfo call_info;
+ gchar *str;
+
+ call_info.index = 0;
+ call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
+ call_info.state = MM_CALL_STATE_TERMINATED;
+ call_info.number = NULL;
+
+ str = g_match_info_fetch (info, 1);
+ mm_dbg ("Call terminated: %s", str);
+ g_free (str);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+set_voice_in_call_unsolicited_events_handlers (MMIfaceModemVoice *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMPortSerialAt *ports[2];
+ GRegex *in_call_event_regex;
+ guint i;
+ GTask *task;
+
+ in_call_event_regex = g_regex_new ("\\r\\n(NO CARRIER|BUSY|NO ANSWER|NO DIALTONE)\\r\\n$",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Enable unsolicited events in given port */
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ mm_dbg ("(%s) %s voice in-call unsolicited events handlers",
+ mm_port_get_device (MM_PORT (ports[i])),
+ enable ? "Setting" : "Removing");
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ in_call_event_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn) in_call_event_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+
+ g_regex_unref (in_call_event_regex);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_setup_in_call_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ set_voice_in_call_unsolicited_events_handlers (self, TRUE, callback, user_data);
+}
+
+static void
+modem_voice_cleanup_in_call_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ set_voice_in_call_unsolicited_events_handlers (self, FALSE, callback, user_data);
+}
+
+/*****************************************************************************/
/* Setup/cleanup voice related unsolicited events (Voice interface) */
static gboolean
@@ -7250,40 +7420,76 @@
}
static void
-ring_received (MMPortSerialAt *port,
- GMatchInfo *info,
+ccwa_received (MMPortSerialAt *port,
+ GMatchInfo *info,
MMBroadbandModem *self)
{
- mm_dbg ("Ringing");
- mm_iface_modem_voice_report_incoming_call (MM_IFACE_MODEM_VOICE (self), NULL);
+ MMCallInfo call_info;
+
+ call_info.index = 0;
+ call_info.direction = MM_CALL_DIRECTION_INCOMING;
+ call_info.state = MM_CALL_STATE_WAITING;
+ call_info.number = mm_get_string_unquoted_from_match_info (info, 1);
+
+ mm_dbg ("Call waiting (%s)", call_info.number);
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+
+ g_free (call_info.number);
}
static void
-cring_received (MMPortSerialAt *port,
- GMatchInfo *info,
+ring_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModem *self)
+{
+ MMCallInfo call_info;
+
+ call_info.index = 0;
+ call_info.direction = MM_CALL_DIRECTION_INCOMING;
+ call_info.state = MM_CALL_STATE_RINGING_IN;
+ call_info.number = NULL;
+
+ mm_dbg ("Ringing");
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+cring_received (MMPortSerialAt *port,
+ GMatchInfo *info,
MMBroadbandModem *self)
{
- gchar *str;
+ MMCallInfo call_info;
+ gchar *str;
/* We could have "VOICE" or "DATA". Now consider only "VOICE" */
-
str = mm_get_string_unquoted_from_match_info (info, 1);
mm_dbg ("Ringing (%s)", str);
g_free (str);
- mm_iface_modem_voice_report_incoming_call (MM_IFACE_MODEM_VOICE (self), NULL);
+ call_info.index = 0;
+ call_info.direction = MM_CALL_DIRECTION_INCOMING;
+ call_info.state = MM_CALL_STATE_RINGING_IN;
+ call_info.number = NULL;
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
}
static void
-clip_received (MMPortSerialAt *port,
- GMatchInfo *info,
+clip_received (MMPortSerialAt *port,
+ GMatchInfo *info,
MMBroadbandModem *self)
{
- gchar *str;
+ MMCallInfo call_info;
- str = mm_get_string_unquoted_from_match_info (info, 1);
- mm_iface_modem_voice_report_incoming_call (MM_IFACE_MODEM_VOICE (self), str);
- g_free (str);
+ call_info.index = 0;
+ call_info.direction = MM_CALL_DIRECTION_INCOMING;
+ call_info.state = MM_CALL_STATE_RINGING_IN;
+ call_info.number = mm_get_string_unquoted_from_match_info (info, 1);
+
+ mm_dbg ("Ringing (%s)", call_info.number);
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+
+ g_free (call_info.number);
}
static void
@@ -7296,12 +7502,14 @@
GRegex *cring_regex;
GRegex *ring_regex;
GRegex *clip_regex;
+ GRegex *ccwa_regex;
guint i;
GTask *task;
cring_regex = mm_voice_cring_regex_get ();
ring_regex = mm_voice_ring_regex_get ();
clip_regex = mm_voice_clip_regex_get ();
+ ccwa_regex = mm_voice_ccwa_regex_get ();
ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
@@ -7332,8 +7540,15 @@
enable ? (MMPortSerialAtUnsolicitedMsgFn) clip_received : NULL,
enable ? self : NULL,
NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ ccwa_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn) ccwa_received : NULL,
+ enable ? self : NULL,
+ NULL);
}
+ g_regex_unref (ccwa_regex);
g_regex_unref (clip_regex);
g_regex_unref (cring_regex);
g_regex_unref (ring_regex);
@@ -7362,77 +7577,452 @@
/*****************************************************************************/
/* Enable unsolicited events (CALL indications) (Voice interface) */
-static gboolean
-modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
- GAsyncResult *res,
- GError **error)
-{
- GError *inner_error = NULL;
-
- mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, &inner_error);
- if (inner_error) {
- g_propagate_error (error, inner_error);
- return FALSE;
- }
-
- return TRUE;
-}
-
-static gboolean
-ring_response_processor (MMBaseModem *self,
- gpointer none,
- const gchar *command,
- const gchar *response,
- gboolean last_command,
- const GError *error,
- GVariant **result,
- GError **result_error)
-{
- if (error) {
- /* If we get a not-supported error and we're not in the last command, we
- * won't set 'result_error', so we'll keep on the sequence */
- if (!g_error_matches (error, MM_MESSAGE_ERROR, MM_MESSAGE_ERROR_NOT_SUPPORTED) ||
- last_command)
- *result_error = g_error_copy (error);
-
- return TRUE;
- }
-
- *result = NULL;
- return FALSE;
-}
-
-static const MMBaseModemAtCommand ring_sequence[] = {
- /* Show caller number on RING. */
- { "+CLIP=1", 3, FALSE, ring_response_processor },
- /* Show difference between data call and voice call */
- { "+CRC=1", 3, FALSE, ring_response_processor },
- { NULL }
-};
+typedef struct {
+ gboolean enable;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+ gchar *clip_command;
+ gboolean clip_primary_done;
+ gboolean clip_secondary_done;
+ gchar *crc_command;
+ gboolean crc_primary_done;
+ gboolean crc_secondary_done;
+ gchar *ccwa_command;
+ gboolean ccwa_primary_done;
+ gboolean ccwa_secondary_done;
+} VoiceUnsolicitedEventsContext;
static void
-modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
+voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx)
{
- mm_base_modem_at_sequence (
- MM_BASE_MODEM (self),
- ring_sequence,
- NULL, /* response_processor_context */
- NULL, /* response_processor_context_free */
- callback,
- user_data);
+ g_clear_object (&ctx->secondary);
+ g_clear_object (&ctx->primary);
+ g_free (ctx->clip_command);
+ g_free (ctx->crc_command);
+ g_free (ctx->ccwa_command);
+ g_slice_free (VoiceUnsolicitedEventsContext, ctx);
+}
+
+static gboolean
+modem_voice_enable_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void run_voice_unsolicited_events_setup (GTask *task);
+
+static void
+voice_unsolicited_events_setup_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
+ mm_dbg ("Couldn't %s voice event reporting: '%s'",
+ ctx->enable ? "enable" : "disable",
+ error->message);
+ g_error_free (error);
+ }
+
+ /* Continue on next port/command */
+ run_voice_unsolicited_events_setup (task);
+}
+
+static void
+run_voice_unsolicited_events_setup (GTask *task)
+{
+ MMBroadbandModem *self;
+ VoiceUnsolicitedEventsContext *ctx;
+ MMPortSerialAt *port = NULL;
+ const gchar *command = NULL;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* CLIP on primary port */
+ if (!ctx->clip_primary_done && ctx->clip_command && ctx->primary) {
+ mm_dbg ("%s +CLIP calling line reporting in primary port...", ctx->enable ? "Enabling" : "Disabling");
+ ctx->clip_primary_done = TRUE;
+ command = ctx->clip_command;
+ port = ctx->primary;
+ }
+ /* CLIP on secondary port */
+ else if (!ctx->clip_secondary_done && ctx->clip_command && ctx->secondary) {
+ mm_dbg ("%s +CLIP calling line reporting in primary port...", ctx->enable ? "Enabling" : "Disabling");
+ ctx->clip_secondary_done = TRUE;
+ command = ctx->clip_command;
+ port = ctx->secondary;
+ }
+ /* CRC on primary port */
+ else if (!ctx->crc_primary_done && ctx->crc_command && ctx->primary) {
+ mm_dbg ("%s +CRC extended format of incoming call indications in primary port...", ctx->enable ? "Enabling" : "Disabling");
+ ctx->crc_primary_done = TRUE;
+ command = ctx->crc_command;
+ port = ctx->primary;
+ }
+ /* CRC on secondary port */
+ else if (!ctx->crc_secondary_done && ctx->crc_command && ctx->secondary) {
+ mm_dbg ("%s +CRC extended format of incoming call indications in secondary port...", ctx->enable ? "Enabling" : "Disabling");
+ ctx->crc_secondary_done = TRUE;
+ command = ctx->crc_command;
+ port = ctx->secondary;
+ }
+ /* CCWA on primary port */
+ else if (!ctx->ccwa_primary_done && ctx->ccwa_command && ctx->primary) {
+ mm_dbg ("%s +CCWA call waiting indications in primary port...", ctx->enable ? "Enabling" : "Disabling");
+ ctx->ccwa_primary_done = TRUE;
+ command = ctx->ccwa_command;
+ port = ctx->primary;
+ }
+ /* CCWA on secondary port */
+ else if (!ctx->ccwa_secondary_done && ctx->ccwa_command && ctx->secondary) {
+ mm_dbg ("%s +CCWA call waiting indications in secondary port...", ctx->enable ? "Enabling" : "Disabling");
+ ctx->ccwa_secondary_done = TRUE;
+ command = ctx->ccwa_command;
+ port = ctx->secondary;
+ }
+
+ /* Enable/Disable unsolicited events in given port */
+ if (port && command) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ port,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)voice_unsolicited_events_setup_ready,
+ task);
+ return;
+ }
+
+ /* Fully done now */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+}
+
+static void
+modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ 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 = TRUE;
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+
+ /* enable +CLIP URCs with calling line identity */
+ ctx->clip_command = g_strdup ("+CLIP=1");
+ /* enable +CRING URCs instead of plain RING */
+ ctx->crc_command = g_strdup ("+CRC=1");
+ /* enable +CCWA call waiting indications */
+ ctx->ccwa_command = g_strdup ("+CCWA=1");
+
+ g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
+
+ run_voice_unsolicited_events_setup (task);
+}
+
+static void
+modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+
+ /* disable +CLIP URCs with calling line identity */
+ ctx->clip_command = g_strdup ("+CLIP=0");
+ /* disable +CRING URCs instead of plain RING */
+ ctx->crc_command = g_strdup ("+CRC=0");
+ /* disable +CCWA call waiting indications */
+ ctx->ccwa_command = g_strdup ("+CCWA=0");
+
+ g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
+
+ run_voice_unsolicited_events_setup (task);
}
/*****************************************************************************/
/* Create CALL (Voice interface) */
static MMBaseCall *
-modem_voice_create_call (MMIfaceModemVoice *self,
+modem_voice_create_call (MMIfaceModemVoice *_self,
MMCallDirection direction,
const gchar *number)
{
- return mm_base_call_new (MM_BASE_MODEM (self), direction, number);
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
+
+ return mm_base_call_new (MM_BASE_MODEM (self),
+ direction,
+ number,
+ /* If +CLCC is supported, we want no incoming timeout.
+ * Also, we're able to support detailed call state updates without
+ * additional vendor-specific commands. */
+ self->priv->clcc_supported, /* skip incoming timeout */
+ self->priv->clcc_supported, /* dialing->ringing supported */
+ self->priv->clcc_supported); /* ringing->active supported */
+}
+
+/*****************************************************************************/
+/* Hold and accept (Voice interface) */
+
+static gboolean
+modem_voice_hold_and_accept_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_voice_hold_and_accept (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CHLD=2",
+ 20,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Hangup and accept (Voice interface) */
+
+static gboolean
+modem_voice_hangup_and_accept_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_voice_hangup_and_accept (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CHLD=1",
+ 20,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Hangup all (Voice interface) */
+
+static gboolean
+modem_voice_hangup_all_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_voice_hangup_all (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CHUP",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Join multiparty (Voice interface) */
+
+static gboolean
+modem_voice_join_multiparty_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_voice_join_multiparty (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CHLD=3",
+ 20,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Leave multiparty (Voice interface) */
+
+static gboolean
+modem_voice_leave_multiparty_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+chld_leave_multiparty_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_leave_multiparty (MMIfaceModemVoice *self,
+ MMBaseCall *call,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ guint idx;
+ gchar *cmd;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ idx = mm_base_call_get_index (call);
+ if (!idx) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "unknown call index");
+ g_object_unref (task);
+ return;
+ }
+
+ cmd = g_strdup_printf ("+CHLD=2%u", idx);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback) chld_leave_multiparty_ready,
+ task);
+ g_free (cmd);
+}
+
+/*****************************************************************************/
+/* Transfer (Voice interface) */
+
+static gboolean
+modem_voice_transfer_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_voice_transfer (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CHLD=4",
+ 20,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Call waiting setup (Voice interface) */
+
+static gboolean
+modem_voice_call_waiting_setup_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_voice_call_waiting_setup (MMIfaceModemVoice *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gchar *cmd;
+
+ /* Enabling or disabling the call waiting service will only be allowed when
+ * the modem is registered in the network, and so, CCWA URC handling will
+ * always be setup at this point (as it's part of the modem enabling phase).
+ * So, just enable or disable the service (second field) but leaving URCs
+ * (first field) always enabled. */
+ cmd = g_strdup_printf ("+CCWA=1,%u", enable);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 60,
+ FALSE,
+ callback,
+ user_data);
+ g_free (cmd);
+}
+
+/*****************************************************************************/
+/* Call waiting query (Voice interface) */
+
+static gboolean
+modem_voice_call_waiting_query_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ gboolean *status,
+ GError **error)
+{
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ return mm_3gpp_parse_ccwa_service_query_response (response, status, error);
+}
+
+static void
+modem_voice_call_waiting_query (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* This operation will only be allowed while enabled, and so, CCWA URC
+ * handling would always be enabled at this point. So, just perform the
+ * query, but leaving URCs enabled either way. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CCWA=1,2",
+ 60,
+ FALSE,
+ callback,
+ user_data);
}
/*****************************************************************************/
@@ -7806,12 +8396,6 @@
guint operating_mode;
} CallManagerStateResults;
-typedef struct {
- MMBroadbandModem *self;
- GSimpleAsyncResult *result;
- MMPortSerialQcdm *qcdm;
-} CallManagerStateContext;
-
static void
cm_state_cleanup_port (MMPortSerial *port)
{
@@ -8235,7 +8819,7 @@
GError *inner_error = NULL;
gboolean value;
- value = g_task_propagate_boolean (G_TASK (res), error);
+ value = g_task_propagate_boolean (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
@@ -9641,7 +10225,6 @@
DISABLING_STEP_IFACE_MESSAGING,
DISABLING_STEP_IFACE_VOICE,
DISABLING_STEP_IFACE_LOCATION,
- DISABLING_STEP_IFACE_CONTACTS,
DISABLING_STEP_IFACE_CDMA,
DISABLING_STEP_IFACE_3GPP_USSD,
DISABLING_STEP_IFACE_3GPP,
@@ -9912,10 +10495,6 @@
/* Fall down to next step */
ctx->step++;
- case DISABLING_STEP_IFACE_CONTACTS:
- /* Fall down to next step */
- ctx->step++;
-
case DISABLING_STEP_IFACE_CDMA:
if (ctx->self->priv->modem_cdma_dbus_skeleton) {
mm_dbg ("Modem has CDMA capabilities, disabling the Modem CDMA interface...");
@@ -10005,7 +10584,6 @@
ENABLING_STEP_IFACE_3GPP,
ENABLING_STEP_IFACE_3GPP_USSD,
ENABLING_STEP_IFACE_CDMA,
- ENABLING_STEP_IFACE_CONTACTS,
ENABLING_STEP_IFACE_LOCATION,
ENABLING_STEP_IFACE_MESSAGING,
ENABLING_STEP_IFACE_VOICE,
@@ -10229,10 +10807,6 @@
/* Fall down to next step */
ctx->step++;
- case ENABLING_STEP_IFACE_CONTACTS:
- /* Fall down to next step */
- ctx->step++;
-
case ENABLING_STEP_IFACE_LOCATION:
if (ctx->self->priv->modem_location_dbus_skeleton) {
mm_dbg ("Modem has location capabilities, enabling the Location interface...");
@@ -10415,7 +10989,6 @@
INITIALIZE_STEP_IFACE_3GPP,
INITIALIZE_STEP_IFACE_3GPP_USSD,
INITIALIZE_STEP_IFACE_CDMA,
- INITIALIZE_STEP_IFACE_CONTACTS,
INITIALIZE_STEP_IFACE_LOCATION,
INITIALIZE_STEP_IFACE_MESSAGING,
INITIALIZE_STEP_IFACE_VOICE,
@@ -10697,10 +11270,6 @@
/* Fall down to next step */
ctx->step++;
- case INITIALIZE_STEP_IFACE_CONTACTS:
- /* Fall down to next step */
- ctx->step++;
-
case INITIALIZE_STEP_IFACE_LOCATION:
/* Initialize the Location interface */
mm_iface_modem_location_initialize (MM_IFACE_MODEM_LOCATION (ctx->self),
@@ -11191,6 +11760,9 @@
case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
self->priv->periodic_signal_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;
@@ -11314,6 +11886,9 @@
case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
g_value_set_boolean (value, self->priv->periodic_signal_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;
@@ -11350,6 +11925,7 @@
self->priv->current_sms_mem2_storage = MM_SMS_STORAGE_UNKNOWN;
self->priv->sim_hot_swap_supported = FALSE;
self->priv->periodic_signal_check_disabled = FALSE;
+ self->priv->periodic_call_list_check_disabled = FALSE;
self->priv->modem_cmer_enable_mode = MM_3GPP_CMER_MODE_NONE;
self->priv->modem_cmer_disable_mode = MM_3GPP_CMER_MODE_NONE;
self->priv->modem_cmer_ind = MM_3GPP_CMER_IND_NONE;
@@ -11648,10 +12224,36 @@
iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_voice_setup_cleanup_unsolicited_events_finish;
iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
- iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
+ iface->enable_unsolicited_events_finish = modem_voice_enable_disable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_voice_enable_disable_unsolicited_events_finish;
iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_voice_setup_cleanup_unsolicited_events_finish;
+
+ iface->setup_in_call_unsolicited_events = modem_voice_setup_in_call_unsolicited_events;
+ iface->setup_in_call_unsolicited_events_finish = modem_voice_setup_cleanup_in_call_unsolicited_events_finish;
+ iface->cleanup_in_call_unsolicited_events = modem_voice_cleanup_in_call_unsolicited_events;
+ iface->cleanup_in_call_unsolicited_events_finish = modem_voice_setup_cleanup_in_call_unsolicited_events_finish;
+
iface->create_call = modem_voice_create_call;
+ 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;
}
static void
@@ -11852,6 +12454,10 @@
MM_IFACE_MODEM_PERIODIC_SIGNAL_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);
diff --git a/src/mm-call-list.c b/src/mm-call-list.c
index 8110064..e131d20 100644
--- a/src/mm-call-list.c
+++ b/src/mm-call-list.c
@@ -54,6 +54,20 @@
/*****************************************************************************/
+void
+mm_call_list_foreach (MMCallList *self,
+ MMCallListForeachFunc callback,
+ gpointer user_data)
+{
+ GList *l;
+
+ g_assert (callback);
+ for (l = self->priv->list; l; l = g_list_next (l))
+ callback (MM_BASE_CALL (l->data), user_data);
+}
+
+/*****************************************************************************/
+
guint
mm_call_list_get_count (MMCallList *self)
{
@@ -84,10 +98,13 @@
/*****************************************************************************/
MMBaseCall *
-mm_call_list_get_first_ringing_in_call (MMCallList *self)
+mm_call_list_get_first_incoming_call (MMCallList *self,
+ MMCallState incoming_state)
{
GList *l;
+ g_assert (incoming_state == MM_CALL_STATE_RINGING_IN || incoming_state == MM_CALL_STATE_WAITING);
+
for (l = self->priv->list; l; l = g_list_next (l)) {
MMBaseCall *call;
MMCallState state;
@@ -101,7 +118,7 @@
NULL);
if (direction == MM_CALL_DIRECTION_INCOMING &&
- state == MM_CALL_STATE_RINGING_IN) {
+ state == incoming_state) {
return call;
}
}
@@ -118,6 +135,19 @@
return g_strcmp0 (mm_base_call_get_path (call), path);
}
+MMBaseCall *
+mm_call_list_get_call (MMCallList *self,
+ const gchar *call_path)
+{
+ GList *l;
+
+ l = g_list_find_custom (self->priv->list,
+ (gpointer)call_path,
+ (GCompareFunc)cmp_call_by_path);
+
+ return (l ? MM_BASE_CALL (l->data) : NULL);
+}
+
gboolean
mm_call_list_delete_call (MMCallList *self,
const gchar *call_path,
diff --git a/src/mm-call-list.h b/src/mm-call-list.h
index 0cf7523..3f85d1d 100644
--- a/src/mm-call-list.h
+++ b/src/mm-call-list.h
@@ -64,10 +64,20 @@
void mm_call_list_add_call (MMCallList *self,
MMBaseCall *call);
+MMBaseCall *mm_call_list_get_call (MMCallList *self,
+ const gchar *call_path);
+
gboolean mm_call_list_delete_call (MMCallList *self,
const gchar *call_path,
GError **error);
-MMBaseCall *mm_call_list_get_first_ringing_in_call (MMCallList *self);
+MMBaseCall *mm_call_list_get_first_incoming_call (MMCallList *self,
+ MMCallState incoming_state);
+
+typedef void (* MMCallListForeachFunc) (MMBaseCall *call,
+ gpointer user_data);
+void mm_call_list_foreach (MMCallList *self,
+ MMCallListForeachFunc callback,
+ gpointer user_data);
#endif /* MM_CALL_LIST_H */
diff --git a/src/mm-charsets.c b/src/mm-charsets.c
index 73dd4da..023dcf8 100644
--- a/src/mm-charsets.c
+++ b/src/mm-charsets.c
@@ -541,7 +541,7 @@
if (c <= 0x7F)
return TRUE;
- for (i = 0; i < sizeof (t) / sizeof (t[0]); i++) {
+ for (i = 0; i < G_N_ELEMENTS (t); i++) {
if (c == t[i])
return TRUE;
}
diff --git a/src/mm-filter.c b/src/mm-filter.c
index e65a628..73c6bde 100644
--- a/src/mm-filter.c
+++ b/src/mm-filter.c
@@ -34,11 +34,50 @@
};
struct _MMFilterPrivate {
- MMFilterRule enabled_rules;
+ MMFilterRule enabled_rules;
+ GList *plugin_whitelist_tags;
+ GArray *plugin_whitelist_product_ids;
};
/*****************************************************************************/
+void
+mm_filter_register_plugin_whitelist_tag (MMFilter *self,
+ const gchar *tag)
+{
+ if (!g_list_find_custom (self->priv->plugin_whitelist_tags, tag, (GCompareFunc) g_strcmp0)) {
+ mm_dbg ("[filter] registered plugin whitelist tag: %s", tag);
+ self->priv->plugin_whitelist_tags = g_list_prepend (self->priv->plugin_whitelist_tags, g_strdup (tag));
+ }
+}
+
+void
+mm_filter_register_plugin_whitelist_product_id (MMFilter *self,
+ guint16 vid,
+ guint16 pid)
+{
+ mm_uint16_pair new_item;
+ guint i;
+
+ if (!self->priv->plugin_whitelist_product_ids)
+ self->priv->plugin_whitelist_product_ids = g_array_sized_new (FALSE, FALSE, sizeof (mm_uint16_pair), 10);
+
+ for (i = 0; i < self->priv->plugin_whitelist_product_ids->len; i++) {
+ mm_uint16_pair *item;
+
+ item = &g_array_index (self->priv->plugin_whitelist_product_ids, mm_uint16_pair, i);
+ if (item->l == vid && item->r == pid)
+ return;
+ }
+
+ new_item.l = vid;
+ new_item.r = pid;
+ g_array_append_val (self->priv->plugin_whitelist_product_ids, new_item);
+ mm_dbg ("[filter] registered plugin whitelist product id: %04x:%04x", vid, pid);
+}
+
+/*****************************************************************************/
+
gboolean
mm_filter_port (MMFilter *self,
MMKernelDevice *port,
@@ -60,6 +99,46 @@
return TRUE;
}
+ /* If the device is explicitly blacklisted, we ignore every port. */
+ if ((self->priv->enabled_rules & MM_FILTER_RULE_EXPLICIT_BLACKLIST) &&
+ (mm_kernel_device_get_global_property_as_boolean (port, ID_MM_DEVICE_IGNORE))) {
+ mm_dbg ("[filter] (%s/%s): port filtered: device is blacklisted", subsystem, name);
+ return FALSE;
+ }
+
+ /* If the device is whitelisted by a plugin, we allow it. */
+ if (self->priv->enabled_rules & MM_FILTER_RULE_PLUGIN_WHITELIST) {
+ GList *l;
+ guint16 vid = 0;
+ guint16 pid = 0;
+
+ for (l = self->priv->plugin_whitelist_tags; l; l = g_list_next (l)) {
+ if (mm_kernel_device_get_global_property_as_boolean (port, (const gchar *)(l->data)) ||
+ mm_kernel_device_get_property_as_boolean (port, (const gchar *)(l->data))) {
+ mm_dbg ("[filter] (%s/%s) port allowed: device is whitelisted by plugin (tag)", subsystem, name);
+ return TRUE;
+ }
+ }
+
+ vid = mm_kernel_device_get_physdev_vid (port);
+ if (vid)
+ pid = mm_kernel_device_get_physdev_pid (port);
+
+ if (vid && pid && self->priv->plugin_whitelist_product_ids) {
+ guint i;
+
+ for (i = 0; i < self->priv->plugin_whitelist_product_ids->len; i++) {
+ mm_uint16_pair *item;
+
+ item = &g_array_index (self->priv->plugin_whitelist_product_ids, mm_uint16_pair, i);
+ if (item->l == vid && item->r == pid) {
+ mm_dbg ("[filter] (%s/%s) port allowed: device is whitelisted by plugin (vid/pid)", subsystem, name);
+ return TRUE;
+ }
+ }
+ }
+ }
+
/* If this is a virtual device, don't allow it */
if ((self->priv->enabled_rules & MM_FILTER_RULE_VIRTUAL) &&
(!mm_kernel_device_get_physdev_sysfs_path (port))) {
@@ -92,16 +171,16 @@
/* Ignore blacklisted tty devices. */
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_BLACKLIST) &&
- (mm_kernel_device_get_global_property_as_boolean (port, ID_MM_DEVICE_IGNORE))) {
- mm_dbg ("[filter] (%s/%s): port filtered: device is blacklisted", subsystem, name);
+ (mm_kernel_device_get_global_property_as_boolean (port, ID_MM_TTY_BLACKLIST))) {
+ mm_dbg ("[filter] (%s/%s): port filtered: tty is blacklisted", subsystem, name);
return FALSE;
}
/* Is the device in the manual-only greylist? If so, return if this is an
* automatic scan. */
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY) &&
- (!manual_scan && mm_kernel_device_get_global_property_as_boolean (port, ID_MM_DEVICE_MANUAL_SCAN_ONLY))) {
- mm_dbg ("[filter] (%s/%s): port filtered: device probed only in manual scan", subsystem, name);
+ (!manual_scan && mm_kernel_device_get_global_property_as_boolean (port, ID_MM_TTY_MANUAL_SCAN_ONLY))) {
+ mm_dbg ("[filter] (%s/%s): port filtered: tty probed only in manual scan", subsystem, name);
return FALSE;
}
@@ -114,12 +193,8 @@
!g_strcmp0 (physdev_subsystem, "pci") ||
!g_strcmp0 (physdev_subsystem, "pnp") ||
!g_strcmp0 (physdev_subsystem, "sdio"))) {
- if (!mm_kernel_device_get_global_property_as_boolean (port, ID_MM_PLATFORM_DRIVER_PROBE)) {
- mm_dbg ("[filter] (%s/%s): port filtered: port's parent platform driver is not whitelisted", subsystem, name);
- return FALSE;
- }
- mm_dbg ("[filter] (%s/%s): port allowed: port's parent platform driver is whitelisted", subsystem, name);
- return TRUE;
+ mm_dbg ("[filter] (%s/%s): port filtered: tty platform driver", subsystem, name);
+ return FALSE;
}
/* Default allowed? */
@@ -143,7 +218,13 @@
}
/*
- * If the TTY kernel driver is cdc-acm and the interface is class=2/subclass=2/protocol=[1-6], allow it.
+ * If the TTY kernel driver is cdc-acm and the interface is not
+ * class=2/subclass=2/protocol=[1-6], forbidden.
+ *
+ * Otherwise, we'll require the modem to have more ports other
+ * than the ttyACM one (see mm_filter_device_and_port()), because
+ * there are lots of Arduino devices out there exposing a single
+ * ttyACM port and wrongly claiming AT protocol support...
*
* Class definitions for Communication Devices 1.2
* Communications Interface Class Control Protocol Codes:
@@ -161,11 +242,12 @@
*/
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_ACM_INTERFACE) &&
(!g_strcmp0 (driver, "cdc_acm")) &&
- (mm_kernel_device_get_interface_class (port) == 2) &&
- (mm_kernel_device_get_interface_subclass (port) == 2) &&
- (mm_kernel_device_get_interface_protocol (port) >= 1) && (mm_kernel_device_get_interface_protocol (port) <= 6)) {
- mm_dbg ("[filter] (%s/%s): port allowed: cdc-acm interface reported AT-capable", subsystem, name);
- return TRUE;
+ ((mm_kernel_device_get_interface_class (port) != 2) ||
+ (mm_kernel_device_get_interface_subclass (port) != 2) ||
+ (mm_kernel_device_get_interface_protocol (port) < 1) ||
+ (mm_kernel_device_get_interface_protocol (port) > 6))) {
+ mm_dbg ("[filter] (%s/%s): port filtered: cdc-acm interface is not AT-capable", subsystem, name);
+ return FALSE;
}
/* Default forbidden? flag the port as maybe-forbidden, and go on */
@@ -184,6 +266,24 @@
/*****************************************************************************/
+static gboolean
+device_has_net_port (MMDevice *device)
+{
+ GList *l;
+
+ for (l = mm_device_peek_port_probe_list (device); l; l = g_list_next (l)) {
+ if (!g_strcmp0 (mm_port_probe_get_port_subsys (MM_PORT_PROBE (l->data)), "net"))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+device_has_multiple_ports (MMDevice *device)
+{
+ return (g_list_length (mm_device_peek_port_probe_list (device)) > 1);
+}
+
gboolean
mm_filter_device_and_port (MMFilter *self,
MMDevice *device,
@@ -191,6 +291,7 @@
{
const gchar *subsystem;
const gchar *name;
+ const gchar *driver;
/* If it wasn't flagged as maybe forbidden, there's nothing to do */
if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (port), FILTER_PORT_MAYBE_FORBIDDEN)))
@@ -200,16 +301,19 @@
name = mm_kernel_device_get_name (port);
/* Check whether this device holds a NET port in addition to this TTY */
- if (self->priv->enabled_rules & MM_FILTER_RULE_TTY_WITH_NET) {
- GList *l;
+ if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_WITH_NET) &&
+ device_has_net_port (device)) {
+ mm_dbg ("[filter] (%s/%s): port allowed: device also exports a net interface", subsystem, name);
+ return TRUE;
+ }
- for (l = mm_device_peek_port_probe_list (device); l; l = g_list_next (l)) {
- if (!g_strcmp0 (mm_port_probe_get_port_subsys (MM_PORT_PROBE (l->data)), "net")) {
- mm_dbg ("[filter] (%s/%s): port allowed: device also exports a net interface (%s)",
- subsystem, name, mm_port_probe_get_port_name (MM_PORT_PROBE (l->data)));
- return TRUE;
- }
- }
+ /* Check whether this device holds any other port in addition to the ttyACM port */
+ driver = mm_kernel_device_get_driver (port);
+ if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_ACM_INTERFACE) &&
+ (!g_strcmp0 (driver, "cdc_acm")) &&
+ device_has_multiple_ports (device)) {
+ mm_dbg ("[filter] (%s/%s): port allowed: device exports multiple interfaces", subsystem, name);
+ return TRUE;
}
mm_dbg ("[filter] (%s/%s) port filtered: forbidden", subsystem, name);
@@ -255,6 +359,15 @@
/*****************************************************************************/
+gboolean
+mm_filter_check_rule_enabled (MMFilter *self,
+ MMFilterRule rule)
+{
+ return !!(self->priv->enabled_rules & rule);
+}
+
+/*****************************************************************************/
+
/* If TTY rule enabled, either DEFAULT_ALLOWED or DEFAULT_FORBIDDEN must be set. */
#define VALIDATE_RULE_TTY(rules) (!(rules & MM_FILTER_RULE_TTY) || \
((rules & (MM_FILTER_RULE_TTY_DEFAULT_ALLOWED | MM_FILTER_RULE_TTY_DEFAULT_FORBIDDEN)) && \
@@ -285,6 +398,8 @@
mm_dbg ("[filter] created");
mm_dbg ("[filter] explicit whitelist: %s", RULE_ENABLED_STR (MM_FILTER_RULE_EXPLICIT_WHITELIST));
+ mm_dbg ("[filter] explicit blacklist: %s", RULE_ENABLED_STR (MM_FILTER_RULE_EXPLICIT_BLACKLIST));
+ mm_dbg ("[filter] plugin whitelist: %s", RULE_ENABLED_STR (MM_FILTER_RULE_PLUGIN_WHITELIST));
mm_dbg ("[filter] virtual devices forbidden: %s", RULE_ENABLED_STR (MM_FILTER_RULE_VIRTUAL));
mm_dbg ("[filter] net devices allowed: %s", RULE_ENABLED_STR (MM_FILTER_RULE_NET));
mm_dbg ("[filter] cdc-wdm devices allowed: %s", RULE_ENABLED_STR (MM_FILTER_RULE_CDC_WDM));
@@ -353,6 +468,17 @@
}
static void
+finalize (GObject *object)
+{
+ MMFilter *self = MM_FILTER (object);
+
+ g_clear_pointer (&self->priv->plugin_whitelist_product_ids, g_array_unref);
+ g_list_free_full (self->priv->plugin_whitelist_tags, g_free);
+
+ G_OBJECT_CLASS (mm_filter_parent_class)->finalize (object);
+}
+
+static void
mm_filter_class_init (MMFilterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
@@ -362,6 +488,7 @@
/* Virtual methods */
object_class->set_property = set_property;
object_class->get_property = get_property;
+ object_class->finalize = finalize;
g_object_class_install_property (
object_class, PROP_ENABLED_RULES,
diff --git a/src/mm-filter.h b/src/mm-filter.h
index 486d65b..0fc4ade 100644
--- a/src/mm-filter.h
+++ b/src/mm-filter.h
@@ -47,22 +47,26 @@
typedef enum { /*< underscore_name=mm_filter_rule >*/
MM_FILTER_RULE_NONE = 0,
MM_FILTER_RULE_EXPLICIT_WHITELIST = 1 << 0,
- MM_FILTER_RULE_VIRTUAL = 1 << 1,
- MM_FILTER_RULE_NET = 1 << 2,
- MM_FILTER_RULE_CDC_WDM = 1 << 3,
- MM_FILTER_RULE_TTY = 1 << 4,
- MM_FILTER_RULE_TTY_BLACKLIST = 1 << 5,
- MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY = 1 << 6,
- MM_FILTER_RULE_TTY_PLATFORM_DRIVER = 1 << 7,
- MM_FILTER_RULE_TTY_DEFAULT_ALLOWED = 1 << 8,
- MM_FILTER_RULE_TTY_DRIVER = 1 << 9,
- MM_FILTER_RULE_TTY_ACM_INTERFACE = 1 << 10,
- MM_FILTER_RULE_TTY_WITH_NET = 1 << 11,
- MM_FILTER_RULE_TTY_DEFAULT_FORBIDDEN = 1 << 12,
+ MM_FILTER_RULE_EXPLICIT_BLACKLIST = 1 << 1,
+ MM_FILTER_RULE_PLUGIN_WHITELIST = 1 << 2,
+ MM_FILTER_RULE_VIRTUAL = 1 << 3,
+ MM_FILTER_RULE_NET = 1 << 4,
+ MM_FILTER_RULE_CDC_WDM = 1 << 5,
+ MM_FILTER_RULE_TTY = 1 << 6,
+ MM_FILTER_RULE_TTY_BLACKLIST = 1 << 7,
+ MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY = 1 << 8,
+ MM_FILTER_RULE_TTY_PLATFORM_DRIVER = 1 << 9,
+ MM_FILTER_RULE_TTY_DEFAULT_ALLOWED = 1 << 10,
+ MM_FILTER_RULE_TTY_DRIVER = 1 << 11,
+ MM_FILTER_RULE_TTY_ACM_INTERFACE = 1 << 12,
+ MM_FILTER_RULE_TTY_WITH_NET = 1 << 13,
+ MM_FILTER_RULE_TTY_DEFAULT_FORBIDDEN = 1 << 14,
} MMFilterRule;
#define MM_FILTER_RULE_ALL \
(MM_FILTER_RULE_EXPLICIT_WHITELIST | \
+ MM_FILTER_RULE_EXPLICIT_BLACKLIST | \
+ MM_FILTER_RULE_PLUGIN_WHITELIST | \
MM_FILTER_RULE_VIRTUAL | \
MM_FILTER_RULE_NET | \
MM_FILTER_RULE_CDC_WDM | \
@@ -80,6 +84,7 @@
* device ports unless they're blacklisted in some way or another. */
#define MM_FILTER_POLICY_DEFAULT \
(MM_FILTER_RULE_EXPLICIT_WHITELIST | \
+ MM_FILTER_RULE_EXPLICIT_BLACKLIST | \
MM_FILTER_RULE_VIRTUAL | \
MM_FILTER_RULE_NET | \
MM_FILTER_RULE_CDC_WDM | \
@@ -93,6 +98,8 @@
* if they are allowed by any of the automatic whitelist rules. */
#define MM_FILTER_POLICY_STRICT \
(MM_FILTER_RULE_EXPLICIT_WHITELIST | \
+ MM_FILTER_RULE_EXPLICIT_BLACKLIST | \
+ MM_FILTER_RULE_PLUGIN_WHITELIST | \
MM_FILTER_RULE_VIRTUAL | \
MM_FILTER_RULE_NET | \
MM_FILTER_RULE_CDC_WDM | \
@@ -107,6 +114,8 @@
* blacklists explicitly */
#define MM_FILTER_POLICY_PARANOID \
(MM_FILTER_RULE_EXPLICIT_WHITELIST | \
+ MM_FILTER_RULE_EXPLICIT_BLACKLIST | \
+ MM_FILTER_RULE_PLUGIN_WHITELIST | \
MM_FILTER_RULE_VIRTUAL | \
MM_FILTER_RULE_NET | \
MM_FILTER_RULE_CDC_WDM | \
@@ -134,4 +143,13 @@
MMDevice *device,
MMKernelDevice *port);
+void mm_filter_register_plugin_whitelist_tag (MMFilter *self,
+ const gchar *tag);
+void mm_filter_register_plugin_whitelist_product_id (MMFilter *self,
+ guint16 vid,
+ guint16 pid);
+
+gboolean mm_filter_check_rule_enabled (MMFilter *self,
+ MMFilterRule rule);
+
#endif /* MM_FILTER_H */
diff --git a/src/mm-iface-modem-3gpp.c b/src/mm-iface-modem-3gpp.c
index 3577140..16af6af 100644
--- a/src/mm-iface-modem-3gpp.c
+++ b/src/mm-iface-modem-3gpp.c
@@ -115,60 +115,71 @@
return ctx;
}
-static gboolean
-reg_state_is_registered (MMModem3gppRegistrationState state)
-{
- return state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
- state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING ||
- state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME_SMS_ONLY ||
- state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY ||
- state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME_CSFB_NOT_PREFERRED ||
- state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED;
-}
+#define REG_STATE_IS_REGISTERED(state) \
+ (state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME || \
+ state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING || \
+ state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME_SMS_ONLY || \
+ state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY || \
+ state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME_CSFB_NOT_PREFERRED || \
+ state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED)
+
+#define REG_STATE_IS_UNKNOWN_IDLE_DENIED(state) \
+ (state == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN || \
+ state == MM_MODEM_3GPP_REGISTRATION_STATE_IDLE || \
+ state == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED)
static MMModem3gppRegistrationState
get_consolidated_reg_state (RegistrationStateContext *ctx)
{
+ MMModem3gppRegistrationState consolidated = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
+
/* Some devices (Blackberries for example) will respond to +CGREG, but
* return ERROR for +CREG, probably because their firmware is just stupid.
* So here we prefer the +CREG response, but if we never got a successful
* +CREG response, we'll take +CGREG instead.
*/
if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
- ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING)
- return ctx->cs;
+ ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
+ consolidated = ctx->cs;
+ goto out;
+ }
if (ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
- ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING)
- return ctx->ps;
+ ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
+ consolidated = ctx->ps;
+ goto out;
+ }
if (ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
- ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING)
- return ctx->eps;
+ ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
+ consolidated = ctx->eps;
+ goto out;
+ }
/* Searching? */
if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING ||
ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING ||
- ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING)
- return MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING;
+ ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING) {
+ consolidated = MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING;
+ goto out;
+ }
- /* If one state is DENIED and the others are UNKNOWN, use DENIED */
- if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED &&
- ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN &&
- ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN)
- return ctx->cs;
- if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN &&
- ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED &&
- ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN)
- return ctx->ps;
- if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN &&
- ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN &&
- ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED)
- return ctx->eps;
+ /* If at least one state is DENIED and the others are UNKNOWN or IDLE, use DENIED */
+ if ((ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED ||
+ ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED ||
+ ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED) &&
+ REG_STATE_IS_UNKNOWN_IDLE_DENIED (ctx->cs) &&
+ REG_STATE_IS_UNKNOWN_IDLE_DENIED (ctx->ps) &&
+ REG_STATE_IS_UNKNOWN_IDLE_DENIED (ctx->eps)) {
+ consolidated = MM_MODEM_3GPP_REGISTRATION_STATE_DENIED;
+ goto out;
+ }
/* Emergency services? */
if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY ||
ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY ||
- ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY)
- return MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY;
+ ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY) {
+ consolidated = MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY;
+ goto out;
+ }
/* Support for additional registration states reported when on LTE.
*
@@ -188,17 +199,26 @@
ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED) {
mm_warn ("3GPP CSFB registration state is consolidated: %s",
mm_modem_3gpp_registration_state_get_string (ctx->cs));
- return ctx->cs;
+ consolidated = ctx->cs;
+ goto out;
}
/* Idle? */
if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_IDLE ||
ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_IDLE ||
- ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_IDLE)
- return MM_MODEM_3GPP_REGISTRATION_STATE_IDLE;
+ ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_IDLE) {
+ consolidated = MM_MODEM_3GPP_REGISTRATION_STATE_IDLE;
+ goto out;
+ }
- /* Just unknown at this point */
- return MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
+ out:
+ mm_dbg ("building consolidated registration state: cs '%s', ps '%s', eps '%s' --> '%s'",
+ mm_modem_3gpp_registration_state_get_string (ctx->cs),
+ mm_modem_3gpp_registration_state_get_string (ctx->ps),
+ mm_modem_3gpp_registration_state_get_string (ctx->eps),
+ mm_modem_3gpp_registration_state_get_string (consolidated));
+
+ return consolidated;
}
/*****************************************************************************/
@@ -314,7 +334,7 @@
}
/* If we got registered, end registration checks */
- if (reg_state_is_registered (current_registration_state)) {
+ if (REG_STATE_IS_REGISTERED (current_registration_state)) {
/* Request immediate access tech and signal update: we may have changed
* from home to roaming or viceversa, both registered states, so there
* wouldn't be an explicit refresh triggered from the modem interface as
@@ -442,7 +462,7 @@
/* If the modem is already registered and the last time it was asked
* automatic registration, we're done */
- if ((current_operator_code || reg_state_is_registered (reg_state)) &&
+ if ((current_operator_code || REG_STATE_IS_REGISTERED (reg_state)) &&
!registration_state_context->manual_registration) {
mm_dbg ("Already registered in network '%s',"
" automatic registration not launched...",
@@ -497,7 +517,7 @@
{
GError *error = NULL;
- if (!MM_IFACE_MODEM_3GPP_GET_INTERFACE (self)->register_in_network_finish (self, res,&error))
+ if (!mm_iface_modem_3gpp_register_in_network_finish (self, res, &error))
g_dbus_method_invocation_take_error (ctx->invocation, error);
else
mm_gdbus_modem3gpp_complete_register (ctx->skeleton, ctx->invocation);
@@ -1303,7 +1323,7 @@
/* Even if registration state didn't change, report access technology,
* but only if something valid to report */
- if (reg_state_is_registered (state) || ctx->reloading_registration_info) {
+ if (REG_STATE_IS_REGISTERED (state) || ctx->reloading_registration_info) {
if (access_tech != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
access_tech,
@@ -1337,7 +1357,7 @@
* location updates, but only if something valid to report. For the case
* where we're registering (loading current registration info after a state
* change to registered), we also allow LAC/CID updates. */
- if (reg_state_is_registered (state) || ctx->reloading_registration_info) {
+ if (REG_STATE_IS_REGISTERED (state) || ctx->reloading_registration_info) {
if ((location_area_code > 0 || tracking_area_code > 0) && cell_id > 0)
mm_iface_modem_location_3gpp_update_lac_tac_ci (MM_IFACE_MODEM_LOCATION (self),
location_area_code,
@@ -1420,7 +1440,7 @@
if (new_state == old_state)
return;
- if (reg_state_is_registered (new_state)) {
+ if (REG_STATE_IS_REGISTERED (new_state)) {
MMModemState modem_state;
/* If already reloading registration info, skip it */
diff --git a/src/mm-iface-modem-firmware.c b/src/mm-iface-modem-firmware.c
index ecfa3b5..a3e68c5 100644
--- a/src/mm-iface-modem-firmware.c
+++ b/src/mm-iface-modem-firmware.c
@@ -348,7 +348,7 @@
mm_iface_modem_get_carrier_config (MM_IFACE_MODEM (self), &aux, NULL);
- ids = g_ptr_array_new_with_free_func ((GDestroyNotify)g_free);
+ ids = g_ptr_array_new_with_free_func (g_free);
if (aux) {
gchar *carrier;
diff --git a/src/mm-iface-modem-location.c b/src/mm-iface-modem-location.c
index 2168140..8391506 100644
--- a/src/mm-iface-modem-location.c
+++ b/src/mm-iface-modem-location.c
@@ -11,7 +11,8 @@
* GNU General Public License for more details:
*
* Copyright (C) 2012 Google, Inc.
- * Copyright (C) 2012 Lanedo GmbH <aleksander@lanedo.com>
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2012-2019 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <ModemManager.h>
@@ -140,7 +141,9 @@
break;
case MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED:
g_assert_not_reached ();
- case MM_MODEM_LOCATION_SOURCE_AGPS:
+ case MM_MODEM_LOCATION_SOURCE_AGPS_MSA:
+ g_assert_not_reached ();
+ case MM_MODEM_LOCATION_SOURCE_AGPS_MSB:
g_assert_not_reached ();
default:
g_warn_if_reached ();
@@ -506,7 +509,8 @@
g_clear_object (&ctx->location_cdma_bs);
break;
case MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED:
- case MM_MODEM_LOCATION_SOURCE_AGPS:
+ case MM_MODEM_LOCATION_SOURCE_AGPS_MSA:
+ case MM_MODEM_LOCATION_SOURCE_AGPS_MSB:
/* Nothing to setup in the context */
default:
break;
@@ -622,7 +626,7 @@
return;
}
- while (ctx->current <= MM_MODEM_LOCATION_SOURCE_AGPS) {
+ while (ctx->current <= MM_MODEM_LOCATION_SOURCE_LAST) {
gchar *source_str;
if (ctx->to_enable & ctx->current) {
@@ -720,8 +724,8 @@
ctx->to_disable = MM_MODEM_LOCATION_SOURCE_NONE;
/* Loop through all known bits in the bitmask to enable/disable specific location sources */
- for (source = MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI;
- source <= MM_MODEM_LOCATION_SOURCE_AGPS;
+ for (source = MM_MODEM_LOCATION_SOURCE_FIRST;
+ source <= MM_MODEM_LOCATION_SOURCE_LAST;
source = source << 1) {
/* skip unsupported sources */
if (!(mm_gdbus_modem_location_get_capabilities (ctx->skeleton) & source))
@@ -765,6 +769,21 @@
return;
}
+ /* MSA A-GPS and MSB A-GPS cannot be set at the same time */
+ if ((ctx->to_enable & MM_MODEM_LOCATION_SOURCE_AGPS_MSA &&
+ currently_enabled & MM_MODEM_LOCATION_SOURCE_AGPS_MSB) ||
+ (ctx->to_enable & MM_MODEM_LOCATION_SOURCE_AGPS_MSB &&
+ currently_enabled & MM_MODEM_LOCATION_SOURCE_AGPS_MSA) ||
+ (ctx->to_enable & MM_MODEM_LOCATION_SOURCE_AGPS_MSA &&
+ ctx->to_enable & MM_MODEM_LOCATION_SOURCE_AGPS_MSB)) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot have both MSA A-GPS and MSB A-GPS enabled at the same time");
+ g_object_unref (task);
+ return;
+ }
+
if (ctx->to_enable != MM_MODEM_LOCATION_SOURCE_NONE) {
str = mm_modem_location_source_build_string_from_mask (ctx->to_enable);
mm_dbg ("Need to enable the following location sources: '%s'", str);
@@ -778,7 +797,7 @@
}
/* Start enabling/disabling location sources */
- ctx->current = MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI;
+ ctx->current = MM_MODEM_LOCATION_SOURCE_FIRST;
setup_gathering_step (task);
}
@@ -982,7 +1001,7 @@
}
/* If A-GPS is NOT supported, set error */
- if (!(mm_gdbus_modem_location_get_capabilities (ctx->skeleton) & MM_MODEM_LOCATION_SOURCE_AGPS)) {
+ if (!(mm_gdbus_modem_location_get_capabilities (ctx->skeleton) & (MM_MODEM_LOCATION_SOURCE_AGPS_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB))) {
g_dbus_method_invocation_return_error (ctx->invocation,
MM_CORE_ERROR,
MM_CORE_ERROR_UNSUPPORTED,
@@ -1496,7 +1515,8 @@
default_sources &= ~(MM_MODEM_LOCATION_SOURCE_GPS_RAW |
MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED |
- MM_MODEM_LOCATION_SOURCE_AGPS);
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
setup_gathering (self,
default_sources,
@@ -1723,7 +1743,8 @@
case INITIALIZATION_STEP_SUPL_SERVER:
/* If the modem supports A-GPS, load SUPL server */
- if (ctx->capabilities & MM_MODEM_LOCATION_SOURCE_AGPS &&
+ if ((ctx->capabilities & (MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB)) &&
MM_IFACE_MODEM_LOCATION_GET_INTERFACE (self)->load_supl_server &&
MM_IFACE_MODEM_LOCATION_GET_INTERFACE (self)->load_supl_server_finish) {
MM_IFACE_MODEM_LOCATION_GET_INTERFACE (self)->load_supl_server (
@@ -1737,7 +1758,8 @@
case INITIALIZATION_STEP_SUPPORTED_ASSISTANCE_DATA:
/* If the modem supports any GPS-related technology, check assistance data types supported */
- if ((ctx->capabilities & (MM_MODEM_LOCATION_SOURCE_AGPS |
+ if ((ctx->capabilities & (MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) &&
MM_IFACE_MODEM_LOCATION_GET_INTERFACE (self)->load_supported_assistance_data &&
diff --git a/src/mm-iface-modem-messaging.c b/src/mm-iface-modem-messaging.c
index 8b3ac8c..fd50f54 100644
--- a/src/mm-iface-modem-messaging.c
+++ b/src/mm-iface-modem-messaging.c
@@ -498,6 +498,8 @@
paths = mm_sms_list_get_paths (list);
mm_gdbus_modem_messaging_set_messages (skeleton, (const gchar *const *)paths);
g_strfreev (paths);
+
+ g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (skeleton));
}
static void
diff --git a/src/mm-iface-modem-time.c b/src/mm-iface-modem-time.c
index d1a0dfb..b8d35bf 100644
--- a/src/mm-iface-modem-time.c
+++ b/src/mm-iface-modem-time.c
@@ -354,6 +354,27 @@
g_object_unref (skeleton);
}
+void
+mm_iface_modem_time_update_network_timezone (MMIfaceModemTime *self,
+ MMNetworkTimezone *tz)
+{
+ MmGdbusModemTime *skeleton;
+ GVariant *dictionary;
+
+ g_object_get (self,
+ MM_IFACE_MODEM_TIME_DBUS_SKELETON, &skeleton,
+ NULL);
+ if (!skeleton)
+ return;
+
+ dictionary = mm_network_timezone_get_dictionary (tz);
+ mm_gdbus_modem_time_set_network_timezone (skeleton, dictionary);
+ if (dictionary)
+ g_variant_unref (dictionary);
+
+ g_object_unref (skeleton);
+}
+
/*****************************************************************************/
typedef struct _DisablingContext DisablingContext;
diff --git a/src/mm-iface-modem-time.h b/src/mm-iface-modem-time.h
index 7712e4c..462d8b9 100644
--- a/src/mm-iface-modem-time.h
+++ b/src/mm-iface-modem-time.h
@@ -130,7 +130,9 @@
/* Implementations of the unsolicited events handling should call this method
* to notify about the updated time */
-void mm_iface_modem_time_update_network_time (MMIfaceModemTime *self,
- const gchar *network_time);
+void mm_iface_modem_time_update_network_time (MMIfaceModemTime *self,
+ const gchar *network_time);
+void mm_iface_modem_time_update_network_timezone (MMIfaceModemTime *self,
+ MMNetworkTimezone *tz);
#endif /* MM_IFACE_MODEM_TIME_H */
diff --git a/src/mm-iface-modem-voice.c b/src/mm-iface-modem-voice.c
index e278899..1c7af9c 100644
--- a/src/mm-iface-modem-voice.c
+++ b/src/mm-iface-modem-voice.c
@@ -10,7 +10,8 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
- * Copyright (C) 2015 - Marco Bascetta <marco.bascetta@sadel.it>
+ * Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
+ * Copyright (C) 2019 Purism SPC
*/
#include <ModemManager.h>
@@ -22,11 +23,15 @@
#include "mm-call-list.h"
#include "mm-log.h"
-#define SUPPORT_CHECKED_TAG "voice-support-checked-tag"
-#define SUPPORTED_TAG "voice-supported-tag"
+#define SUPPORT_CHECKED_TAG "voice-support-checked-tag"
+#define SUPPORTED_TAG "voice-supported-tag"
+#define CALL_LIST_POLLING_CONTEXT_TAG "voice-call-list-polling-context-tag"
+#define IN_CALL_EVENT_CONTEXT_TAG "voice-in-call-event-context-tag"
static GQuark support_checked_quark;
static GQuark supported_quark;
+static GQuark call_list_polling_context_quark;
+static GQuark in_call_event_context_quark;
/*****************************************************************************/
@@ -38,13 +43,21 @@
/*****************************************************************************/
+/* new calls will inherit audio settings if the modem is already in-call state */
+static void update_audio_settings_in_call (MMIfaceModemVoice *self,
+ MMBaseCall *call);
+
static MMBaseCall *
create_incoming_call (MMIfaceModemVoice *self,
const gchar *number)
{
+ MMBaseCall *call;
+
g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call != NULL);
- return MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self, MM_CALL_DIRECTION_INCOMING, number);
+ call = MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self, MM_CALL_DIRECTION_INCOMING, number);
+ update_audio_settings_in_call (self, call);
+ return call;
}
static MMBaseCall *
@@ -52,6 +65,7 @@
MMCallProperties *properties,
GError **error)
{
+ MMBaseCall *call;
const gchar *number;
/* Don't create CALL from properties if either number is missing */
@@ -66,43 +80,180 @@
/* Create a call object as defined by the interface */
g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call != NULL);
- return MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self, MM_CALL_DIRECTION_OUTGOING, number);
+ call = MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->create_call (self, MM_CALL_DIRECTION_OUTGOING, number);
+ update_audio_settings_in_call (self, call);
+ return call;
+}
+
+/*****************************************************************************/
+/* Common helper to match call info against a known call object */
+
+static gboolean
+match_single_call_info (const MMCallInfo *call_info,
+ MMBaseCall *call)
+{
+ MMCallState state;
+ MMCallDirection direction;
+ const gchar *number;
+ guint idx;
+ gboolean match_direction_and_state = FALSE;
+ gboolean match_index = FALSE;
+ gboolean match_terminated = FALSE;
+
+ /* try to look for a matching call by direction/number/index */
+ state = mm_base_call_get_state (call);
+ direction = mm_base_call_get_direction (call);
+ number = mm_base_call_get_number (call);
+ idx = mm_base_call_get_index (call);
+
+ /* Match index */
+ if (call_info->index && (call_info->index == idx))
+ match_index = TRUE;
+
+ /* Match direction and state.
+ * We cannot apply this match if both call info and call have an index set
+ * and they're different already. */
+ if ((call_info->direction == direction) &&
+ (call_info->state == state) &&
+ (!call_info->index || !idx || match_index))
+ match_direction_and_state = TRUE;
+
+ /* Match special terminated event.
+ * We cannot apply this match if the call is part of a multiparty
+ * call, because we don't know which of the calls in the multiparty
+ * is the one that finished. Must rely on other reports that do
+ * provide call index. */
+ if ((call_info->state == MM_CALL_STATE_TERMINATED) &&
+ (call_info->direction == MM_CALL_DIRECTION_UNKNOWN) &&
+ !call_info->index &&
+ !call_info->number &&
+ !mm_base_call_get_multiparty (call))
+ match_terminated = TRUE;
+
+ /* If no clear match, nothing to do */
+ if (!match_index && !match_direction_and_state && !match_terminated)
+ return FALSE;
+
+ mm_dbg ("call info matched (matched direction/state %s, matched index %s, matched terminated %s) with call at '%s'",
+ match_direction_and_state ? "yes" : "no",
+ match_index ? "yes" : "no",
+ match_terminated ? "yes" : "no",
+ mm_base_call_get_path (call));
+
+ /* Early detect if a known incoming call that was created
+ * from a plain CRING URC (i.e. without caller number)
+ * needs to have the number provided.
+ */
+ if (call_info->number && !number) {
+ mm_dbg (" number set: %s", call_info->number);
+ mm_base_call_set_number (call, call_info->number);
+ }
+
+ /* Early detect if a known incoming/outgoing call does
+ * not have a known call index yet.
+ */
+ if (call_info->index && !idx) {
+ mm_dbg (" index set: %u", call_info->index);
+ mm_base_call_set_index (call, call_info->index);
+ }
+
+ /* Update state if it changed */
+ if (call_info->state != state) {
+ mm_dbg (" state updated: %s", mm_call_state_get_string (call_info->state));
+ mm_base_call_change_state (call, call_info->state, MM_CALL_STATE_REASON_UNKNOWN);
+ }
+
+ /* refresh if incoming and new state is not terminated */
+ if ((call_info->state != MM_CALL_STATE_TERMINATED) &&
+ (direction == MM_CALL_DIRECTION_INCOMING)) {
+ mm_dbg (" incoming refreshed");
+ mm_base_call_incoming_refresh (call);
+ }
+
+ return TRUE;
}
/*****************************************************************************/
-void
-mm_iface_modem_voice_report_incoming_call (MMIfaceModemVoice *self,
- const gchar *number)
+typedef struct {
+ const MMCallInfo *call_info;
+} ReportCallForeachContext;
+
+static void
+report_call_foreach (MMBaseCall *call,
+ ReportCallForeachContext *ctx)
{
- MMBaseCall *call = NULL;
- MMCallList *list = NULL;
+ /* Do nothing if already matched */
+ if (!ctx->call_info)
+ return;
+
+ /* fully ignore already terminated calls */
+ if (mm_base_call_get_state (call) == MM_CALL_STATE_TERMINATED)
+ return;
+
+ /* Reset call info in context if the call info matches an existing call */
+ if (match_single_call_info (ctx->call_info, call))
+ ctx->call_info = NULL;
+}
+
+void
+mm_iface_modem_voice_report_call (MMIfaceModemVoice *self,
+ const MMCallInfo *call_info)
+{
+ ReportCallForeachContext ctx = { 0 };
+ MMBaseCall *call = NULL;
+ MMCallList *list = NULL;
+
+ /* When reporting single call, the only mandatory parameter is the state:
+ * - index is optional (e.g. unavailable when receiving +CLIP URCs)
+ * - number is optional (e.g. unavailable when receiving +CRING URCs)
+ * - direction is optional (e.g. unavailable when receiving some vendor-specific URCs)
+ */
+ g_assert (call_info->state != MM_CALL_STATE_UNKNOWN);
+
+ /* Early debugging of the call state update */
+ mm_dbg ("call at index %u: direction %s, state %s, number %s",
+ 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");
g_object_get (MM_BASE_MODEM (self),
MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
NULL);
if (!list) {
- mm_warn ("Cannot create incoming call: missing call list");
+ mm_warn ("Cannot process call state update: missing call list");
return;
}
- call = mm_call_list_get_first_ringing_in_call (list);
+ /* Iterate over all known calls and try to match a known one */
+ ctx.call_info = call_info;
+ mm_call_list_foreach (list, (MMCallListForeachFunc)report_call_foreach, &ctx);
- /* If call exists already, refresh its validity and set number if it wasn't set */
- if (call) {
- if (number && !mm_gdbus_call_get_number (MM_GDBUS_CALL (call)))
- mm_gdbus_call_set_number (MM_GDBUS_CALL (call), number);
- mm_base_call_incoming_refresh (call);
- g_object_unref (list);
- return;
+ /* If call info matched with an existing one, the context call info would have been reseted */
+ if (!ctx.call_info)
+ goto out;
+
+ /* If call info didn't match with any known call, it may be because we're being
+ * reported a NEW incoming call. If that's not the case, we'll ignore the report. */
+ if ((call_info->direction != MM_CALL_DIRECTION_INCOMING) ||
+ ((call_info->state != MM_CALL_STATE_WAITING) && (call_info->state != MM_CALL_STATE_RINGING_IN))) {
+ mm_dbg ("unhandled call state update reported: direction: %s, state %s",
+ mm_call_direction_get_string (call_info->direction),
+ mm_call_state_get_string (call_info->state));
+ goto out;
}
mm_dbg ("Creating new incoming call...");
- call = create_incoming_call (self, number);
+ call = create_incoming_call (self, call_info->number);
- /* Set the state as ringing in */
- mm_base_call_change_state (call, MM_CALL_STATE_RINGING_IN, MM_CALL_STATE_REASON_INCOMING_NEW);
+ /* Set the state */
+ mm_base_call_change_state (call, call_info->state, MM_CALL_STATE_REASON_INCOMING_NEW);
+
+ /* Set the index, if known */
+ if (call_info->index)
+ mm_base_call_set_index (call, call_info->index);
/* Start its validity timeout */
mm_base_call_incoming_refresh (call);
@@ -111,6 +262,181 @@
mm_base_call_export (call);
mm_call_list_add_call (list, call);
g_object_unref (call);
+
+ out:
+ g_object_unref (list);
+}
+
+/*****************************************************************************/
+/* Full current call list reporting
+ *
+ * This method receives as input a list with all the currently active calls,
+ * including the specific state they're in.
+ *
+ * This method should:
+ * - Check whether we're reporting a new call (i.e. not in our internal call
+ * list yet). We'll create a new call object if so.
+ * - Check whether any of the known calls has changed state, and if so,
+ * update it.
+ * - Check whether any of the known calls is NOT given in the input list of
+ * call infos, which would mean the call is terminated.
+ */
+
+typedef struct {
+ GList *call_info_list;
+} ReportAllCallsForeachContext;
+
+static void
+report_all_calls_foreach (MMBaseCall *call,
+ ReportAllCallsForeachContext *ctx)
+{
+ GList *l;
+
+ /* fully ignore already terminated calls */
+ if (mm_base_call_get_state (call) == MM_CALL_STATE_TERMINATED)
+ return;
+
+ /* Iterate over the call info list */
+ for (l = ctx->call_info_list; l; l = g_list_next (l)) {
+ MMCallInfo *call_info = (MMCallInfo *)(l->data);
+
+ /* if match found, delete item from list and halt iteration right away */
+ if (match_single_call_info (call_info, call)) {
+ ctx->call_info_list = g_list_delete_link (ctx->call_info_list, l);
+ return;
+ }
+ }
+
+ /* not found in list! this call is now terminated */
+ mm_base_call_change_state (call, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_UNKNOWN);
+}
+
+void
+mm_iface_modem_voice_report_all_calls (MMIfaceModemVoice *self,
+ GList *call_info_list)
+{
+ ReportAllCallsForeachContext ctx = { 0 };
+ MMCallList *list = NULL;
+ GList *l;
+
+ /* Early debugging of the full list of calls */
+ mm_dbg ("Reported %u ongoing calls", g_list_length (call_info_list));
+ for (l = call_info_list; l; l = g_list_next (l)) {
+ MMCallInfo *call_info = (MMCallInfo *)(l->data);
+
+ /* When reporting full list of calls, index and state are mandatory */
+ g_assert (call_info->index != 0);
+ g_assert (call_info->state != MM_CALL_STATE_UNKNOWN);
+
+ mm_dbg ("call at index %u: direction %s, state %s, number %s",
+ 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");
+ }
+
+ /* Retrieve list of known calls */
+ g_object_get (MM_BASE_MODEM (self),
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ mm_warn ("Cannot report all calls: missing call list");
+ return;
+ }
+
+ /* Iterate over all the calls already known to us.
+ * Whenever a known call is updated, it will be removed from the call info list */
+ ctx.call_info_list = g_list_copy (call_info_list);
+ mm_call_list_foreach (list, (MMCallListForeachFunc)report_all_calls_foreach, &ctx);
+
+ /* Once processed, the call info list will have all calls that were unknown to
+ * us, i.e. the new calls to create. We really only expect new incoming calls, so
+ * we'll warn if we get any outgoing call reported here. */
+ for (l = ctx.call_info_list; l; l = g_list_next (l)) {
+ MMCallInfo *call_info = (MMCallInfo *)(l->data);
+
+ if (call_info->direction == MM_CALL_DIRECTION_OUTGOING) {
+ mm_warn ("unexpected outgoing call to number '%s' reported in call list: state %s",
+ call_info->number ? call_info->number : "n/a",
+ mm_call_state_get_string (call_info->state));
+ continue;
+ }
+
+ if (call_info->direction == MM_CALL_DIRECTION_INCOMING) {
+ MMBaseCall *call;
+
+ /* We only expect either RINGING-IN or WAITING states */
+ if ((call_info->state != MM_CALL_STATE_RINGING_IN) &&
+ (call_info->state != MM_CALL_STATE_WAITING)) {
+ mm_warn ("unexpected incoming call to number '%s' reported in call list: state %s",
+ call_info->number ? call_info->number : "n/a",
+ mm_call_state_get_string (call_info->state));
+ continue;
+ }
+
+ mm_dbg ("Creating new incoming call...");
+ call = create_incoming_call (self, call_info->number);
+
+ /* Set the state and the index */
+ mm_base_call_change_state (call, call_info->state, MM_CALL_STATE_REASON_INCOMING_NEW);
+ mm_base_call_set_index (call, call_info->index);
+
+ /* Start its validity timeout */
+ mm_base_call_incoming_refresh (call);
+
+ /* Only export once properly created */
+ mm_base_call_export (call);
+ mm_call_list_add_call (list, call);
+ g_object_unref (call);
+ continue;
+ }
+
+ mm_warn ("unexpected call to number '%s' reported in call list: state %s, direction unknown",
+ call_info->number ? call_info->number : "n/a",
+ mm_call_state_get_string (call_info->state));
+ }
+ g_list_free (ctx.call_info_list);
+ g_object_unref (list);
+}
+
+/*****************************************************************************/
+/* Incoming DTMF reception, not associated to a specific call */
+
+typedef struct {
+ guint index;
+ const gchar *dtmf;
+} ReceivedDtmfContext;
+
+static void
+received_dtmf_foreach (MMBaseCall *call,
+ ReceivedDtmfContext *ctx)
+{
+ if ((!ctx->index || (ctx->index == mm_base_call_get_index (call))) &&
+ (mm_base_call_get_state (call) == MM_CALL_STATE_ACTIVE))
+ mm_base_call_received_dtmf (call, ctx->dtmf);
+}
+
+void
+mm_iface_modem_voice_received_dtmf (MMIfaceModemVoice *self,
+ guint index,
+ const gchar *dtmf)
+{
+ MMCallList *list = NULL;
+ ReceivedDtmfContext ctx = {
+ .index = index,
+ .dtmf = dtmf
+ };
+
+ /* Retrieve list of known calls */
+ g_object_get (MM_BASE_MODEM (self),
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ mm_warn ("Cannot report received DTMF: missing call list");
+ return;
+ }
+
+ mm_call_list_foreach (list, (MMCallListForeachFunc)received_dtmf_foreach, &ctx);
g_object_unref (list);
}
@@ -368,9 +694,1766 @@
/*****************************************************************************/
+typedef struct {
+ MmGdbusModemVoice *skeleton;
+ GDBusMethodInvocation *invocation;
+ MMIfaceModemVoice *self;
+ GList *active_calls;
+ MMBaseCall *next_call;
+} HandleHoldAndAcceptContext;
+
static void
-update_message_list (MmGdbusModemVoice *skeleton,
- MMCallList *list)
+handle_hold_and_accept_context_free (HandleHoldAndAcceptContext *ctx)
+{
+ g_list_free_full (ctx->active_calls, g_object_unref);
+ g_clear_object (&ctx->next_call);
+ g_object_unref (ctx->skeleton);
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->self);
+ g_slice_free (HandleHoldAndAcceptContext, ctx);
+}
+
+static void
+hold_and_accept_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ HandleHoldAndAcceptContext *ctx)
+{
+ GError *error = NULL;
+ GList *l;
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hold_and_accept_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_hold_and_accept_context_free (ctx);
+ return;
+ }
+
+ for (l = ctx->active_calls; l; l = g_list_next (l))
+ mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_HELD, MM_CALL_STATE_REASON_UNKNOWN);
+ if (ctx->next_call)
+ mm_base_call_change_state (ctx->next_call, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
+
+ mm_gdbus_modem_voice_complete_hold_and_accept (ctx->skeleton, ctx->invocation);
+ handle_hold_and_accept_context_free (ctx);
+}
+
+static void
+prepare_hold_and_accept_foreach (MMBaseCall *call,
+ HandleHoldAndAcceptContext *ctx)
+{
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_ACTIVE:
+ ctx->active_calls = g_list_append (ctx->active_calls, g_object_ref (call));
+ break;
+ case MM_CALL_STATE_WAITING:
+ g_clear_object (&ctx->next_call);
+ ctx->next_call = g_object_ref (call);
+ break;
+ case MM_CALL_STATE_HELD:
+ if (!ctx->next_call)
+ ctx->next_call = g_object_ref (call);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+handle_hold_and_accept_auth_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ HandleHoldAndAcceptContext *ctx)
+{
+ MMModemState modem_state = MM_MODEM_STATE_UNKNOWN;
+ GError *error = NULL;
+ MMCallList *list = NULL;
+
+ if (!mm_base_modem_authorize_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_hold_and_accept_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_STATE, &modem_state,
+ NULL);
+
+ if (modem_state < MM_MODEM_STATE_ENABLED) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot hold and accept: device not yet enabled");
+ handle_hold_and_accept_context_free (ctx);
+ return;
+ }
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hold_and_accept ||
+ !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hold_and_accept_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot hold and accept: unsupported");
+ handle_hold_and_accept_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot hold and accept: missing call list");
+ handle_hold_and_accept_context_free (ctx);
+ return;
+ }
+ mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_hold_and_accept_foreach, ctx);
+ g_object_unref (list);
+
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hold_and_accept (MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)hold_and_accept_ready,
+ ctx);
+}
+
+static gboolean
+handle_hold_and_accept (MmGdbusModemVoice *skeleton,
+ GDBusMethodInvocation *invocation,
+ MMIfaceModemVoice *self)
+{
+ HandleHoldAndAcceptContext *ctx;
+
+ ctx = g_slice_new0 (HandleHoldAndAcceptContext);
+ ctx->skeleton = g_object_ref (skeleton);
+ ctx->invocation = g_object_ref (invocation);
+ ctx->self = g_object_ref (self);
+
+ mm_base_modem_authorize (MM_BASE_MODEM (self),
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_hold_and_accept_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ MmGdbusModemVoice *skeleton;
+ GDBusMethodInvocation *invocation;
+ MMIfaceModemVoice *self;
+ GList *active_calls;
+ MMBaseCall *next_call;
+} HandleHangupAndAcceptContext;
+
+static void
+handle_hangup_and_accept_context_free (HandleHangupAndAcceptContext *ctx)
+{
+ g_list_free_full (ctx->active_calls, g_object_unref);
+ g_clear_object (&ctx->next_call);
+ g_object_unref (ctx->skeleton);
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->self);
+ g_slice_free (HandleHangupAndAcceptContext, ctx);
+}
+
+static void
+hangup_and_accept_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ HandleHangupAndAcceptContext *ctx)
+{
+ GError *error = NULL;
+ GList *l;
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_and_accept_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_hangup_and_accept_context_free (ctx);
+ return;
+ }
+
+ for (l = ctx->active_calls; l; l = g_list_next (l))
+ mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED);
+ if (ctx->next_call)
+ mm_base_call_change_state (ctx->next_call, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
+
+ mm_gdbus_modem_voice_complete_hangup_and_accept (ctx->skeleton, ctx->invocation);
+ handle_hangup_and_accept_context_free (ctx);
+}
+
+static void
+prepare_hangup_and_accept_foreach (MMBaseCall *call,
+ HandleHangupAndAcceptContext *ctx)
+{
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_ACTIVE:
+ ctx->active_calls = g_list_append (ctx->active_calls, g_object_ref (call));
+ break;
+ case MM_CALL_STATE_WAITING:
+ g_clear_object (&ctx->next_call);
+ ctx->next_call = g_object_ref (call);
+ break;
+ case MM_CALL_STATE_HELD:
+ if (!ctx->next_call)
+ ctx->next_call = g_object_ref (call);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+handle_hangup_and_accept_auth_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ HandleHangupAndAcceptContext *ctx)
+{
+ MMModemState modem_state = MM_MODEM_STATE_UNKNOWN;
+ GError *error = NULL;
+ MMCallList *list = NULL;
+
+ if (!mm_base_modem_authorize_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_hangup_and_accept_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_STATE, &modem_state,
+ NULL);
+
+ if (modem_state < MM_MODEM_STATE_ENABLED) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot hangup and accept: device not yet enabled");
+ handle_hangup_and_accept_context_free (ctx);
+ return;
+ }
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_and_accept ||
+ !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_and_accept_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot hangup and accept: unsupported");
+ handle_hangup_and_accept_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot hangup and accept: missing call list");
+ handle_hangup_and_accept_context_free (ctx);
+ return;
+ }
+ mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_hangup_and_accept_foreach, ctx);
+ g_object_unref (list);
+
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_and_accept (MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)hangup_and_accept_ready,
+ ctx);
+}
+
+static gboolean
+handle_hangup_and_accept (MmGdbusModemVoice *skeleton,
+ GDBusMethodInvocation *invocation,
+ MMIfaceModemVoice *self)
+{
+ HandleHangupAndAcceptContext *ctx;
+
+ ctx = g_slice_new0 (HandleHangupAndAcceptContext);
+ ctx->skeleton = g_object_ref (skeleton);
+ ctx->invocation = g_object_ref (invocation);
+ ctx->self = g_object_ref (self);
+
+ mm_base_modem_authorize (MM_BASE_MODEM (self),
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_hangup_and_accept_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ MmGdbusModemVoice *skeleton;
+ GDBusMethodInvocation *invocation;
+ MMIfaceModemVoice *self;
+ GList *calls;
+} HandleHangupAllContext;
+
+static void
+handle_hangup_all_context_free (HandleHangupAllContext *ctx)
+{
+ g_list_free_full (ctx->calls, g_object_unref);
+ g_object_unref (ctx->skeleton);
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->self);
+ g_slice_free (HandleHangupAllContext, ctx);
+}
+
+static void
+hangup_all_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ HandleHangupAllContext *ctx)
+{
+ GError *error = NULL;
+ GList *l;
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_all_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_hangup_all_context_free (ctx);
+ return;
+ }
+
+ for (l = ctx->calls; l; l = g_list_next (l))
+ mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED);
+
+ mm_gdbus_modem_voice_complete_hangup_all (ctx->skeleton, ctx->invocation);
+ handle_hangup_all_context_free (ctx);
+}
+
+static void
+prepare_hangup_all_foreach (MMBaseCall *call,
+ HandleHangupAllContext *ctx)
+{
+ /* The implementation of this operation will usually be done with +CHUP, and we
+ * know that +CHUP is implemented in different ways by different manufacturers.
+ *
+ * The 3GPP TS27.007 spec for +CHUP states that the "Execution command causes
+ * the TA to hangup the current call of the MT." This sentence leaves a bit of open
+ * interpretation to the implementors, because a current call can be considered only
+ * the active ones, or otherwise any call (active, held or waiting).
+ *
+ * And so, the u-blox TOBY-L4 takes one interpretation and "In case of multiple
+ * calls, all active calls will be released, while waiting and held calls are not".
+ *
+ * And the Cinterion PLS-8 takes a different interpretation and cancels all calls,
+ * including the waiting and held ones.
+ *
+ * In this logic, we're going to terminate exclusively the ACTIVE calls only, and we
+ * will leave the possible termination of waiting/held calls to be reported via
+ * call state updates, e.g. +CLCC polling or other plugin-specific method. In the
+ * case of the Cinterion PLS-8, we'll detect the termination of the waiting and
+ * held calls via ^SLCC URCs.
+ */
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_DIALING:
+ case MM_CALL_STATE_RINGING_OUT:
+ case MM_CALL_STATE_RINGING_IN:
+ case MM_CALL_STATE_ACTIVE:
+ ctx->calls = g_list_append (ctx->calls, g_object_ref (call));
+ break;
+ case MM_CALL_STATE_WAITING:
+ case MM_CALL_STATE_HELD:
+ default:
+ break;
+ }
+}
+
+static void
+handle_hangup_all_auth_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ HandleHangupAllContext *ctx)
+{
+ MMModemState modem_state = MM_MODEM_STATE_UNKNOWN;
+ GError *error = NULL;
+ MMCallList *list = NULL;
+
+ if (!mm_base_modem_authorize_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_hangup_all_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_STATE, &modem_state,
+ NULL);
+
+ if (modem_state < MM_MODEM_STATE_ENABLED) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot hangup all: device not yet enabled");
+ handle_hangup_all_context_free (ctx);
+ return;
+ }
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_all ||
+ !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_all_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot hangup all: unsupported");
+ handle_hangup_all_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot hangup all: missing call list");
+ handle_hangup_all_context_free (ctx);
+ return;
+ }
+ mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_hangup_all_foreach, ctx);
+ g_object_unref (list);
+
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->hangup_all (MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)hangup_all_ready,
+ ctx);
+}
+
+static gboolean
+handle_hangup_all (MmGdbusModemVoice *skeleton,
+ GDBusMethodInvocation *invocation,
+ MMIfaceModemVoice *self)
+{
+ HandleHangupAllContext *ctx;
+
+ ctx = g_slice_new0 (HandleHangupAllContext);
+ ctx->skeleton = g_object_ref (skeleton);
+ ctx->invocation = g_object_ref (invocation);
+ ctx->self = g_object_ref (self);
+
+ mm_base_modem_authorize (MM_BASE_MODEM (self),
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_hangup_all_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ MmGdbusModemVoice *skeleton;
+ GDBusMethodInvocation *invocation;
+ MMIfaceModemVoice *self;
+ GList *calls;
+} HandleTransferContext;
+
+static void
+handle_transfer_context_free (HandleTransferContext *ctx)
+{
+ g_list_free_full (ctx->calls, g_object_unref);
+ g_object_unref (ctx->skeleton);
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->self);
+ g_slice_free (HandleTransferContext, ctx);
+}
+
+static void
+transfer_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ HandleTransferContext *ctx)
+{
+ GError *error = NULL;
+ GList *l;
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->transfer_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_transfer_context_free (ctx);
+ return;
+ }
+
+ for (l = ctx->calls; l; l = g_list_next (l))
+ mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TRANSFERRED);
+
+ mm_gdbus_modem_voice_complete_transfer (ctx->skeleton, ctx->invocation);
+ handle_transfer_context_free (ctx);
+}
+
+static void
+prepare_transfer_foreach (MMBaseCall *call,
+ HandleTransferContext *ctx)
+{
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_ACTIVE:
+ case MM_CALL_STATE_HELD:
+ ctx->calls = g_list_append (ctx->calls, g_object_ref (call));
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+handle_transfer_auth_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ HandleTransferContext *ctx)
+{
+ MMModemState modem_state = MM_MODEM_STATE_UNKNOWN;
+ GError *error = NULL;
+ MMCallList *list = NULL;
+
+ if (!mm_base_modem_authorize_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_transfer_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_STATE, &modem_state,
+ NULL);
+
+ if (modem_state < MM_MODEM_STATE_ENABLED) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot transfer: device not yet enabled");
+ handle_transfer_context_free (ctx);
+ return;
+ }
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->transfer ||
+ !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->transfer_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot transfer: unsupported");
+ handle_transfer_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot transfer: missing call list");
+ handle_transfer_context_free (ctx);
+ return;
+ }
+ mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_transfer_foreach, ctx);
+ g_object_unref (list);
+
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->transfer (MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)transfer_ready,
+ ctx);
+}
+
+static gboolean
+handle_transfer (MmGdbusModemVoice *skeleton,
+ GDBusMethodInvocation *invocation,
+ MMIfaceModemVoice *self)
+{
+ HandleTransferContext *ctx;
+
+ ctx = g_slice_new0 (HandleTransferContext);
+ ctx->skeleton = g_object_ref (skeleton);
+ ctx->invocation = g_object_ref (invocation);
+ ctx->self = g_object_ref (self);
+
+ mm_base_modem_authorize (MM_BASE_MODEM (self),
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_transfer_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ MmGdbusModemVoice *skeleton;
+ GDBusMethodInvocation *invocation;
+ MMIfaceModemVoice *self;
+ gboolean enable;
+} HandleCallWaitingSetupContext;
+
+static void
+handle_call_waiting_setup_context_free (HandleCallWaitingSetupContext *ctx)
+{
+ g_object_unref (ctx->skeleton);
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->self);
+ g_slice_free (HandleCallWaitingSetupContext, ctx);
+}
+
+static void
+call_waiting_setup_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ HandleCallWaitingSetupContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_setup_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_call_waiting_setup_context_free (ctx);
+ return;
+ }
+
+ mm_gdbus_modem_voice_complete_call_waiting_setup (ctx->skeleton, ctx->invocation);
+ handle_call_waiting_setup_context_free (ctx);
+}
+
+static void
+handle_call_waiting_setup_auth_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ HandleCallWaitingSetupContext *ctx)
+{
+ MMModemState modem_state = MM_MODEM_STATE_UNKNOWN;
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_call_waiting_setup_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_STATE, &modem_state,
+ NULL);
+
+ if (modem_state < MM_MODEM_STATE_ENABLED) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot setup call waiting: device not yet enabled");
+ handle_call_waiting_setup_context_free (ctx);
+ return;
+ }
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_setup ||
+ !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_setup_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot setup call waiting: unsupported");
+ handle_call_waiting_setup_context_free (ctx);
+ return;
+ }
+
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_setup (MM_IFACE_MODEM_VOICE (self),
+ ctx->enable,
+ (GAsyncReadyCallback)call_waiting_setup_ready,
+ ctx);
+}
+
+static gboolean
+handle_call_waiting_setup (MmGdbusModemVoice *skeleton,
+ GDBusMethodInvocation *invocation,
+ gboolean enable,
+ MMIfaceModemVoice *self)
+{
+ HandleCallWaitingSetupContext *ctx;
+
+ ctx = g_slice_new0 (HandleCallWaitingSetupContext);
+ ctx->skeleton = g_object_ref (skeleton);
+ ctx->invocation = g_object_ref (invocation);
+ ctx->self = g_object_ref (self);
+ ctx->enable = enable;
+
+ mm_base_modem_authorize (MM_BASE_MODEM (self),
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_call_waiting_setup_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ MmGdbusModemVoice *skeleton;
+ GDBusMethodInvocation *invocation;
+ MMIfaceModemVoice *self;
+ gboolean enable;
+} HandleCallWaitingQueryContext;
+
+static void
+handle_call_waiting_query_context_free (HandleCallWaitingQueryContext *ctx)
+{
+ g_object_unref (ctx->skeleton);
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->self);
+ g_slice_free (HandleCallWaitingQueryContext, ctx);
+}
+
+static void
+call_waiting_query_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ HandleCallWaitingQueryContext *ctx)
+{
+ GError *error = NULL;
+ gboolean status = FALSE;
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_query_finish (self, res, &status, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_call_waiting_query_context_free (ctx);
+ return;
+ }
+
+ mm_gdbus_modem_voice_complete_call_waiting_query (ctx->skeleton, ctx->invocation, status);
+ handle_call_waiting_query_context_free (ctx);
+}
+
+static void
+handle_call_waiting_query_auth_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ HandleCallWaitingQueryContext *ctx)
+{
+ MMModemState modem_state = MM_MODEM_STATE_UNKNOWN;
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (self, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_call_waiting_query_context_free (ctx);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_STATE, &modem_state,
+ NULL);
+
+ if (modem_state < MM_MODEM_STATE_ENABLED) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot query call waiting: device not yet enabled");
+ handle_call_waiting_query_context_free (ctx);
+ return;
+ }
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_query ||
+ !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_query_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot query call waiting: unsupported");
+ handle_call_waiting_query_context_free (ctx);
+ return;
+ }
+
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->call_waiting_query (MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)call_waiting_query_ready,
+ ctx);
+}
+
+static gboolean
+handle_call_waiting_query (MmGdbusModemVoice *skeleton,
+ GDBusMethodInvocation *invocation,
+ MMIfaceModemVoice *self)
+{
+ HandleCallWaitingQueryContext *ctx;
+
+ ctx = g_slice_new0 (HandleCallWaitingQueryContext);
+ ctx->skeleton = g_object_ref (skeleton);
+ ctx->invocation = g_object_ref (invocation);
+ ctx->self = g_object_ref (self);
+
+ mm_base_modem_authorize (MM_BASE_MODEM (self),
+ invocation,
+ MM_AUTHORIZATION_VOICE,
+ (GAsyncReadyCallback)handle_call_waiting_query_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Leave one of the calls from the multiparty call */
+
+typedef struct {
+ MMBaseCall *call;
+ GList *other_calls;
+} LeaveMultipartyContext;
+
+static void
+leave_multiparty_context_free (LeaveMultipartyContext *ctx)
+{
+ g_list_free_full (ctx->other_calls, g_object_unref);
+ g_object_unref (ctx->call);
+ g_slice_free (LeaveMultipartyContext, ctx);
+}
+
+static void
+prepare_leave_multiparty_foreach (MMBaseCall *call,
+ LeaveMultipartyContext *ctx)
+{
+ /* ignore call that is leaving */
+ if ((call == ctx->call) || (g_strcmp0 (mm_base_call_get_path (call), mm_base_call_get_path (ctx->call)) == 0))
+ return;
+
+ /* ignore non-multiparty calls */
+ if (!mm_base_call_get_multiparty (call))
+ return;
+
+ /* ignore calls not currently ongoing */
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_ACTIVE:
+ case MM_CALL_STATE_HELD:
+ ctx->other_calls = g_list_append (ctx->other_calls, g_object_ref (call));
+ break;
+ default:
+ break;
+ }
+}
+
+gboolean
+mm_iface_modem_voice_leave_multiparty_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+leave_multiparty_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ LeaveMultipartyContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If there is only one remaining call that was part of the multiparty, consider that
+ * one also no longer part of any multiparty, and put it on hold right away */
+ if (g_list_length (ctx->other_calls) == 1) {
+ mm_base_call_set_multiparty (MM_BASE_CALL (ctx->other_calls->data), FALSE);
+ mm_base_call_change_state (MM_BASE_CALL (ctx->other_calls->data), MM_CALL_STATE_HELD, MM_CALL_STATE_REASON_UNKNOWN);
+ }
+ /* If there are still more than one calls in the multiparty, just change state of all
+ * of them. */
+ else {
+ GList *l;
+
+ for (l = ctx->other_calls; l; l = g_list_next (l))
+ mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_HELD, MM_CALL_STATE_REASON_UNKNOWN);
+ }
+
+ /* The call that left would now be active */
+ mm_base_call_set_multiparty (ctx->call, FALSE);
+ mm_base_call_change_state (ctx->call, MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_UNKNOWN);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_iface_modem_voice_leave_multiparty (MMIfaceModemVoice *self,
+ MMBaseCall *call,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LeaveMultipartyContext *ctx;
+ MMCallList *list = NULL;
+ MMCallState call_state;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* validate multiparty status */
+ if (!mm_base_call_get_multiparty (call)) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "this call is not part of a multiparty call");
+ g_object_unref (task);
+ return;
+ }
+ /* validate call state */
+ call_state = mm_base_call_get_state (call);
+ if ((call_state != MM_CALL_STATE_ACTIVE) && (call_state != MM_CALL_STATE_HELD)) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "invalid call state (%s): must be either active or held",
+ mm_call_state_get_string (call_state));
+ g_object_unref (task);
+ return;
+ }
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty ||
+ !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty_finish) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot leave multiparty: unsupported");
+ g_object_unref (task);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "Cannot leave multiparty: missing call list");
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (LeaveMultipartyContext);
+ ctx->call = g_object_ref (call);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) leave_multiparty_context_free);
+
+ mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_leave_multiparty_foreach, ctx);
+ g_object_unref (list);
+
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->leave_multiparty (self,
+ call,
+ (GAsyncReadyCallback)leave_multiparty_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Join calls into a multiparty call */
+
+typedef struct {
+ MMBaseCall *call;
+ GList *all_calls;
+ gboolean added;
+} JoinMultipartyContext;
+
+static void
+join_multiparty_context_free (JoinMultipartyContext *ctx)
+{
+ g_list_free_full (ctx->all_calls, g_object_unref);
+ g_object_unref (ctx->call);
+ g_slice_free (JoinMultipartyContext, ctx);
+}
+
+static void
+prepare_join_multiparty_foreach (MMBaseCall *call,
+ JoinMultipartyContext *ctx)
+{
+ /* always add call that is being added */
+ if ((call == ctx->call) || (g_strcmp0 (mm_base_call_get_path (call), mm_base_call_get_path (ctx->call)) == 0))
+ ctx->added = TRUE;
+
+ /* ignore calls not currently ongoing */
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_ACTIVE:
+ case MM_CALL_STATE_HELD:
+ ctx->all_calls = g_list_append (ctx->all_calls, g_object_ref (call));
+ break;
+ default:
+ break;
+ }
+}
+
+gboolean
+mm_iface_modem_voice_join_multiparty_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+join_multiparty_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ JoinMultipartyContext *ctx;
+ GList *l;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ for (l = ctx->all_calls; l; l = g_list_next (l)) {
+ mm_base_call_set_multiparty (MM_BASE_CALL (l->data), TRUE);
+ mm_base_call_change_state (MM_BASE_CALL (l->data), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_UNKNOWN);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_iface_modem_voice_join_multiparty (MMIfaceModemVoice *self,
+ MMBaseCall *call,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ JoinMultipartyContext *ctx;
+ MMCallList *list = NULL;
+ MMCallState call_state;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* validate multiparty status */
+ if (mm_base_call_get_multiparty (call)) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "this call is already part of a multiparty call");
+ g_object_unref (task);
+ return;
+ }
+ /* validate call state */
+ call_state = mm_base_call_get_state (call);
+ if (call_state != MM_CALL_STATE_HELD) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "invalid call state (%s): must be held",
+ mm_call_state_get_string (call_state));
+ g_object_unref (task);
+ return;
+ }
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty ||
+ !MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty_finish) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot join multiparty: unsupported");
+ g_object_unref (task);
+ return;
+ }
+
+ g_object_get (self,
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "Cannot join multiparty: missing call list");
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (JoinMultipartyContext);
+ ctx->call = g_object_ref (call);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) join_multiparty_context_free);
+
+ mm_call_list_foreach (list, (MMCallListForeachFunc)prepare_join_multiparty_foreach, ctx);
+ g_object_unref (list);
+
+ /* our logic makes sure that we would be adding the incoming call into the multiparty call */
+ g_assert (ctx->added);
+
+ /* NOTE: we do not give the call we want to join, because the join operation acts on all
+ * active/held calls. */
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->join_multiparty (self,
+ (GAsyncReadyCallback)join_multiparty_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* In-call setup operation
+ *
+ * It will setup URC handlers for all in-call URCs, and also setup the audio
+ * channel if the plugin requires to do so.
+ */
+
+typedef enum {
+ IN_CALL_SETUP_STEP_FIRST,
+ IN_CALL_SETUP_STEP_UNSOLICITED_EVENTS,
+ IN_CALL_SETUP_STEP_AUDIO_CHANNEL,
+ IN_CALL_SETUP_STEP_LAST,
+} InCallSetupStep;
+
+typedef struct {
+ InCallSetupStep step;
+ MMPort *audio_port;
+ MMCallAudioFormat *audio_format;
+} InCallSetupContext;
+
+static void
+in_call_setup_context_free (InCallSetupContext *ctx)
+{
+ g_clear_object (&ctx->audio_port);
+ g_clear_object (&ctx->audio_format);
+ g_slice_free (InCallSetupContext, ctx);
+}
+
+static gboolean
+in_call_setup_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ MMPort **audio_port, /* optional */
+ MMCallAudioFormat **audio_format, /* optional */
+ GError **error)
+{
+ InCallSetupContext *ctx;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ ctx = g_task_get_task_data (G_TASK (res));
+ if (audio_port) {
+ *audio_port = ctx->audio_port;
+ ctx->audio_port = NULL;
+ }
+ if (audio_format) {
+ *audio_format = ctx->audio_format;
+ ctx->audio_format = NULL;
+ }
+
+ return TRUE;
+}
+
+static void in_call_setup_context_step (GTask *task);
+
+static void
+setup_in_call_audio_channel_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ InCallSetupContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_audio_channel_finish (self,
+ res,
+ &ctx->audio_port,
+ &ctx->audio_format,
+ &error)) {
+ mm_warn ("Couldn't setup in-call audio channel: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ ctx->step++;
+ in_call_setup_context_step (task);
+}
+
+static void
+setup_in_call_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ InCallSetupContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_unsolicited_events_finish (self, res, &error)) {
+ mm_warn ("Couldn't setup in-call unsolicited events: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ ctx->step++;
+ in_call_setup_context_step (task);
+}
+
+static void
+in_call_setup_context_step (GTask *task)
+{
+ MMIfaceModemVoice *self;
+ InCallSetupContext *ctx;
+
+ if (g_task_return_error_if_cancelled (task))
+ return;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case IN_CALL_SETUP_STEP_FIRST:
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_SETUP_STEP_UNSOLICITED_EVENTS:
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_unsolicited_events &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_unsolicited_events_finish) {
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_unsolicited_events (
+ self,
+ (GAsyncReadyCallback) setup_in_call_unsolicited_events_ready,
+ task);
+ break;
+ }
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_SETUP_STEP_AUDIO_CHANNEL:
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_audio_channel &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_audio_channel_finish) {
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_in_call_audio_channel (
+ self,
+ (GAsyncReadyCallback) setup_in_call_audio_channel_ready,
+ task);
+ break;
+ }
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_SETUP_STEP_LAST:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+}
+
+static void
+in_call_setup (MMIfaceModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ InCallSetupContext *ctx;
+
+ ctx = g_slice_new0 (InCallSetupContext);
+ ctx->step = IN_CALL_SETUP_STEP_FIRST;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) in_call_setup_context_free);
+
+ in_call_setup_context_step (task);
+}
+
+/*****************************************************************************/
+/* In-call cleanup operation
+ *
+ * It will cleanup audio channel settings and remove all in-call URC handlers.
+ */
+
+typedef enum {
+ IN_CALL_CLEANUP_STEP_FIRST,
+ IN_CALL_CLEANUP_STEP_AUDIO_CHANNEL,
+ IN_CALL_CLEANUP_STEP_UNSOLICITED_EVENTS,
+ IN_CALL_CLEANUP_STEP_LAST,
+} InCallCleanupStep;
+
+typedef struct {
+ InCallCleanupStep step;
+} InCallCleanupContext;
+
+static gboolean
+in_call_cleanup_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void in_call_cleanup_context_step (GTask *task);
+
+static void
+cleanup_in_call_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ InCallCleanupContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_unsolicited_events_finish (self, res, &error)) {
+ mm_warn ("Couldn't cleanup in-call unsolicited events: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ ctx->step++;
+ in_call_cleanup_context_step (task);
+}
+
+static void
+cleanup_in_call_audio_channel_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ InCallCleanupContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_audio_channel_finish (self, res, &error)) {
+ mm_warn ("Couldn't cleanup in-call audio channel: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ ctx->step++;
+ in_call_cleanup_context_step (task);
+}
+
+static void
+in_call_cleanup_context_step (GTask *task)
+{
+ MMIfaceModemVoice *self;
+ InCallCleanupContext *ctx;
+
+ if (g_task_return_error_if_cancelled (task))
+ return;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case IN_CALL_CLEANUP_STEP_FIRST:
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_CLEANUP_STEP_AUDIO_CHANNEL:
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_audio_channel &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_audio_channel_finish) {
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_audio_channel (
+ self,
+ (GAsyncReadyCallback) cleanup_in_call_audio_channel_ready,
+ task);
+ break;
+ }
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_CLEANUP_STEP_UNSOLICITED_EVENTS:
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_unsolicited_events &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_unsolicited_events_finish) {
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->cleanup_in_call_unsolicited_events (
+ self,
+ (GAsyncReadyCallback) cleanup_in_call_unsolicited_events_ready,
+ task);
+ break;
+ }
+ ctx->step++;
+ /* fall-through */
+ case IN_CALL_CLEANUP_STEP_LAST:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+}
+
+static void
+in_call_cleanup (MMIfaceModemVoice *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ InCallCleanupContext *ctx;
+
+ ctx = g_new0 (InCallCleanupContext, 1);
+ ctx->step = IN_CALL_CLEANUP_STEP_FIRST;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ in_call_cleanup_context_step (task);
+}
+
+/*****************************************************************************/
+/* In-call event handling logic
+ *
+ * This procedure will run a in-call setup async function whenever we detect
+ * that there is at least one call that is ongoing. This setup function will
+ * try to setup in-call unsolicited events as well as any audio channel
+ * requirements.
+ *
+ * The procedure will run a in-call cleanup async function whenever we detect
+ * that there are no longer any ongoing calls. The cleanup function will
+ * cleanup the audio channel and remove the in-call unsolicited event handlers.
+ */
+
+typedef struct {
+ guint check_id;
+ GCancellable *setup_cancellable;
+ GCancellable *cleanup_cancellable;
+ gboolean in_call_state;
+ MMPort *audio_port;
+ MMCallAudioFormat *audio_format;
+} InCallEventContext;
+
+static void
+in_call_event_context_free (InCallEventContext *ctx)
+{
+ if (ctx->check_id)
+ g_source_remove (ctx->check_id);
+ if (ctx->cleanup_cancellable) {
+ g_cancellable_cancel (ctx->cleanup_cancellable);
+ g_clear_object (&ctx->cleanup_cancellable);
+ }
+ if (ctx->setup_cancellable) {
+ g_cancellable_cancel (ctx->setup_cancellable);
+ g_clear_object (&ctx->setup_cancellable);
+ }
+ g_clear_object (&ctx->audio_port);
+ g_clear_object (&ctx->audio_format);
+ g_slice_free (InCallEventContext, ctx);
+}
+
+static InCallEventContext *
+get_in_call_event_context (MMIfaceModemVoice *self)
+{
+ InCallEventContext *ctx;
+
+ if (G_UNLIKELY (!in_call_event_context_quark))
+ in_call_event_context_quark = g_quark_from_static_string (IN_CALL_EVENT_CONTEXT_TAG);
+
+ ctx = g_object_get_qdata (G_OBJECT (self), in_call_event_context_quark);
+ if (!ctx) {
+ /* Create context and keep it as object data */
+ ctx = g_slice_new0 (InCallEventContext);
+ g_object_set_qdata_full (
+ G_OBJECT (self),
+ in_call_event_context_quark,
+ ctx,
+ (GDestroyNotify)in_call_event_context_free);
+ }
+
+ return ctx;
+}
+
+static void
+call_list_foreach_audio_settings (MMBaseCall *call,
+ InCallEventContext *ctx)
+{
+ if (mm_base_call_get_state (call) != MM_CALL_STATE_TERMINATED)
+ return;
+ mm_base_call_change_audio_settings (call, ctx->audio_port, ctx->audio_format);
+}
+
+static void
+update_audio_settings_in_ongoing_calls (MMIfaceModemVoice *self)
+{
+ MMCallList *list = NULL;
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+
+ g_object_get (MM_BASE_MODEM (self),
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ mm_warn ("Cannot update audio settings in active calls: missing internal call list");
+ return;
+ }
+
+ mm_call_list_foreach (list, (MMCallListForeachFunc) call_list_foreach_audio_settings, ctx);
+ g_clear_object (&list);
+}
+
+static void
+update_audio_settings_in_call (MMIfaceModemVoice *self,
+ MMBaseCall *call)
+{
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+ mm_base_call_change_audio_settings (call, ctx->audio_port, ctx->audio_format);
+}
+
+static void
+call_list_foreach_count_in_call (MMBaseCall *call,
+ gpointer user_data)
+{
+ guint *n_calls_in_call = (guint *)user_data;
+
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_DIALING:
+ case MM_CALL_STATE_RINGING_OUT:
+ case MM_CALL_STATE_HELD:
+ case MM_CALL_STATE_ACTIVE:
+ *n_calls_in_call = *n_calls_in_call + 1;
+ break;
+ case MM_CALL_STATE_RINGING_IN:
+ case MM_CALL_STATE_WAITING:
+ /* NOTE: ringing-in and waiting calls are NOT yet in-call, e.g. there must
+ * be no audio settings enabled and we must not enable in-call URC handling
+ * yet. */
+ default:
+ break;
+ }
+}
+
+static void
+in_call_cleanup_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+
+ if (!in_call_cleanup_finish (self, res, &error)) {
+ /* ignore cancelled operations */
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && !g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED))
+ mm_warn ("Cannot cleanup in-call modem state: %s", error->message);
+ g_clear_error (&error);
+ } else {
+ mm_dbg ("modem is no longer in-call state");
+ ctx->in_call_state = FALSE;
+ g_clear_object (&ctx->audio_port);
+ g_clear_object (&ctx->audio_format);
+ }
+
+ g_clear_object (&ctx->cleanup_cancellable);
+}
+
+static void
+in_call_setup_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+
+ if (!in_call_setup_finish (self, res, &ctx->audio_port, &ctx->audio_format, &error)) {
+ /* ignore cancelled operations */
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && !g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED))
+ mm_warn ("Cannot setup in-call modem state: %s", error->message);
+ g_clear_error (&error);
+ } else {
+ mm_dbg ("modem is now in-call state");
+ ctx->in_call_state = TRUE;
+ update_audio_settings_in_ongoing_calls (self);
+ }
+
+ g_clear_object (&ctx->setup_cancellable);
+}
+
+static gboolean
+call_list_check_in_call_events (MMIfaceModemVoice *self)
+{
+ InCallEventContext *ctx;
+ MMCallList *list = NULL;
+ guint n_calls_in_call = 0;
+
+ ctx = get_in_call_event_context (self);
+ ctx->check_id = 0;
+
+ g_object_get (MM_BASE_MODEM (self),
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+ if (!list) {
+ mm_warn ("Cannot update in-call state: missing internal call list");
+ goto out;
+ }
+
+ mm_call_list_foreach (list, (MMCallListForeachFunc) call_list_foreach_count_in_call, &n_calls_in_call);
+
+ /* Need to setup in-call events? */
+ if (n_calls_in_call > 0 && !ctx->in_call_state) {
+ /* if setup already ongoing, do nothing */
+ if (ctx->setup_cancellable)
+ goto out;
+
+ /* cancel ongoing cleanup if any */
+ if (ctx->cleanup_cancellable) {
+ g_cancellable_cancel (ctx->cleanup_cancellable);
+ g_clear_object (&ctx->cleanup_cancellable);
+ }
+
+ /* run setup */
+ mm_dbg ("Setting up in-call state...");
+ ctx->setup_cancellable = g_cancellable_new ();
+ in_call_setup (self, ctx->setup_cancellable, (GAsyncReadyCallback) in_call_setup_ready, NULL);
+ goto out;
+ }
+
+ /* Need to cleanup in-call events? */
+ if (n_calls_in_call == 0 && ctx->in_call_state) {
+ /* if cleanup already ongoing, do nothing */
+ if (ctx->cleanup_cancellable)
+ goto out;
+
+ /* cancel ongoing setup if any */
+ if (ctx->setup_cancellable) {
+ g_cancellable_cancel (ctx->setup_cancellable);
+ g_clear_object (&ctx->setup_cancellable);
+ }
+
+ /* run cleanup */
+ mm_dbg ("Cleaning up in-call state...");
+ ctx->cleanup_cancellable = g_cancellable_new ();
+ in_call_cleanup (self, ctx->cleanup_cancellable, (GAsyncReadyCallback) in_call_cleanup_ready, NULL);
+ goto out;
+ }
+
+ out:
+ g_clear_object (&list);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+call_state_changed (MMIfaceModemVoice *self)
+{
+ InCallEventContext *ctx;
+
+ ctx = get_in_call_event_context (self);
+ if (ctx->check_id)
+ return;
+
+ /* Process check for in-call events in an idle, so that we can combine
+ * together in the same check multiple call state updates happening
+ * at the same time for different calls (e.g. when swapping active/held
+ * calls). */
+ ctx->check_id = g_idle_add ((GSourceFunc)call_list_check_in_call_events, self);
+}
+
+static void
+setup_in_call_event_handling (MMCallList *call_list,
+ const gchar *call_path_added,
+ MMIfaceModemVoice *self)
+{
+ MMBaseCall *call;
+
+ call = mm_call_list_get_call (call_list, call_path_added);
+ g_assert (call);
+
+ g_signal_connect_swapped (call,
+ "state-changed",
+ G_CALLBACK (call_state_changed),
+ self);
+}
+
+/*****************************************************************************/
+/* Call list polling logic
+ *
+ * The call list polling is exclusively used to detect detailed call state
+ * updates while a call is being established. Therefore, if there is no call
+ * being established (i.e. all terminated, unknown or active), then there is
+ * no polling to do.
+ *
+ * Any time we add a new call to the list, we'll setup polling if it's not
+ * already running, and the polling logic itself will decide when the polling
+ * should stop.
+ */
+
+#define CALL_LIST_POLLING_TIMEOUT_SECS 2
+
+typedef struct {
+ guint polling_id;
+ gboolean polling_ongoing;
+} CallListPollingContext;
+
+static void
+call_list_polling_context_free (CallListPollingContext *ctx)
+{
+ if (ctx->polling_id)
+ g_source_remove (ctx->polling_id);
+ g_slice_free (CallListPollingContext, ctx);
+}
+
+static CallListPollingContext *
+get_call_list_polling_context (MMIfaceModemVoice *self)
+{
+ CallListPollingContext *ctx;
+
+ if (G_UNLIKELY (!call_list_polling_context_quark))
+ call_list_polling_context_quark = (g_quark_from_static_string (
+ CALL_LIST_POLLING_CONTEXT_TAG));
+
+ ctx = g_object_get_qdata (G_OBJECT (self), call_list_polling_context_quark);
+ if (!ctx) {
+ /* Create context and keep it as object data */
+ ctx = g_slice_new0 (CallListPollingContext);
+
+ g_object_set_qdata_full (
+ G_OBJECT (self),
+ call_list_polling_context_quark,
+ ctx,
+ (GDestroyNotify)call_list_polling_context_free);
+ }
+
+ return ctx;
+}
+
+static gboolean call_list_poll (MMIfaceModemVoice *self);
+
+static void
+load_call_list_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res)
+{
+ CallListPollingContext *ctx;
+ GList *call_info_list = NULL;
+ GError *error = NULL;
+
+ ctx = get_call_list_polling_context (self);
+ ctx->polling_ongoing = FALSE;
+
+ g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list_finish);
+ if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list_finish (self, res, &call_info_list, &error)) {
+ mm_warn ("couldn't load call list: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* Always report the list even if NULL (it would mean no ongoing calls) */
+ mm_iface_modem_voice_report_all_calls (self, call_info_list);
+ mm_3gpp_call_info_list_free (call_info_list);
+
+ /* setup the polling again */
+ g_assert (!ctx->polling_id);
+ ctx->polling_id = g_timeout_add_seconds (CALL_LIST_POLLING_TIMEOUT_SECS,
+ (GSourceFunc) call_list_poll,
+ self);
+}
+
+static void
+call_list_foreach_count_establishing (MMBaseCall *call,
+ gpointer user_data)
+{
+ guint *n_calls_establishing = (guint *)user_data;
+
+ switch (mm_base_call_get_state (call)) {
+ case MM_CALL_STATE_DIALING:
+ case MM_CALL_STATE_RINGING_OUT:
+ case MM_CALL_STATE_RINGING_IN:
+ case MM_CALL_STATE_HELD:
+ case MM_CALL_STATE_WAITING:
+ *n_calls_establishing = *n_calls_establishing + 1;
+ break;
+ default:
+ break;
+ }
+}
+
+static gboolean
+call_list_poll (MMIfaceModemVoice *self)
+{
+ CallListPollingContext *ctx;
+ MMCallList *list = NULL;
+ guint n_calls_establishing = 0;
+
+ ctx = get_call_list_polling_context (self);
+ ctx->polling_id = 0;
+
+ g_object_get (MM_BASE_MODEM (self),
+ MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+ NULL);
+
+ if (!list) {
+ mm_warn ("Cannot poll call list: missing internal call list");
+ goto out;
+ }
+
+ mm_call_list_foreach (list, (MMCallListForeachFunc) call_list_foreach_count_establishing, &n_calls_establishing);
+
+ /* If there is at least ONE call being established, we need the call list */
+ if (n_calls_establishing > 0) {
+ mm_dbg ("%u calls being established: call list polling required", n_calls_establishing);
+ ctx->polling_ongoing = TRUE;
+ g_assert (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list);
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list (self,
+ (GAsyncReadyCallback)load_call_list_ready,
+ NULL);
+ } else
+ mm_dbg ("no calls being established: call list polling stopped");
+
+out:
+ g_clear_object (&list);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+setup_call_list_polling (MMCallList *call_list,
+ const gchar *call_path_added,
+ MMIfaceModemVoice *self)
+{
+ CallListPollingContext *ctx;
+
+ ctx = get_call_list_polling_context (self);
+
+ if (!ctx->polling_id && !ctx->polling_ongoing)
+ ctx->polling_id = g_timeout_add_seconds (CALL_LIST_POLLING_TIMEOUT_SECS,
+ (GSourceFunc) call_list_poll,
+ self);
+}
+
+/*****************************************************************************/
+
+static void
+update_call_list (MmGdbusModemVoice *skeleton,
+ MMCallList *list)
{
gchar **paths;
@@ -382,22 +2465,22 @@
}
static void
-call_added (MMCallList *list,
- const gchar *call_path,
- MmGdbusModemVoice *skeleton)
+call_added (MMCallList *list,
+ const gchar *call_path,
+ MmGdbusModemVoice *skeleton)
{
- mm_dbg ("Added CALL at '%s'", call_path);
- update_message_list (skeleton, list);
+ mm_dbg ("Added call at '%s'", call_path);
+ update_call_list (skeleton, list);
mm_gdbus_modem_voice_emit_call_added (skeleton, call_path);
}
static void
-call_deleted (MMCallList *list,
- const gchar *call_path,
- MmGdbusModemVoice *skeleton)
+call_deleted (MMCallList *list,
+ const gchar *call_path,
+ MmGdbusModemVoice *skeleton)
{
- mm_dbg ("Deleted CALL at '%s'", call_path);
- update_message_list (skeleton, list);
+ mm_dbg ("Deleted call at '%s'", call_path);
+ update_call_list (skeleton, list);
mm_gdbus_modem_voice_emit_call_deleted (skeleton, call_path);
}
@@ -669,6 +2752,29 @@
G_CALLBACK (call_deleted),
ctx->skeleton);
+ /* Setup monitoring for in-call event handling */
+ g_signal_connect (list,
+ MM_CALL_ADDED,
+ G_CALLBACK (setup_in_call_event_handling),
+ self);
+
+ /* Unless we're told not to, setup call list polling logic */
+ if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list &&
+ MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->load_call_list_finish) {
+ gboolean periodic_call_list_check_disabled = FALSE;
+
+ g_object_get (self,
+ MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, &periodic_call_list_check_disabled,
+ NULL);
+ if (!periodic_call_list_check_disabled) {
+ mm_dbg ("periodic call list polling will be used if supported");
+ g_signal_connect (list,
+ MM_CALL_ADDED,
+ G_CALLBACK (setup_call_list_polling),
+ self);
+ }
+ }
+
g_object_unref (list);
/* Fall down to next step */
@@ -863,21 +2969,18 @@
ctx->step++;
case INITIALIZATION_STEP_LAST:
- /* We are done without errors! */
-
- /* Handle method invocations */
- g_signal_connect (ctx->skeleton,
- "handle-create-call",
- G_CALLBACK (handle_create),
- self);
- g_signal_connect (ctx->skeleton,
- "handle-delete-call",
- G_CALLBACK (handle_delete),
- self);
- g_signal_connect (ctx->skeleton,
- "handle-list-calls",
- G_CALLBACK (handle_list),
- self);
+ /* Setup all method handlers */
+ g_object_connect (ctx->skeleton,
+ "signal::handle-create-call", G_CALLBACK (handle_create), self,
+ "signal::handle-delete-call", G_CALLBACK (handle_delete), self,
+ "signal::handle-list-calls", G_CALLBACK (handle_list), self,
+ "signal::handle-hangup-and-accept", G_CALLBACK (handle_hangup_and_accept), self,
+ "signal::handle-hold-and-accept", G_CALLBACK (handle_hold_and_accept), self,
+ "signal::handle-hangup-all", G_CALLBACK (handle_hangup_all), self,
+ "signal::handle-transfer", G_CALLBACK (handle_transfer), self,
+ "signal::handle-call-waiting-setup", G_CALLBACK (handle_call_waiting_setup), self,
+ "signal::handle-call-waiting-query", G_CALLBACK (handle_call_waiting_query), self,
+ NULL);
/* Finally, export the new interface */
mm_gdbus_object_skeleton_set_modem_voice (MM_GDBUS_OBJECT_SKELETON (self),
@@ -970,6 +3073,14 @@
MM_TYPE_CALL_LIST,
G_PARAM_READWRITE));
+ g_object_interface_install_property
+ (g_iface,
+ g_param_spec_boolean (MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED,
+ "Periodic call list checks disabled",
+ "Whether periodic call list check are disabled.",
+ FALSE,
+ G_PARAM_READWRITE));
+
initialized = TRUE;
}
diff --git a/src/mm-iface-modem-voice.h b/src/mm-iface-modem-voice.h
index e82c546..7f793dc 100644
--- a/src/mm-iface-modem-voice.h
+++ b/src/mm-iface-modem-voice.h
@@ -10,7 +10,8 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
- * Copyright (C) 2015 - Marco Bascetta <marco.bascetta@sadel.it>
+ * Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
+ * Copyright (C) 2019 Purism SPC
*/
#ifndef MM_IFACE_MODEM_VOICE_H
@@ -29,8 +30,9 @@
#define MM_IS_IFACE_MODEM_VOICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_IFACE_MODEM_VOICE))
#define MM_IFACE_MODEM_VOICE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_IFACE_MODEM_VOICE, MMIfaceModemVoice))
-#define MM_IFACE_MODEM_VOICE_DBUS_SKELETON "iface-modem-voice-dbus-skeleton"
-#define MM_IFACE_MODEM_VOICE_CALL_LIST "iface-modem-voice-call-list"
+#define MM_IFACE_MODEM_VOICE_DBUS_SKELETON "iface-modem-voice-dbus-skeleton"
+#define MM_IFACE_MODEM_VOICE_CALL_LIST "iface-modem-voice-call-list"
+#define MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED "iface-modem-voice-periodic-call-list-check-disabled"
typedef struct _MMIfaceModemVoice MMIfaceModemVoice;
@@ -77,10 +79,120 @@
GAsyncResult *res,
GError **error);
- /* Create CALL objects */
+ /* Asynchronous setup of in-call unsolicited events */
+ void (* setup_in_call_unsolicited_events) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* setup_in_call_unsolicited_events_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Asynchronous cleanup of in-call unsolicited events */
+ void (* cleanup_in_call_unsolicited_events) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* cleanup_in_call_unsolicited_events_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Asynchronous setup of in-call audio channel */
+ void (* setup_in_call_audio_channel) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* setup_in_call_audio_channel_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ MMPort **audio_port, /* optional */
+ MMCallAudioFormat **audio_format, /* optional */
+ GError **error);
+
+ /* Asynchronous cleanup of in-call audio channel */
+ void (* cleanup_in_call_audio_channel) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* cleanup_in_call_audio_channel_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Load full list of calls (MMCallInfo list) */
+ void (* load_call_list) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* load_call_list_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GList **call_info_list,
+ GError **error);
+
+ /* Create call objects */
MMBaseCall * (* create_call) (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number);
+
+ /* Hold and accept */
+ void (* hold_and_accept) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* hold_and_accept_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Hangup and accept */
+ void (* hangup_and_accept) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* hangup_and_accept_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Hangup all */
+ void (* hangup_all) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* hangup_all_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Join multiparty */
+ void (* join_multiparty) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* join_multiparty_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Leave multiparty */
+ void (* leave_multiparty) (MMIfaceModemVoice *self,
+ MMBaseCall *call,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* leave_multiparty_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Transfer */
+ void (* transfer) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* transfer_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Call waiting setup */
+ void (* call_waiting_setup) (MMIfaceModemVoice *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* call_waiting_setup_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+ /* Call waiting query */
+ void (* call_waiting_query) (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* call_waiting_query_finish) (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ gboolean *status,
+ GError **error);
};
GType mm_iface_modem_voice_get_type (void);
@@ -116,10 +228,40 @@
/* Bind properties for simple GetStatus() */
void mm_iface_modem_voice_bind_simple_status (MMIfaceModemVoice *self,
- MMSimpleStatus *status);
+ MMSimpleStatus *status);
-/* Incoming call management */
-void mm_iface_modem_voice_report_incoming_call (MMIfaceModemVoice *self,
- const gchar *number);
+/* Single call info reporting */
+void mm_iface_modem_voice_report_call (MMIfaceModemVoice *self,
+ const MMCallInfo *call_info);
+
+/* Full current call list reporting (MMCallInfo list) */
+void mm_iface_modem_voice_report_all_calls (MMIfaceModemVoice *self,
+ GList *call_info_list);
+
+/* Report an incoming DTMF received */
+void mm_iface_modem_voice_received_dtmf (MMIfaceModemVoice *self,
+ guint index,
+ const gchar *dtmf);
+
+/* Join/Leave multiparty calls
+ *
+ * These actions are provided in the Call API, but implemented in the
+ * modem Voice interface because they really affect multiple calls at
+ * the same time.
+ */
+void mm_iface_modem_voice_join_multiparty (MMIfaceModemVoice *self,
+ MMBaseCall *call,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_iface_modem_voice_join_multiparty_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_iface_modem_voice_leave_multiparty (MMIfaceModemVoice *self,
+ MMBaseCall *call,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_iface_modem_voice_leave_multiparty_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
#endif /* MM_IFACE_MODEM_VOICE_H */
diff --git a/src/mm-iface-modem.c b/src/mm-iface-modem.c
index 66db38a..c284524 100644
--- a/src/mm-iface-modem.c
+++ b/src/mm-iface-modem.c
@@ -372,6 +372,7 @@
g_assert (ctx->pin_check_timeout_id == 0);
MM_IFACE_MODEM_GET_INTERFACE (self)->load_unlock_required (
self,
+ (ctx->retries == MAX_RETRIES), /* last_attempt? */
(GAsyncReadyCallback)load_unlock_required_ready,
task);
}
@@ -425,7 +426,7 @@
/*****************************************************************************/
-static MMModemState get_current_consolidated_state (MMIfaceModem *self, MMModemState modem_state);
+static MMModemState get_consolidated_subsystem_state (MMIfaceModem *self);
typedef struct {
MMBaseBearer *self;
@@ -490,7 +491,7 @@
new_state = MM_MODEM_STATE_DISCONNECTING;
break;
case MM_BEARER_STATUS_DISCONNECTED:
- new_state = get_current_consolidated_state (self, MM_MODEM_STATE_UNKNOWN);
+ new_state = get_consolidated_subsystem_state (self);
break;
}
@@ -753,6 +754,7 @@
return;
}
+#if ! defined WITH_AT_COMMAND_VIA_DBUS
/* If we are not in Debug mode, report an error */
if (!mm_context_get_debug ()) {
g_dbus_method_invocation_return_error (ctx->invocation,
@@ -763,6 +765,7 @@
handle_command_context_free (ctx);
return;
}
+#endif
/* If command is not implemented, report an error */
if (!MM_IFACE_MODEM_GET_INTERFACE (self)->command ||
@@ -1518,7 +1521,7 @@
/* Enabled may really be searching or registered */
if (new_state == MM_MODEM_STATE_ENABLED)
- new_state = get_current_consolidated_state (self, new_state);
+ new_state = get_consolidated_subsystem_state (self);
/* Update state only if different */
if (new_state != old_state) {
@@ -1612,9 +1615,12 @@
}
static MMModemState
-get_current_consolidated_state (MMIfaceModem *self, MMModemState modem_state)
+get_consolidated_subsystem_state (MMIfaceModem *self)
{
- MMModemState consolidated = modem_state;
+ /* Use as initial state ENABLED, which is the minimum one expected. Do not
+ * use the old modem state as initial state, as that would disallow reporting
+ * e.g. ENABLED if the old state was REGISTERED (as ENABLED < REGISTERED). */
+ MMModemState consolidated = MM_MODEM_STATE_ENABLED;
GArray *subsystem_states;
if (G_UNLIKELY (!state_update_context_quark))
@@ -1698,7 +1704,7 @@
g_array_append_val (subsystem_states, s);
}
- return get_current_consolidated_state (self, modem_state);
+ return get_consolidated_subsystem_state (self);
}
void
@@ -2252,9 +2258,14 @@
/*****************************************************************************/
/* Current bands setting */
+#define AFTER_SET_LOAD_CURRENT_BANDS_RETRIES 5
+#define AFTER_SET_LOAD_CURRENT_BANDS_TIMEOUT_SECS 1
+
typedef struct {
MmGdbusModem *skeleton;
- GArray *bands_array;
+ GArray *bands_array;
+ GArray *supported_bands_array; /* when ANY requested */
+ guint retries;
} SetCurrentBandsContext;
static void
@@ -2264,6 +2275,8 @@
g_object_unref (ctx->skeleton);
if (ctx->bands_array)
g_array_unref (ctx->bands_array);
+ if (ctx->supported_bands_array)
+ g_array_unref (ctx->supported_bands_array);
g_slice_free (SetCurrentBandsContext, ctx);
}
@@ -2284,48 +2297,116 @@
/* Never show just 'any' in the interface */
if (ctx->bands_array->len == 1 && g_array_index (ctx->bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
- GArray *supported_bands;
-
- supported_bands = (mm_common_bands_variant_to_garray (mm_gdbus_modem_get_supported_bands (ctx->skeleton)));
- mm_common_bands_garray_sort (supported_bands);
- mm_gdbus_modem_set_current_bands (ctx->skeleton, mm_common_bands_garray_to_variant (supported_bands));
- g_array_unref (supported_bands);
- } else {
- mm_common_bands_garray_sort (ctx->bands_array);
- mm_gdbus_modem_set_current_bands (ctx->skeleton, mm_common_bands_garray_to_variant (ctx->bands_array));
+ g_assert (ctx->supported_bands_array);
+ g_array_unref (ctx->bands_array);
+ ctx->bands_array = g_array_ref (ctx->supported_bands_array);
}
+ mm_common_bands_garray_sort (ctx->bands_array);
+ mm_gdbus_modem_set_current_bands (ctx->skeleton, mm_common_bands_garray_to_variant (ctx->bands_array));
+
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
+static void set_current_bands_reload_schedule (GTask *task);
+
static void
after_set_load_current_bands_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
- GError *error = NULL;
- GArray *current_bands;
SetCurrentBandsContext *ctx;
+ GArray *current_bands;
+ GError *error = NULL;
+ GArray *requested_bands = NULL;
ctx = g_task_get_task_data (task);
current_bands = MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_bands_finish (self, res, &error);
if (!current_bands) {
- /* Errors when getting bands won't be critical */
+ /* If we can retry, do it */
+ if (ctx->retries > 0) {
+ mm_dbg ("couldn't load current bands: '%s' (will retry)", error->message);
+ g_clear_error (&error);
+ set_current_bands_reload_schedule (task);
+ goto out;
+ }
+
+ /* Errors when reloading bands won't be critical */
mm_warn ("couldn't load current bands: '%s'", error->message);
- g_error_free (error);
- /* Default to the ones we requested */
+ g_clear_error (&error);
set_current_bands_complete_with_defaults (task);
- return;
+ goto out;
}
+ if ((ctx->bands_array->len == 1) && (g_array_index (ctx->bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY))
+ requested_bands = g_array_ref (ctx->supported_bands_array);
+ else
+ requested_bands = g_array_ref (ctx->bands_array);
+
+ /* Compare arrays */
+ if (!mm_common_bands_garray_cmp (current_bands, requested_bands)) {
+ gchar *requested_str;
+ gchar *current_str;
+
+ /* If we can retry, do it */
+ if (ctx->retries > 0) {
+ mm_dbg ("reloaded current bands different to the requested ones (will retry)");
+ set_current_bands_reload_schedule (task);
+ goto out;
+ }
+
+ requested_str = mm_common_build_bands_string ((MMModemBand *)requested_bands->data, requested_bands->len);
+ current_str = mm_common_build_bands_string ((MMModemBand *)current_bands->data, current_bands->len);
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "reloaded current bands (%s) different to the requested ones (%s)",
+ current_str, requested_str);
+ g_free (requested_str);
+ g_free (current_str);
+ }
+
+ /* Store as current the last loaded ones and set operation result */
mm_common_bands_garray_sort (current_bands);
mm_gdbus_modem_set_current_bands (ctx->skeleton, mm_common_bands_garray_to_variant (current_bands));
- g_array_unref (current_bands);
- g_task_return_boolean (task, TRUE);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
g_object_unref (task);
+
+ out:
+ g_array_unref (requested_bands);
+ g_array_unref (current_bands);
+}
+
+static gboolean
+set_current_bands_reload (GTask *task)
+{
+ MMIfaceModem *self;
+ SetCurrentBandsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (ctx->retries > 0);
+ ctx->retries--;
+
+ MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_bands (
+ self,
+ (GAsyncReadyCallback)after_set_load_current_bands_ready,
+ task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+set_current_bands_reload_schedule (GTask *task)
+{
+ g_timeout_add_seconds (AFTER_SET_LOAD_CURRENT_BANDS_TIMEOUT_SECS,
+ (GSourceFunc) set_current_bands_reload,
+ task);
}
static void
@@ -2343,10 +2424,7 @@
if (MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_bands &&
MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_bands_finish) {
- MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_bands (
- self,
- (GAsyncReadyCallback)after_set_load_current_bands_ready,
- task);
+ set_current_bands_reload (task);
return;
}
@@ -2419,7 +2497,6 @@
gpointer user_data)
{
SetCurrentBandsContext *ctx;
- GArray *supported_bands_array;
GArray *current_bands_array;
GError *error = NULL;
gchar *bands_string;
@@ -2440,6 +2517,7 @@
/* Setup context */
ctx = g_slice_new0 (SetCurrentBandsContext);
+ ctx->retries = AFTER_SET_LOAD_CURRENT_BANDS_RETRIES;
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_bands_context_free);
@@ -2460,8 +2538,8 @@
bands_array->len);
/* Get list of supported bands */
- supported_bands_array = (mm_common_bands_variant_to_garray (
- mm_gdbus_modem_get_supported_bands (ctx->skeleton)));
+ ctx->supported_bands_array = (mm_common_bands_variant_to_garray (
+ mm_gdbus_modem_get_supported_bands (ctx->skeleton)));
/* Set ctx->bands_array to target list of bands before comparing with current list
* of bands. If input list of bands contains only ANY, target list of bands is set
@@ -2470,8 +2548,8 @@
g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
guint i;
- for (i = 0; i < supported_bands_array->len; i++) {
- MMModemBand band = g_array_index (supported_bands_array, MMModemBand, i);
+ for (i = 0; i < ctx->supported_bands_array->len; i++) {
+ MMModemBand band = g_array_index (ctx->supported_bands_array, MMModemBand, i);
if (band != MM_MODEM_BAND_ANY &&
band != MM_MODEM_BAND_UNKNOWN) {
@@ -2479,7 +2557,7 @@
ctx->bands_array = g_array_sized_new (FALSE,
FALSE,
sizeof (MMModemBand),
- supported_bands_array->len);
+ ctx->supported_bands_array->len);
g_array_append_val (ctx->bands_array, band);
}
}
@@ -2495,7 +2573,6 @@
mm_dbg ("Requested list of bands (%s) is equal to the current ones, skipping re-set",
bands_string);
g_free (bands_string);
- g_array_unref (supported_bands_array);
g_array_unref (current_bands_array);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
@@ -2510,13 +2587,12 @@
}
/* Validate input list of bands */
- if (!validate_bands (supported_bands_array,
+ if (!validate_bands (ctx->supported_bands_array,
ctx->bands_array,
&error)) {
mm_dbg ("Requested list of bands (%s) cannot be handled",
bands_string);
g_free (bands_string);
- g_array_unref (supported_bands_array);
g_array_unref (current_bands_array);
g_task_return_error (task, error);
g_object_unref (task);
@@ -2530,7 +2606,6 @@
(GAsyncReadyCallback)set_current_bands_ready,
task);
- g_array_unref (supported_bands_array);
g_array_unref (current_bands_array);
g_free (bands_string);
}
@@ -2622,10 +2697,14 @@
/*****************************************************************************/
/* Set current modes */
+#define AFTER_SET_LOAD_CURRENT_MODES_RETRIES 5
+#define AFTER_SET_LOAD_CURRENT_MODES_TIMEOUT_SECS 1
+
typedef struct {
MmGdbusModem *skeleton;
- MMModemMode allowed;
- MMModemMode preferred;
+ MMModemMode allowed;
+ MMModemMode preferred;
+ guint retries;
} SetCurrentModesContext;
static void
@@ -2633,7 +2712,7 @@
{
if (ctx->skeleton)
g_object_unref (ctx->skeleton);
- g_free (ctx);
+ g_slice_free (SetCurrentModesContext, ctx);
}
gboolean
@@ -2644,6 +2723,8 @@
return g_task_propagate_boolean (G_TASK (res), error);
}
+static void set_current_modes_reload_schedule (GTask *task);
+
static void
after_set_load_current_modes_ready (MMIfaceModem *self,
GAsyncResult *res,
@@ -2661,20 +2742,94 @@
&allowed,
&preferred,
&error)) {
+ /* If we can retry, do it */
+ if (ctx->retries > 0) {
+ mm_dbg ("couldn't load current allowed/preferred modes: '%s'", error->message);
+ g_error_free (error);
+ set_current_modes_reload_schedule (task);
+ return;
+ }
+
/* Errors when getting allowed/preferred won't be critical */
mm_warn ("couldn't load current allowed/preferred modes: '%s'", error->message);
- g_error_free (error);
+ g_clear_error (&error);
/* If errors getting allowed modes, default to the ones we asked for */
mm_gdbus_modem_set_current_modes (ctx->skeleton, g_variant_new ("(uu)", ctx->allowed, ctx->preferred));
- } else
- mm_gdbus_modem_set_current_modes (ctx->skeleton, g_variant_new ("(uu)", allowed, preferred));
+ goto out;
+ }
- /* Done */
- g_task_return_boolean (task, TRUE);
+ /* Store as current the last loaded ones and set operation result */
+ mm_gdbus_modem_set_current_modes (ctx->skeleton, g_variant_new ("(uu)", allowed, preferred));
+
+ /* Compare modes. If the requested one was ANY, we won't consider an error if the
+ * result differs. */
+ if (((allowed != ctx->allowed) || (preferred != ctx->preferred)) && (ctx->allowed != MM_MODEM_MODE_ANY)) {
+ gchar *requested_allowed_str;
+ gchar *requested_preferred_str;
+ gchar *current_allowed_str;
+ gchar *current_preferred_str;
+
+ /* If we can retry, do it */
+ if (ctx->retries > 0) {
+ mm_dbg ("reloaded current modes different to the requested ones (will retry)");
+ set_current_modes_reload_schedule (task);
+ return;
+ }
+
+ requested_allowed_str = mm_modem_mode_build_string_from_mask (ctx->allowed);
+ requested_preferred_str = mm_modem_mode_build_string_from_mask (ctx->preferred);
+ current_allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ current_preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "reloaded modes (allowed '%s' and preferred '%s') different "
+ "to the requested ones (allowed '%s' and preferred '%s')",
+ requested_allowed_str, requested_preferred_str,
+ current_allowed_str, current_preferred_str);
+
+ g_free (requested_allowed_str);
+ g_free (requested_preferred_str);
+ g_free (current_allowed_str);
+ g_free (current_preferred_str);
+ }
+
+out:
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
+static gboolean
+set_current_modes_reload (GTask *task)
+{
+ MMIfaceModem *self;
+ SetCurrentModesContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (ctx->retries > 0);
+ ctx->retries--;
+
+ MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_modes (
+ self,
+ (GAsyncReadyCallback)after_set_load_current_modes_ready,
+ task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+set_current_modes_reload_schedule (GTask *task)
+{
+ g_timeout_add_seconds (AFTER_SET_LOAD_CURRENT_MODES_TIMEOUT_SECS,
+ (GSourceFunc) set_current_modes_reload,
+ task);
+}
+
static void
set_current_modes_ready (MMIfaceModem *self,
GAsyncResult *res,
@@ -2691,10 +2846,7 @@
if (MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_modes &&
MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_modes_finish) {
- MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_modes (
- self,
- (GAsyncReadyCallback)after_set_load_current_modes_ready,
- task);
+ set_current_modes_reload (task);
return;
}
@@ -2738,7 +2890,8 @@
}
/* Setup context */
- ctx = g_new0 (SetCurrentModesContext, 1);
+ ctx = g_slice_new0 (SetCurrentModesContext);
+ ctx->retries = AFTER_SET_LOAD_CURRENT_MODES_RETRIES;
ctx->allowed = allowed;
ctx->preferred = preferred;
diff --git a/src/mm-iface-modem.h b/src/mm-iface-modem.h
index 51cb02b..9df3903 100644
--- a/src/mm-iface-modem.h
+++ b/src/mm-iface-modem.h
@@ -120,6 +120,7 @@
/* Loading of the UnlockRequired property */
void (*load_unlock_required) (MMIfaceModem *self,
+ gboolean last_attempt,
GAsyncReadyCallback callback,
gpointer user_data);
MMModemLock (*load_unlock_required_finish) (MMIfaceModem *self,
diff --git a/src/mm-modem-helpers-qmi.c b/src/mm-modem-helpers-qmi.c
index 86e1803..ef6e048 100644
--- a/src/mm-modem-helpers-qmi.c
+++ b/src/mm-modem-helpers-qmi.c
@@ -275,6 +275,13 @@
{ QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_21, MM_MODEM_BAND_EUTRAN_21 },
{ QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_24, MM_MODEM_BAND_EUTRAN_24 },
{ QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_25, MM_MODEM_BAND_EUTRAN_25 },
+ { QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_26, MM_MODEM_BAND_EUTRAN_26 },
+ { QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_27, MM_MODEM_BAND_EUTRAN_27 },
+ { QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_28, MM_MODEM_BAND_EUTRAN_28 },
+ { QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_29, MM_MODEM_BAND_EUTRAN_29 },
+ { QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_30, MM_MODEM_BAND_EUTRAN_30 },
+ { QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_31, MM_MODEM_BAND_EUTRAN_31 },
+ { QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_32, MM_MODEM_BAND_EUTRAN_32 },
{ QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_33, MM_MODEM_BAND_EUTRAN_33 },
{ QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_34, MM_MODEM_BAND_EUTRAN_34 },
{ QMI_DMS_LTE_BAND_CAPABILITY_EUTRAN_35, MM_MODEM_BAND_EUTRAN_35 },
@@ -472,6 +479,13 @@
{ QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_21, MM_MODEM_BAND_EUTRAN_21 },
{ QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_24, MM_MODEM_BAND_EUTRAN_24 },
{ QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_25, MM_MODEM_BAND_EUTRAN_25 },
+ { QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_26, MM_MODEM_BAND_EUTRAN_26 },
+ { QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_27, MM_MODEM_BAND_EUTRAN_27 },
+ { QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_28, MM_MODEM_BAND_EUTRAN_28 },
+ { QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_29, MM_MODEM_BAND_EUTRAN_29 },
+ { QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_30, MM_MODEM_BAND_EUTRAN_30 },
+ { QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_31, MM_MODEM_BAND_EUTRAN_31 },
+ { QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_32, MM_MODEM_BAND_EUTRAN_32 },
{ QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_33, MM_MODEM_BAND_EUTRAN_33 },
{ QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_34, MM_MODEM_BAND_EUTRAN_34 },
{ QMI_NAS_LTE_BAND_PREFERENCE_EUTRAN_35, MM_MODEM_BAND_EUTRAN_35 },
@@ -567,7 +581,8 @@
*qmi_bands = 0;
*qmi_lte_bands = 0;
- memset (extended_qmi_lte_bands, 0, extended_qmi_lte_bands_size * sizeof (guint64));
+ if (extended_qmi_lte_bands)
+ memset (extended_qmi_lte_bands, 0, extended_qmi_lte_bands_size * sizeof (guint64));
for (i = 0; i < mm_bands->len; i++) {
MMModemBand band;
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index cf008d7..f7115cb 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -544,16 +544,152 @@
GRegex *
mm_voice_clip_regex_get (void)
{
- /* Example:
- * <CR><LF>+CLIP: "+393351391306",145,,,,0<CR><LF>
- * \_ Number \_ Type \_ Validity
+ /*
+ * Only first 2 fields are mandatory:
+ * +CLIP: <number>,<type>[,<subaddr>,<satype>[,[<alpha>][,<CLI_validity>]]]
+ *
+ * Example:
+ * <CR><LF>+CLIP: "+393351391306",145,,,,0<CR><LF>
+ * \_ Number \_ Type
*/
- return g_regex_new ("\\r\\n\\+CLIP:\\s*(\\S+),\\s*(\\d+),\\s*,\\s*,\\s*,\\s*(\\d+)\\r\\n",
+ return g_regex_new ("\\r\\n\\+CLIP:\\s*([^,\\s]*)\\s*,\\s*(\\d+)\\s*,?(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
+GRegex *
+mm_voice_ccwa_regex_get (void)
+{
+ /*
+ * Only first 3 fields are mandatory, but we read only the first one
+ * +CCWA: <number>,<type>,<class>,[<alpha>][,<CLI_validity>[,<subaddr>,<satype>[,<priority>]]]
+ *
+ * Example:
+ * <CR><LF>+CCWA: "+393351391306",145,1
+ * \_ Number \_ Type
+ */
+ return g_regex_new ("\\r\\n\\+CCWA:\\s*([^,\\s]*)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,?(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE,
+ 0,
+ NULL);
+}
+
+static void
+call_info_free (MMCallInfo *info)
+{
+ if (!info)
+ return;
+ g_free (info->number);
+ g_slice_free (MMCallInfo, info);
+}
+
+gboolean
+mm_3gpp_parse_clcc_response (const gchar *str,
+ GList **out_list,
+ GError **error)
+{
+ GRegex *r;
+ GList *list = NULL;
+ GError *inner_error = NULL;
+ GMatchInfo *match_info = NULL;
+
+ static const MMCallDirection call_direction[] = {
+ [0] = MM_CALL_DIRECTION_OUTGOING,
+ [1] = MM_CALL_DIRECTION_INCOMING,
+ };
+
+ static const MMCallState 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 10
+ * +CLCC: <idx>,<dir>,<stat>,<mode>,<mpty>[,<number>,<type>[,<alpha>[,<priority>[,<CLI validity>]]]]
+ * +CLCC: <idx>,<dir>,<stat>,<mode>,<mpty>[,<number>,<type>[,<alpha>[,<priority>[,<CLI validity>]]]]
+ * ...
+ */
+
+ r = g_regex_new ("\\+CLCC:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)" /* mandatory fields */
+ "(?:,\\s*([^,]*),\\s*(\\d+)" /* number and type */
+ "(?:,\\s*([^,]*)" /* alpha */
+ "(?:,\\s*(\\d*)" /* priority */
+ "(?:,\\s*(\\d*)" /* CLI validity */
+ ")?)?)?)?$",
+ 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 +CLCC line");
+ goto next;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &aux) ||
+ (aux >= G_N_ELEMENTS (call_direction))) {
+ mm_warn ("couldn't parse call direction from +CLCC line");
+ goto next;
+ }
+ call_info->direction = call_direction[aux];
+
+ if (!mm_get_uint_from_match_info (match_info, 3, &aux) ||
+ (aux >= G_N_ELEMENTS (call_state))) {
+ mm_warn ("couldn't parse call state from +CLCC line");
+ goto next;
+ }
+ call_info->state = call_state[aux];
+
+ if (g_match_info_get_match_count (match_info) >= 7)
+ call_info->number = mm_get_string_unquoted_from_match_info (match_info, 6);
+
+ list = g_list_append (list, call_info);
+ call_info = NULL;
+
+ next:
+ 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_3gpp_call_info_list_free (list);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *out_list = list;
+
+ return TRUE;
+}
+
+void
+mm_3gpp_call_info_list_free (GList *call_info_list)
+{
+ g_list_free_full (call_info_list, (GDestroyNotify) call_info_free);
+}
+
/*************************************************************************/
static MMFlowControl
@@ -1404,6 +1540,131 @@
/*************************************************************************/
+static guint
+find_max_allowed_cid (GList *context_format_list,
+ MMBearerIpFamily ip_family)
+{
+ GList *l;
+
+ for (l = context_format_list; l; l = g_list_next (l)) {
+ MM3gppPdpContextFormat *format = l->data;
+
+ /* Found exact PDP type? */
+ if (format->pdp_type == ip_family)
+ return format->max_cid;
+ }
+ return 0;
+}
+
+guint
+mm_3gpp_select_best_cid (const gchar *apn,
+ MMBearerIpFamily ip_family,
+ GList *context_list,
+ GList *context_format_list,
+ gboolean *cid_reused,
+ gboolean *cid_overwritten)
+{
+ GList *l;
+ guint prev_cid = 0;
+ guint exact_cid = 0;
+ guint unused_cid = 0;
+ guint max_cid = 0;
+ guint max_allowed_cid = 0;
+ guint blank_cid = 0;
+ gchar *ip_family_str;
+
+ ip_family_str = mm_bearer_ip_family_build_string_from_mask (ip_family);
+ mm_dbg ("Looking for best CID matching APN '%s' and PDP type '%s'...",
+ apn, ip_family_str);
+ g_free (ip_family_str);
+
+ /* Look for the exact PDP context we want */
+ for (l = context_list; l; l = g_list_next (l)) {
+ MM3gppPdpContext *pdp = l->data;
+
+ /* Match PDP type */
+ if (pdp->pdp_type == ip_family) {
+ /* Try to match exact APN and PDP type */
+ if (mm_3gpp_cmp_apn_name (apn, pdp->apn)) {
+ exact_cid = pdp->cid;
+ break;
+ }
+
+ /* Same PDP type but with no APN set? we may use that one if no exact match found */
+ if ((!pdp->apn || !pdp->apn[0]) && !blank_cid)
+ blank_cid = pdp->cid;
+ }
+
+ /* If an unused CID was not found yet and the previous CID is not (CID - 1),
+ * this means that (previous CID + 1) is an unused CID that can be used.
+ * This logic will allow us using unused CIDs that are available in the gaps
+ * between already defined contexts.
+ */
+ if (!unused_cid && prev_cid && ((prev_cid + 1) < pdp->cid))
+ unused_cid = prev_cid + 1;
+
+ /* Update previous CID value to the current CID for use in the next loop,
+ * unless an unused CID was already found.
+ */
+ if (!unused_cid)
+ prev_cid = pdp->cid;
+
+ /* Update max CID if we found a bigger one */
+ if (max_cid < pdp->cid)
+ max_cid = pdp->cid;
+ }
+
+ /* Always prefer an exact match */
+ if (exact_cid) {
+ mm_dbg ("Found exact context at CID %u", exact_cid);
+ *cid_reused = TRUE;
+ *cid_overwritten = FALSE;
+ return exact_cid;
+ }
+
+ /* Try to use an unused CID detected in between the already defined contexts */
+ if (unused_cid) {
+ mm_dbg ("Found unused context at CID %u", unused_cid);
+ *cid_reused = FALSE;
+ *cid_overwritten = FALSE;
+ return unused_cid;
+ }
+
+ /* If the max existing CID found during CGDCONT? is below the max allowed
+ * CID, then we can use the next available CID because it's an unused one. */
+ max_allowed_cid = find_max_allowed_cid (context_format_list, ip_family);
+ if (max_cid && (max_cid < max_allowed_cid)) {
+ mm_dbg ("Found unused context at CID %u (<%u)", max_cid + 1, max_allowed_cid);
+ *cid_reused = FALSE;
+ *cid_overwritten = FALSE;
+ return (max_cid + 1);
+ }
+
+ /* Rewrite a context defined with no APN, if any */
+ if (blank_cid) {
+ mm_dbg ("Rewriting context with empty APN at CID %u", blank_cid);
+ *cid_reused = FALSE;
+ *cid_overwritten = TRUE;
+ return blank_cid;
+ }
+
+ /* Rewrite the last existing one found */
+ if (max_cid) {
+ mm_dbg ("Rewriting last context detected at CID %u", max_cid);
+ *cid_reused = FALSE;
+ *cid_overwritten = TRUE;
+ return max_cid;
+ }
+
+ /* Otherwise, just fallback to CID=1 */
+ mm_dbg ("Falling back to CID 1");
+ *cid_reused = FALSE;
+ *cid_overwritten = TRUE;
+ return 1;
+}
+
+/*************************************************************************/
+
static void
mm_3gpp_pdp_context_format_free (MM3gppPdpContextFormat *format)
{
@@ -1876,7 +2137,7 @@
if (!r)
return FALSE;
- if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
+ if (!g_regex_match (r, reply, 0, &match_info)) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
@@ -1927,7 +2188,7 @@
r = g_regex_new ("\\+CMGR:\\s*(\\d+)\\s*,([^,]*),\\s*(\\d+)\\s*([^\\r\\n]*)", 0, 0, NULL);
g_assert (r);
- if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
+ if (!g_regex_match (r, reply, 0, &match_info)) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
@@ -2009,7 +2270,7 @@
G_REGEX_RAW, 0, NULL);
g_assert (r != NULL);
- if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL) &&
+ if (g_regex_match (r, reply, 0, &match_info) &&
mm_get_uint_from_match_info (match_info, 1, sw1) &&
mm_get_uint_from_match_info (match_info, 2, sw2))
*hex = mm_get_string_unquoted_from_match_info (match_info, 3);
@@ -2588,6 +2849,85 @@
}
/*************************************************************************/
+/* CCWA service query response parser */
+
+gboolean
+mm_3gpp_parse_ccwa_service_query_response (const gchar *response,
+ gboolean *status,
+ GError **error)
+{
+ GRegex *r;
+ GError *inner_error = NULL;
+ GMatchInfo *match_info = NULL;
+ gint class_1_status = -1;
+
+ /*
+ * AT+CCWA=<n>[,<mode>]
+ * +CCWA: <status>,<class1>
+ * [+CCWA: <status>,<class2>
+ * [...]]
+ * OK
+ *
+ * If <classX> is 255 it applies to ALL classes.
+ *
+ * We're only interested in class 1 (voice)
+ */
+ r = g_regex_new ("\\+CCWA:\\s*(\\d+),\\s*(\\d+)$",
+ 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, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto out;
+
+ /* Parse the results */
+ while (g_match_info_matches (match_info)) {
+ guint st;
+ guint class;
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &class))
+ mm_warn ("couldn't parse class from +CCWA line");
+ else if (class == 1 || class == 255) {
+ if (!mm_get_uint_from_match_info (match_info, 1, &st))
+ mm_warn ("couldn't parse status from +CCWA line");
+ else {
+ class_1_status = st;
+ break;
+ }
+ }
+ g_match_info_next (match_info, NULL);
+ }
+
+out:
+ g_clear_pointer (&match_info, g_match_info_free);
+ g_regex_unref (r);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (class_1_status < 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "call waiting status for voice class missing");
+ return FALSE;
+ }
+
+ if (class_1_status != 0 && class_1_status != 1) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "call waiting status for voice class invalid: %d", class_1_status);
+ return FALSE;
+ }
+
+ if (status)
+ *status = (gboolean) class_1_status;
+
+ return TRUE;
+}
+
+/*************************************************************************/
static MMSmsStorage
storage_from_str (const gchar *str)
@@ -2648,7 +2988,7 @@
array = g_array_new (FALSE, FALSE, sizeof (MMSmsStorage));
/* Got a range group to match */
- if (g_regex_match_full (r, split[i], strlen (split[i]), 0, 0, &match_info, NULL)) {
+ if (g_regex_match (r, split[i], 0, &match_info)) {
while (g_match_info_matches (match_info)) {
gchar *str;
@@ -2812,7 +3152,7 @@
if (!r)
return FALSE;
- if (g_regex_match_full (r, p, strlen (p), 0, 0, &match_info, NULL)) {
+ if (g_regex_match (r, p, 0, &match_info)) {
while (g_match_info_matches (match_info)) {
str = g_match_info_fetch (match_info, 1);
charsets |= mm_modem_charset_from_string (str);
@@ -2854,7 +3194,7 @@
g_assert (r != NULL);
*out_facilities = MM_MODEM_3GPP_FACILITY_NONE;
- if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
+ if (g_regex_match (r, reply, 0, &match_info)) {
while (g_match_info_matches (match_info)) {
gchar *str;
@@ -3171,7 +3511,7 @@
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) cind_response_free);
- if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
+ if (g_regex_match (r, reply, 0, &match_info)) {
while (g_match_info_matches (match_info)) {
MM3gppCindResponse *resp;
gchar *desc, *tmp;
@@ -3228,7 +3568,7 @@
r = g_regex_new ("(\\d+)[^0-9]+", G_REGEX_UNGREEDY, 0, NULL);
g_assert (r != NULL);
- if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
+ if (!g_regex_match (r, reply, 0, &match_info)) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse the +CIND response '%s': didn't match",
reply);
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
index 237046a..73a13dd 100644
--- a/src/mm-modem-helpers.h
+++ b/src/mm-modem-helpers.h
@@ -93,9 +93,23 @@
/*****************************************************************************/
/* VOICE specific helpers and utilities */
/*****************************************************************************/
+
GRegex *mm_voice_ring_regex_get (void);
GRegex *mm_voice_cring_regex_get (void);
GRegex *mm_voice_clip_regex_get (void);
+GRegex *mm_voice_ccwa_regex_get (void);
+
+/* +CLCC response parser */
+typedef struct {
+ guint index;
+ MMCallDirection direction;
+ MMCallState state;
+ gchar *number; /* optional */
+} MMCallInfo;
+gboolean mm_3gpp_parse_clcc_response (const gchar *str,
+ GList **out_list,
+ GError **error);
+void mm_3gpp_call_info_list_free (GList *call_info_list);
/*****************************************************************************/
/* SERIAL specific helpers and utilities */
@@ -178,6 +192,14 @@
GList *mm_3gpp_parse_cgdcont_read_response (const gchar *reply,
GError **error);
+/* Select best CID to use during connection */
+guint mm_3gpp_select_best_cid (const gchar *apn,
+ MMBearerIpFamily ip_family,
+ GList *context_list,
+ GList *context_format_list,
+ gboolean *cid_reused,
+ gboolean *cid_overwritten);
+
/* AT+CGACT? (active PDP context query) response parser */
typedef struct {
guint cid;
@@ -383,6 +405,12 @@
MMModem3gppEpsUeModeOperation *out_mode,
GError **error);
+/* CCWA service query response parser */
+gboolean mm_3gpp_parse_ccwa_service_query_response (const gchar *response,
+ gboolean *status,
+ GError **error);
+
+
/* Additional 3GPP-specific helpers */
MMModem3gppFacility mm_3gpp_acronym_to_facility (const gchar *str);
@@ -403,29 +431,19 @@
char *mm_3gpp_parse_iccid (const char *raw_iccid, GError **error);
-gboolean
-mm_3gpp_rscp_level_to_rscp (guint rscp_level,
- gdouble *out_rscp);
-gboolean
-mm_3gpp_rxlev_to_rssi (guint rxlev,
- gdouble *out_rssi);
-
-gboolean
-mm_3gpp_ecn0_level_to_ecio (guint ecn0_level,
- gdouble *out_ecio);
-
-gboolean
-mm_3gpp_rsrq_level_to_rsrq (guint rsrq_level,
- gdouble *out_rsrq);
-
-gboolean
-mm_3gpp_rsrp_level_to_rsrp (guint rsrp_level,
- gdouble *out_rsrp);
-
-gboolean
-mm_3gpp_rssnr_level_to_rssnr (gint rssnr_level,
- gdouble *out_rssnr);
+gboolean mm_3gpp_rscp_level_to_rscp (guint rscp_level,
+ gdouble *out_rscp);
+gboolean mm_3gpp_rxlev_to_rssi (guint rxlev,
+ gdouble *out_rssi);
+gboolean mm_3gpp_ecn0_level_to_ecio (guint ecn0_level,
+ gdouble *out_ecio);
+gboolean mm_3gpp_rsrq_level_to_rsrq (guint rsrq_level,
+ gdouble *out_rsrq);
+gboolean mm_3gpp_rsrp_level_to_rsrp (guint rsrp_level,
+ gdouble *out_rsrp);
+gboolean mm_3gpp_rssnr_level_to_rssnr (gint rssnr_level,
+ gdouble *out_rssnr);
/*****************************************************************************/
/* CDMA specific helpers and utilities */
diff --git a/src/mm-plugin-manager.c b/src/mm-plugin-manager.c
index 2123bde..f0045af 100644
--- a/src/mm-plugin-manager.c
+++ b/src/mm-plugin-manager.c
@@ -1496,6 +1496,36 @@
/*****************************************************************************/
+static void
+register_plugin_whitelist_tags (MMPluginManager *self,
+ MMPlugin *plugin)
+{
+ const gchar **tags;
+ guint i;
+
+ if (!mm_filter_check_rule_enabled (self->priv->filter, MM_FILTER_RULE_PLUGIN_WHITELIST))
+ return;
+
+ tags = mm_plugin_get_allowed_udev_tags (plugin);
+ for (i = 0; tags && tags[i]; i++)
+ mm_filter_register_plugin_whitelist_tag (self->priv->filter, tags[i]);
+}
+
+static void
+register_plugin_whitelist_product_ids (MMPluginManager *self,
+ MMPlugin *plugin)
+{
+ const mm_uint16_pair *product_ids;
+ guint i;
+
+ if (!mm_filter_check_rule_enabled (self->priv->filter, MM_FILTER_RULE_PLUGIN_WHITELIST))
+ return;
+
+ product_ids = mm_plugin_get_allowed_product_ids (plugin);
+ for (i = 0; product_ids && product_ids[i].l; i++)
+ mm_filter_register_plugin_whitelist_product_id (self->priv->filter, product_ids[i].l, product_ids[i].r);
+}
+
static MMPlugin *
load_plugin (const gchar *path)
{
@@ -1609,6 +1639,10 @@
else
/* Vendor specific plugin */
self->priv->plugins = g_list_append (self->priv->plugins, plugin);
+
+ /* Register plugin whitelist rules in filter, if any */
+ register_plugin_whitelist_tags (self, plugin);
+ register_plugin_whitelist_product_ids (self, plugin);
}
/* Check the generic plugin once all looped */
diff --git a/src/mm-plugin.c b/src/mm-plugin.c
index 08fe2cd..7150b03 100644
--- a/src/mm-plugin.c
+++ b/src/mm-plugin.c
@@ -37,6 +37,13 @@
#include "mm-log.h"
#include "mm-daemon-enums-types.h"
+#if defined WITH_QMI
+# include "mm-broadband-modem-qmi.h"
+#endif
+#if defined WITH_MBIM
+# include "mm-broadband-modem-mbim.h"
+#endif
+
G_DEFINE_TYPE (MMPlugin, mm_plugin, G_TYPE_OBJECT)
/* Virtual port corresponding to the embedded modem */
@@ -134,6 +141,18 @@
return self->priv->name;
}
+const gchar **
+mm_plugin_get_allowed_udev_tags (MMPlugin *self)
+{
+ return (const gchar **) self->priv->udev_tags;
+}
+
+const mm_uint16_pair *
+mm_plugin_get_allowed_product_ids (MMPlugin *self)
+{
+ return self->priv->product_ids;
+}
+
/*****************************************************************************/
static gboolean
@@ -920,9 +939,22 @@
/* Grab each port */
for (l = port_probes; l; l = g_list_next (l)) {
- GError *inner_error = NULL;
- MMPortProbe *probe = MM_PORT_PROBE (l->data);
- gboolean grabbed;
+ GError *inner_error = NULL;
+ MMPortProbe *probe;
+ gboolean grabbed = FALSE;
+ gboolean force_ignored = FALSE;
+ const gchar *subsys;
+ const gchar *name;
+ const gchar *driver;
+ MMPortType port_type;
+
+ probe = MM_PORT_PROBE (l->data);
+
+ subsys = mm_port_probe_get_port_subsys (probe);
+ name = mm_port_probe_get_port_name (probe);
+ port_type = mm_port_probe_get_port_type (probe);
+
+ driver = mm_kernel_device_get_driver (mm_port_probe_peek_port (probe));
/* If grabbing a port fails, just warn. We'll decide if the modem is
* valid or not when all ports get organized */
@@ -931,45 +963,82 @@
* probed and accepted by the generic plugin, which is overwritten
* by the specific one when needed. */
if (apply_subsystem_filter (self, mm_port_probe_peek_port (probe))) {
- grabbed = FALSE;
inner_error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_UNSUPPORTED,
"unsupported subsystem: '%s'",
- mm_port_probe_get_port_subsys (probe));
+ subsys);
+ goto next;
}
+
/* Ports that are explicitly blacklisted will be grabbed as ignored */
- else if (mm_port_probe_is_ignored (probe)) {
- mm_dbg ("(%s/%s): port is blacklisted",
- mm_port_probe_get_port_subsys (probe),
- mm_port_probe_get_port_name (probe));
- grabbed = mm_base_modem_grab_port (modem,
- mm_port_probe_peek_port (probe),
- MM_PORT_TYPE_IGNORED,
- MM_PORT_SERIAL_AT_FLAG_NONE,
- &inner_error);
+ if (mm_port_probe_is_ignored (probe)) {
+ mm_dbg ("(%s/%s): port is blacklisted", subsys, name);
+ force_ignored = TRUE;
+ goto grab_port;
}
-#if !defined WITH_QMI
- else if (mm_port_probe_get_port_type (probe) == MM_PORT_TYPE_NET &&
- !g_strcmp0 (mm_kernel_device_get_driver (mm_port_probe_peek_port (probe)), "qmi_wwan")) {
- /* Try to generically grab the port, but flagged as ignored */
- grabbed = mm_base_modem_grab_port (modem,
- mm_port_probe_peek_port (probe),
- MM_PORT_TYPE_IGNORED,
- MM_PORT_SERIAL_AT_FLAG_NONE,
- &inner_error);
+
+#if defined WITH_QMI
+ if (MM_IS_BROADBAND_MODEM_QMI (modem) &&
+ port_type == MM_PORT_TYPE_NET &&
+ g_strcmp0 (driver, "qmi_wwan") != 0) {
+ /* Non-QMI net ports are ignored in QMI modems */
+ mm_dbg ("(%s/%s): ignoring non-QMI net port in QMI modem", subsys, name);
+ force_ignored = TRUE;
+ goto grab_port;
+ }
+
+ if (!MM_IS_BROADBAND_MODEM_QMI (modem) &&
+ port_type == MM_PORT_TYPE_NET &&
+ g_strcmp0 (driver, "qmi_wwan") == 0) {
+ /* QMI net ports are ignored in non-QMI modems */
+ mm_dbg ("(%s/%s): ignoring QMI net port in non-QMI modem", subsys, name);
+ force_ignored = TRUE;
+ goto grab_port;
+ }
+#else
+ if (port_type == MM_PORT_TYPE_NET &&
+ g_strcmp0 (driver, "qmi_wwan") == 0) {
+ /* QMI net ports are ignored if QMI support not built */
+ mm_dbg ("(%s/%s): ignoring QMI net port as QMI support isn't available", subsys, name);
+ force_ignored = TRUE;
+ goto grab_port;
}
#endif
-#if !defined WITH_MBIM
- else if (mm_port_probe_get_port_type (probe) == MM_PORT_TYPE_NET &&
- !g_strcmp0 (mm_kernel_device_get_driver (mm_port_probe_peek_port (probe)), "cdc_mbim")) {
- /* Try to generically grab the port, but flagged as ignored */
+
+#if defined WITH_MBIM
+ if (MM_IS_BROADBAND_MODEM_MBIM (modem) &&
+ port_type == MM_PORT_TYPE_NET &&
+ g_strcmp0 (driver, "cdc_mbim") != 0) {
+ /* Non-MBIM net ports are ignored in MBIM modems */
+ mm_dbg ("(%s/%s): ignoring non-MBIM net port in MBIM modem", subsys, name);
+ force_ignored = TRUE;
+ goto grab_port;
+ }
+
+ if (!MM_IS_BROADBAND_MODEM_MBIM (modem) &&
+ port_type == MM_PORT_TYPE_NET &&
+ g_strcmp0 (driver, "cdc_mbim") == 0) {
+ /* MBIM net ports are ignored in non-MBIM modems */
+ mm_dbg ("(%s/%s): ignoring MBIM net port in non-MBIM modem", subsys, name);
+ force_ignored = TRUE;
+ goto grab_port;
+ }
+#else
+ if (port_type == MM_PORT_TYPE_NET &&
+ g_strcmp0 (driver, "cdc_mbim") == 0) {
+ mm_dbg ("(%s/%s): ignoring MBIM net port as MBIM support isn't available", subsys, name);
+ force_ignored = TRUE;
+ goto grab_port;
+ }
+#endif
+
+ grab_port:
+ if (force_ignored)
grabbed = mm_base_modem_grab_port (modem,
mm_port_probe_peek_port (probe),
MM_PORT_TYPE_IGNORED,
MM_PORT_SERIAL_AT_FLAG_NONE,
&inner_error);
- }
-#endif
else if (MM_PLUGIN_GET_CLASS (self)->grab_port)
grabbed = MM_PLUGIN_GET_CLASS (self)->grab_port (MM_PLUGIN (self),
modem,
@@ -981,10 +1050,11 @@
mm_port_probe_get_port_type (probe),
MM_PORT_SERIAL_AT_FLAG_NONE,
&inner_error);
+
+ next:
if (!grabbed) {
mm_warn ("Could not grab port (%s/%s): '%s'",
- mm_port_probe_get_port_subsys (MM_PORT_PROBE (l->data)),
- mm_port_probe_get_port_name (MM_PORT_PROBE (l->data)),
+ subsys, name,
inner_error ? inner_error->message : "unknown error");
g_clear_error (&inner_error);
}
diff --git a/src/mm-plugin.h b/src/mm-plugin.h
index 3b2b88b..926840b 100644
--- a/src/mm-plugin.h
+++ b/src/mm-plugin.h
@@ -106,7 +106,7 @@
/* Plugins need to provide a method to create a modem object given
* a list of port probes (Mandatory) */
- MMBaseModem *(*create_modem) (MMPlugin *plugin,
+ MMBaseModem *(*create_modem) (MMPlugin *self,
const gchar *uid,
const gchar **drivers,
guint16 vendor,
@@ -116,7 +116,7 @@
/* Plugins need to provide a method to grab independent ports
* identified by port probes (Optional) */
- gboolean (*grab_port) (MMPlugin *plugin,
+ gboolean (*grab_port) (MMPlugin *self,
MMBaseModem *modem,
MMPortProbe *probe,
GError **error);
@@ -124,25 +124,27 @@
GType mm_plugin_get_type (void);
-const gchar *mm_plugin_get_name (MMPlugin *plugin);
+const gchar *mm_plugin_get_name (MMPlugin *self);
+const gchar **mm_plugin_get_allowed_udev_tags (MMPlugin *self);
+const mm_uint16_pair *mm_plugin_get_allowed_product_ids (MMPlugin *self);
/* This method will run all pre-probing filters, to see if we can discard this
* plugin from the probing logic as soon as possible. */
-MMPluginSupportsHint mm_plugin_discard_port_early (MMPlugin *plugin,
+MMPluginSupportsHint mm_plugin_discard_port_early (MMPlugin *self,
MMDevice *device,
MMKernelDevice *port);
-void mm_plugin_supports_port (MMPlugin *plugin,
+void mm_plugin_supports_port (MMPlugin *self,
MMDevice *device,
MMKernelDevice *port,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
-MMPluginSupportsResult mm_plugin_supports_port_finish (MMPlugin *plugin,
+MMPluginSupportsResult mm_plugin_supports_port_finish (MMPlugin *self,
GAsyncResult *result,
GError **error);
-MMBaseModem *mm_plugin_create_modem (MMPlugin *plugin,
+MMBaseModem *mm_plugin_create_modem (MMPlugin *self,
MMDevice *device,
GError **error);
diff --git a/src/mm-port-serial-at.c b/src/mm-port-serial-at.c
index fef910e..67525af 100644
--- a/src/mm-port-serial-at.c
+++ b/src/mm-port-serial-at.c
@@ -427,6 +427,7 @@
buf,
timeout_seconds,
allow_cached,
+ is_raw, /* raw commands always run next, never queued last */
cancellable,
(GAsyncReadyCallback)serial_command_ready,
simple);
diff --git a/src/mm-port-serial-qcdm.c b/src/mm-port-serial-qcdm.c
index 4d26e39..ec1833f 100644
--- a/src/mm-port-serial-qcdm.c
+++ b/src/mm-port-serial-qcdm.c
@@ -193,6 +193,7 @@
command,
timeout_seconds,
FALSE, /* never cached */
+ FALSE, /* always queued last */
cancellable,
(GAsyncReadyCallback)serial_command_ready,
task);
diff --git a/src/mm-port-serial.c b/src/mm-port-serial.c
index d739313..052d0dd 100644
--- a/src/mm-port-serial.c
+++ b/src/mm-port-serial.c
@@ -163,6 +163,7 @@
GByteArray *command,
guint32 timeout_seconds,
gboolean allow_cached,
+ gboolean run_next,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
@@ -203,7 +204,12 @@
if (!allow_cached)
port_serial_set_cached_reply (self, ctx->command, NULL);
- g_queue_push_tail (self->priv->queue, ctx);
+ /* If requested to run next, push to the head of the queue so that it really is
+ * the next one sent */
+ if (run_next)
+ g_queue_push_head (self->priv->queue, ctx);
+ else
+ g_queue_push_tail (self->priv->queue, ctx);
if (g_queue_get_length (self->priv->queue) == 1)
port_serial_schedule_queue_process (self, 0);
@@ -1381,7 +1387,10 @@
return;
if (self->priv->connected_id) {
- g_signal_handler_disconnect (self, self->priv->connected_id);
+ /* Don't assume it's always connected, because it may be automatically connected during
+ * object disposal, and this method is also called in finalize() */
+ if (g_signal_handler_is_connected (self, self->priv->connected_id))
+ g_signal_handler_disconnect (self, self->priv->connected_id);
self->priv->connected_id = 0;
}
@@ -1587,9 +1596,18 @@
}
}
- if (error)
+ if (error) {
+ /* An error during port reopening may mean that the device is
+ * already gone. Note that we won't get a HUP in the TTY when
+ * the port is gone during the reopen wait time, because there's
+ * no channel I/O monitoring in place.
+ *
+ * If we ever see this, we'll flag the port as forced close right
+ * away, because the open count would anyway be broken afterwards.
+ */
+ port_serial_close_force (self);
g_task_return_error (task, error);
- else
+ } else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
@@ -1719,23 +1737,30 @@
return g_task_propagate_boolean (G_TASK (res), error);
}
+static gboolean
+flash_cancel_cb (GTask *task)
+{
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Flash cancelled");
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
void
mm_port_serial_flash_cancel (MMPortSerial *self)
{
GTask *task;
+ /* Do nothing if there is no flash task */
if (!self->priv->flash_task)
return;
- /* Recover task */
+ /* Recover task and schedule it to be cancelled in an idle.
+ * We do NOT want this cancellation to happen right away,
+ * because the object reference in the flashing task may
+ * be the last one valid. */
task = self->priv->flash_task;
self->priv->flash_task = NULL;
-
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_CANCELLED,
- "Flash cancelled");
- g_object_unref (task);
+ g_idle_add ((GSourceFunc)flash_cancel_cb, task);
}
static gboolean
diff --git a/src/mm-port-serial.h b/src/mm-port-serial.h
index bac79b4..b30bad7 100644
--- a/src/mm-port-serial.h
+++ b/src/mm-port-serial.h
@@ -144,6 +144,7 @@
GByteArray *command,
guint32 timeout_seconds,
gboolean allow_cached,
+ gboolean run_next,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c
index 1e0ae78..9629fab 100644
--- a/src/mm-shared-qmi.c
+++ b/src/mm-shared-qmi.c
@@ -143,6 +143,31 @@
/*****************************************************************************/
/* Register in network (3GPP interface) */
+/* wait this amount of time at most if we don't get the serving system
+ * indication earlier */
+#define REGISTER_IN_NETWORK_TIMEOUT_SECS 25
+
+typedef struct {
+ guint timeout_id;
+ gulong serving_system_indication_id;
+ GCancellable *cancellable;
+ gulong cancellable_id;
+ QmiClientNas *client;
+} RegisterInNetworkContext;
+
+static void
+register_in_network_context_free (RegisterInNetworkContext *ctx)
+{
+ g_assert (!ctx->cancellable_id);
+ g_assert (!ctx->timeout_id);
+ if (ctx->client) {
+ g_assert (!ctx->serving_system_indication_id);
+ g_object_unref (ctx->client);
+ }
+ g_clear_object (&ctx->cancellable);
+ g_slice_free (RegisterInNetworkContext, ctx);
+}
+
gboolean
mm_shared_qmi_3gpp_register_in_network_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
@@ -152,27 +177,150 @@
}
static void
+register_in_network_cancelled (GCancellable *cancellable,
+ GTask *task)
+{
+ RegisterInNetworkContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ g_assert (ctx->cancellable);
+ g_assert (ctx->cancellable_id);
+ ctx->cancellable_id = 0;
+
+ g_assert (ctx->timeout_id);
+ g_source_remove (ctx->timeout_id);
+ ctx->timeout_id = 0;
+
+ g_assert (ctx->client);
+ g_assert (ctx->serving_system_indication_id);
+ g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id);
+ ctx->serving_system_indication_id = 0;
+
+ g_assert (g_task_return_error_if_cancelled (task));
+ g_object_unref (task);
+}
+
+static gboolean
+register_in_network_timeout (GTask *task)
+{
+ RegisterInNetworkContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ g_assert (ctx->timeout_id);
+ ctx->timeout_id = 0;
+
+ g_assert (ctx->client);
+ g_assert (ctx->serving_system_indication_id);
+ g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id);
+ ctx->serving_system_indication_id = 0;
+
+ g_assert (!ctx->cancellable || ctx->cancellable_id);
+ g_cancellable_disconnect (ctx->cancellable, ctx->cancellable_id);
+ ctx->cancellable_id = 0;
+
+ /* the 3GPP interface will take care of checking if the registration is
+ * the one we asked for */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+register_in_network_ready (GTask *task,
+ QmiIndicationNasServingSystemOutput *output)
+{
+ RegisterInNetworkContext *ctx;
+ QmiNasRegistrationState registration_state;
+
+ /* ignore indication updates reporting "searching" */
+ qmi_indication_nas_serving_system_output_get_serving_system (
+ output,
+ ®istration_state,
+ NULL, /* cs_attach_state */
+ NULL, /* ps_attach_state */
+ NULL, /* selected_network */
+ NULL, /* radio_interfaces */
+ NULL);
+ if (registration_state == QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED_SEARCHING)
+ return;
+
+ ctx = g_task_get_task_data (task);
+
+ g_assert (ctx->client);
+ g_assert (ctx->serving_system_indication_id);
+ g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id);
+ ctx->serving_system_indication_id = 0;
+
+ g_assert (ctx->timeout_id);
+ g_source_remove (ctx->timeout_id);
+ ctx->timeout_id = 0;
+
+ g_assert (!ctx->cancellable || ctx->cancellable_id);
+ g_cancellable_disconnect (ctx->cancellable, ctx->cancellable_id);
+ ctx->cancellable_id = 0;
+
+ /* the 3GPP interface will take care of checking if the registration is
+ * the one we asked for */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
initiate_network_register_ready (QmiClientNas *client,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
QmiMessageNasInitiateNetworkRegisterOutput *output;
+ RegisterInNetworkContext *ctx;
+
+ ctx = g_task_get_task_data (task);
output = qmi_client_nas_initiate_network_register_finish (client, res, &error);
if (!output || !qmi_message_nas_initiate_network_register_output_get_result (output, &error)) {
- if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
+ /* No effect would mean we're already in the desired network */
+ if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
+ g_task_return_boolean (task, TRUE);
+ g_error_free (error);
+ } else {
g_prefix_error (&error, "Couldn't initiate network register: ");
g_task_return_error (task, error);
- goto out;
}
- g_error_free (error);
+ g_object_unref (task);
+ goto out;
}
- g_task_return_boolean (task, TRUE);
+ /* Registration attempt started, now we need to monitor "serving system" indications
+ * to get notified when the registration changed. Note that we won't need to process
+ * the indication, because we already have that logic setup (and it runs before this
+ * new signal handler), we just need to get notified of when it happens. We will also
+ * setup a maximum operation timeuot plus a cancellability point, as this operation
+ * may be explicitly cancelled by the 3GPP interface if a new registration request
+ * arrives while the current one is being processed.
+ *
+ * Task is shared among cancellable, indication and timeout. The first one triggered
+ * will cancel the others.
+ */
+
+ if (ctx->cancellable)
+ ctx->cancellable_id = g_cancellable_connect (ctx->cancellable,
+ G_CALLBACK (register_in_network_cancelled),
+ task,
+ NULL);
+
+ ctx->serving_system_indication_id = g_signal_connect_swapped (client,
+ "serving-system",
+ G_CALLBACK (register_in_network_ready),
+ task);
+
+ ctx->timeout_id = g_timeout_add_seconds (REGISTER_IN_NETWORK_TIMEOUT_SECS,
+ (GSourceFunc) register_in_network_timeout,
+ task);
out:
- g_object_unref (task);
if (output)
qmi_message_nas_initiate_network_register_output_unref (output);
@@ -186,9 +334,10 @@
gpointer user_data)
{
GTask *task;
+ RegisterInNetworkContext *ctx;
QmiMessageNasInitiateNetworkRegisterInput *input;
guint16 mcc = 0;
- guint16 mnc = 0;
+ guint16 mnc;
QmiClient *client = NULL;
GError *error = NULL;
@@ -198,7 +347,12 @@
callback, user_data))
return;
- task = g_task_new (self, NULL, callback, user_data);
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ ctx = g_slice_new0 (RegisterInNetworkContext);
+ ctx->client = g_object_ref (client);
+ ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)register_in_network_context_free);
/* Parse input MCC/MNC */
if (operator_id && !mm_3gpp_parse_operator_id (operator_id, &mcc, &mnc, &error)) {
@@ -2871,10 +3025,9 @@
case LOAD_CARRIER_CONFIG_STEP_LAST:
/* We will now store the loaded information so that we can later on use it
* if needed during the automatic carrier config switching operation */
- g_assert (!priv->config_list);
g_assert (priv->config_active_i < 0 && !priv->config_active_default);
g_assert (ctx->config_active_i >= 0 || ctx->config_active_default);
- priv->config_list = g_array_ref (ctx->config_list);
+ priv->config_list = ctx->config_list ? g_array_ref (ctx->config_list) : NULL;
priv->config_active_i = ctx->config_active_i;
priv->config_active_default = ctx->config_active_default;
@@ -3975,12 +4128,13 @@
}
/*****************************************************************************/
-/* Location: internal helper: select operation mode (assisted/standalone) */
+/* Location: internal helper: select operation mode (msa/msb/standalone) */
typedef enum {
GPS_OPERATION_MODE_UNKNOWN,
GPS_OPERATION_MODE_STANDALONE,
- GPS_OPERATION_MODE_ASSISTED,
+ GPS_OPERATION_MODE_AGPS_MSA,
+ GPS_OPERATION_MODE_AGPS_MSB,
} GpsOperationMode;
typedef struct {
@@ -4040,7 +4194,19 @@
qmi_message_pds_set_default_tracking_session_output_unref (output);
- mm_dbg ("A-GPS %s", ctx->mode == GPS_OPERATION_MODE_ASSISTED ? "enabled" : "disabled");
+ switch (ctx->mode) {
+ case GPS_OPERATION_MODE_AGPS_MSA:
+ mm_dbg ("MSA A-GPS operation mode enabled");
+ break;
+ case GPS_OPERATION_MODE_AGPS_MSB:
+ mm_dbg ("MSB A-GPS operation mode enabled");
+ break;
+ case GPS_OPERATION_MODE_STANDALONE:
+ mm_dbg ("Standalone mode enabled (A-GPS disabled)");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
@@ -4087,15 +4253,24 @@
qmi_message_pds_get_default_tracking_session_output_unref (output);
- if (ctx->mode == GPS_OPERATION_MODE_ASSISTED) {
+ if (ctx->mode == GPS_OPERATION_MODE_AGPS_MSA) {
if (session_operation == QMI_PDS_OPERATING_MODE_MS_ASSISTED) {
- mm_dbg ("A-GPS already enabled");
+ mm_dbg ("MSA A-GPS already enabled");
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
- mm_dbg ("Need to enable A-GPS");
+ mm_dbg ("Need to enable MSA A-GPS");
session_operation = QMI_PDS_OPERATING_MODE_MS_ASSISTED;
+ } else if (ctx->mode == GPS_OPERATION_MODE_AGPS_MSB) {
+ if (session_operation == QMI_PDS_OPERATING_MODE_MS_BASED) {
+ mm_dbg ("MSB A-GPS already enabled");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+ mm_dbg ("Need to enable MSB A-GPS");
+ session_operation = QMI_PDS_OPERATING_MODE_MS_BASED;
} else if (ctx->mode == GPS_OPERATION_MODE_STANDALONE) {
if (session_operation == QMI_PDS_OPERATING_MODE_STANDALONE) {
mm_dbg ("A-GPS already disabled");
@@ -4164,7 +4339,19 @@
return;
}
- mm_dbg ("A-GPS %s", ctx->mode == GPS_OPERATION_MODE_ASSISTED ? "enabled" : "disabled");
+ switch (ctx->mode) {
+ case GPS_OPERATION_MODE_AGPS_MSA:
+ mm_dbg ("MSA A-GPS operation mode enabled");
+ break;
+ case GPS_OPERATION_MODE_AGPS_MSB:
+ mm_dbg ("MSB A-GPS operation mode enabled");
+ break;
+ case GPS_OPERATION_MODE_STANDALONE:
+ mm_dbg ("Standalone mode enabled (A-GPS disabled)");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
@@ -4236,15 +4423,24 @@
qmi_indication_loc_get_operation_mode_output_get_operation_mode (output, &mode, NULL);
- if (ctx->mode == GPS_OPERATION_MODE_ASSISTED) {
+ if (ctx->mode == GPS_OPERATION_MODE_AGPS_MSA) {
if (mode == QMI_LOC_OPERATION_MODE_MSA) {
- mm_dbg ("A-GPS already enabled");
+ mm_dbg ("MSA A-GPS already enabled");
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
- mm_dbg ("Need to enable A-GPS");
+ mm_dbg ("Need to enable MSA A-GPS");
mode = QMI_LOC_OPERATION_MODE_MSA;
+ } else if (ctx->mode == GPS_OPERATION_MODE_AGPS_MSB) {
+ if (mode == QMI_LOC_OPERATION_MODE_MSB) {
+ mm_dbg ("MSB A-GPS already enabled");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+ mm_dbg ("Need to enable MSB A-GPS");
+ mode = QMI_LOC_OPERATION_MODE_MSB;
} else if (ctx->mode == GPS_OPERATION_MODE_STANDALONE) {
if (mode == QMI_LOC_OPERATION_MODE_STANDALONE) {
mm_dbg ("A-GPS already disabled");
@@ -4413,8 +4609,9 @@
GAsyncResult *res,
GTask *task)
{
- GError *error = NULL;
- Private *priv;
+ MMModemLocationSource source;
+ Private *priv;
+ GError *error = NULL;
if (!set_gps_operation_mode_finish (self, res, &error)) {
g_task_return_error (task, error);
@@ -4422,9 +4619,10 @@
return;
}
+ source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
priv = get_private (self);
- priv->enabled_sources &= ~MM_MODEM_LOCATION_SOURCE_AGPS;
+ priv->enabled_sources &= ~source;
g_task_return_boolean (task, TRUE);
g_object_unref (task);
@@ -4450,8 +4648,9 @@
/* NOTE: no parent disable_location_gathering() implementation */
if (!(source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
- MM_MODEM_LOCATION_SOURCE_GPS_RAW |
- MM_MODEM_LOCATION_SOURCE_AGPS))) {
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB))) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
@@ -4460,7 +4659,7 @@
g_assert (!(priv->pds_client && priv->loc_client));
/* Disable A-GPS? */
- if (source == MM_MODEM_LOCATION_SOURCE_AGPS) {
+ if (source == MM_MODEM_LOCATION_SOURCE_AGPS_MSA || source == MM_MODEM_LOCATION_SOURCE_AGPS_MSB) {
set_gps_operation_mode (self,
GPS_OPERATION_MODE_STANDALONE,
(GAsyncReadyCallback)set_gps_operation_mode_standalone_ready,
@@ -4521,12 +4720,13 @@
}
static void
-set_gps_operation_mode_assisted_ready (MMSharedQmi *self,
- GAsyncResult *res,
- GTask *task)
+set_gps_operation_mode_agps_ready (MMSharedQmi *self,
+ GAsyncResult *res,
+ GTask *task)
{
- GError *error = NULL;
- Private *priv;
+ MMModemLocationSource source;
+ Private *priv;
+ GError *error = NULL;
if (!set_gps_operation_mode_finish (self, res, &error)) {
g_task_return_error (task, error);
@@ -4534,9 +4734,10 @@
return;
}
+ source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
priv = get_private (self);
- priv->enabled_sources |= MM_MODEM_LOCATION_SOURCE_AGPS;
+ priv->enabled_sources |= source;
g_task_return_boolean (task, TRUE);
g_object_unref (task);
@@ -4565,17 +4766,27 @@
/* We only consider GPS related sources in this shared QMI implementation */
if (!(source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
- MM_MODEM_LOCATION_SOURCE_AGPS))) {
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB))) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
- /* Enabling A-GPS? */
- if (source == MM_MODEM_LOCATION_SOURCE_AGPS) {
+ /* Enabling MSA A-GPS? */
+ if (source == MM_MODEM_LOCATION_SOURCE_AGPS_MSA) {
set_gps_operation_mode (self,
- GPS_OPERATION_MODE_ASSISTED,
- (GAsyncReadyCallback)set_gps_operation_mode_assisted_ready,
+ GPS_OPERATION_MODE_AGPS_MSA,
+ (GAsyncReadyCallback)set_gps_operation_mode_agps_ready,
+ task);
+ return;
+ }
+
+ /* Enabling MSB A-GPS? */
+ if (source == MM_MODEM_LOCATION_SOURCE_AGPS_MSB) {
+ set_gps_operation_mode (self,
+ GPS_OPERATION_MODE_AGPS_MSB,
+ (GAsyncReadyCallback)set_gps_operation_mode_agps_ready,
task);
return;
}
@@ -4658,17 +4869,13 @@
/* Now our own checks */
- /* If we have support for the PDS client, GPS and A-GPS location is supported */
- if (mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDS, MM_PORT_QMI_FLAG_DEFAULT, NULL))
+ /* If we have support for the PDS or LOC client, GPS and A-GPS location is supported */
+ if ((mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDS, MM_PORT_QMI_FLAG_DEFAULT, NULL)) ||
+ (mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_LOC, MM_PORT_QMI_FLAG_DEFAULT, NULL)))
sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
- MM_MODEM_LOCATION_SOURCE_AGPS);
-
- /* If we have support for the LOC client, GPS location is supported */
- if (mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_LOC, MM_PORT_QMI_FLAG_DEFAULT, NULL))
- sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
- MM_MODEM_LOCATION_SOURCE_GPS_RAW |
- MM_MODEM_LOCATION_SOURCE_AGPS);
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
/* So we're done, complete */
g_task_return_int (task, sources);
diff --git a/src/mm-sms-list.c b/src/mm-sms-list.c
index 8d6aa15..ec3adca 100644
--- a/src/mm-sms-list.c
+++ b/src/mm-sms-list.c
@@ -278,9 +278,12 @@
l = g_list_find_custom (self->priv->list,
GUINT_TO_POINTER (concat_reference),
(GCompareFunc)cmp_sms_by_concat_reference);
- if (l)
+ if (l) {
/* Try to take the part */
+ mm_dbg ("Found existing multipart SMS object with reference '%u': adding new part",
+ concat_reference);
return mm_base_sms_multipart_take_part (MM_BASE_SMS (l->data), part, error);
+ }
/* Create new Multipart */
sms = mm_base_sms_multipart_new (self->priv->modem,
@@ -293,6 +296,9 @@
if (!sms)
return FALSE;
+ mm_dbg ("Creating new multipart SMS object: need to receive %u parts with reference '%u'",
+ mm_sms_part_get_concat_max (part),
+ concat_reference);
self->priv->list = g_list_prepend (self->priv->list, sms);
g_signal_emit (self, signals[SIGNAL_ADDED], 0,
mm_base_sms_get_path (sms),
@@ -343,15 +349,17 @@
/* Did we just get a part of a multi-part SMS? */
if (mm_sms_part_should_concat (part)) {
if (mm_sms_part_get_index (part) != SMS_PART_INVALID_INDEX)
- mm_dbg ("SMS part at '%s/%u' is from a multipart SMS (reference: '%u', sequence: '%u')",
+ mm_dbg ("SMS part at '%s/%u' is from a multipart SMS (reference: '%u', sequence: '%u/%u')",
mm_sms_storage_get_string (storage),
mm_sms_part_get_index (part),
mm_sms_part_get_concat_reference (part),
- mm_sms_part_get_concat_sequence (part));
+ mm_sms_part_get_concat_sequence (part),
+ mm_sms_part_get_concat_max (part));
else
- mm_dbg ("SMS part (not stored) is from a multipart SMS (reference: '%u', sequence: '%u')",
+ mm_dbg ("SMS part (not stored) is from a multipart SMS (reference: '%u', sequence: '%u/%u')",
mm_sms_part_get_concat_reference (part),
- mm_sms_part_get_concat_sequence (part));
+ mm_sms_part_get_concat_sequence (part),
+ mm_sms_part_get_concat_max (part));
return take_multipart (self, part, state, storage, error);
}
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
index d5b09ab..18c7fdb 100644
--- a/src/tests/test-modem-helpers.c
+++ b/src/tests/test-modem-helpers.c
@@ -19,6 +19,7 @@
#include <stdlib.h>
#include <math.h>
+#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-modem-helpers.h"
#include "mm-log.h"
@@ -2681,6 +2682,171 @@
}
/*****************************************************************************/
+/* CID selection logic */
+
+typedef struct {
+ const gchar *apn;
+ MMBearerIpFamily ip_family;
+ const gchar *cgdcont_test;
+ const gchar *cgdcont_query;
+ guint expected_cid;
+ gboolean expected_cid_reused;
+ gboolean expected_cid_overwritten;
+} CidSelectionTest;
+
+static const CidSelectionTest cid_selection_tests[] = {
+ /* Test: exact APN match */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
+ .cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
+ "+CGDCONT: 2,\"IP\",\"ac.vodafone.es\",\"\",0,0\r\n"
+ "+CGDCONT: 3,\"IP\",\"inet.es\",\"\",0,0\r\n",
+ .expected_cid = 2,
+ .expected_cid_reused = TRUE,
+ .expected_cid_overwritten = FALSE
+ },
+ /* Test: exact APN match reported as activated */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
+ .cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
+ "+CGDCONT: 2,\"IP\",\"ac.vodafone.es.MNC001.MCC214.GPRS\",\"\",0,0\r\n"
+ "+CGDCONT: 3,\"IP\",\"inet.es\",\"\",0,0\r\n",
+ .expected_cid = 2,
+ .expected_cid_reused = TRUE,
+ .expected_cid_overwritten = FALSE
+ },
+ /* Test: first empty slot in between defined contexts */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
+ .cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
+ "+CGDCONT: 10,\"IP\",\"inet.es\",\"\",0,0\r\n",
+ .expected_cid = 2,
+ .expected_cid_reused = FALSE,
+ .expected_cid_overwritten = FALSE
+ },
+ /* Test: first empty slot in between defined contexts, different PDP types */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
+ .cgdcont_query = "+CGDCONT: 1,\"IPV6\",\"telefonica.es\",\"\",0,0\r\n"
+ "+CGDCONT: 10,\"IP\",\"inet.es\",\"\",0,0\r\n",
+ .expected_cid = 2,
+ .expected_cid_reused = FALSE,
+ .expected_cid_overwritten = FALSE
+ },
+ /* Test: first empty slot after last context found */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
+ .cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
+ "+CGDCONT: 2,\"IP\",\"inet.es\",\"\",0,0\r\n",
+ .expected_cid = 3,
+ .expected_cid_reused = FALSE,
+ .expected_cid_overwritten = FALSE
+ },
+ /* Test: first empty slot after last context found, different PDP types */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
+ .cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
+ "+CGDCONT: 2,\"IPV6\",\"inet.es\",\"\",0,0\r\n",
+ .expected_cid = 3,
+ .expected_cid_reused = FALSE,
+ .expected_cid_overwritten = FALSE
+ },
+ /* Test: no empty slot, rewrite context with empty APN */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = "+CGDCONT: (1-3),\"IP\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-3),\"IPV6\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-3),\"IPV4V6\",,,(0,1),(0,1)\r\n",
+ .cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
+ "+CGDCONT: 2,\"IP\",\"\",\"\",0,0\r\n"
+ "+CGDCONT: 3,\"IP\",\"inet.es\",\"\",0,0\r\n",
+ .expected_cid = 2,
+ .expected_cid_reused = FALSE,
+ .expected_cid_overwritten = TRUE
+ },
+ /* Test: no empty slot, rewrite last context found */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = "+CGDCONT: (1-3),\"IP\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-3),\"IPV6\",,,(0,1),(0,1)\r\n"
+ "+CGDCONT: (1-3),\"IPV4V6\",,,(0,1),(0,1)\r\n",
+ .cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
+ "+CGDCONT: 2,\"IP\",\"vzwinternet\",\"\",0,0\r\n"
+ "+CGDCONT: 3,\"IP\",\"inet.es\",\"\",0,0\r\n",
+ .expected_cid = 3,
+ .expected_cid_reused = FALSE,
+ .expected_cid_overwritten = TRUE
+ },
+ /* Test: CGDCONT? and CGDCONT=? failures, fallback to CID=1 (a.g. some Android phones) */
+ {
+ .apn = "ac.vodafone.es",
+ .ip_family = MM_BEARER_IP_FAMILY_IPV4,
+ .cgdcont_test = NULL,
+ .cgdcont_query = NULL,
+ .expected_cid = 1,
+ .expected_cid_reused = FALSE,
+ .expected_cid_overwritten = TRUE
+ },
+};
+
+static void
+test_cid_selection (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cid_selection_tests); i++) {
+ const CidSelectionTest *test;
+ GList *context_list;
+ GList *context_format_list;
+ guint cid;
+ gboolean cid_reused;
+ gboolean cid_overwritten;
+
+ test = &cid_selection_tests[i];
+
+ context_format_list = test->cgdcont_test ? mm_3gpp_parse_cgdcont_test_response (test->cgdcont_test, NULL) : NULL;
+ context_list = test->cgdcont_query ? mm_3gpp_parse_cgdcont_read_response (test->cgdcont_query, NULL) : NULL;
+
+ cid = mm_3gpp_select_best_cid (test->apn, test->ip_family,
+ context_list, context_format_list,
+ &cid_reused, &cid_overwritten);
+
+ g_assert_cmpuint (cid, ==, test->expected_cid);
+ g_assert_cmpuint (cid_reused, ==, test->expected_cid_reused);
+ g_assert_cmpuint (cid_overwritten, ==, test->expected_cid_overwritten);
+
+ mm_3gpp_pdp_context_format_list_free (context_format_list);
+ mm_3gpp_pdp_context_list_free (context_list);
+ }
+}
+
+/*****************************************************************************/
/* Test CPMS responses */
static gboolean
@@ -3926,6 +4092,259 @@
}
/*****************************************************************************/
+/* +CLIP URC */
+
+typedef struct {
+ const gchar *str;
+ const gchar *number;
+ guint type;
+} ClipUrcTest;
+
+static const ClipUrcTest clip_urc_tests[] = {
+ { "\r\n+CLIP: \"123456789\",129\r\n", "123456789", 129 },
+ { "\r\n+CLIP: \"123456789\",129,,,,0\r\n", "123456789", 129 },
+};
+
+static void
+test_clip_indication (void)
+{
+ GRegex *r;
+ guint i;
+
+ r = mm_voice_clip_regex_get ();
+
+ for (i = 0; i < G_N_ELEMENTS (clip_urc_tests); i++) {
+ GMatchInfo *match_info = NULL;
+ gchar *number;
+ guint type;
+
+ g_assert (g_regex_match (r, clip_urc_tests[i].str, 0, &match_info));
+ g_assert (g_match_info_matches (match_info));
+
+ number = mm_get_string_unquoted_from_match_info (match_info, 1);
+ g_assert_cmpstr (number, ==, clip_urc_tests[i].number);
+
+ g_assert (mm_get_uint_from_match_info (match_info, 2, &type));
+ g_assert_cmpuint (type, ==, clip_urc_tests[i].type);
+
+ g_free (number);
+ g_match_info_free (match_info);
+ }
+
+ g_regex_unref (r);
+}
+
+/*****************************************************************************/
+/* +CCWA URC */
+
+typedef struct {
+ const gchar *str;
+ const gchar *number;
+ guint type;
+ guint class;
+} CcwaUrcTest;
+
+static const CcwaUrcTest ccwa_urc_tests[] = {
+ { "\r\n+CCWA: \"123456789\",129,1\r\n", "123456789", 129, 1 },
+ { "\r\n+CCWA: \"123456789\",129,1,,0\r\n", "123456789", 129, 1 },
+ { "\r\n+CCWA: \"123456789\",129,1,,0,,,\r\n", "123456789", 129, 1 },
+};
+
+static void
+test_ccwa_indication (void)
+{
+ GRegex *r;
+ guint i;
+
+ r = mm_voice_ccwa_regex_get ();
+
+ for (i = 0; i < G_N_ELEMENTS (ccwa_urc_tests); i++) {
+ GMatchInfo *match_info = NULL;
+ gchar *number;
+ guint type;
+ guint class;
+
+ g_assert (g_regex_match (r, ccwa_urc_tests[i].str, 0, &match_info));
+ g_assert (g_match_info_matches (match_info));
+
+ number = mm_get_string_unquoted_from_match_info (match_info, 1);
+ g_assert_cmpstr (number, ==, ccwa_urc_tests[i].number);
+
+ g_assert (mm_get_uint_from_match_info (match_info, 2, &type));
+ g_assert_cmpuint (type, ==, ccwa_urc_tests[i].type);
+
+ g_assert (mm_get_uint_from_match_info (match_info, 3, &class));
+ g_assert_cmpuint (class, ==, ccwa_urc_tests[i].class);
+
+ g_free (number);
+ g_match_info_free (match_info);
+ }
+
+ g_regex_unref (r);
+}
+
+/*****************************************************************************/
+/* +CCWA service query response testing */
+
+static void
+common_test_ccwa_response (const gchar *response,
+ gboolean expected_status,
+ gboolean expected_error)
+{
+ gboolean status = FALSE;
+ GError *error = NULL;
+ gboolean result;
+
+ result = mm_3gpp_parse_ccwa_service_query_response (response, &status, &error);
+
+ if (expected_error) {
+ g_assert (!result);
+ g_assert (error);
+ g_error_free (error);
+ } else {
+ g_assert (result);
+ g_assert_no_error (error);
+ g_assert_cmpuint (status, ==, expected_status);
+ }
+}
+
+typedef struct {
+ const gchar *response;
+ gboolean expected_status;
+ gboolean expected_error;
+} TestCcwa;
+
+static TestCcwa test_ccwa[] = {
+ { "+CCWA: 0,255", FALSE, FALSE }, /* all disabled */
+ { "+CCWA: 1,255", TRUE, FALSE }, /* all enabled */
+ { "+CCWA: 0,1\r\n"
+ "+CCWA: 0,4\r\n", FALSE, FALSE }, /* voice and fax disabled */
+ { "+CCWA: 1,1\r\n"
+ "+CCWA: 1,4\r\n", TRUE, FALSE }, /* voice and fax enabled */
+ { "+CCWA: 0,2\r\n"
+ "+CCWA: 0,4\r\n"
+ "+CCWA: 0,8\r\n", FALSE, TRUE }, /* data, fax, sms disabled, voice not given */
+ { "+CCWA: 1,2\r\n"
+ "+CCWA: 1,4\r\n"
+ "+CCWA: 1,8\r\n", FALSE, TRUE }, /* data, fax, sms enabled, voice not given */
+ { "+CCWA: 2,1\r\n"
+ "+CCWA: 2,4\r\n", FALSE, TRUE }, /* voice and fax enabled but unexpected state */
+};
+
+static void
+test_ccwa_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (test_ccwa); i++)
+ common_test_ccwa_response (test_ccwa[i].response, test_ccwa[i].expected_status, test_ccwa[i].expected_error);
+}
+
+/*****************************************************************************/
+/* Test +CLCC URCs */
+
+static void
+common_test_clcc_response (const gchar *str,
+ const MMCallInfo *expected_call_info_list,
+ guint expected_call_info_list_size)
+{
+ GError *error = NULL;
+ gboolean result;
+ GList *call_info_list = NULL;
+ GList *l;
+
+ result = mm_3gpp_parse_clcc_response (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);
+ }
+
+ mm_3gpp_call_info_list_free (call_info_list);
+}
+
+static void
+test_clcc_response_empty (void)
+{
+ const gchar *response = "";
+
+ common_test_clcc_response (response, NULL, 0);
+}
+
+static void
+test_clcc_response_single (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "123456789" }
+ };
+
+ const gchar *response =
+ "+CLCC: 1,1,0,0,0,\"123456789\",161";
+
+ common_test_clcc_response (response, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_clcc_response_single_long (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_RINGING_IN, "123456789" }
+ };
+
+ /* NOTE: priority field is EMPTY */
+ const gchar *response =
+ "+CLCC: 1,1,4,0,0,\"123456789\",129,\"\",,0";
+
+ common_test_clcc_response (response, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_clcc_response_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" },
+ { 4, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, "000000000" },
+ { 5, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, "555555555" },
+ };
+
+ const gchar *response =
+ "+CLCC: 1,1,0,0,1\r\n" /* number unknown */
+ "+CLCC: 2,1,0,0,1,\"123456789\",161\r\n"
+ "+CLCC: 3,1,0,0,1,\"987654321\",161,\"Alice\"\r\n"
+ "+CLCC: 4,1,0,0,1,\"000000000\",161,\"Bob\",1\r\n"
+ "+CLCC: 5,1,5,0,0,\"555555555\",161,\"Mallory\",2,0\r\n";
+
+ common_test_clcc_response (response, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+/*****************************************************************************/
typedef struct {
gchar *str;
@@ -4193,6 +4612,8 @@
g_test_suite_add (suite, TESTCASE (test_cgdcont_read_response_nokia, NULL));
g_test_suite_add (suite, TESTCASE (test_cgdcont_read_response_samsung, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cid_selection, NULL));
+
g_test_suite_add (suite, TESTCASE (test_cgact_read_response_none, NULL));
g_test_suite_add (suite, TESTCASE (test_cgact_read_response_single_inactive, NULL));
g_test_suite_add (suite, TESTCASE (test_cgact_read_response_single_active, NULL));
@@ -4236,6 +4657,15 @@
g_test_suite_add (suite, TESTCASE (test_cesq_response, NULL));
g_test_suite_add (suite, TESTCASE (test_cesq_response_to_signal, NULL));
+ g_test_suite_add (suite, TESTCASE (test_clip_indication, NULL));
+ g_test_suite_add (suite, TESTCASE (test_ccwa_indication, NULL));
+ g_test_suite_add (suite, TESTCASE (test_ccwa_response, NULL));
+
+ g_test_suite_add (suite, TESTCASE (test_clcc_response_empty, NULL));
+ g_test_suite_add (suite, TESTCASE (test_clcc_response_single, NULL));
+ g_test_suite_add (suite, TESTCASE (test_clcc_response_single_long, NULL));
+ g_test_suite_add (suite, TESTCASE (test_clcc_response_multiple, NULL));
+
g_test_suite_add (suite, TESTCASE (test_parse_uint_list, NULL));
g_test_suite_add (suite, TESTCASE (test_bcd_to_string, NULL));
diff --git a/test/Makefile.am b/test/Makefile.am
index cd2b5d5..20e2a06 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -67,6 +67,56 @@
$(NULL)
################################################################################
+# mmsmspdu
+################################################################################
+
+noinst_PROGRAMS += mmsmspdu
+
+mmsmspdu_SOURCES = mmsmspdu.c
+
+mmsmspdu_CPPFLAGS = \
+ $(MM_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ -I$(top_srcdir)/libmm-glib \
+ -I$(top_srcdir)/libmm-glib/generated \
+ -I$(top_builddir)/libmm-glib/generated
+ $(NULL)
+
+mmsmspdu_LDADD = \
+ $(MM_LIBS) \
+ $(top_builddir)/libmm-glib/libmm-glib.la \
+ $(top_builddir)/src/libhelpers.la \
+ $(NULL)
+
+################################################################################
+# mmsmsmonitor
+################################################################################
+
+noinst_PROGRAMS += mmsmsmonitor
+
+mmsmsmonitor_SOURCES = mmsmsmonitor.c
+
+mmsmsmonitor_CPPFLAGS = \
+ $(MM_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ -I$(top_srcdir)/libmm-glib \
+ -I$(top_srcdir)/libmm-glib/generated \
+ -I$(top_builddir)/libmm-glib/generated
+ $(NULL)
+
+mmsmsmonitor_LDADD = \
+ $(MM_LIBS) \
+ $(top_builddir)/libmm-glib/libmm-glib.la \
+ $(top_builddir)/src/libhelpers.la \
+ $(NULL)
+
+################################################################################
# mmcli-test-sms
################################################################################
diff --git a/test/lsudev.c b/test/lsudev.c
index 7b8860e..fe5d44c 100644
--- a/test/lsudev.c
+++ b/test/lsudev.c
@@ -30,94 +30,94 @@
static void
signal_handler (int signo)
{
- if (signo == SIGINT || signo == SIGTERM) {
- g_message ("Caught signal %d, shutting down...", signo);
- g_main_loop_quit (loop);
- }
+ if (signo == SIGINT || signo == SIGTERM) {
+ g_message ("Caught signal %d, shutting down...", signo);
+ g_main_loop_quit (loop);
+ }
}
static void
setup_signals (void)
{
- struct sigaction action;
- sigset_t mask;
+ struct sigaction action;
+ sigset_t mask;
- sigemptyset (&mask);
- action.sa_handler = signal_handler;
- action.sa_mask = mask;
- action.sa_flags = 0;
- sigaction (SIGTERM, &action, NULL);
- sigaction (SIGINT, &action, NULL);
+ sigemptyset (&mask);
+ action.sa_handler = signal_handler;
+ action.sa_mask = mask;
+ action.sa_flags = 0;
+ sigaction (SIGTERM, &action, NULL);
+ sigaction (SIGINT, &action, NULL);
}
static void
println (guint indent, const char *fmt, ...)
{
- va_list args;
- GString *output;
- int i;
+ va_list args;
+ GString *output;
+ int i;
- g_return_if_fail (fmt != NULL);
+ g_return_if_fail (fmt != NULL);
- output = g_string_sized_new (250);
+ output = g_string_sized_new (250);
- for (i = 0; i < indent; i++)
- g_string_append_c (output, ' ');
+ for (i = 0; i < indent; i++)
+ g_string_append_c (output, ' ');
- va_start (args, fmt);
- g_string_append_vprintf (output, fmt, args);
- va_end (args);
+ va_start (args, fmt);
+ g_string_append_vprintf (output, fmt, args);
+ va_end (args);
- g_print ("%s\n", output->str);
- g_string_free (output, TRUE);
+ g_print ("%s\n", output->str);
+ g_string_free (output, TRUE);
}
static void
dump_device_and_parent (GUdevDevice *device, guint indent)
{
- const char **list, **iter;
- GUdevDevice *parent;
- char propstr[500];
- guint32 namelen = 0, i;
+ const char **list, **iter;
+ GUdevDevice *parent;
+ char propstr[500];
+ guint32 namelen = 0, i;
- println (indent, "------------------------------------------------------");
- println (indent, "Name: %s", g_udev_device_get_name (device));
- println (indent, "Type: %s", g_udev_device_get_devtype (device));
- println (indent, "Subsys: %s", g_udev_device_get_subsystem (device));
- println (indent, "Number: %s", g_udev_device_get_number (device));
- println (indent, "Path: %s", g_udev_device_get_sysfs_path (device));
- println (indent, "Driver: %s", g_udev_device_get_driver (device));
- println (indent, "Action: %s", g_udev_device_get_action (device));
- println (indent, "Seq Num: %lu", g_udev_device_get_seqnum (device));
- println (indent, "Dev File: %s", g_udev_device_get_device_file (device));
+ println (indent, "------------------------------------------------------");
+ println (indent, "Name: %s", g_udev_device_get_name (device));
+ println (indent, "Type: %s", g_udev_device_get_devtype (device));
+ println (indent, "Subsys: %s", g_udev_device_get_subsystem (device));
+ println (indent, "Number: %s", g_udev_device_get_number (device));
+ println (indent, "Path: %s", g_udev_device_get_sysfs_path (device));
+ println (indent, "Driver: %s", g_udev_device_get_driver (device));
+ println (indent, "Action: %s", g_udev_device_get_action (device));
+ println (indent, "Seq Num: %lu", g_udev_device_get_seqnum (device));
+ println (indent, "Dev File: %s", g_udev_device_get_device_file (device));
- println (indent, "");
- println (indent, "Properties:");
+ println (indent, "");
+ println (indent, "Properties:");
- /* Get longest property name length for alignment */
- list = (const char **) g_udev_device_get_property_keys (device);
- for (iter = list; iter && *iter; iter++) {
- if (strlen (*iter) > namelen)
- namelen = strlen (*iter);
- }
- namelen++;
+ /* Get longest property name length for alignment */
+ list = (const char **) g_udev_device_get_property_keys (device);
+ for (iter = list; iter && *iter; iter++) {
+ if (strlen (*iter) > namelen)
+ namelen = strlen (*iter);
+ }
+ namelen++;
- for (iter = list; iter && *iter; iter++) {
- strcpy (propstr, *iter);
- strcat (propstr, ":");
- for (i = 0; i < namelen - strlen (*iter); i++)
- strcat (propstr, " ");
- strcat (propstr, g_udev_device_get_property (device, *iter));
- println (indent + 2, "%s", propstr);
- }
+ for (iter = list; iter && *iter; iter++) {
+ strcpy (propstr, *iter);
+ strcat (propstr, ":");
+ for (i = 0; i < namelen - strlen (*iter); i++)
+ strcat (propstr, " ");
+ strcat (propstr, g_udev_device_get_property (device, *iter));
+ println (indent + 2, "%s", propstr);
+ }
- println (indent, "");
+ println (indent, "");
- parent = g_udev_device_get_parent (device);
- if (parent) {
- dump_device_and_parent (parent, indent + 4);
- g_object_unref (parent);
- }
+ parent = g_udev_device_get_parent (device);
+ if (parent) {
+ dump_device_and_parent (parent, indent + 4);
+ g_object_unref (parent);
+ }
}
static void
@@ -126,53 +126,53 @@
GUdevDevice *device,
gpointer user_data)
{
- const char *expected_subsys = user_data;
- const char *subsys;
+ const char *expected_subsys = user_data;
+ const char *subsys;
- g_return_if_fail (client != NULL);
- g_return_if_fail (action != NULL);
- g_return_if_fail (device != NULL);
+ g_return_if_fail (client != NULL);
+ g_return_if_fail (action != NULL);
+ g_return_if_fail (device != NULL);
- /* A bit paranoid */
- subsys = g_udev_device_get_subsystem (device);
- g_return_if_fail (subsys != NULL);
+ /* A bit paranoid */
+ subsys = g_udev_device_get_subsystem (device);
+ g_return_if_fail (subsys != NULL);
- g_return_if_fail (!strcmp (subsys, expected_subsys));
+ g_return_if_fail (!strcmp (subsys, expected_subsys));
- g_print ("---- (EVENT: %s) ----\n", action);
- dump_device_and_parent (device, 0);
- g_print ("\n");
+ g_print ("---- (EVENT: %s) ----\n", action);
+ dump_device_and_parent (device, 0);
+ g_print ("\n");
}
int
main (int argc, char *argv[])
{
- GUdevClient *client;
- const char *subsys[2] = { NULL, NULL };
- GList *list, *iter;
+ GUdevClient *client;
+ const char *subsys[2] = { NULL, NULL };
+ GList *list, *iter;
- if (argc != 2) {
- g_warning ("Usage: %s [subsystem]", argv[0]);
- return 1;
- }
+ if (argc != 2) {
+ g_warning ("Usage: %s [subsystem]", argv[0]);
+ return 1;
+ }
- loop = g_main_loop_new (NULL, FALSE);
+ loop = g_main_loop_new (NULL, FALSE);
- setup_signals ();
+ setup_signals ();
- subsys[0] = argv[1];
- client = g_udev_client_new (subsys);
- g_signal_connect (client, "uevent", G_CALLBACK (handle_uevent), (gpointer) subsys[0]);
+ subsys[0] = argv[1];
+ client = g_udev_client_new (subsys);
+ g_signal_connect (client, "uevent", G_CALLBACK (handle_uevent), (gpointer) subsys[0]);
- list = g_udev_client_query_by_subsystem (client, subsys[0]);
- for (iter = list; iter; iter = g_list_next (iter)) {
- dump_device_and_parent (G_UDEV_DEVICE (iter->data), 0);
- g_print ("\n");
- g_object_unref (G_UDEV_DEVICE (iter->data));
- }
+ list = g_udev_client_query_by_subsystem (client, subsys[0]);
+ for (iter = list; iter; iter = g_list_next (iter)) {
+ dump_device_and_parent (G_UDEV_DEVICE (iter->data), 0);
+ g_print ("\n");
+ g_object_unref (G_UDEV_DEVICE (iter->data));
+ }
- g_main_loop_run (loop);
+ g_main_loop_run (loop);
- return 0;
+ return 0;
}
diff --git a/test/mmsmsmonitor.c b/test/mmsmsmonitor.c
new file mode 100644
index 0000000..de89521
--- /dev/null
+++ b/test/mmsmsmonitor.c
@@ -0,0 +1,195 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <gio/gio.h>
+#include <libmm-glib.h>
+
+#define PROGRAM_NAME "mmsmsmonitor"
+#define PROGRAM_VERSION PACKAGE_VERSION
+
+/* Globals */
+static GMainLoop *loop;
+static GList *monitored_sms_list;
+
+/* Context */
+static gboolean version_flag;
+
+static GOptionEntry main_entries[] = {
+ { "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag,
+ "Print version",
+ NULL
+ },
+ { NULL }
+};
+
+static gboolean
+signals_handler (void)
+{
+ if (loop && g_main_loop_is_running (loop)) {
+ g_printerr ("%s\n",
+ "cancelling the main loop...\n");
+ g_main_loop_quit (loop);
+ }
+ return TRUE;
+}
+
+static void
+print_version_and_exit (void)
+{
+ g_print ("\n"
+ PROGRAM_NAME " " PROGRAM_VERSION "\n"
+ "Copyright (2019) Aleksander Morgado\n"
+ "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n"
+ "\n");
+ exit (EXIT_SUCCESS);
+}
+
+static void
+sms_state_updated (MMSms *sms)
+{
+ g_print ("[%s] sms updated: %s\n",
+ mm_sms_get_path (sms),
+ mm_sms_state_get_string (mm_sms_get_state (sms)));
+}
+
+static gboolean
+sms_added (MMModemMessaging *modem_messaging,
+ const gchar *sms_path,
+ gboolean received)
+{
+ GList *sms_list;
+ GList *l;
+ MMSms *new_sms = NULL;
+
+ sms_list = mm_modem_messaging_list_sync (modem_messaging, NULL, NULL);
+ for (l = sms_list; l && !new_sms; l = g_list_next (l)) {
+ MMSms *l_sms = MM_SMS (l->data);
+
+ if (g_strcmp0 (mm_sms_get_path (l_sms), sms_path) == 0)
+ new_sms = l_sms;
+ }
+ g_assert (new_sms);
+
+ g_print ("[%s] new sms: %s\n",
+ mm_sms_get_path (new_sms),
+ mm_sms_state_get_string (mm_sms_get_state (new_sms)));
+ g_signal_connect (new_sms, "notify::state", G_CALLBACK (sms_state_updated), NULL);
+ monitored_sms_list = g_list_append (monitored_sms_list, g_object_ref (new_sms));
+
+ g_list_free_full (sms_list, g_object_unref);
+ return TRUE;
+}
+
+static void
+list_all_sms_found (MMModemMessaging *modem_messaging)
+{
+ GList *sms_list;
+ GList *l;
+
+ sms_list = mm_modem_messaging_list_sync (modem_messaging, NULL, NULL);
+ for (l = sms_list; l; l = g_list_next (l)) {
+ MMSms *l_sms = MM_SMS (l->data);
+
+ g_print ("[%s] sms found: %s\n",
+ mm_sms_get_path (l_sms),
+ mm_sms_state_get_string (mm_sms_get_state (l_sms)));
+ g_signal_connect (l_sms, "notify::state", G_CALLBACK (sms_state_updated), NULL);
+ monitored_sms_list = g_list_append (monitored_sms_list, g_object_ref (l_sms));
+ }
+ g_list_free_full (sms_list, g_object_unref);
+}
+
+int main (int argc, char **argv)
+{
+ GOptionContext *context;
+ GDBusConnection *connection;
+ MMManager *manager;
+ GList *modem_list;
+ GList *l;
+ gchar *name_owner;
+ GError *error = NULL;
+
+ setlocale (LC_ALL, "");
+
+ /* Setup option context, process it and destroy it */
+ context = g_option_context_new ("- ModemManager SMS monitor");
+ g_option_context_add_main_entries (context, main_entries, NULL);
+ g_option_context_parse (context, &argc, &argv, NULL);
+ g_option_context_free (context);
+
+ if (version_flag)
+ print_version_and_exit ();
+
+ /* Setup dbus connection to use */
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!connection) {
+ g_printerr ("error: couldn't get bus: %s\n",
+ error ? error->message : "unknown error");
+ exit (EXIT_FAILURE);
+ }
+
+ manager = mm_manager_new_sync (connection,
+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ &error);
+ if (!manager) {
+ g_printerr ("error: couldn't create manager: %s\n",
+ error ? error->message : "unknown error");
+ exit (EXIT_FAILURE);
+ }
+
+ name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager));
+ if (!name_owner) {
+ g_printerr ("error: couldn't find the ModemManager process in the bus\n");
+ exit (EXIT_FAILURE);
+ }
+ g_free (name_owner);
+
+ modem_list = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (manager));
+ for (l = modem_list; l; l = g_list_next (l)) {
+ MMObject *obj;
+ MMModemMessaging *modem_messaging;
+
+ obj = MM_OBJECT (l->data);
+ modem_messaging = MM_MODEM_MESSAGING (mm_object_peek_modem_messaging (obj));
+
+ g_signal_connect (modem_messaging, "added", G_CALLBACK (sms_added), NULL);
+ list_all_sms_found (modem_messaging);
+ }
+
+ g_unix_signal_add (SIGINT, (GSourceFunc) signals_handler, NULL);
+ g_unix_signal_add (SIGHUP, (GSourceFunc) signals_handler, NULL);
+ g_unix_signal_add (SIGTERM, (GSourceFunc) signals_handler, NULL);
+
+ /* Setup main loop and shedule start in idle */
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ /* Cleanup */
+ g_main_loop_unref (loop);
+ g_list_free_full (modem_list, g_object_unref);
+ g_object_unref (manager);
+ return EXIT_SUCCESS;
+}
diff --git a/test/mmsmspdu.c b/test/mmsmspdu.c
new file mode 100644
index 0000000..cb56c71
--- /dev/null
+++ b/test/mmsmspdu.c
@@ -0,0 +1,233 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-log.h"
+#include "mm-sms-part-3gpp.h"
+
+#define PROGRAM_NAME "mmsmspdu"
+#define PROGRAM_VERSION PACKAGE_VERSION
+
+/* Context */
+static gchar *pdu;
+static gboolean verbose_flag;
+static gboolean version_flag;
+
+static GOptionEntry main_entries[] = {
+ { "pdu", 'p', 0, G_OPTION_ARG_STRING, &pdu,
+ "PDU contents",
+ "[0123456789ABCDEF..]"
+ },
+ { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag,
+ "Run action with verbose logs",
+ NULL
+ },
+ { "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag,
+ "Print version",
+ NULL
+ },
+ { NULL }
+};
+
+static void
+show_part_info (MMSmsPart *part)
+{
+ MMSmsPduType pdu_type;
+ const gchar *smsc;
+ const gchar *number;
+ const gchar *timestamp;
+ const gchar *text;
+ MMSmsEncoding encoding;
+ gint class;
+ guint validity_relative;
+ gboolean delivery_report_request;
+ guint concat_reference;
+ guint concat_max;
+ guint concat_sequence;
+ const GByteArray *data;
+
+ pdu_type = mm_sms_part_get_pdu_type (part);
+ g_print ("pdu type: %s\n", mm_sms_pdu_type_get_string (pdu_type));
+
+ smsc = mm_sms_part_get_smsc (part);
+ g_print ("smsc: %s\n", smsc ? smsc : "n/a");
+
+ number = mm_sms_part_get_number (part);
+ g_print ("number: %s\n", number ? number : "n/a");
+
+ timestamp = mm_sms_part_get_timestamp (part);
+ g_print ("timestamp: %s\n", timestamp ? timestamp : "n/a");
+
+ encoding = mm_sms_part_get_encoding (part);
+ switch (encoding) {
+ case MM_SMS_ENCODING_GSM7:
+ g_print ("encoding: GSM7\n");
+ break;
+ case MM_SMS_ENCODING_UCS2:
+ g_print ("encoding: UCS2\n");
+ break;
+ case MM_SMS_ENCODING_8BIT:
+ g_print ("encoding: 8BIT\n");
+ break;
+ default:
+ g_print ("encoding: unknown (0x%x)\n", encoding);
+ break;
+ }
+
+ text = mm_sms_part_get_text (part);
+ g_print ("text: %s\n", text ? text : "n/a");
+
+ data = mm_sms_part_get_data (part);
+ if (data) {
+ gchar *data_str;
+
+ data_str = mm_utils_bin2hexstr (data->data, data->len);
+ g_print ("data: %s\n", data_str);
+ g_free (data_str);
+ } else
+ g_print ("data: n/a\n");
+
+ class = mm_sms_part_get_class (part);
+ if (class != -1)
+ g_print ("class: %d\n", class);
+ else
+ g_print ("class: n/a\n");
+
+ validity_relative = mm_sms_part_get_validity_relative (part);
+ if (validity_relative != 0)
+ g_print ("validity relative: %d\n", validity_relative);
+ else
+ g_print ("validity relative: n/a\n");
+
+ delivery_report_request = mm_sms_part_get_delivery_report_request (part);
+ g_print ("delivery report request: %s\n", delivery_report_request ? "yes" : "no");
+
+ concat_reference = mm_sms_part_get_concat_reference (part);
+ g_print ("concat reference: %d\n", concat_reference);
+
+ concat_max = mm_sms_part_get_concat_max (part);
+ g_print ("concat max: %d\n", concat_max);
+
+ concat_sequence = mm_sms_part_get_concat_sequence (part);
+ g_print ("concat sequence: %d\n", concat_sequence);
+
+ if (mm_sms_part_get_pdu_type (part) == MM_SMS_PDU_TYPE_STATUS_REPORT) {
+ const gchar *discharge_timestamp;
+ guint message_reference;
+ guint delivery_state;
+
+ message_reference = mm_sms_part_get_message_reference (part);
+ g_print ("message reference: %d\n", message_reference);
+
+ discharge_timestamp = mm_sms_part_get_discharge_timestamp (part);
+ g_print ("discharge timestamp: %s\n", discharge_timestamp ? discharge_timestamp : "n/a");
+
+ delivery_state = mm_sms_part_get_delivery_state (part);
+ g_print ("delivery state: %s\n", mm_sms_delivery_state_get_string_extended (delivery_state));
+ }
+
+ if (MM_SMS_PART_IS_CDMA (part)) {
+ MMSmsCdmaTeleserviceId teleservice_id;
+ MMSmsCdmaServiceCategory service_category;
+
+ teleservice_id = mm_sms_part_get_cdma_teleservice_id (part);
+ g_print ("teleservice id: %s\n", mm_sms_cdma_teleservice_id_get_string (teleservice_id));
+
+ service_category = mm_sms_part_get_cdma_service_category (part);
+ g_print ("service category: %s\n", mm_sms_cdma_service_category_get_string (service_category));
+ }
+}
+
+void
+_mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ gchar *msg;
+
+ if (!verbose_flag)
+ return;
+
+ va_start (args, fmt);
+ msg = g_strdup_vprintf (fmt, args);
+ va_end (args);
+ g_print ("%s\n", msg);
+ g_free (msg);
+}
+
+static void
+print_version_and_exit (void)
+{
+ g_print ("\n"
+ PROGRAM_NAME " " PROGRAM_VERSION "\n"
+ "Copyright (2019) Aleksander Morgado\n"
+ "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n"
+ "\n");
+ exit (EXIT_SUCCESS);
+}
+
+int main (int argc, char **argv)
+{
+ GOptionContext *context;
+ GError *error = NULL;
+ MMSmsPart *part;
+
+ setlocale (LC_ALL, "");
+
+ /* Setup option context, process it and destroy it */
+ context = g_option_context_new ("- ModemManager SMS PDU parser");
+ g_option_context_add_main_entries (context, main_entries, NULL);
+ g_option_context_parse (context, &argc, &argv, NULL);
+ g_option_context_free (context);
+
+ if (version_flag)
+ print_version_and_exit ();
+
+ /* No pdu given? */
+ if (!pdu) {
+ g_printerr ("error: no PDU specified\n");
+ exit (EXIT_FAILURE);
+ }
+
+ part = mm_sms_part_3gpp_new_from_pdu (0, pdu, &error);
+ if (!part) {
+ g_printerr ("error: couldn't parse PDU: %s\n", error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ show_part_info (part);
+
+ mm_sms_part_free (part);
+ g_free (pdu);
+
+ return EXIT_SUCCESS;
+}