Merge remote-tracking branch 'cros/upstream' into 'cros/master'

Contains the following patches:
4a0de586 base-call: if start() fails, always terminated (Aleksander Morgado)
2c0281aa call: disallow non-emergency calls when in emergency-only state (Aleksander Morgado)
d6f9d5e9 sim: load emergency numbers from EF_ECC (Aleksander Morgado)
1f6c0069 api,sim: new 'Emergency Numbers' property (Aleksander Morgado)
45cf1ba2 cinterion: don't cache voice support check AT commands (Aleksander Morgado)
57e78413 iface-modem-voice: always completely reinitialize the interface (Aleksander Morgado)
98066d24 broadband-modem: don't cache voice support check AT commands (Aleksander Morgado)
5fef1417 broadband-modem: ignore SIM-related errors in voice support check (Aleksander Morgado)
6347a7d7 base-call: catch terminated errors before ATD replies (Aleksander Morgado)
eaf66547 broadband-modem: keep ports context open while in-call (Aleksander Morgado)
c99f767f broadband-modem: reorder generic port context logic in sources (Aleksander Morgado)
ba1f3af3 broadband-modem: new generic ports_context_open() (Aleksander Morgado)
7c8d0d17 broadband-modem: skip redundant ATE0/AT+CMEE=1 settings (Aleksander Morgado)
657833a4 voice,api: new 'EmergencyOnly' boolean flag (Aleksander Morgado)
79718443 iface-modem-voice: all methods available even if not enabled (Aleksander Morgado)
ed3a1264 iface-modem-voice: call list setup during initialization (Aleksander Morgado)
00a39cd0 broadband-modem: voice interface available in failed/locked state (Aleksander Morgado)
27405a44 broadband-modem: new initialization step when failed/locked (Aleksander Morgado)
d555c4c4 iface-modem-simple: allow single ongoing Connect() attempt (Aleksander Morgado)
f228d0dd base-bearer,iface-modem-simple: log user requests to connect/disconnect (Aleksander Morgado)
667d1613 x22x: add support for the Alcatel X602D (Aleksander Morgado)
407cfa0c x22x: ignore unhandled URCs in the Alcatel X602D (Aleksander Morgado)
f30d3d46 ci: ignore errors on apt update (Aleksander Morgado)
5316e9e4 simtech: handle 'MISSED_CALL' URCs (Aleksander Morgado)
eb66e8ae simtech: setup USB audio channel when in-call (Aleksander Morgado)
e3a1364d core: allow flagging ports as 'audio' (Aleksander Morgado)
d61cb4a2 simtech: handle '+RXDTMF' URCs reporting DTMF tones (Aleksander Morgado)
345922ca simtech: handle non-standard '+CRING' URCs (Aleksander Morgado)
395b2217 simtech: handle 'VOICE CALL' URCs (Aleksander Morgado)
1a17996c iface-modem-voice: ignore unknown calls reported as terminated (Aleksander Morgado)
089faef8 modem-helpers: support reporting 'terminated' call state in +CLCC (Aleksander Morgado)
89c9566e simtech: implement +CLCC URC based call list management (Aleksander Morgado)
b99597b5 tests,helpers: minor alignment fix (Aleksander Morgado)
d5d0ed0b tests: print MM logs only on verbose test mode (Aleksander Morgado)
03223fe7 tests,sms: print PDUs only on verbose test mode (Aleksander Morgado)
3b18d21b tests: avoid g_print() and use g_debug() instead (Aleksander Morgado)
cc4e7f6c shared-qmi: fix NAS SSP support check during supported caps loading (Aleksander Morgado)
c8d715e5 api: set enum values not part of the API as private (Aleksander Morgado)
33140e1f mm-broadband-modem-mbim: fix incorrect log message (Eric Caruso)
6cbeb96a simtech: +CNSMOD value may have multiple digits (Aleksander Morgado)
e1d18afe simtech: add support for reporting LTE (Aleksander Morgado)
61f3c3c8 simtech: rework access tech value mapping (Aleksander Morgado)
7fcefc7a simtech: rework enabling/disabling unsolicited events (Aleksander Morgado)
1277ebb4 iface-modem: allow plugins to disable access technology polling (Aleksander Morgado)
45a238bc iface-modem: allow disabling signal quality polling during runtime (Aleksander Morgado)
c820e02f simtech: enable +CSQ URC support (Aleksander Morgado)
765f26a4 iface-modem: don't log error if it's in progress (Aleksander Morgado)
1f6f9eec simtech: keep access tech URC regex in private struct (Aleksander Morgado)
c98a1a9d simtech: disable CMER/CIND support explicitly (Aleksander Morgado)
feb6c826 broadband-modem: allow disabling +CIND URC setup (Aleksander Morgado)
81795f30 simtech: implement GPS support with AT+CGPS (Aleksander Morgado)
583d53a7 simtech: port type hints for the SIM7000/SIM7600 family (Aleksander Morgado)
eeb1f739 simtech: remove unused ID_MM_SIMTECH_TAGGED tag (Aleksander Morgado)
55c3f7d3 systemd: always start MM after polkit service if enabling policy (Aleksander Morgado)
803c7705 broadband-modem-qmi: fix when >1 PRI images for the same modem image (Aleksander Morgado)
227c2907 mmcli,firmware: don't use tabs when printing human-friendly list (Aleksander Morgado)
6977b41a broadband-modem-qmi: prefer ASCII unique IDs (Aleksander Morgado)
a55543d1 broadband-modem-qmi: fix listing images when 'Get Stored Image Info' is unsupported (Aleksander Morgado)
1b29f752 broadband-modem-qmi: move pri/modem pair list building to separate method (Aleksander Morgado)
7986b273 broadband-modem-qmi: move pri/modem list index lookup to separate method (Aleksander Morgado)
5b8da2e1 broadband-modem-qmi: add note about index_of_running_image known issue (Aleksander Morgado)
66192288 broadband-modem-qmi: fix minor typo in comment (Aleksander Morgado)
3f15293a daemon: register G_IO_ERROR_CANCELLED as "Cancelled" in DBus (Aleksander Morgado)
cf0d3f36 iface-modem: avoid using MM_CORE_ERROR_CANCELLED (Aleksander Morgado)
651bfd62 port-serial: avoid using MM_CORE_ERROR_CANCELLED (Aleksander Morgado)
fa453a17 iface-modem-voice: avoid using MM_CORE_ERROR_CANCELLED (Aleksander Morgado)
cff3be23 broadband-modem: avoid using MM_CORE_ERROR_CANCELLED (Aleksander Morgado)
7974933d broadband-modem-mbim: avoid using MM_CORE_ERROR_CANCELLED (Aleksander Morgado)
c0e1bde0 base-modem-at: avoid using MM_CORE_ERROR_CANCELLED (Aleksander Morgado)
def5c76c huawei,bearer: avoid using MM_CORE_ERROR_CANCELLED (Aleksander Morgado)
6de9c8cb base-bearer: avoid using MM_CORE_ERROR_CANCELLED (Aleksander Morgado)
15b81560 iface-modem-simple: fix Simple.Disconnect() when bearer path given (Aleksander Morgado)
3a9c5531 libmm-glib,oma: avoid plural in type (Aleksander Morgado)
19652e10 libmm-glib,call: fix typo in documentation (Aleksander Morgado)
a64223ca po: update Polish translation (Piotr Drąg)
0a85254d cli: fix typo when looking for a specific call fails (Aleksander Morgado)
4352c273 po: updated after new Time policy setup (Aleksander Morgado)
8a652179 policy: USSD policy requires user authentication in strict mode (Aleksander Morgado)
958c2434 polkit,conf: trivial comment update regarding Signal.Setup() (Aleksander Morgado)
1cd24a31 iface-modem-firmware: use the explicit Firmware policy (Aleksander Morgado)
110f6fd0 polkit,conf: add missing firmware management methods (Aleksander Morgado)
2e5af74d polkit,conf: add missing OMA methods (Aleksander Morgado)
7a7b3aa5 polkit,time: protect GetNetworkTime() with a new 'Time' policy rule (Aleksander Morgado)
be2500b0 polkit,conf: add missing supplementary voice service methods (Aleksander Morgado)
01acd9a0 polkit,conf: add missing InjectAssistanceData (Aleksander Morgado)
9e24d1c1 polkit,conf: add missing SetInitialEpsBearerSettings (Aleksander Morgado)
614c7fbc polkit,conf: add missing SetEpsUeModeOperation (Aleksander Morgado)
f2182a73 polkit,conf: add missing SetCurrentCapabilities (Aleksander Morgado)
3696a009 polkit,conf: add missing SetPowerState (Aleksander Morgado)
d3e75eec policy,conf: use SetCurrentBands instead of SetBands (Aleksander Morgado)
e21e7ddf policy: Use SetCurrentModes instead of SetAllowedModes (Mohammed Sadiq)
79a39656 broadband-modem: shutdown firmware interface on dispose() (Aleksander Morgado)
dcf50efd ublox: fix calling setup_unsolicited_events_finish() (Aleksander Morgado)
a1148f87 cinterion: fix calling setup_unsolicited_events_finish() (Aleksander Morgado)
c8af22ab cli,messaging: trivial fix in comment (Aleksander Morgado)
6c9027b2 docs,libmm-glib: add missing references to call waiting query/setup (Aleksander Morgado)
16145c29 sms-part-3gpp: SMS timestamp in ISO8601 format (amol.lad)

Change-Id: I81f3c5805b6bd8adab694e3440a526b9e4e435a4
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fe7068b..6727160 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,7 +4,7 @@
   - build
 
 before_script:
-    - apt update
+    - apt update || true
     - apt -y install autoconf automake libtool libglib2.0-dev libgudev-1.0-dev libgettextpo-dev autopoint xsltproc dbus
 
 build-no-qmi:
diff --git a/cli/mmcli-common.c b/cli/mmcli-common.c
index c149927..1c63d84 100644
--- a/cli/mmcli-common.c
+++ b/cli/mmcli-common.c
@@ -1485,7 +1485,7 @@
     manager = mmcli_get_manager_sync (connection);
     modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (manager));
     if (!modems) {
-        g_printerr ("error: couldn't find sms at '%s': 'no modems found'\n",
+        g_printerr ("error: couldn't find call at '%s': 'no modems found'\n",
                     call_path);
         exit (EXIT_FAILURE);
     }
diff --git a/cli/mmcli-modem-messaging.c b/cli/mmcli-modem-messaging.c
index 0f38e97..e56d9bc 100644
--- a/cli/mmcli-modem-messaging.c
+++ b/cli/mmcli-modem-messaging.c
@@ -437,7 +437,7 @@
 
     ensure_modem_messaging ();
 
-    /* Request to get location status? */
+    /* Request to get messaging status? */
     if (status_flag) {
         g_debug ("Printing messaging status...");
         print_messaging_status ();
diff --git a/cli/mmcli-modem-voice.c b/cli/mmcli-modem-voice.c
index 87c27be..878984b 100644
--- a/cli/mmcli-modem-voice.c
+++ b/cli/mmcli-modem-voice.c
@@ -47,6 +47,7 @@
 static Context *ctx;
 
 /* Options */
+static gboolean status_flag;
 static gboolean list_flag;
 static gchar *create_str;
 static gchar *delete_str;
@@ -59,6 +60,10 @@
 static gboolean call_waiting_query_flag;
 
 static GOptionEntry entries[] = {
+    { "voice-status", 0, 0, G_OPTION_ARG_NONE, &status_flag,
+      "Show status of voice support.",
+      NULL
+    },
     { "voice-list-calls", 0, 0, G_OPTION_ARG_NONE, &list_flag,
       "List calls available in a given modem",
       NULL
@@ -126,7 +131,8 @@
     if (checked)
         return !!n_actions;
 
-    n_actions = (list_flag +
+    n_actions = (status_flag +
+                 list_flag +
                  !!create_str +
                  !!delete_str +
                  hold_and_accept_flag +
@@ -142,6 +148,9 @@
         exit (EXIT_FAILURE);
     }
 
+    if (status_flag)
+        mmcli_force_sync_operation ();
+
     checked = TRUE;
     return !!n_actions;
 }
@@ -168,11 +177,6 @@
 static void
 ensure_modem_voice (void)
 {
-    if (mm_modem_get_state (mm_object_peek_modem (ctx->object)) < MM_MODEM_STATE_ENABLED) {
-        g_printerr ("error: modem not enabled yet\n");
-        exit (EXIT_FAILURE);
-    }
-
     if (!ctx->modem_voice) {
         g_printerr ("error: modem has no voice capabilities\n");
         exit (EXIT_FAILURE);
@@ -203,6 +207,13 @@
 }
 
 static void
+print_voice_status (void)
+{
+    mmcli_output_string (MMC_F_VOICE_EMERGENCY_ONLY, mm_modem_voice_get_emergency_only (ctx->modem_voice) ? "yes" : "no");
+    mmcli_output_dump ();
+}
+
+static void
 output_call_info (MMCall *call)
 {
     gchar *extra;
@@ -493,6 +504,9 @@
 
     ensure_modem_voice ();
 
+    if (status_flag)
+        g_assert_not_reached ();
+
     /* Request to list call? */
     if (list_flag) {
         g_debug ("Asynchronously listing calls in modem...");
@@ -639,6 +653,13 @@
 
     ensure_modem_voice ();
 
+    /* Request to get voice status? */
+    if (status_flag) {
+        g_debug ("Printing voice status...");
+        print_voice_status ();
+        return;
+    }
+
     /* Request to list the call? */
     if (list_flag) {
         GList *result;
diff --git a/cli/mmcli-output.c b/cli/mmcli-output.c
index 4fd9db3..338e658 100644
--- a/cli/mmcli-output.c
+++ b/cli/mmcli-output.c
@@ -66,6 +66,7 @@
     [MMC_S_MODEM_LOCATION_CDMABS]   = { "CDMA BS"            },
     [MMC_S_MODEM_FIRMWARE]          = { "Firmware"           },
     [MMC_S_MODEM_FIRMWARE_FASTBOOT] = { "Fastboot settings"  },
+    [MMC_S_MODEM_VOICE]             = { "Voice"              },
     [MMC_S_BEARER_GENERAL]          = { "General"            },
     [MMC_S_BEARER_STATUS]           = { "Status"             },
     [MMC_S_BEARER_PROPERTIES]       = { "Properties"         },
@@ -196,6 +197,7 @@
     [MMC_F_FIRMWARE_DEVICE_IDS]               = { "modem.firmware.device-ids",                       "device ids",               MMC_S_MODEM_FIRMWARE,          },
     [MMC_F_FIRMWARE_VERSION]                  = { "modem.firmware.version",                          "version",                  MMC_S_MODEM_FIRMWARE,          },
     [MMC_F_FIRMWARE_FASTBOOT_AT]              = { "modem.firmware.fastboot.at",                      "at command",               MMC_S_MODEM_FIRMWARE_FASTBOOT, },
+    [MMC_F_VOICE_EMERGENCY_ONLY]              = { "modem.voice.emergency-only",                      "emergency only",           MMC_S_MODEM_VOICE,             },
     [MMC_F_BEARER_GENERAL_DBUS_PATH]          = { "bearer.dbus-path",                                "dbus path",                MMC_S_BEARER_GENERAL,          },
     [MMC_F_BEARER_GENERAL_TYPE]               = { "bearer.type",                                     "type",                     MMC_S_BEARER_GENERAL,          },
     [MMC_F_BEARER_STATUS_CONNECTED]           = { "bearer.status.connected",                         "connected",                MMC_S_BEARER_STATUS,           },
@@ -256,6 +258,7 @@
     [MMC_F_SIM_PROPERTIES_ICCID]              = { "sim.properties.iccid",                            "iccid",                    MMC_S_SIM_PROPERTIES,          },
     [MMC_F_SIM_PROPERTIES_OPERATOR_ID]        = { "sim.properties.operator-code",                    "operator id",              MMC_S_SIM_PROPERTIES,          },
     [MMC_F_SIM_PROPERTIES_OPERATOR_NAME]      = { "sim.properties.operator-name",                    "operator name",            MMC_S_SIM_PROPERTIES,          },
+    [MMC_F_SIM_PROPERTIES_EMERGENCY_NUMBERS]  = { "sim.properties.emergency-numbers",                "emergency numbers",        MMC_S_SIM_PROPERTIES,          },
     [MMC_F_MODEM_LIST_DBUS_PATH]              = { "modem-list",                                      "modems",                   MMC_S_UNKNOWN,                 },
     [MMC_F_SMS_LIST_DBUS_PATH]                = { "modem.messaging.sms",                             "sms messages",             MMC_S_UNKNOWN,                 },
     [MMC_F_CALL_LIST_DBUS_PATH]               = { "modem.voice.call",                                "calls",                    MMC_S_UNKNOWN,                 },
@@ -622,21 +625,21 @@
                            gboolean              selected)
 {
     g_ptr_array_add (array, g_strdup (mm_firmware_properties_get_unique_id (props)));
-    g_ptr_array_add (array, g_strdup_printf ("\tcurrent: %s", selected ? "yes" : "no"));
+    g_ptr_array_add (array, g_strdup_printf ("    current: %s", selected ? "yes" : "no"));
 
     if (mm_firmware_properties_get_image_type (props) == MM_FIRMWARE_IMAGE_TYPE_GOBI) {
         const gchar *aux;
 
         if ((aux = mm_firmware_properties_get_gobi_pri_version (props)) != NULL)
-            g_ptr_array_add (array, g_strdup_printf ("\tgobi pri version: %s", aux));
+            g_ptr_array_add (array, g_strdup_printf ("    gobi pri version: %s", aux));
         if ((aux = mm_firmware_properties_get_gobi_pri_info (props)) != NULL)
-            g_ptr_array_add (array, g_strdup_printf ("\tgobi pri info: %s", aux));
+            g_ptr_array_add (array, g_strdup_printf ("    gobi pri info: %s", aux));
         if ((aux = mm_firmware_properties_get_gobi_boot_version (props)) != NULL)
-            g_ptr_array_add (array, g_strdup_printf ("\tgobi boot version: %s", aux));
+            g_ptr_array_add (array, g_strdup_printf ("    gobi boot version: %s", aux));
         if ((aux = mm_firmware_properties_get_gobi_pri_unique_id (props)) != NULL)
-            g_ptr_array_add (array, g_strdup_printf ("\tgobi pri unique id: %s", aux));
+            g_ptr_array_add (array, g_strdup_printf ("    gobi pri unique id: %s", aux));
         if ((aux = mm_firmware_properties_get_gobi_modem_unique_id (props)) != NULL)
-            g_ptr_array_add (array, g_strdup_printf ("\tgobi modem unique id: %s", aux));
+            g_ptr_array_add (array, g_strdup_printf ("    gobi modem unique id: %s", aux));
     }
 }
 
diff --git a/cli/mmcli-output.h b/cli/mmcli-output.h
index 135d429..bd7b317 100644
--- a/cli/mmcli-output.h
+++ b/cli/mmcli-output.h
@@ -63,6 +63,7 @@
     MMC_S_MODEM_LOCATION_CDMABS,
     MMC_S_MODEM_FIRMWARE,
     MMC_S_MODEM_FIRMWARE_FASTBOOT,
+    MMC_S_MODEM_VOICE,
     MMC_S_BEARER_GENERAL,
     MMC_S_BEARER_STATUS,
     MMC_S_BEARER_PROPERTIES,
@@ -209,6 +210,8 @@
     MMC_F_FIRMWARE_DEVICE_IDS,
     MMC_F_FIRMWARE_VERSION,
     MMC_F_FIRMWARE_FASTBOOT_AT,
+    /* Voice section */
+    MMC_F_VOICE_EMERGENCY_ONLY,
     /* Bearer general section */
     MMC_F_BEARER_GENERAL_DBUS_PATH,
     MMC_F_BEARER_GENERAL_TYPE,
@@ -272,6 +275,7 @@
     MMC_F_SIM_PROPERTIES_ICCID,
     MMC_F_SIM_PROPERTIES_OPERATOR_ID,
     MMC_F_SIM_PROPERTIES_OPERATOR_NAME,
+    MMC_F_SIM_PROPERTIES_EMERGENCY_NUMBERS,
     /* Lists */
     MMC_F_MODEM_LIST_DBUS_PATH,
     MMC_F_SMS_LIST_DBUS_PATH,
diff --git a/cli/mmcli-sim.c b/cli/mmcli-sim.c
index d31f07c..e7f885e 100644
--- a/cli/mmcli-sim.c
+++ b/cli/mmcli-sim.c
@@ -158,11 +158,12 @@
 static void
 print_sim_info (MMSim *sim)
 {
-    mmcli_output_string (MMC_F_SIM_GENERAL_DBUS_PATH,        mm_sim_get_path (sim));
-    mmcli_output_string (MMC_F_SIM_PROPERTIES_IMSI,          mm_sim_get_imsi (sim));
-    mmcli_output_string (MMC_F_SIM_PROPERTIES_ICCID,         mm_sim_get_identifier (sim));
-    mmcli_output_string (MMC_F_SIM_PROPERTIES_OPERATOR_ID,   mm_sim_get_operator_identifier (sim));
-    mmcli_output_string (MMC_F_SIM_PROPERTIES_OPERATOR_NAME, mm_sim_get_operator_name (sim));
+    mmcli_output_string       (MMC_F_SIM_GENERAL_DBUS_PATH,            mm_sim_get_path (sim));
+    mmcli_output_string       (MMC_F_SIM_PROPERTIES_IMSI,              mm_sim_get_imsi (sim));
+    mmcli_output_string       (MMC_F_SIM_PROPERTIES_ICCID,             mm_sim_get_identifier (sim));
+    mmcli_output_string       (MMC_F_SIM_PROPERTIES_OPERATOR_ID,       mm_sim_get_operator_identifier (sim));
+    mmcli_output_string       (MMC_F_SIM_PROPERTIES_OPERATOR_NAME,     mm_sim_get_operator_name (sim));
+    mmcli_output_string_array (MMC_F_SIM_PROPERTIES_EMERGENCY_NUMBERS, (const gchar **) mm_sim_get_emergency_numbers (sim), FALSE);
     mmcli_output_dump ();
 }
 
diff --git a/configure.ac b/configure.ac
index d3c9beb..667fb9d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -288,6 +288,7 @@
     with_polkit=strict
 fi
 
+MM_POLKIT_SERVICE=""
 if test "x$with_polkit" != "xno"; then
 	if test "x$have_polkit" = "xno"; then
 		AC_MSG_ERROR(PolicyKit development headers are required)
@@ -296,9 +297,11 @@
     case "x$with_polkit" in
         "xpermissive")
             MM_DEFAULT_USER_POLICY="yes"
+            MM_POLKIT_SERVICE="polkit.service"
             ;;
         "xstrict")
             MM_DEFAULT_USER_POLICY="auth_self_keep"
+            MM_POLKIT_SERVICE="polkit.service"
             ;;
         *)
             AC_MSG_ERROR([Wrong value for --with-polkit: $with_polkit])
@@ -311,6 +314,7 @@
     AC_SUBST(MM_DEFAULT_USER_POLICY)
 fi
 
+AC_SUBST(MM_POLKIT_SERVICE)
 AM_CONDITIONAL(WITH_POLKIT, [test "x$with_polkit" != "xno"])
 
 dnl-----------------------------------------------------------------------------
diff --git a/data/Makefile.am b/data/Makefile.am
index ee05bdf..447cb3d 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -5,7 +5,8 @@
        -e 's|@sbindir[@]|$(sbindir)|g' \
        -e 's|@sysconfdir[@]|$(sysconfdir)|g' \
        -e 's|@localstatedir[@]|$(localstatedir)|g' \
-       -e 's|@libexecdir[@]|$(libexecdir)|g'
+       -e 's|@libexecdir[@]|$(libexecdir)|g' \
+       -e 's|@MM_POLKIT_SERVICE[@]|$(MM_POLKIT_SERVICE)|g'
 
 
 # DBus Service file
diff --git a/data/ModemManager.service.in b/data/ModemManager.service.in
index 420d22b..23bf809 100644
--- a/data/ModemManager.service.in
+++ b/data/ModemManager.service.in
@@ -1,5 +1,7 @@
 [Unit]
 Description=Modem Manager
+After=@MM_POLKIT_SERVICE@
+Requires=@MM_POLKIT_SERVICE@
 
 [Service]
 Type=dbus
diff --git a/data/org.freedesktop.ModemManager1.conf.polkit b/data/org.freedesktop.ModemManager1.conf.polkit
index 14240ea..8087da6 100644
--- a/data/org.freedesktop.ModemManager1.conf.polkit
+++ b/data/org.freedesktop.ModemManager1.conf.polkit
@@ -53,6 +53,10 @@
 
     <allow send_destination="org.freedesktop.ModemManager1"
            send_interface="org.freedesktop.ModemManager1.Modem"
+           send_member="SetPowerState"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem"
            send_member="Reset"/>
 
     <allow send_destination="org.freedesktop.ModemManager1"
@@ -61,16 +65,30 @@
 
     <allow send_destination="org.freedesktop.ModemManager1"
            send_interface="org.freedesktop.ModemManager1.Modem"
-           send_member="SetAllowedModes"/>
+           send_member="SetCurrentCapabilities"/>
 
     <allow send_destination="org.freedesktop.ModemManager1"
            send_interface="org.freedesktop.ModemManager1.Modem"
-           send_member="SetBands"/>
+           send_member="SetCurrentModes"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem"
+           send_member="SetCurrentBands"/>
 
     <allow send_destination="org.freedesktop.ModemManager1"
            send_interface="org.freedesktop.ModemManager1.Modem"
            send_member="Command"/>
 
+    <!-- org.freedesktop.ModemManager1.Modem.Firmware.xml -->
+
+    <!-- Protected by the Device.Control policy rule -->
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Firmware"
+           send_member="List"/>
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Firmware"
+           send_member="Select"/>
+
     <!-- org.freedesktop.ModemManager1.Modem.Simple.xml -->
 
     <!-- Allowed for everyone -->
@@ -98,6 +116,14 @@
            send_interface="org.freedesktop.ModemManager1.Modem.Modem3gpp"
            send_member="Scan"/>
 
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Modem3gpp"
+           send_member="SetEpsUeModeOperation"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Modem3gpp"
+           send_member="SetInitialEpsBearerSettings"/>
+
     <!-- org.freedesktop.ModemManager1.Modem.ModemCdma.xml -->
 
     <!-- Protected by the Device.Control policy rule -->
@@ -109,6 +135,25 @@
            send_interface="org.freedesktop.ModemManager1.Modem.ModemCdma"
            send_member="ActivateManual"/>
 
+    <!-- org.freedesktop.ModemManager1.Modem.Oma.xml -->
+
+    <!-- Protected by the Device.Control policy rule -->
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Oma"
+           send_member="Setup"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Oma"
+           send_member="StartClientInitiatedSession"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Oma"
+           send_member="AcceptNetworkInitiatedSession"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Oma"
+           send_member="CancelSession"/>
+
     <!-- org.freedesktop.ModemManager1.Sim.xml -->
 
     <!-- Protected by the Device.Control policy rule -->
@@ -167,6 +212,10 @@
 
     <allow send_destination="org.freedesktop.ModemManager1"
            send_interface="org.freedesktop.ModemManager1.Modem.Location"
+           send_member="InjectAssistanceData"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Location"
            send_member="SetGpsRefreshRate"/>
 
     <!-- Protected by the Location policy rule -->
@@ -217,6 +266,30 @@
            send_interface="org.freedesktop.ModemManager1.Modem.Voice"
            send_member="DeleteCall"/>
 
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Voice"
+           send_member="HoldAndAccept"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Voice"
+           send_member="HangupAndAccept"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Voice"
+           send_member="HangupAll"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Voice"
+           send_member="Transfer"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Voice"
+           send_member="CallWaitingSetup"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Voice"
+           send_member="CallWaitingQuery"/>
+
     <!-- org.freedesktop.ModemManager1.Call.xml -->
 
     <!-- Protected by the Voice policy rule -->
@@ -230,6 +303,18 @@
 
     <allow send_destination="org.freedesktop.ModemManager1"
            send_interface="org.freedesktop.ModemManager1.Call"
+           send_member="Deflect"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Call"
+           send_member="JoinMultiparty"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Call"
+           send_member="LeaveMultiparty"/>
+
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Call"
            send_member="Hangup"/>
 
     <allow send_destination="org.freedesktop.ModemManager1"
@@ -238,11 +323,18 @@
 
     <!-- org.freedesktop.ModemManager1.Modem.Signal.xml -->
 
-    <!-- Allowed for everyone -->
+    <!-- Protected by the Device.Control policy rule -->
     <allow send_destination="org.freedesktop.ModemManager1"
            send_interface="org.freedesktop.ModemManager1.Modem.Signal"
            send_member="Setup"/>
 
+    <!-- org.freedesktop.ModemManager1.Modem.Time.xml -->
+
+    <!-- Protected by the Time policy rule -->
+    <allow send_destination="org.freedesktop.ModemManager1"
+           send_interface="org.freedesktop.ModemManager1.Modem.Time"
+           send_member="GetNetworkTime"/>
+
   </policy>
 
   <policy user="root">
diff --git a/data/org.freedesktop.ModemManager1.policy.in.in b/data/org.freedesktop.ModemManager1.policy.in.in
index 7edb20c..301b6e9 100644
--- a/data/org.freedesktop.ModemManager1.policy.in.in
+++ b/data/org.freedesktop.ModemManager1.policy.in.in
@@ -54,6 +54,15 @@
     </defaults>
   </action>
 
+  <action id="org.freedesktop.ModemManager1.Time">
+    <description>Query network time and timezone information</description>
+    <message>System policy prevents querying network time information.</message>
+    <defaults>
+      <allow_inactive>no</allow_inactive>
+      <allow_active>@MM_DEFAULT_USER_POLICY@</allow_active>
+    </defaults>
+  </action>
+
   <action id="org.freedesktop.ModemManager1.Location">
     <description>Enable and view geographic location and positioning information</description>
     <message>System policy prevents enabling or viewing geographic location information.</message>
@@ -68,7 +77,7 @@
     <message>System policy prevents querying or utilizing network information and services.</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
-      <allow_active>yes</allow_active>
+      <allow_active>@MM_DEFAULT_USER_POLICY@</allow_active>
     </defaults>
   </action>
 
diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt
index 690db8b..bc3eeb9 100644
--- a/docs/reference/libmm-glib/libmm-glib-sections.txt
+++ b/docs/reference/libmm-glib/libmm-glib-sections.txt
@@ -1001,6 +1001,7 @@
 <SUBSECTION Getters>
 mm_modem_voice_get_path
 mm_modem_voice_dup_path
+mm_modem_voice_get_emergency_only
 
 <SUBSECTION Methods>
 mm_modem_voice_create_call
@@ -1024,6 +1025,12 @@
 mm_modem_voice_transfer
 mm_modem_voice_transfer_finish
 mm_modem_voice_transfer_sync
+mm_modem_voice_call_waiting_query
+mm_modem_voice_call_waiting_query_finish
+mm_modem_voice_call_waiting_query_sync
+mm_modem_voice_call_waiting_setup
+mm_modem_voice_call_waiting_setup_finish
+mm_modem_voice_call_waiting_setup_sync
 <SUBSECTION Standard>
 MMModemVoiceClass
 MMModemVoicePrivate
@@ -2847,6 +2854,7 @@
 <SUBSECTION Getters>
 mm_gdbus_modem_voice_get_calls
 mm_gdbus_modem_voice_dup_calls
+mm_gdbus_modem_voice_get_emergency_only
 <SUBSECTION Methods>
 mm_gdbus_modem_voice_call_create_call
 mm_gdbus_modem_voice_call_create_call_finish
@@ -2869,8 +2877,15 @@
 mm_gdbus_modem_voice_call_transfer
 mm_gdbus_modem_voice_call_transfer_finish
 mm_gdbus_modem_voice_call_transfer_sync
+mm_gdbus_modem_voice_call_call_waiting_query
+mm_gdbus_modem_voice_call_call_waiting_query_finish
+mm_gdbus_modem_voice_call_call_waiting_query_sync
+mm_gdbus_modem_voice_call_call_waiting_setup
+mm_gdbus_modem_voice_call_call_waiting_setup_finish
+mm_gdbus_modem_voice_call_call_waiting_setup_sync
 <SUBSECTION Private>
 mm_gdbus_modem_voice_set_calls
+mm_gdbus_modem_voice_set_emergency_only
 mm_gdbus_modem_voice_emit_call_added
 mm_gdbus_modem_voice_emit_call_deleted
 mm_gdbus_modem_voice_complete_create_call
@@ -2880,6 +2895,8 @@
 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_complete_call_waiting_query
+mm_gdbus_modem_voice_complete_call_waiting_setup
 mm_gdbus_modem_voice_interface_info
 mm_gdbus_modem_voice_override_properties
 <SUBSECTION Standard>
diff --git a/include/ModemManager-enums.h b/include/ModemManager-enums.h
index f84eafc..c26e169 100644
--- a/include/ModemManager-enums.h
+++ b/include/ModemManager-enums.h
@@ -514,6 +514,7 @@
  * @MM_MODEM_PORT_TYPE_GPS: GPS port.
  * @MM_MODEM_PORT_TYPE_QMI: QMI port.
  * @MM_MODEM_PORT_TYPE_MBIM: MBIM port.
+ * @MM_MODEM_PORT_TYPE_AUDIO: Audio port.
  *
  * Type of modem port.
  */
@@ -524,7 +525,8 @@
     MM_MODEM_PORT_TYPE_QCDM    = 4,
     MM_MODEM_PORT_TYPE_GPS     = 5,
     MM_MODEM_PORT_TYPE_QMI     = 6,
-    MM_MODEM_PORT_TYPE_MBIM    = 7
+    MM_MODEM_PORT_TYPE_MBIM    = 7,
+    MM_MODEM_PORT_TYPE_AUDIO   = 8,
 } MMModemPortType;
 
 /**
@@ -942,8 +944,7 @@
     MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED = 1 << 4,
     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 */
+#if defined (MM_COMPILATION) /*< private >*/
     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
diff --git a/include/ModemManager-tags.h b/include/ModemManager-tags.h
index ed65af5..8c7a76d 100644
--- a/include/ModemManager-tags.h
+++ b/include/ModemManager-tags.h
@@ -178,6 +178,16 @@
 #define ID_MM_PORT_TYPE_GPS "ID_MM_PORT_TYPE_GPS"
 
 /**
+ * ID_MM_PORT_TYPE_AUDIO:
+ *
+ * This is a port-specific tag applied to TTYs that we know in advance
+ * are audio ports.
+ *
+ * This tag also prevents AT and QCDM probing in the port.
+ */
+#define ID_MM_PORT_TYPE_AUDIO "ID_MM_PORT_TYPE_AUDIO"
+
+/**
  * ID_MM_TTY_BAUDRATE:
  *
  * This is a port-specific tag applied to TTYs that require a specific
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.Voice.xml b/introspection/org.freedesktop.ModemManager1.Modem.Voice.xml
index 59f11e9..698b8e3 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.Voice.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.Voice.xml
@@ -176,5 +176,15 @@
     -->
     <property name="Calls" type="ao" access="read" />
 
+    <!--
+        EmergencyOnly:
+
+        A flag indicating whether emergency calls are the only allowed ones.
+
+        If this flag is set, users should only attempt voice calls to
+	emergency numbers, as standard voice calls will likely fail.
+    -->
+    <property name="EmergencyOnly" type="b" access="read" />
+
   </interface>
 </node>
diff --git a/introspection/org.freedesktop.ModemManager1.Sim.xml b/introspection/org.freedesktop.ModemManager1.Sim.xml
index ca28334..1db9b66 100644
--- a/introspection/org.freedesktop.ModemManager1.Sim.xml
+++ b/introspection/org.freedesktop.ModemManager1.Sim.xml
@@ -97,5 +97,15 @@
     -->
     <property name="OperatorName" type="s" access="read" />
 
+    <!--
+        EmergencyNumbers:
+
+        List of emergency numbers programmed in the SIM card.
+
+        These numbers should be treated as numbers for emergency calls in
+	addition to 112 and 911.
+    -->
+    <property name="EmergencyNumbers" type="as" access="read" />
+
   </interface>
 </node>
diff --git a/libmm-glib/mm-call.c b/libmm-glib/mm-call.c
index 91439f5..7d6b2b3 100644
--- a/libmm-glib/mm-call.c
+++ b/libmm-glib/mm-call.c
@@ -644,7 +644,7 @@
 
 /**
  * mm_call_join_multiparty_sync:
- * @self: A #MMCall.g
+ * @self: A #MMCall.
  * @cancellable: (allow-none): A #GCancellable or %NULL.
  * @error: Return location for error or %NULL.
  *
diff --git a/libmm-glib/mm-modem-oma.c b/libmm-glib/mm-modem-oma.c
index 77c682d..3002c8a 100644
--- a/libmm-glib/mm-modem-oma.c
+++ b/libmm-glib/mm-modem-oma.c
@@ -113,7 +113,7 @@
 /**
  * mm_modem_oma_setup:
  * @self: A #MMModemOma.
- * @features: Mask of #MMOmaFeatures to enable.
+ * @features: Mask of #MMOmaFeature values to enable.
  * @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.
@@ -140,7 +140,7 @@
 /**
  * mm_modem_oma_setup_sync:
  * @self: A #MMModemOma.
- * @features: Mask of #MMOmaFeatures to enable.
+ * @features: Mask of #MMOmaFeature values to enable.
  * @cancellable: (allow-none): A #GCancellable or %NULL.
  * @error: Return location for error or %NULL.
  *
diff --git a/libmm-glib/mm-modem-voice.c b/libmm-glib/mm-modem-voice.c
index e23a730..ce22e0b 100644
--- a/libmm-glib/mm-modem-voice.c
+++ b/libmm-glib/mm-modem-voice.c
@@ -81,6 +81,20 @@
     RETURN_NON_EMPTY_STRING (value);
 }
 
+/**
+ * mm_modem_voice_get_emergency_only:
+ * @self: A #MMModemVoice.
+ *
+ * Checks whether emergency calls only are allowed.
+ *
+ * Returns: %TRUE if only emergency calls are allowed, %FALSE otherwise..
+ */
+gboolean
+mm_modem_voice_get_emergency_only (MMModemVoice *self)
+{
+    return mm_gdbus_modem_voice_get_emergency_only (MM_GDBUS_MODEM_VOICE (self));
+}
+
 /*****************************************************************************/
 
 typedef struct {
diff --git a/libmm-glib/mm-modem-voice.h b/libmm-glib/mm-modem-voice.h
index f43a60a..d3a8fc9 100644
--- a/libmm-glib/mm-modem-voice.h
+++ b/libmm-glib/mm-modem-voice.h
@@ -70,8 +70,9 @@
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMModemVoice, g_object_unref)
 #endif
 
-const gchar *mm_modem_voice_get_path (MMModemVoice *self);
-gchar       *mm_modem_voice_dup_path (MMModemVoice *self);
+const gchar *mm_modem_voice_get_path           (MMModemVoice *self);
+gchar       *mm_modem_voice_dup_path           (MMModemVoice *self);
+gboolean     mm_modem_voice_get_emergency_only (MMModemVoice *self);
 
 void    mm_modem_voice_create_call          (MMModemVoice *self,
                                              MMCallProperties *properties,
diff --git a/libmm-glib/mm-sim.c b/libmm-glib/mm-sim.c
index 052f7e5..b79a362 100644
--- a/libmm-glib/mm-sim.c
+++ b/libmm-glib/mm-sim.c
@@ -246,6 +246,45 @@
 /*****************************************************************************/
 
 /**
+ * mm_sim_get_emergency_numbers:
+ * @self: A #MMSim.
+ *
+ * Gets the list of emergency call numbers programmed in the SIM card.
+ *
+ * <warning>The returned value is only valid until the property changes so
+ * it is only safe to use this function on the thread where
+ * @self was constructed. Use mm_sim_dup_emergency_numbers() if on another
+ * thread.</warning>
+ *
+ * Returns: (transfer none): The emergency numbers, or %NULL if none available. Do not free the returned value, it belongs to @self.
+ */
+const gchar * const *
+mm_sim_get_emergency_numbers (MMSim *self)
+{
+    g_return_val_if_fail (MM_IS_SIM (self), NULL);
+
+    return mm_gdbus_sim_get_emergency_numbers (MM_GDBUS_SIM (self));
+}
+
+/**
+ * mm_sim_dup_emergency_numbers:
+ * @self: A #MMSim.
+ *
+ * Gets a copy of the list of emergency call numbers programmed in the SIM card.
+ *
+ * Returns: (transfer full): The emergency numbers, or %NULL if none available. The returned value should be freed with g_strfreev().
+ */
+gchar **
+mm_sim_dup_emergency_numbers (MMSim *self)
+{
+    g_return_val_if_fail (MM_IS_SIM (self), NULL);
+
+    return mm_gdbus_sim_dup_emergency_numbers (MM_GDBUS_SIM (self));
+}
+
+/*****************************************************************************/
+
+/**
  * mm_sim_send_pin_finish:
  * @self: A #MMSim.
  * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_sim_send_pin().
diff --git a/libmm-glib/mm-sim.h b/libmm-glib/mm-sim.h
index 0f162ef..90b49b5 100644
--- a/libmm-glib/mm-sim.h
+++ b/libmm-glib/mm-sim.h
@@ -82,6 +82,9 @@
 const gchar *mm_sim_get_operator_name       (MMSim *self);
 gchar       *mm_sim_dup_operator_name       (MMSim *self);
 
+const gchar * const  *mm_sim_get_emergency_numbers (MMSim *self);
+gchar               **mm_sim_dup_emergency_numbers (MMSim *self);
+
 void     mm_sim_send_pin        (MMSim *self,
                                  const gchar *pin,
                                  GCancellable *cancellable,
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index dd66d2f..ffc0c0e 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -590,15 +590,43 @@
 # plugin: simtech
 ################################################################################
 
+noinst_LTLIBRARIES += libhelpers-simtech.la
+libhelpers_simtech_la_SOURCES = \
+	simtech/mm-modem-helpers-simtech.c \
+	simtech/mm-modem-helpers-simtech.h \
+	$(NULL)
+
+noinst_PROGRAMS += test-modem-helpers-simtech
+test_modem_helpers_simtech_SOURCES = \
+	simtech/tests/test-modem-helpers-simtech.c \
+	$(NULL)
+test_modem_helpers_simtech_CPPFLAGS = \
+	-I$(top_srcdir)/plugins/simtech \
+	$(NULL)
+test_modem_helpers_simtech_LDADD = \
+	$(builddir)/libhelpers-simtech.la \
+	$(top_builddir)/src/libhelpers.la \
+	$(top_builddir)/libmm-glib/libmm-glib.la \
+	$(NULL)
+
 pkglib_LTLIBRARIES += libmm-plugin-simtech.la
 libmm_plugin_simtech_la_SOURCES = \
 	simtech/mm-plugin-simtech.c \
 	simtech/mm-plugin-simtech.h \
+	simtech/mm-shared-simtech.c \
+	simtech/mm-shared-simtech.h \
 	simtech/mm-broadband-modem-simtech.h \
 	simtech/mm-broadband-modem-simtech.c \
 	$(NULL)
+if WITH_QMI
+libmm_plugin_simtech_la_SOURCES += \
+	simtech/mm-broadband-modem-qmi-simtech.c \
+	simtech/mm-broadband-modem-qmi-simtech.h \
+	$(NULL)
+endif
 libmm_plugin_simtech_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
 libmm_plugin_simtech_la_LDFLAGS  = $(PLUGIN_COMMON_LINKER_FLAGS)
+libmm_plugin_simtech_la_LIBADD   = $(builddir)/libhelpers-simtech.la
 
 dist_udevrules_DATA += simtech/77-mm-simtech-port-types.rules
 
diff --git a/plugins/cinterion/mm-shared-cinterion.c b/plugins/cinterion/mm-shared-cinterion.c
index 5cb35a8..23f3fe1 100644
--- a/plugins/cinterion/mm-shared-cinterion.c
+++ b/plugins/cinterion/mm-shared-cinterion.c
@@ -1228,8 +1228,8 @@
 
     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);
+    if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
+        mm_warn ("Couldn't setup parent voice unsolicited events: %s", error->message);
         g_error_free (error);
     }
 
@@ -1315,7 +1315,10 @@
     mm_base_modem_at_command (MM_BASE_MODEM (self),
                               "^SLCC=?",
                               3,
-                              TRUE,
+                              /* Do NOT cache as the reply may be different if PIN locked
+                               * or unlocked. E.g. we may not support ^SLCC for emergency
+                               * voice calls. */
+                              FALSE,
                               (GAsyncReadyCallback) slcc_format_check_ready,
                               task);
 }
diff --git a/plugins/cinterion/tests/test-modem-helpers-cinterion.c b/plugins/cinterion/tests/test-modem-helpers-cinterion.c
index eef0a70..875cf74 100644
--- a/plugins/cinterion/tests/test-modem-helpers-cinterion.c
+++ b/plugins/cinterion/tests/test-modem-helpers-cinterion.c
@@ -699,7 +699,7 @@
     g_assert_no_error (error);
     g_assert (result);
 
-    g_print ("found %u calls\n", g_list_length (call_info_list));
+    g_debug ("found %u calls", g_list_length (call_info_list));
 
     if (expected_call_info_list) {
         g_assert (call_info_list);
@@ -712,7 +712,7 @@
         gboolean                   found = FALSE;
         guint                      i;
 
-        g_print ("call at index %u: direction %s, state %s, number %s\n",
+        g_debug ("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),
@@ -865,17 +865,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/huawei/mm-broadband-bearer-huawei.c b/plugins/huawei/mm-broadband-bearer-huawei.c
index c2830e1..01a46c6 100644
--- a/plugins/huawei/mm-broadband-bearer-huawei.c
+++ b/plugins/huawei/mm-broadband-bearer-huawei.c
@@ -333,9 +333,7 @@
                                            NULL, /* Do not care the AT response */
                                            NULL);
 
-        g_task_return_new_error (task,
-                                 MM_CORE_ERROR,
-                                 MM_CORE_ERROR_CANCELLED,
+        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED,
                                  "Huawei connection operation has been cancelled");
         g_object_unref (task);
         return;
diff --git a/plugins/huawei/tests/test-modem-helpers-huawei.c b/plugins/huawei/tests/test-modem-helpers-huawei.c
index 70c40db..97b11a8 100644
--- a/plugins/huawei/tests/test-modem-helpers-huawei.c
+++ b/plugins/huawei/tests/test-modem-helpers-huawei.c
@@ -391,7 +391,6 @@
         g_assert (combinations != NULL);
         g_assert_cmpuint (combinations->len, ==, n_expected_combinations);
 
-#if defined ENABLE_TEST_MESSAGE_TRACES
         for (j = 0; j < combinations->len; j++) {
             MMHuaweiPrefmodeCombination *single;
             gchar *allowed_str;
@@ -409,7 +408,6 @@
             g_free (allowed_str);
             g_free (preferred_str);
         }
-#endif
 
         for (j = 0; j < combinations->len; j++) {
             MMHuaweiPrefmodeCombination *single;
@@ -629,7 +627,6 @@
         g_assert (combinations != NULL);
         g_assert_cmpuint (combinations->len, ==, n_expected_combinations);
 
-#if defined ENABLE_TEST_MESSAGE_TRACES
         for (j = 0; j < combinations->len; j++) {
             MMHuaweiSyscfgCombination *single;
             gchar *allowed_str;
@@ -648,7 +645,6 @@
             g_free (allowed_str);
             g_free (preferred_str);
         }
-#endif
 
         for (j = 0; j < combinations->len; j++) {
             MMHuaweiSyscfgCombination *single;
@@ -898,7 +894,6 @@
         g_assert (combinations != NULL);
         g_assert_cmpuint (combinations->len, ==, n_expected_combinations);
 
-#if defined ENABLE_TEST_MESSAGE_TRACES
         for (j = 0; j < combinations->len; j++) {
             MMHuaweiSyscfgexCombination *single;
             gchar *allowed_str;
@@ -916,7 +911,6 @@
             g_free (allowed_str);
             g_free (preferred_str);
         }
-#endif
 
         for (j = 0; j < combinations->len; j++) {
             MMHuaweiSyscfgexCombination *single;
@@ -1280,17 +1274,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/icera/tests/test-modem-helpers-icera.c b/plugins/icera/tests/test-modem-helpers-icera.c
index 3761b02..592b8b2 100644
--- a/plugins/icera/tests/test-modem-helpers-icera.c
+++ b/plugins/icera/tests/test-modem-helpers-icera.c
@@ -182,17 +182,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/linktop/tests/test-modem-helpers-linktop.c b/plugins/linktop/tests/test-modem-helpers-linktop.c
index 27119bb..827d044 100644
--- a/plugins/linktop/tests/test-modem-helpers-linktop.c
+++ b/plugins/linktop/tests/test-modem-helpers-linktop.c
@@ -25,8 +25,6 @@
 #include "mm-modem-helpers.h"
 #include "mm-modem-helpers-linktop.h"
 
-/* #define ENABLE_TEST_MESSAGE_TRACES */
-
 /*****************************************************************************/
 
 typedef struct {
@@ -68,17 +66,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/mbm/tests/test-modem-helpers-mbm.c b/plugins/mbm/tests/test-modem-helpers-mbm.c
index 8c573af..cca121b 100644
--- a/plugins/mbm/tests/test-modem-helpers-mbm.c
+++ b/plugins/mbm/tests/test-modem-helpers-mbm.c
@@ -28,8 +28,6 @@
 #include "mm-modem-helpers.h"
 #include "mm-modem-helpers-mbm.h"
 
-#define ENABLE_TEST_MESSAGE_TRACES
-
 /*****************************************************************************/
 /* Test *E2IPCFG responses */
 
@@ -262,17 +260,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/sierra/tests/test-modem-helpers-sierra.c b/plugins/sierra/tests/test-modem-helpers-sierra.c
index a53f27e..629572e 100644
--- a/plugins/sierra/tests/test-modem-helpers-sierra.c
+++ b/plugins/sierra/tests/test-modem-helpers-sierra.c
@@ -122,17 +122,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/simtech/77-mm-simtech-port-types.rules b/plugins/simtech/77-mm-simtech-port-types.rules
index 715eb00..c138ed9 100644
--- a/plugins/simtech/77-mm-simtech-port-types.rules
+++ b/plugins/simtech/77-mm-simtech-port-types.rules
@@ -21,16 +21,20 @@
 ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
 ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
 ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
-ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{ID_MM_SIMTECH_TAGGED}="1"
 
 # Prolink PH-300
 ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
 ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
-ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{ID_MM_SIMTECH_TAGGED}="1"
 
 # SCT UM300
 ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
 ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
-ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{ID_MM_SIMTECH_TAGGED}="1"
+
+# SIM7000, SIM7100, SIM7600...
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_TYPE_AUDIO}="1"
 
 LABEL="mm_simtech_port_types_end"
diff --git a/plugins/simtech/mm-broadband-modem-qmi-simtech.c b/plugins/simtech/mm-broadband-modem-qmi-simtech.c
new file mode 100644
index 0000000..392f562
--- /dev/null
+++ b/plugins/simtech/mm-broadband-modem-qmi-simtech.c
@@ -0,0 +1,124 @@
+/* -*- 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#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-simtech.h"
+#include "mm-shared-simtech.h"
+
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init    (MMIfaceModemVoice    *iface);
+static void shared_simtech_init       (MMSharedSimtech      *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice    *iface_modem_voice_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiSimtech, mm_broadband_modem_qmi_simtech, MM_TYPE_BROADBAND_MODEM_QMI, 0,
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_SIMTECH, shared_simtech_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemQmiSimtech *
+mm_broadband_modem_qmi_simtech_new (const gchar  *device,
+                                    const gchar **drivers,
+                                    const gchar  *plugin,
+                                    guint16       vendor_id,
+                                    guint16       product_id)
+{
+    return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH,
+                         MM_BASE_MODEM_DEVICE,     device,
+                         MM_BASE_MODEM_DRIVERS,    drivers,
+                         MM_BASE_MODEM_PLUGIN,     plugin,
+                         MM_BASE_MODEM_VENDOR_ID,  vendor_id,
+                         MM_BASE_MODEM_PRODUCT_ID, product_id,
+                         MM_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE,
+                         NULL);
+}
+
+static void
+mm_broadband_modem_qmi_simtech_init (MMBroadbandModemQmiSimtech *self)
+{
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+    iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+    iface->load_capabilities                 = mm_shared_simtech_location_load_capabilities;
+    iface->load_capabilities_finish          = mm_shared_simtech_location_load_capabilities_finish;
+    iface->enable_location_gathering         = mm_shared_simtech_enable_location_gathering;
+    iface->enable_location_gathering_finish  = mm_shared_simtech_enable_location_gathering_finish;
+    iface->disable_location_gathering        = mm_shared_simtech_disable_location_gathering;
+    iface->disable_location_gathering_finish = mm_shared_simtech_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedSimtech *self)
+{
+    return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+    iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+    iface->check_support                     = mm_shared_simtech_voice_check_support;
+    iface->check_support_finish              = mm_shared_simtech_voice_check_support_finish;
+    iface->enable_unsolicited_events         = mm_shared_simtech_voice_enable_unsolicited_events;
+    iface->enable_unsolicited_events_finish  = mm_shared_simtech_voice_enable_unsolicited_events_finish;
+    iface->disable_unsolicited_events        = mm_shared_simtech_voice_disable_unsolicited_events;
+    iface->disable_unsolicited_events_finish = mm_shared_simtech_voice_disable_unsolicited_events_finish;
+    iface->setup_unsolicited_events          = mm_shared_simtech_voice_setup_unsolicited_events;
+    iface->setup_unsolicited_events_finish   = mm_shared_simtech_voice_setup_unsolicited_events_finish;
+    iface->cleanup_unsolicited_events        = mm_shared_simtech_voice_cleanup_unsolicited_events;
+    iface->cleanup_unsolicited_events_finish = mm_shared_simtech_voice_cleanup_unsolicited_events_finish;
+    iface->setup_in_call_audio_channel          = mm_shared_simtech_voice_setup_in_call_audio_channel;
+    iface->setup_in_call_audio_channel_finish   = mm_shared_simtech_voice_setup_in_call_audio_channel_finish;
+    iface->cleanup_in_call_audio_channel        = mm_shared_simtech_voice_cleanup_in_call_audio_channel;
+    iface->cleanup_in_call_audio_channel_finish = mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish;
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedSimtech *self)
+{
+    return iface_modem_voice_parent;
+}
+
+static void
+shared_simtech_init (MMSharedSimtech *iface)
+{
+    iface->peek_parent_location_interface = peek_parent_location_interface;
+    iface->peek_parent_voice_interface    = peek_parent_voice_interface;
+}
+
+static void
+mm_broadband_modem_qmi_simtech_class_init (MMBroadbandModemQmiSimtechClass *klass)
+{
+}
diff --git a/plugins/simtech/mm-broadband-modem-qmi-simtech.h b/plugins/simtech/mm-broadband-modem-qmi-simtech.h
new file mode 100644
index 0000000..2f5b819
--- /dev/null
+++ b/plugins/simtech/mm-broadband-modem-qmi-simtech.h
@@ -0,0 +1,47 @@
+/* -*- 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_BROADBAND_MODEM_QMI_SIMTECH_QMI_H
+#define MM_BROADBAND_MODEM_QMI_SIMTECH_QMI_H
+
+#include "mm-broadband-modem-qmi.h"
+
+#define MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH            (mm_broadband_modem_qmi_simtech_get_type ())
+#define MM_BROADBAND_MODEM_QMI_SIMTECH(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtech))
+#define MM_BROADBAND_MODEM_QMI_SIMTECH_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtechClass))
+#define MM_IS_BROADBAND_MODEM_QMI_SIMTECH(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH))
+#define MM_IS_BROADBAND_MODEM_QMI_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH))
+#define MM_BROADBAND_MODEM_QMI_SIMTECH_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtechClass))
+
+typedef struct _MMBroadbandModemQmiSimtech MMBroadbandModemQmiSimtech;
+typedef struct _MMBroadbandModemQmiSimtechClass MMBroadbandModemQmiSimtechClass;
+
+struct _MMBroadbandModemQmiSimtech {
+    MMBroadbandModemQmi parent;
+};
+
+struct _MMBroadbandModemQmiSimtechClass{
+    MMBroadbandModemQmiClass parent;
+};
+
+GType mm_broadband_modem_qmi_simtech_get_type (void);
+
+MMBroadbandModemQmiSimtech *mm_broadband_modem_qmi_simtech_new (const gchar  *device,
+                                                                const gchar **drivers,
+                                                                const gchar  *plugin,
+                                                                guint16       vendor_id,
+                                                                guint16       product_id);
+
+#endif /* MM_BROADBAND_MODEM_QMI_SIMTECH_H */
diff --git a/plugins/simtech/mm-broadband-modem-simtech.c b/plugins/simtech/mm-broadband-modem-simtech.c
index b200114..21781dc 100644
--- a/plugins/simtech/mm-broadband-modem-simtech.c
+++ b/plugins/simtech/mm-broadband-modem-simtech.c
@@ -32,17 +32,41 @@
 #include "mm-base-modem-at.h"
 #include "mm-iface-modem.h"
 #include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-shared-simtech.h"
 #include "mm-broadband-modem-simtech.h"
 
-static void iface_modem_init (MMIfaceModem *iface);
-static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_init          (MMIfaceModem         *iface);
+static void iface_modem_3gpp_init     (MMIfaceModem3gpp     *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init    (MMIfaceModemVoice    *iface);
+static void shared_simtech_init       (MMSharedSimtech      *iface);
 
-static MMIfaceModem *iface_modem_parent;
-static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModem         *iface_modem_parent;
+static MMIfaceModem3gpp     *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice    *iface_modem_voice_parent;
 
 G_DEFINE_TYPE_EXTENDED (MMBroadbandModemSimtech, mm_broadband_modem_simtech, 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_3GPP, iface_modem_3gpp_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_SIMTECH, shared_simtech_init))
+
+typedef enum {
+    FEATURE_SUPPORT_UNKNOWN,
+    FEATURE_NOT_SUPPORTED,
+    FEATURE_SUPPORTED
+} FeatureSupport;
+
+struct _MMBroadbandModemSimtechPrivate {
+    FeatureSupport  cnsmod_support;
+    FeatureSupport  autocsq_support;
+    GRegex         *cnsmod_regex;
+    GRegex         *csq_regex;
+};
 
 /*****************************************************************************/
 /* Setup/Cleanup unsolicited events (3GPP interface) */
@@ -50,22 +74,19 @@
 static MMModemAccessTechnology
 simtech_act_to_mm_act (int nsmod)
 {
-    if (nsmod == 1)
-        return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
-    else if (nsmod == 2)
-        return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
-    else if (nsmod == 3)
-        return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
-    else if (nsmod == 4)
-        return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
-    else if (nsmod == 5)
-        return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
-    else if (nsmod == 6)
-        return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
-    else if (nsmod == 7)
-        return MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
+    static const MMModemAccessTechnology simtech_act_to_mm_act_map[] = {
+        [0] = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN,
+        [1] = MM_MODEM_ACCESS_TECHNOLOGY_GSM,
+        [2] = MM_MODEM_ACCESS_TECHNOLOGY_GPRS,
+        [3] = MM_MODEM_ACCESS_TECHNOLOGY_EDGE,
+        [4] = MM_MODEM_ACCESS_TECHNOLOGY_UMTS,
+        [5] = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA,
+        [6] = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA,
+        [7] = MM_MODEM_ACCESS_TECHNOLOGY_HSPA,
+        [8] = MM_MODEM_ACCESS_TECHNOLOGY_LTE,
+    };
 
-    return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+    return (nsmod < G_N_ELEMENTS (simtech_act_to_mm_act_map) ? simtech_act_to_mm_act_map[nsmod] : MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
 }
 
 static void
@@ -85,19 +106,33 @@
 }
 
 static void
+simtech_signal_changed (MMPortSerialAt *port,
+                        GMatchInfo *match_info,
+                        MMBroadbandModemSimtech *self)
+{
+    guint quality = 0;
+
+    if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+        return;
+
+    if (quality != 99)
+        quality = CLAMP (quality, 0, 31) * 100 / 31;
+    else
+        quality = 0;
+
+    mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
 set_unsolicited_events_handlers (MMBroadbandModemSimtech *self,
                                  gboolean enable)
 {
     MMPortSerialAt *ports[2];
     guint i;
-    GRegex *regex;
 
     ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
     ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
 
-    regex = g_regex_new ("\\r\\n\\+CNSMOD:\\s*(\\d)\\r\\n",
-                         G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
-
     /* Enable unsolicited events in given port */
     for (i = 0; i < G_N_ELEMENTS (ports); i++) {
         if (!ports[i])
@@ -106,13 +141,19 @@
         /* Access technology related */
         mm_port_serial_at_add_unsolicited_msg_handler (
             ports[i],
-            regex,
+            self->priv->cnsmod_regex,
             enable ? (MMPortSerialAtUnsolicitedMsgFn)simtech_tech_changed : NULL,
             enable ? self : NULL,
             NULL);
-    }
 
-    g_regex_unref (regex);
+        /* Signal quality related */
+        mm_port_serial_at_add_unsolicited_msg_handler (
+            ports[i],
+            self->priv->csq_regex,
+            enable ? (MMPortSerialAtUnsolicitedMsgFn)simtech_signal_changed : NULL,
+            enable ? self : NULL,
+            NULL);
+    }
 }
 
 static gboolean
@@ -182,7 +223,21 @@
 }
 
 /*****************************************************************************/
-/* Enabling unsolicited events (3GPP interface) */
+/* Enable unsolicited events (3GPP interface) */
+
+typedef enum {
+    ENABLE_UNSOLICITED_EVENTS_STEP_FIRST,
+    ENABLE_UNSOLICITED_EVENTS_STEP_PARENT,
+    ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_CNSMOD,
+    ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_CNSMOD,
+    ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_AUTOCSQ,
+    ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_AUTOCSQ,
+    ENABLE_UNSOLICITED_EVENTS_STEP_LAST,
+} EnableUnsolicitedEventsStep;
+
+typedef struct {
+    EnableUnsolicitedEventsStep step;
+} EnableUnsolicitedEventsContext;
 
 static gboolean
 modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp  *self,
@@ -192,35 +247,113 @@
     return g_task_propagate_boolean (G_TASK (res), error);
 }
 
-static void
-own_enable_unsolicited_events_ready (MMBaseModem  *self,
-                                     GAsyncResult *res,
-                                     GTask        *task)
-{
-    GError *error = NULL;
+static void enable_unsolicited_events_context_step (GTask *task);
 
-    mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
-    if (error)
-        g_task_return_error (task, error);
-    else
-        g_task_return_boolean (task, TRUE);
-    g_object_unref (task);
+static void
+autocsq_set_enabled_ready (MMBaseModem  *self,
+                           GAsyncResult *res,
+                           GTask        *task)
+{
+    EnableUnsolicitedEventsContext *ctx;
+    GError                         *error = NULL;
+    gboolean                        csq_urcs_enabled = FALSE;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!mm_base_modem_at_command_finish (self, res, &error)) {
+        mm_dbg ("Couldn't enable automatic signal quality reporting: %s", error->message);
+        g_error_free (error);
+    } else
+        csq_urcs_enabled = TRUE;
+
+    /* Disable access technology polling if we can use the +CSQ URCs */
+    g_object_set (self,
+                  MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, csq_urcs_enabled,
+                  NULL);
+
+    /* go to next step */
+    ctx->step++;
+    enable_unsolicited_events_context_step (task);
 }
 
-static const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
-    /* Autoreport access technology changes */
-    { "+CNSMOD=1", 20, FALSE, NULL },
-    /* Autoreport CSQ (first arg), and only report when it changes (second arg) */
-    { "+AUTOCSQ=1,1", 5, FALSE, NULL },
-    { NULL }
-};
+static void
+autocsq_test_ready (MMBaseModem  *_self,
+                    GAsyncResult *res,
+                    GTask        *task)
+{
+    MMBroadbandModemSimtech        *self;
+    EnableUnsolicitedEventsContext *ctx;
+
+    self = MM_BROADBAND_MODEM_SIMTECH (_self);
+    ctx  = g_task_get_task_data (task);
+
+    if (!mm_base_modem_at_command_finish (_self, res, NULL))
+        self->priv->autocsq_support = FEATURE_NOT_SUPPORTED;
+    else
+        self->priv->autocsq_support = FEATURE_SUPPORTED;
+
+    /* go to next step */
+    ctx->step++;
+    enable_unsolicited_events_context_step (task);
+}
+
+static void
+cnsmod_set_enabled_ready (MMBaseModem  *self,
+                          GAsyncResult *res,
+                          GTask        *task)
+{
+    EnableUnsolicitedEventsContext *ctx;
+    GError                         *error = NULL;
+    gboolean                        cnsmod_urcs_enabled = FALSE;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!mm_base_modem_at_command_finish (self, res, &error)) {
+        mm_dbg ("Couldn't enable automatic access technology reporting: %s", error->message);
+        g_error_free (error);
+    } else
+        cnsmod_urcs_enabled = TRUE;
+
+    /* Disable access technology polling if we can use the +CNSMOD URCs */
+    g_object_set (self,
+                  MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED, cnsmod_urcs_enabled,
+                  NULL);
+
+    /* go to next step */
+    ctx->step++;
+    enable_unsolicited_events_context_step (task);
+}
+
+static void
+cnsmod_test_ready (MMBaseModem  *_self,
+                   GAsyncResult *res,
+                   GTask        *task)
+{
+    MMBroadbandModemSimtech        *self;
+    EnableUnsolicitedEventsContext *ctx;
+
+    self = MM_BROADBAND_MODEM_SIMTECH (_self);
+    ctx  = g_task_get_task_data (task);
+
+    if (!mm_base_modem_at_command_finish (_self, res, NULL))
+        self->priv->cnsmod_support = FEATURE_NOT_SUPPORTED;
+    else
+        self->priv->cnsmod_support = FEATURE_SUPPORTED;
+
+    /* go to next step */
+    ctx->step++;
+    enable_unsolicited_events_context_step (task);
+}
 
 static void
 parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
                                         GAsyncResult     *res,
                                         GTask            *task)
 {
-    GError *error = NULL;
+    EnableUnsolicitedEventsContext *ctx;
+    GError                         *error = NULL;
+
+    ctx = g_task_get_task_data (task);
 
     if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
         g_task_return_error (task, error);
@@ -228,16 +361,91 @@
         return;
     }
 
-    /* Our own enable now */
-    mm_base_modem_at_sequence_full (
-        MM_BASE_MODEM (self),
-        mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
-        unsolicited_enable_sequence,
-        NULL, /* response_processor_context */
-        NULL, /* response_processor_context_free */
-        NULL, /* cancellable */
-        (GAsyncReadyCallback)own_enable_unsolicited_events_ready,
-        task);
+    /* go to next step */
+    ctx->step++;
+    enable_unsolicited_events_context_step (task);
+}
+
+static void
+enable_unsolicited_events_context_step (GTask *task)
+{
+    MMBroadbandModemSimtech        *self;
+    EnableUnsolicitedEventsContext *ctx;
+
+    self = g_task_get_source_object (task);
+    ctx  = g_task_get_task_data (task);
+
+    switch (ctx->step) {
+    case ENABLE_UNSOLICITED_EVENTS_STEP_FIRST:
+        /* fall down to next step */
+        ctx->step++;
+
+    case ENABLE_UNSOLICITED_EVENTS_STEP_PARENT:
+        iface_modem_3gpp_parent->enable_unsolicited_events (
+            MM_IFACE_MODEM_3GPP (self),
+            (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+            task);
+        return;
+
+    case ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_CNSMOD:
+        if (self->priv->cnsmod_support == FEATURE_SUPPORT_UNKNOWN) {
+            mm_base_modem_at_command (MM_BASE_MODEM (self),
+                                      "+CNSMOD=?",
+                                      3,
+                                      TRUE,
+                                      (GAsyncReadyCallback)cnsmod_test_ready,
+                                      task);
+            return;
+        }
+        /* fall down to next step */
+        ctx->step++;
+
+    case ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_CNSMOD:
+        if (self->priv->cnsmod_support == FEATURE_SUPPORTED) {
+            mm_base_modem_at_command (MM_BASE_MODEM (self),
+                                      /* Autoreport +CNSMOD when it changes */
+                                      "+CNSMOD=1",
+                                      20,
+                                      FALSE,
+                                      (GAsyncReadyCallback)cnsmod_set_enabled_ready,
+                                      task);
+            return;
+        }
+        /* fall down to next step */
+        ctx->step++;
+
+    case ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_AUTOCSQ:
+        if (self->priv->autocsq_support == FEATURE_SUPPORT_UNKNOWN) {
+            mm_base_modem_at_command (MM_BASE_MODEM (self),
+                                      "+AUTOCSQ=?",
+                                      3,
+                                      TRUE,
+                                      (GAsyncReadyCallback)autocsq_test_ready,
+                                      task);
+            return;
+        }
+        /* fall down to next step */
+        ctx->step++;
+
+    case ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_AUTOCSQ:
+        if (self->priv->autocsq_support == FEATURE_SUPPORTED) {
+            mm_base_modem_at_command (MM_BASE_MODEM (self),
+                                      /* Autoreport+ CSQ (first arg), and only report when it changes (second arg) */
+                                      "+AUTOCSQ=1,1",
+                                      20,
+                                      FALSE,
+                                      (GAsyncReadyCallback)autocsq_set_enabled_ready,
+                                      task);
+            return;
+        }
+        /* fall down to next step */
+        ctx->step++;
+
+    case ENABLE_UNSOLICITED_EVENTS_STEP_LAST:
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
 }
 
 static void
@@ -245,80 +453,173 @@
                                       GAsyncReadyCallback  callback,
                                       gpointer             user_data)
 {
-    /* Chain up parent's enable */
-    iface_modem_3gpp_parent->enable_unsolicited_events (
-        self,
-        (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
-        g_task_new (self, NULL, callback, user_data));
+    EnableUnsolicitedEventsContext *ctx;
+    GTask                          *task;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    ctx = g_new (EnableUnsolicitedEventsContext, 1);
+    ctx->step = ENABLE_UNSOLICITED_EVENTS_STEP_FIRST;
+    g_task_set_task_data (task, ctx, g_free);
+
+    enable_unsolicited_events_context_step (task);
 }
 
 /*****************************************************************************/
-/* Disabling unsolicited events (3GPP interface) */
+/* Disable unsolicited events (3GPP interface) */
+
+typedef enum {
+    DISABLE_UNSOLICITED_EVENTS_STEP_FIRST,
+    DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_AUTOCSQ,
+    DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_CNSMOD,
+    DISABLE_UNSOLICITED_EVENTS_STEP_PARENT,
+    DISABLE_UNSOLICITED_EVENTS_STEP_LAST,
+} DisableUnsolicitedEventsStep;
+
+typedef struct {
+    DisableUnsolicitedEventsStep step;
+} DisableUnsolicitedEventsContext;
 
 static gboolean
-modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
-                                              GAsyncResult     *res,
-                                              GError          **error)
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp  *self,
+                                              GAsyncResult      *res,
+                                              GError           **error)
 {
     return g_task_propagate_boolean (G_TASK (res), error);
 }
 
-static const MMBaseModemAtCommand unsolicited_disable_sequence[] = {
-    { "+CNSMOD=0",  3, FALSE, NULL },
-    { "+AUTOCSQ=0", 3, FALSE, NULL },
-    { NULL }
-};
+static void disable_unsolicited_events_context_step (GTask *task);
 
 static void
 parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
                                          GAsyncResult     *res,
                                          GTask            *task)
 {
-    GError *error = NULL;
+    DisableUnsolicitedEventsContext *ctx;
+    GError                         *error = NULL;
 
-    if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
-        g_task_return_error (task, error);
-    else
-        g_task_return_boolean (task, TRUE);
-    g_object_unref (task);
-}
+    ctx = g_task_get_task_data (task);
 
-static void
-own_disable_unsolicited_events_ready (MMBaseModem  *self,
-                                      GAsyncResult *res,
-                                      GTask        *task)
-{
-    GError *error = NULL;
-
-    mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
-    if (error) {
+    if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) {
         g_task_return_error (task, error);
         g_object_unref (task);
         return;
     }
 
-    /* Next, chain up parent's disable */
-    iface_modem_3gpp_parent->disable_unsolicited_events (
-        MM_IFACE_MODEM_3GPP (self),
-        (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
-        task);
+    /* go to next step */
+    ctx->step++;
+    disable_unsolicited_events_context_step (task);
 }
 
 static void
-modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
-                                       GAsyncReadyCallback callback,
-                                       gpointer user_data)
+cnsmod_set_disabled_ready (MMBaseModem  *self,
+                           GAsyncResult *res,
+                           GTask        *task)
 {
-    /* Our own disable first */
-    mm_base_modem_at_sequence_full (
-        MM_BASE_MODEM (self),
-        mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
-        unsolicited_disable_sequence,
-        NULL, /* response_processor_context */
-        NULL, /* response_processor_context_free */
-        NULL, /* cancellable */
-        (GAsyncReadyCallback)own_disable_unsolicited_events_ready,
-        g_task_new (self, NULL, callback, user_data));
+    DisableUnsolicitedEventsContext *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 disable automatic access technology reporting: %s", error->message);
+        g_error_free (error);
+    }
+
+    /* go to next step */
+    ctx->step++;
+    disable_unsolicited_events_context_step (task);
+}
+
+static void
+autocsq_set_disabled_ready (MMBaseModem  *self,
+                            GAsyncResult *res,
+                            GTask        *task)
+{
+    DisableUnsolicitedEventsContext *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 disable automatic signal quality reporting: %s", error->message);
+        g_error_free (error);
+    }
+
+    /* go to next step */
+    ctx->step++;
+    disable_unsolicited_events_context_step (task);
+}
+
+static void
+disable_unsolicited_events_context_step (GTask *task)
+{
+    MMBroadbandModemSimtech         *self;
+    DisableUnsolicitedEventsContext *ctx;
+
+    self = g_task_get_source_object (task);
+    ctx  = g_task_get_task_data (task);
+
+    switch (ctx->step) {
+    case DISABLE_UNSOLICITED_EVENTS_STEP_FIRST:
+        /* fall down to next step */
+        ctx->step++;
+
+    case DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_AUTOCSQ:
+        if (self->priv->autocsq_support == FEATURE_SUPPORTED) {
+            mm_base_modem_at_command (MM_BASE_MODEM (self),
+                                      "+AUTOCSQ=0",
+                                      20,
+                                      FALSE,
+                                      (GAsyncReadyCallback)autocsq_set_disabled_ready,
+                                      task);
+            return;
+        }
+        /* fall down to next step */
+        ctx->step++;
+
+    case DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_CNSMOD:
+        if (self->priv->cnsmod_support == FEATURE_SUPPORTED) {
+            mm_base_modem_at_command (MM_BASE_MODEM (self),
+                                      "+CNSMOD=0",
+                                      20,
+                                      FALSE,
+                                      (GAsyncReadyCallback)cnsmod_set_disabled_ready,
+                                      task);
+            return;
+        }
+        /* fall down to next step */
+        ctx->step++;
+
+    case DISABLE_UNSOLICITED_EVENTS_STEP_PARENT:
+        iface_modem_3gpp_parent->disable_unsolicited_events (
+            MM_IFACE_MODEM_3GPP (self),
+            (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+            task);
+        return;
+
+    case DISABLE_UNSOLICITED_EVENTS_STEP_LAST:
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp    *self,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+    DisableUnsolicitedEventsContext *ctx;
+    GTask                          *task;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    ctx = g_new (DisableUnsolicitedEventsContext, 1);
+    ctx->step = DISABLE_UNSOLICITED_EVENTS_STEP_FIRST;
+    g_task_set_task_data (task, ctx, g_free);
+
+    disable_unsolicited_events_context_step (task);
 }
 
 /*****************************************************************************/
@@ -377,21 +678,31 @@
 }
 
 static void
-load_access_technologies (MMIfaceModem        *self,
+load_access_technologies (MMIfaceModem        *_self,
                           GAsyncReadyCallback  callback,
                           gpointer             user_data)
 {
-    GTask *task;
+    MMBroadbandModemSimtech *self;
+    GTask                   *task;
 
+    self = MM_BROADBAND_MODEM_SIMTECH (_self);
     task = g_task_new (self, NULL, callback, user_data);
 
     /* Launch query only for 3GPP modems */
-    if (!mm_iface_modem_is_3gpp (self)) {
+    if (!mm_iface_modem_is_3gpp (_self)) {
         g_task_return_int (task, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
         g_object_unref (task);
         return;
     }
 
+    g_assert (self->priv->cnsmod_support != FEATURE_SUPPORT_UNKNOWN);
+    if (self->priv->cnsmod_support == FEATURE_NOT_SUPPORTED) {
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+                                 "Loading access technologies with +CNSMOD is not supported");
+        g_object_unref (task);
+        return;
+    }
+
     mm_base_modem_at_command (
         MM_BASE_MODEM (self),
         "AT+CNSMOD?",
@@ -402,6 +713,85 @@
 }
 
 /*****************************************************************************/
+/* Load signal quality (Modem interface) */
+
+static guint
+load_signal_quality_finish (MMIfaceModem  *self,
+                            GAsyncResult  *res,
+                            GError       **error)
+{
+    gssize value;
+
+    value = g_task_propagate_int (G_TASK (res), error);
+    return value < 0 ? 0 : value;
+}
+
+static void
+csq_query_ready (MMBaseModem  *self,
+                 GAsyncResult *res,
+                 GTask        *task)
+{
+    const gchar *response, *p;
+    GError      *error = NULL;
+    gint         quality;
+    gint         ber;
+
+    response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+    if (!response) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    /* Given that we may have enabled AUTOCSQ support, it is totally possible
+     * to get an empty string at this point, because the +CSQ reply may have
+     * been processed as an URC already. If we ever see this, we should not return
+     * an error, because that would reset the reported signal quality to 0 :/
+     * So, in this case, return the last cached signal quality value. */
+    if (!response[0]) {
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS,
+                                 "already refreshed via URCs");
+        g_object_unref (task);
+        return;
+    }
+
+    p = mm_strip_tag (response, "+CSQ:");
+    if (sscanf (p, "%d, %d", &quality, &ber)) {
+        if (quality != 99)
+            quality = CLAMP (quality, 0, 31) * 100 / 31;
+        else
+            quality = 0;
+        g_task_return_int (task, quality);
+        g_object_unref (task);
+        return;
+    }
+
+    g_task_return_new_error (task,
+                             MM_CORE_ERROR,
+                             MM_CORE_ERROR_FAILED,
+                             "Could not parse signal quality results");
+    g_object_unref (task);
+}
+
+static void
+load_signal_quality (MMIfaceModem        *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),
+        "+CSQ",
+        3,
+        FALSE,
+        (GAsyncReadyCallback)csq_query_ready,
+        task);
+}
+
+/*****************************************************************************/
 /* Load supported modes (Modem interface) */
 
 static GArray *
@@ -819,12 +1209,53 @@
                          MM_BASE_MODEM_PLUGIN, plugin,
                          MM_BASE_MODEM_VENDOR_ID, vendor_id,
                          MM_BASE_MODEM_PRODUCT_ID, product_id,
+                         MM_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE,
                          NULL);
 }
 
 static void
 mm_broadband_modem_simtech_init (MMBroadbandModemSimtech *self)
 {
+    /* Initialize private data */
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                              MM_TYPE_BROADBAND_MODEM_SIMTECH,
+                                              MMBroadbandModemSimtechPrivate);
+
+    self->priv->cnsmod_support = FEATURE_SUPPORT_UNKNOWN;
+    self->priv->autocsq_support = FEATURE_SUPPORT_UNKNOWN;
+
+    self->priv->cnsmod_regex = g_regex_new ("\\r\\n\\+CNSMOD:\\s*(\\d+)\\r\\n",
+                                            G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+    self->priv->csq_regex    = g_regex_new ("\\r\\n\\+CSQ:\\s*(\\d+),(\\d+)\\r\\n",
+                                            G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+    MMBroadbandModemSimtech *self = MM_BROADBAND_MODEM_SIMTECH (object);
+
+    g_regex_unref (self->priv->cnsmod_regex);
+    g_regex_unref (self->priv->csq_regex);
+
+    G_OBJECT_CLASS (mm_broadband_modem_simtech_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+    iface_modem_parent = g_type_interface_peek_parent (iface);
+
+    iface->load_signal_quality = load_signal_quality;
+    iface->load_signal_quality_finish = load_signal_quality_finish;
+    iface->load_access_technologies = load_access_technologies;
+    iface->load_access_technologies_finish = load_access_technologies_finish;
+    iface->load_supported_modes = load_supported_modes;
+    iface->load_supported_modes_finish = load_supported_modes_finish;
+    iface->load_current_modes = load_current_modes;
+    iface->load_current_modes_finish = load_current_modes_finish;
+    iface->set_current_modes = set_current_modes;
+    iface->set_current_modes_finish = set_current_modes_finish;
 }
 
 static void
@@ -844,24 +1275,68 @@
 }
 
 static void
-iface_modem_init (MMIfaceModem *iface)
+iface_modem_location_init (MMIfaceModemLocation *iface)
 {
-    iface_modem_parent = g_type_interface_peek_parent (iface);
+    iface_modem_location_parent = g_type_interface_peek_parent (iface);
 
-    iface->load_access_technologies = load_access_technologies;
-    iface->load_access_technologies_finish = load_access_technologies_finish;
-    iface->load_supported_modes = load_supported_modes;
-    iface->load_supported_modes_finish = load_supported_modes_finish;
-    iface->load_current_modes = load_current_modes;
-    iface->load_current_modes_finish = load_current_modes_finish;
-    iface->set_current_modes = set_current_modes;
-    iface->set_current_modes_finish = set_current_modes_finish;
+    iface->load_capabilities                 = mm_shared_simtech_location_load_capabilities;
+    iface->load_capabilities_finish          = mm_shared_simtech_location_load_capabilities_finish;
+    iface->enable_location_gathering         = mm_shared_simtech_enable_location_gathering;
+    iface->enable_location_gathering_finish  = mm_shared_simtech_enable_location_gathering_finish;
+    iface->disable_location_gathering        = mm_shared_simtech_disable_location_gathering;
+    iface->disable_location_gathering_finish = mm_shared_simtech_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedSimtech *self)
+{
+    return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+    iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+    iface->check_support                     = mm_shared_simtech_voice_check_support;
+    iface->check_support_finish              = mm_shared_simtech_voice_check_support_finish;
+    iface->enable_unsolicited_events         = mm_shared_simtech_voice_enable_unsolicited_events;
+    iface->enable_unsolicited_events_finish  = mm_shared_simtech_voice_enable_unsolicited_events_finish;
+    iface->disable_unsolicited_events        = mm_shared_simtech_voice_disable_unsolicited_events;
+    iface->disable_unsolicited_events_finish = mm_shared_simtech_voice_disable_unsolicited_events_finish;
+    iface->setup_unsolicited_events          = mm_shared_simtech_voice_setup_unsolicited_events;
+    iface->setup_unsolicited_events_finish   = mm_shared_simtech_voice_setup_unsolicited_events_finish;
+    iface->cleanup_unsolicited_events        = mm_shared_simtech_voice_cleanup_unsolicited_events;
+    iface->cleanup_unsolicited_events_finish = mm_shared_simtech_voice_cleanup_unsolicited_events_finish;
+    iface->setup_in_call_audio_channel          = mm_shared_simtech_voice_setup_in_call_audio_channel;
+    iface->setup_in_call_audio_channel_finish   = mm_shared_simtech_voice_setup_in_call_audio_channel_finish;
+    iface->cleanup_in_call_audio_channel        = mm_shared_simtech_voice_cleanup_in_call_audio_channel;
+    iface->cleanup_in_call_audio_channel_finish = mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish;
+
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedSimtech *self)
+{
+    return iface_modem_voice_parent;
+}
+
+static void
+shared_simtech_init (MMSharedSimtech *iface)
+{
+    iface->peek_parent_location_interface = peek_parent_location_interface;
+    iface->peek_parent_voice_interface    = peek_parent_voice_interface;
 }
 
 static void
 mm_broadband_modem_simtech_class_init (MMBroadbandModemSimtechClass *klass)
 {
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
     MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
 
+    g_type_class_add_private (object_class, sizeof (MMBroadbandModemSimtechPrivate));
+
+    object_class->finalize = finalize;
+
     broadband_modem_class->setup_ports = setup_ports;
 }
diff --git a/plugins/simtech/mm-broadband-modem-simtech.h b/plugins/simtech/mm-broadband-modem-simtech.h
index 6ee32b1..a2b57fe 100644
--- a/plugins/simtech/mm-broadband-modem-simtech.h
+++ b/plugins/simtech/mm-broadband-modem-simtech.h
@@ -29,9 +29,11 @@
 
 typedef struct _MMBroadbandModemSimtech MMBroadbandModemSimtech;
 typedef struct _MMBroadbandModemSimtechClass MMBroadbandModemSimtechClass;
+typedef struct _MMBroadbandModemSimtechPrivate MMBroadbandModemSimtechPrivate;
 
 struct _MMBroadbandModemSimtech {
     MMBroadbandModem parent;
+    MMBroadbandModemSimtechPrivate *priv;
 };
 
 struct _MMBroadbandModemSimtechClass{
diff --git a/plugins/simtech/mm-modem-helpers-simtech.c b/plugins/simtech/mm-modem-helpers-simtech.c
new file mode 100644
index 0000000..d452e0b
--- /dev/null
+++ b/plugins/simtech/mm-modem-helpers-simtech.c
@@ -0,0 +1,209 @@
+/* -*- 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 <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "ModemManager.h"
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-log.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers-simtech.h"
+#include "mm-modem-helpers.h"
+
+
+/*****************************************************************************/
+/* +CLCC test parser
+ *
+ * Example (SIM7600E):
+ *   AT+CLCC=?
+ *   +CLCC: (0-1)
+ */
+
+gboolean
+mm_simtech_parse_clcc_test (const gchar  *response,
+                            gboolean     *clcc_urcs_supported,
+                            GError      **error)
+{
+    g_assert (response);
+
+    response = mm_strip_tag (response, "+CLCC:");
+
+    /* 3GPP specifies that the output of AT+CLCC=? should be just OK, so support
+     * that */
+    if (!response[0]) {
+        *clcc_urcs_supported = FALSE;
+        return TRUE;
+    }
+
+    /* As per 3GPP TS 27.007, the AT+CLCC command doesn't expect any argument,
+     * as it only is designed to report the current call list, nothing else.
+     * In the case of the Simtech plugin, though, we are going to support +CLCC
+     * URCs that can be enabled/disabled via AT+CLCC=1/0. We therefore need to
+     * detect whether this URC management is possible or not, for now with a
+     * simple check looking for the specific "(0-1)" string.
+     */
+    if (!strncmp (response, "(0-1)", 5)) {
+        *clcc_urcs_supported = TRUE;
+        return TRUE;
+    }
+
+    g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                 "unexpected +CLCC test response: '%s'", response);
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+GRegex *
+mm_simtech_get_clcc_urc_regex (void)
+{
+    return g_regex_new ("\\r\\n(\\+CLCC: .*\\r\\n)+",
+                        G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_simtech_parse_clcc_list (const gchar *str,
+                            GList      **out_list,
+                            GError     **error)
+{
+    /* Parse the URC contents as a plain +CLCC response, but make sure to skip first
+     * EOL in the string because the plain +CLCC response would never have that.
+     */
+    return mm_3gpp_parse_clcc_response (mm_strip_tag (str, "\r\n"), out_list, error);
+}
+
+void
+mm_simtech_call_info_list_free (GList *call_info_list)
+{
+    mm_3gpp_call_info_list_free (call_info_list);
+}
+
+/*****************************************************************************/
+
+/*
+ * <CR><LF>VOICE CALL: BEGIN<CR><LF>
+ * <CR><LF>VOICE CALL: END: 000041<CR><LF>
+ */
+GRegex *
+mm_simtech_get_voice_call_urc_regex (void)
+{
+    return g_regex_new ("\\r\\nVOICE CALL:\\s*([A-Z]+)(?::\\s*(\\d+))?\\r\\n",
+                        G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_simtech_parse_voice_call_urc (GMatchInfo  *match_info,
+                                 gboolean    *start_or_stop,
+                                 guint       *duration,
+                                 GError     **error)
+{
+    GError *inner_error = NULL;
+    gchar  *str;
+
+    str = mm_get_string_unquoted_from_match_info (match_info, 1);
+    if (!str) {
+        inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                   "Couldn't read voice call URC action");
+        goto out;
+    }
+
+    if (g_strcmp0 (str, "BEGIN") == 0) {
+        *start_or_stop = TRUE;
+        *duration = 0;
+        goto out;
+    }
+
+    if (g_strcmp0 (str, "END") == 0) {
+        *start_or_stop = FALSE;
+        if (!mm_get_uint_from_match_info (match_info, 2, duration))
+            *duration = 0;
+        goto out;
+    }
+
+    inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                               "Unknown voice call URC action: %s", str);
+
+out:
+    g_free (str);
+    if (inner_error) {
+        g_propagate_error (error, inner_error);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+/*
+ * <CR><LF>MISSED_CALL: 11:01AM 07712345678<CR><LF>
+ */
+GRegex *
+mm_simtech_get_missed_call_urc_regex (void)
+{
+    return g_regex_new ("\\r\\nMISSED_CALL:\\s*(.+)\\r\\n",
+                        G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_simtech_parse_missed_call_urc (GMatchInfo  *match_info,
+                                  gchar      **details,
+                                  GError     **error)
+{
+    gchar *str;
+
+    str = mm_get_string_unquoted_from_match_info (match_info, 1);
+    if (!str) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                     "Couldn't read missed call URC details");
+        return FALSE;
+    }
+
+    *details = str;
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+/*
+ * Using TWO <CR> instead of one...
+ * <CR><CR><LF>+CRING: VOICE<CR><CR><LF>
+ */
+GRegex *
+mm_simtech_get_cring_urc_regex (void)
+{
+    return g_regex_new ("(?:\\r)+\\n\\+CRING:\\s*(\\S+)(?:\\r)+\\n",
+                        G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+/*****************************************************************************/
+
+/*
+ * <CR><CR><LF>+RXDTMF: 8<CR><CR><LF>
+ * <CR><CR><LF>+RXDTMF: *<CR><CR><LF>
+ * <CR><CR><LF>+RXDTMF: 7<CR><CR><LF>
+ *
+ * Note! using TWO <CR> instead of one...
+ */
+GRegex *
+mm_simtech_get_rxdtmf_urc_regex (void)
+{
+    return g_regex_new ("(?:\\r)+\\n\\+RXDTMF:\\s*([0-9A-D\\*\\#])(?:\\r)+\\n",
+                        G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
diff --git a/plugins/simtech/mm-modem-helpers-simtech.h b/plugins/simtech/mm-modem-helpers-simtech.h
new file mode 100644
index 0000000..efcc23f
--- /dev/null
+++ b/plugins/simtech/mm-modem-helpers-simtech.h
@@ -0,0 +1,66 @@
+/* -*- 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_MODEM_HELPERS_SIMTECH_H
+#define MM_MODEM_HELPERS_SIMTECH_H
+
+#include <glib.h>
+
+#include <ModemManager.h>
+#include <mm-base-bearer.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+/*****************************************************************************/
+/* +CLCC URC helpers */
+
+gboolean mm_simtech_parse_clcc_test (const gchar  *response,
+                                     gboolean     *clcc_urcs_supported,
+                                     GError      **error);
+
+GRegex   *mm_simtech_get_clcc_urc_regex  (void);
+gboolean  mm_simtech_parse_clcc_list     (const gchar *str,
+                                          GList      **out_list,
+                                          GError     **error);
+void      mm_simtech_call_info_list_free (GList       *call_info_list);
+
+/*****************************************************************************/
+/* VOICE CALL URC helpers */
+
+GRegex   *mm_simtech_get_voice_call_urc_regex (void);
+gboolean  mm_simtech_parse_voice_call_urc     (GMatchInfo  *match_info,
+                                               gboolean    *start_or_stop,
+                                               guint       *duration,
+                                               GError     **error);
+
+/*****************************************************************************/
+/* MISSED_CALL URC helpers */
+
+GRegex   *mm_simtech_get_missed_call_urc_regex (void);
+gboolean  mm_simtech_parse_missed_call_urc     (GMatchInfo  *match_info,
+                                                gchar      **details,
+                                                GError     **error);
+
+/*****************************************************************************/
+/* Non-standard CRING URC helpers */
+
+GRegex *mm_simtech_get_cring_urc_regex (void);
+
+/*****************************************************************************/
+/* +RXDTMF URC helpers */
+
+GRegex *mm_simtech_get_rxdtmf_urc_regex (void);
+
+#endif  /* MM_MODEM_HELPERS_SIMTECH_H */
diff --git a/plugins/simtech/mm-plugin-simtech.c b/plugins/simtech/mm-plugin-simtech.c
index fb3a795..79afeb5 100644
--- a/plugins/simtech/mm-plugin-simtech.c
+++ b/plugins/simtech/mm-plugin-simtech.c
@@ -26,7 +26,7 @@
 #include "mm-broadband-modem-simtech.h"
 
 #if defined WITH_QMI
-#include "mm-broadband-modem-qmi.h"
+#include "mm-broadband-modem-qmi-simtech.h"
 #endif
 
 G_DEFINE_TYPE (MMPluginSimtech, mm_plugin_simtech, MM_TYPE_PLUGIN)
@@ -48,11 +48,11 @@
 #if defined WITH_QMI
     if (mm_port_probe_list_has_qmi_port (probes)) {
         mm_dbg ("QMI-powered SimTech modem found...");
-        return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
-                                                          drivers,
-                                                          mm_plugin_get_name (self),
-                                                          vendor,
-                                                          product));
+        return MM_BASE_MODEM (mm_broadband_modem_qmi_simtech_new (uid,
+                                                                  drivers,
+                                                                  mm_plugin_get_name (self),
+                                                                  vendor,
+                                                                  product));
     }
 #endif
 
diff --git a/plugins/simtech/mm-shared-simtech.c b/plugins/simtech/mm-shared-simtech.c
new file mode 100644
index 0000000..b7f98ac
--- /dev/null
+++ b/plugins/simtech/mm-shared-simtech.c
@@ -0,0 +1,1284 @@
+/* -*- 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 <glib-object.h>
+#include <gio/gio.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-iface-modem-location.h"
+#include "mm-base-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-shared-simtech.h"
+#include "mm-modem-helpers-simtech.h"
+
+/*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "shared-simtech-private-tag"
+static GQuark private_quark;
+
+typedef enum {
+    FEATURE_SUPPORT_UNKNOWN,
+    FEATURE_NOT_SUPPORTED,
+    FEATURE_SUPPORTED,
+} FeatureSupport;
+
+typedef struct {
+    /* location */
+    MMIfaceModemLocation  *iface_modem_location_parent;
+    MMModemLocationSource  supported_sources;
+    MMModemLocationSource  enabled_sources;
+    FeatureSupport         cgps_support;
+    /* voice */
+    MMIfaceModemVoice     *iface_modem_voice_parent;
+    FeatureSupport         cpcmreg_support;
+    FeatureSupport         clcc_urc_support;
+    GRegex                *clcc_urc_regex;
+    GRegex                *voice_call_regex;
+    GRegex                *missed_call_regex;
+    GRegex                *cring_regex;
+    GRegex                *rxdtmf_regex;
+} Private;
+
+static void
+private_free (Private *ctx)
+{
+    g_regex_unref (ctx->rxdtmf_regex);
+    g_regex_unref (ctx->cring_regex);
+    g_regex_unref (ctx->missed_call_regex);
+    g_regex_unref (ctx->voice_call_regex);
+    g_regex_unref (ctx->clcc_urc_regex);
+    g_slice_free (Private, ctx);
+}
+
+static Private *
+get_private (MMSharedSimtech *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);
+        priv->supported_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+        priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+        priv->cgps_support = FEATURE_SUPPORT_UNKNOWN;
+        priv->cpcmreg_support = FEATURE_SUPPORT_UNKNOWN;
+        priv->clcc_urc_support = FEATURE_SUPPORT_UNKNOWN;
+        priv->clcc_urc_regex = mm_simtech_get_clcc_urc_regex ();
+        priv->voice_call_regex = mm_simtech_get_voice_call_urc_regex ();
+        priv->missed_call_regex = mm_simtech_get_missed_call_urc_regex ();
+        priv->cring_regex = mm_simtech_get_cring_urc_regex ();
+        priv->rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex ();
+
+        /* Setup parent class' MMIfaceModemLocation and MMIfaceModemVoice */
+
+        g_assert (MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_location_interface);
+        priv->iface_modem_location_parent = MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_location_interface (self);
+
+        g_assert (MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_voice_interface);
+        priv->iface_modem_voice_parent = MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_voice_interface (self);
+
+        g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+    }
+
+    return priv;
+}
+
+/*****************************************************************************/
+/* GPS trace received */
+
+static void
+trace_received (MMPortSerialGps      *port,
+                const gchar          *trace,
+                MMIfaceModemLocation *self)
+{
+    /* Helper to debug GPS location related issues. Don't depend on a real GPS
+     * fix for debugging, just use some random values to update */
+#if 0
+    if (g_str_has_prefix (trace, "$GPGGA")) {
+        GString *str;
+        GDateTime *now;
+
+        now = g_date_time_new_now_utc ();
+        str = g_string_new ("");
+        g_string_append_printf (str,
+                                "$GPGGA,%02u%02u%02u,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47",
+                                g_date_time_get_hour (now),
+                                g_date_time_get_minute (now),
+                                g_date_time_get_second (now));
+        mm_iface_modem_location_gps_update (self, str->str);
+        g_string_free (str, TRUE);
+        g_date_time_unref (now);
+        return;
+    }
+#endif
+
+    mm_iface_modem_location_gps_update (self, trace);
+}
+
+/*****************************************************************************/
+/* Location capabilities loading (Location interface) */
+
+MMModemLocationSource
+mm_shared_simtech_location_load_capabilities_finish (MMIfaceModemLocation  *self,
+                                                     GAsyncResult          *res,
+                                                     GError               **error)
+{
+    GError *inner_error = NULL;
+    gssize aux;
+
+    aux = g_task_propagate_int (G_TASK (res), &inner_error);
+    if (inner_error) {
+        g_propagate_error (error, inner_error);
+        return MM_MODEM_LOCATION_SOURCE_NONE;
+    }
+    return (MMModemLocationSource) aux;
+}
+
+static void probe_gps_features (GTask *task);
+
+static void
+cgps_test_ready (MMBaseModem  *self,
+                 GAsyncResult *res,
+                 GTask        *task)
+{
+    Private *priv;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    if (!mm_base_modem_at_command_finish (self, res, NULL))
+        priv->cgps_support = FEATURE_NOT_SUPPORTED;
+    else
+        priv->cgps_support = FEATURE_SUPPORTED;
+
+    probe_gps_features (task);
+}
+
+static void
+probe_gps_features (GTask *task)
+{
+    MMSharedSimtech       *self;
+    MMModemLocationSource  sources;
+    Private               *priv;
+
+    self = MM_SHARED_SIMTECH (g_task_get_source_object (task));
+    priv = get_private (self);
+
+    /* Need to check if CGPS supported... */
+    if (priv->cgps_support == FEATURE_SUPPORT_UNKNOWN) {
+        mm_base_modem_at_command (MM_BASE_MODEM (self), "+CGPS=?", 3, TRUE, (GAsyncReadyCallback) cgps_test_ready, task);
+        return;
+    }
+
+    /* All GPS features probed */
+
+    /* Recover parent sources */
+    sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+    if (priv->cgps_support == FEATURE_SUPPORTED) {
+        mm_dbg ("GPS commands supported: GPS capabilities enabled");
+
+        /* We only flag as supported by this implementation those sources not already
+         * supported by the parent implementation */
+        if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA))
+            priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
+        if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW))
+            priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
+        if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))
+            priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
+
+        sources |= priv->supported_sources;
+
+        /* Add handler for the NMEA traces in the GPS data port */
+        mm_port_serial_gps_add_trace_handler (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)),
+                                              (MMPortSerialGpsTraceFn)trace_received,
+                                              self,
+                                              NULL);
+    } else
+        mm_dbg ("No GPS command supported: no GPS capabilities");
+
+    g_task_return_int (task, (gssize) sources);
+    g_object_unref (task);
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+                                GAsyncResult         *res,
+                                GTask                *task)
+{
+    MMModemLocationSource  sources;
+    GError                *error = NULL;
+    Private               *priv;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+    if (error) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    /* Now our own check. If we don't have any GPS port, we're done */
+    if (!mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) {
+        mm_dbg ("No GPS data port found: no GPS capabilities");
+        g_task_return_int (task, sources);
+        g_object_unref (task);
+        return;
+    }
+
+    /* Cache sources supported by the parent */
+    g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
+
+    /* Probe all GPS features */
+    probe_gps_features (task);
+}
+
+void
+mm_shared_simtech_location_load_capabilities (MMIfaceModemLocation *self,
+                                              GAsyncReadyCallback   callback,
+                                              gpointer              user_data)
+{
+    Private *priv;
+    GTask   *task;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+    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);
+
+    priv->iface_modem_location_parent->load_capabilities (self,
+                                                          (GAsyncReadyCallback)parent_load_capabilities_ready,
+                                                          task);
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+gboolean
+mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation  *self,
+                                                     GAsyncResult          *res,
+                                                     GError               **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+disable_cgps_ready (MMBaseModem  *self,
+                    GAsyncResult *res,
+                    GTask        *task)
+{
+    MMModemLocationSource  source;
+    Private               *priv;
+    GError                *error = NULL;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    mm_base_modem_at_command_finish (self, res, &error);
+
+    /* Only use the GPS port in NMEA/RAW setups */
+    source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
+    if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+        MMPortSerialGps *gps_port;
+
+        /* Even if we get an error here, we try to close the GPS port */
+        gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
+        if (gps_port)
+            mm_port_serial_close (MM_PORT_SERIAL (gps_port));
+    }
+
+    if (error)
+        g_task_return_error (task, error);
+    else {
+        priv->enabled_sources &= source;
+        g_task_return_boolean (task, TRUE);
+    }
+    g_object_unref (task);
+}
+
+static void
+parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
+                                         GAsyncResult         *res,
+                                         GTask                *task)
+{
+    GError  *error;
+    Private *priv;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    g_assert (priv->iface_modem_location_parent);
+    if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error))
+        g_task_return_error (task, error);
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+void
+mm_shared_simtech_disable_location_gathering (MMIfaceModemLocation  *self,
+                                              MMModemLocationSource  source,
+                                              GAsyncReadyCallback    callback,
+                                              gpointer               user_data)
+{
+    MMModemLocationSource  enabled_sources;
+    Private               *priv;
+    GTask                 *task;
+
+    task = g_task_new (self, NULL, callback, user_data);
+    g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+    g_assert (priv->iface_modem_location_parent);
+
+    /* Only consider request if it applies to one of the sources we are
+     * supporting, otherwise run parent disable */
+    if (!(priv->supported_sources & source)) {
+        /* If disabling implemented by the parent, run it. */
+        if (priv->iface_modem_location_parent->disable_location_gathering &&
+            priv->iface_modem_location_parent->disable_location_gathering_finish) {
+            priv->iface_modem_location_parent->disable_location_gathering (self,
+                                                                           source,
+                                                                           (GAsyncReadyCallback)parent_disable_location_gathering_ready,
+                                                                           task);
+            return;
+        }
+        /* Otherwise, we're done */
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    /* 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_GPS_UNMANAGED));
+
+    /* Flag as disabled to see how many others we would have left enabled */
+    enabled_sources = priv->enabled_sources;
+    enabled_sources &= ~source;
+
+    /* If there are still GPS-related sources enabled, do nothing else */
+    if (enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+                           MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+                           MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+        priv->enabled_sources &= ~source;
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    /* Stop GPS engine if all GPS-related sources are disabled */
+    g_assert (priv->cgps_support == FEATURE_SUPPORTED);
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              "+CGPS=0",
+                              10,
+                              FALSE,
+                              (GAsyncReadyCallback) disable_cgps_ready,
+                              task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+gboolean
+mm_shared_simtech_enable_location_gathering_finish (MMIfaceModemLocation  *self,
+                                                    GAsyncResult          *res,
+                                                    GError               **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+enable_cgps_ready (MMBaseModem  *self,
+                   GAsyncResult *res,
+                   GTask        *task)
+{
+    MMModemLocationSource  source;
+    GError                *error = NULL;
+    Private               *priv;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    if (!mm_base_modem_at_command_finish (self, res, &error)) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    /* Only use the GPS port in NMEA/RAW setups */
+    source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
+    if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+                  MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+        MMPortSerialGps *gps_port;
+
+        gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
+        if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) {
+            if (error)
+                g_task_return_error (task, error);
+            else
+                g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                         "Couldn't open raw GPS serial port");
+            g_object_unref (task);
+            return;
+        }
+    }
+
+    priv->enabled_sources |= source;
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *self,
+                                        GAsyncResult         *res,
+                                        GTask                *task)
+{
+    GError  *error;
+    Private *priv;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    g_assert (priv->iface_modem_location_parent);
+    if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error))
+        g_task_return_error (task, error);
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+void
+mm_shared_simtech_enable_location_gathering (MMIfaceModemLocation  *self,
+                                             MMModemLocationSource  source,
+                                             GAsyncReadyCallback    callback,
+                                             gpointer               user_data)
+{
+    Private *priv;
+    GTask   *task;
+
+    task = g_task_new (self, NULL, callback, user_data);
+    g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+    priv = get_private (MM_SHARED_SIMTECH (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)) {
+        priv->iface_modem_location_parent->enable_location_gathering (self,
+                                                                      source,
+                                                                      (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+                                                                      task);
+        return;
+    }
+
+    /* 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_GPS_UNMANAGED));
+
+    /* If GPS already started, store new flag and we're done */
+    if (priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+                                 MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+                                 MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+        priv->enabled_sources |= source;
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    g_assert (priv->cgps_support == FEATURE_SUPPORTED);
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              "+CGPS=1,1",
+                              10,
+                              FALSE,
+                              (GAsyncReadyCallback) enable_cgps_ready,
+                              task);
+}
+
+/*****************************************************************************/
+/* Common enable/disable voice unsolicited events */
+
+typedef struct {
+    gboolean        enable;
+    MMPortSerialAt *primary;
+    MMPortSerialAt *secondary;
+    gchar          *clcc_command;
+    gboolean        clcc_primary_done;
+    gboolean        clcc_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->clcc_command);
+    g_slice_free (VoiceUnsolicitedEventsContext, ctx);
+}
+
+static gboolean
+common_voice_enable_disable_unsolicited_events_finish (MMSharedSimtech  *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
+clcc_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 +CLCC 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)
+{
+    MMSharedSimtech               *self;
+    Private                       *priv;
+    VoiceUnsolicitedEventsContext *ctx;
+    MMPortSerialAt                *port = NULL;
+
+    self = MM_SHARED_SIMTECH (g_task_get_source_object (task));
+    priv = get_private (self);
+    ctx  = g_task_get_task_data (task);
+
+    /* If +CLCC URCs not supported, we're done */
+    if (priv->clcc_urc_support == FEATURE_NOT_SUPPORTED) {
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    if (!ctx->clcc_primary_done && ctx->primary) {
+        mm_dbg ("%s +CLCC extended list of current calls reporting in primary port...",
+                ctx->enable ? "Enabling" : "Disabling");
+        ctx->clcc_primary_done = TRUE;
+        port = ctx->primary;
+    } else if (!ctx->clcc_secondary_done && ctx->secondary) {
+        mm_dbg ("%s +CLCC extended list of current calls reporting in secondary port...",
+                ctx->enable ? "Enabling" : "Disabling");
+        ctx->clcc_secondary_done = TRUE;
+        port = ctx->secondary;
+    }
+
+    if (port) {
+        mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+                                       port,
+                                       ctx->clcc_command,
+                                       3,
+                                       FALSE,
+                                       FALSE,
+                                       NULL,
+                                       (GAsyncReadyCallback)clcc_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 (MMSharedSimtech     *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->clcc_command = g_strdup ("+CLCC=1");
+    else
+        ctx->clcc_command = g_strdup ("+CLCC=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_simtech_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_SIMTECH (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 (MMSharedSimtech *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 Simtech-specific voice unsolicited events: %s", error->message);
+        g_error_free (error);
+    }
+
+    priv = get_private (MM_SHARED_SIMTECH (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_simtech_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_SIMTECH (self),
+                                                    FALSE,
+                                                    (GAsyncReadyCallback) voice_disable_unsolicited_events_ready,
+                                                    task);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_simtech_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 (MMSharedSimtech *self,
+                                       GAsyncResult      *res,
+                                       GTask             *task)
+{
+    GError *error = NULL;
+
+    if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
+        mm_warn ("Couldn't enable Simtech-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_SIMTECH (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_SIMTECH (self),
+                                                    TRUE,
+                                                    (GAsyncReadyCallback) voice_enable_unsolicited_events_ready,
+                                                    task);
+}
+
+void
+mm_shared_simtech_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_SIMTECH (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
+clcc_urc_received (MMPortSerialAt  *port,
+                   GMatchInfo      *match_info,
+                   MMSharedSimtech *self)
+{
+    gchar  *full;
+    GError *error = NULL;
+    GList  *call_info_list = NULL;
+
+    full = g_match_info_fetch (match_info, 0);
+
+    if (!mm_simtech_parse_clcc_list (full, &call_info_list, &error)) {
+        mm_warn ("couldn't parse +CLCC list in URC: %s", error->message);
+        g_error_free (error);
+    } else
+        mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list);
+
+    mm_simtech_call_info_list_free (call_info_list);
+    g_free (full);
+}
+
+static void
+missed_call_urc_received (MMPortSerialAt  *port,
+                          GMatchInfo      *match_info,
+                          MMSharedSimtech *self)
+{
+    GError *error = NULL;
+    gchar  *details = NULL;
+
+    if (!mm_simtech_parse_missed_call_urc (match_info, &details, &error)) {
+        mm_warn ("couldn't parse missed call URC: %s", error->message);
+        g_error_free (error);
+        return;
+    }
+
+    mm_dbg ("missed call reported: %s", details);
+    g_free (details);
+}
+
+static void
+voice_call_urc_received (MMPortSerialAt  *port,
+                         GMatchInfo      *match_info,
+                         MMSharedSimtech *self)
+{
+    GError   *error = NULL;
+    gboolean  start_or_stop = FALSE; /* start = TRUE, stop = FALSE */
+    guint     duration = 0;
+
+    if (!mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error)) {
+        mm_warn ("couldn't parse VOICE CALL URC: %s", error->message);
+        g_error_free (error);
+        return;
+    }
+
+    if (start_or_stop) {
+        mm_dbg ("voice call started");
+        return;
+    }
+
+    if (duration) {
+        mm_dbg ("voice call finished (duration: %us)", duration);
+        return;
+    }
+
+    mm_dbg ("voice call finished");
+}
+
+static void
+cring_urc_received (MMPortSerialAt  *port,
+                    GMatchInfo      *info,
+                    MMSharedSimtech *self)
+{
+    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);
+
+    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
+rxdtmf_urc_received (MMPortSerialAt  *port,
+                     GMatchInfo      *match_info,
+                     MMSharedSimtech *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 (MMSharedSimtech *self,
+                                               gboolean         enable)
+{
+    Private        *priv;
+    MMPortSerialAt *ports[2];
+    guint           i;
+
+    priv = get_private (MM_SHARED_SIMTECH (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;
+
+        if (priv->clcc_urc_support == FEATURE_SUPPORTED)
+            mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+                                                           priv->clcc_urc_regex,
+                                                           enable ? (MMPortSerialAtUnsolicitedMsgFn)clcc_urc_received : NULL,
+                                                           enable ? self : NULL,
+                                                           NULL);
+
+        mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+                                                       priv->voice_call_regex,
+                                                       enable ? (MMPortSerialAtUnsolicitedMsgFn)voice_call_urc_received : NULL,
+                                                       enable ? self : NULL,
+                                                       NULL);
+
+        mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+                                                       priv->missed_call_regex,
+                                                       enable ? (MMPortSerialAtUnsolicitedMsgFn)missed_call_urc_received : NULL,
+                                                       enable ? self : NULL,
+                                                       NULL);
+
+        mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+                                                       priv->cring_regex,
+                                                       enable ? (MMPortSerialAtUnsolicitedMsgFn)cring_urc_received : NULL,
+                                                       enable ? self : NULL,
+                                                       NULL);
+
+        mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+                                                       priv->rxdtmf_regex,
+                                                       enable ? (MMPortSerialAtUnsolicitedMsgFn)rxdtmf_urc_received : NULL,
+                                                       enable ? self : NULL,
+                                                       NULL);
+    }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_simtech_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_SIMTECH (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_simtech_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_SIMTECH (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_SIMTECH (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_simtech_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_SIMTECH (self));
+
+    if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
+        mm_warn ("Couldn't setup parent voice unsolicited events: %s", error->message);
+        g_error_free (error);
+    }
+
+    /* our own setup next */
+    common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), TRUE);
+
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+void
+mm_shared_simtech_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_SIMTECH (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);
+}
+
+/*****************************************************************************/
+/* In-call audio channel setup/cleanup */
+
+gboolean
+mm_shared_simtech_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice  *self,
+                                                            GAsyncResult       *res,
+                                                            MMPort            **audio_port,   /* optional */
+                                                            MMCallAudioFormat **audio_format, /* optional */
+                                                            GError            **error)
+{
+    Private *priv;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    if (!g_task_propagate_boolean (G_TASK (res), error))
+        return FALSE;
+
+    if (audio_format)
+        *audio_format = NULL;
+
+    if (audio_port) {
+        if (priv->cpcmreg_support == FEATURE_SUPPORTED)
+            *audio_port = MM_PORT (mm_base_modem_get_port_audio (MM_BASE_MODEM (self)));
+        else
+            *audio_port = NULL;
+    }
+
+    return TRUE;
+}
+
+gboolean
+mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice  *self,
+                                                              GAsyncResult       *res,
+                                                              GError            **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cpcmreg_set_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
+common_setup_cleanup_in_call_audio_channel (MMSharedSimtech     *self,
+                                            gboolean             setup,
+                                            GAsyncReadyCallback  callback,
+                                            gpointer             user_data)
+{
+    GTask   *task;
+    Private *priv;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    /* Do nothing if CPCMREG isn't supported */
+    if (priv->cpcmreg_support != FEATURE_SUPPORTED) {
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              setup ? "+CPCMREG=1" : "+CPCMREG=0",
+                              3,
+                              FALSE,
+                              (GAsyncReadyCallback) cpcmreg_set_ready,
+                              task);
+}
+
+void
+mm_shared_simtech_voice_setup_in_call_audio_channel (MMIfaceModemVoice   *self,
+                                                     GAsyncReadyCallback  callback,
+                                                     gpointer             user_data)
+{
+    common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), TRUE, callback, user_data);
+}
+
+void
+mm_shared_simtech_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice   *self,
+                                                       GAsyncReadyCallback  callback,
+                                                       gpointer             user_data)
+{
+    common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), FALSE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Check if Voice supported (Voice interface) */
+
+gboolean
+mm_shared_simtech_voice_check_support_finish (MMIfaceModemVoice  *self,
+                                              GAsyncResult       *res,
+                                              GError            **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cpcmreg_format_check_ready (MMBroadbandModem *self,
+                            GAsyncResult     *res,
+                            GTask            *task)
+{
+    Private *priv;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    priv->cpcmreg_support = (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ?
+                             FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED);
+    mm_dbg ("modem %s USB audio control", (priv->cpcmreg_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support");
+
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+clcc_format_check_ready (MMBroadbandModem *self,
+                         GAsyncResult     *res,
+                         GTask            *task)
+{
+    Private     *priv;
+    GError      *error = NULL;
+    const gchar *response;
+    gboolean     clcc_urc_supported = FALSE;
+
+    priv = get_private (MM_SHARED_SIMTECH (self));
+
+    response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+    if (response && !mm_simtech_parse_clcc_test (response, &clcc_urc_supported, &error)) {
+        mm_dbg ("failed checking CLCC URC support: %s", error->message);
+        g_clear_error (&error);
+    }
+
+    priv->clcc_urc_support = (clcc_urc_supported ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED);
+    mm_dbg ("modem %s +CLCC URCs", (priv->clcc_urc_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support");
+
+    /* If +CLCC URC supported we won't need polling in the parent */
+    g_object_set (self,
+                  MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->clcc_urc_support == FEATURE_SUPPORTED),
+                  NULL);
+
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              "+CPCMREG=?",
+                              3,
+                              TRUE,
+                              (GAsyncReadyCallback) cpcmreg_format_check_ready,
+                              task);
+}
+
+static void
+parent_voice_check_support_ready (MMIfaceModemVoice *self,
+                                  GAsyncResult      *res,
+                                  GTask             *task)
+{
+    Private *priv;
+    GError  *error = NULL;
+
+    priv = get_private (MM_SHARED_SIMTECH (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 +CLCC URCs are available */
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              "+CLCC=?",
+                              3,
+                              TRUE,
+                              (GAsyncReadyCallback) clcc_format_check_ready,
+                              task);
+}
+
+void
+mm_shared_simtech_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_SIMTECH (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);
+}
+
+/*****************************************************************************/
+
+static void
+shared_simtech_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_simtech_get_type (void)
+{
+    static GType shared_simtech_type = 0;
+
+    if (!G_UNLIKELY (shared_simtech_type)) {
+        static const GTypeInfo info = {
+            sizeof (MMSharedSimtech),  /* class_size */
+            shared_simtech_init,       /* base_init */
+            NULL,                  /* base_finalize */
+        };
+
+        shared_simtech_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedSimtech", &info, 0);
+        g_type_interface_add_prerequisite (shared_simtech_type, MM_TYPE_IFACE_MODEM_LOCATION);
+    }
+
+    return shared_simtech_type;
+}
diff --git a/plugins/simtech/mm-shared-simtech.h b/plugins/simtech/mm-shared-simtech.h
new file mode 100644
index 0000000..37a221c
--- /dev/null
+++ b/plugins/simtech/mm-shared-simtech.h
@@ -0,0 +1,129 @@
+/* -*- 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_SHARED_SIMTECH_H
+#define MM_SHARED_SIMTECH_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-modem.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+
+#define MM_TYPE_SHARED_SIMTECH               (mm_shared_simtech_get_type ())
+#define MM_SHARED_SIMTECH(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_SIMTECH, MMSharedSimtech))
+#define MM_IS_SHARED_SIMTECH(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_SIMTECH))
+#define MM_SHARED_SIMTECH_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_SIMTECH, MMSharedSimtech))
+
+typedef struct _MMSharedSimtech MMSharedSimtech;
+
+struct _MMSharedSimtech {
+    GTypeInterface g_iface;
+
+    /* Peek location interface of the parent class of the object */
+    MMIfaceModemLocation *  (* peek_parent_location_interface) (MMSharedSimtech *self);
+
+    /* Peek voice interface of the parent class of the object */
+    MMIfaceModemVoice *  (* peek_parent_voice_interface) (MMSharedSimtech *self);
+};
+
+GType mm_shared_simtech_get_type (void);
+
+/*****************************************************************************/
+/* Location interface */
+
+void                  mm_shared_simtech_location_load_capabilities        (MMIfaceModemLocation  *self,
+                                                                           GAsyncReadyCallback    callback,
+                                                                           gpointer               user_data);
+MMModemLocationSource mm_shared_simtech_location_load_capabilities_finish (MMIfaceModemLocation  *self,
+                                                                           GAsyncResult          *res,
+                                                                           GError               **error);
+
+void                  mm_shared_simtech_enable_location_gathering         (MMIfaceModemLocation   *self,
+                                                                           MMModemLocationSource   source,
+                                                                           GAsyncReadyCallback     callback,
+                                                                           gpointer                user_data);
+gboolean              mm_shared_simtech_enable_location_gathering_finish  (MMIfaceModemLocation   *self,
+                                                                           GAsyncResult           *res,
+                                                                           GError                **error);
+
+void                  mm_shared_simtech_disable_location_gathering        (MMIfaceModemLocation   *self,
+                                                                           MMModemLocationSource   source,
+                                                                           GAsyncReadyCallback     callback,
+                                                                           gpointer                user_data);
+gboolean              mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation   *self,
+                                                                           GAsyncResult           *res,
+                                                                           GError                **error);
+
+
+/*****************************************************************************/
+/* Voice interface */
+
+void     mm_shared_simtech_voice_check_support                     (MMIfaceModemVoice   *self,
+                                                                    GAsyncReadyCallback  callback,
+                                                                    gpointer             user_data);
+gboolean mm_shared_simtech_voice_check_support_finish              (MMIfaceModemVoice    *self,
+                                                                    GAsyncResult         *res,
+                                                                    GError              **error);
+
+void     mm_shared_simtech_voice_setup_unsolicited_events          (MMIfaceModemVoice    *self,
+                                                                    GAsyncReadyCallback   callback,
+                                                                    gpointer              user_data);
+gboolean mm_shared_simtech_voice_setup_unsolicited_events_finish   (MMIfaceModemVoice    *self,
+                                                                    GAsyncResult         *res,
+                                                                    GError              **error);
+
+void     mm_shared_simtech_voice_cleanup_unsolicited_events        (MMIfaceModemVoice    *self,
+                                                                    GAsyncReadyCallback   callback,
+                                                                    gpointer              user_data);
+gboolean mm_shared_simtech_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice    *self,
+                                                                    GAsyncResult         *res,
+                                                                    GError              **error);
+
+void     mm_shared_simtech_voice_enable_unsolicited_events         (MMIfaceModemVoice    *self,
+                                                                    GAsyncReadyCallback   callback,
+                                                                    gpointer              user_data);
+gboolean mm_shared_simtech_voice_enable_unsolicited_events_finish  (MMIfaceModemVoice    *self,
+                                                                    GAsyncResult         *res,
+                                                                    GError              **error);
+
+void     mm_shared_simtech_voice_disable_unsolicited_events        (MMIfaceModemVoice    *self,
+                                                                    GAsyncReadyCallback   callback,
+                                                                    gpointer              user_data);
+gboolean mm_shared_simtech_voice_disable_unsolicited_events_finish (MMIfaceModemVoice    *self,
+                                                                    GAsyncResult         *res,
+                                                                    GError              **error);
+
+void     mm_shared_simtech_voice_setup_in_call_audio_channel          (MMIfaceModemVoice    *self,
+                                                                       GAsyncReadyCallback   callback,
+                                                                       gpointer              user_data);
+gboolean mm_shared_simtech_voice_setup_in_call_audio_channel_finish   (MMIfaceModemVoice    *self,
+                                                                       GAsyncResult         *res,
+                                                                       MMPort              **audio_port,   /* optional */
+                                                                       MMCallAudioFormat   **audio_format, /* optional */
+                                                                       GError              **error);
+void     mm_shared_simtech_voice_cleanup_in_call_audio_channel        (MMIfaceModemVoice    *self,
+                                                                       GAsyncReadyCallback   callback,
+                                                                       gpointer              user_data);
+gboolean mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice    *self,
+                                                                       GAsyncResult         *res,
+                                                                       GError              **error);
+
+#endif  /* MM_SHARED_SIMTECH_H */
diff --git a/plugins/simtech/tests/test-modem-helpers-simtech.c b/plugins/simtech/tests/test-modem-helpers-simtech.c
new file mode 100644
index 0000000..2e93d89
--- /dev/null
+++ b/plugins/simtech/tests/test-modem-helpers-simtech.c
@@ -0,0 +1,363 @@
+/* -*- 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 <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-simtech.h"
+
+/*****************************************************************************/
+/* Test +CLCC URCs */
+
+static void
+common_test_clcc_urc (const gchar      *urc,
+                      const MMCallInfo *expected_call_info_list,
+                      guint             expected_call_info_list_size)
+{
+    GError     *error = NULL;
+    GRegex     *clcc_regex = NULL;
+    gboolean    result;
+    GMatchInfo *match_info = NULL;
+    gchar      *str;
+    GList      *call_info_list = NULL;
+    GList      *l;
+
+    clcc_regex = mm_simtech_get_clcc_urc_regex ();
+
+    /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+    result = g_regex_match_full (clcc_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_simtech_parse_clcc_list (str, &call_info_list, &error);
+    g_assert_no_error (error);
+    g_assert (result);
+
+    g_debug ("found %u calls", 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_debug ("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");
+
+        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 (clcc_regex);
+    g_free (str);
+
+    mm_simtech_call_info_list_free (call_info_list);
+}
+
+static void
+test_clcc_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+CLCC: 1,1,0,0,0,\"123456789\",161"
+        "\r\n";
+
+    common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_clcc_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+CLCC: 1,1,0,0,0" /* number unknown */
+        "\r\n+CLCC: 2,1,0,0,0,\"123456789\",161"
+        "\r\n+CLCC: 3,1,0,0,0,\"987654321\",161,\"Alice\""
+        "\r\n";
+
+    common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_clcc_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+CLCC: 1,1,0,0,0,\"123456789\",161"
+        "\r\n+CLCC: 2,1,5,0,0,\"987654321\",161"
+        "\r\n^CIEV: 1,0" /* some different URC after our match */
+        "\r\n";
+
+    common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+/*****************************************************************************/
+
+static void
+common_test_voice_call_urc (const gchar *urc,
+                            gboolean     expected_start_or_stop,
+                            guint        expected_duration)
+{
+    GError     *error = NULL;
+    gboolean    start_or_stop = FALSE; /* start = TRUE, stop = FALSE */
+    guint       duration = 0;
+    GRegex     *voice_call_regex = NULL;
+    gboolean    result;
+    GMatchInfo *match_info = NULL;
+
+    voice_call_regex = mm_simtech_get_voice_call_urc_regex ();
+
+    /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+    result = g_regex_match_full (voice_call_regex, urc, -1, 0, 0, &match_info, &error);
+    g_assert_no_error (error);
+    g_assert (result);
+
+    result = mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error);
+    g_assert_no_error (error);
+    g_assert (result);
+
+    g_assert_cmpuint (expected_start_or_stop, ==, start_or_stop);
+    g_assert_cmpuint (expected_duration, ==, duration);
+
+    g_match_info_free (match_info);
+    g_regex_unref (voice_call_regex);
+}
+
+static void
+test_voice_call_begin_urc (void)
+{
+    common_test_voice_call_urc ("\r\nVOICE CALL: BEGIN\r\n", TRUE, 0);
+}
+
+static void
+test_voice_call_end_urc (void)
+{
+    common_test_voice_call_urc ("\r\nVOICE CALL: END\r\n", FALSE, 0);
+}
+
+static void
+test_voice_call_end_duration_urc (void)
+{
+    common_test_voice_call_urc ("\r\nVOICE CALL: END: 000041\r\n", FALSE, 41);
+}
+
+/*****************************************************************************/
+
+static void
+common_test_missed_call_urc (const gchar *urc,
+                             const gchar *expected_details)
+{
+    GError     *error = NULL;
+    gchar      *details = NULL;
+    GRegex     *missed_call_regex = NULL;
+    gboolean    result;
+    GMatchInfo *match_info = NULL;
+
+    missed_call_regex = mm_simtech_get_missed_call_urc_regex ();
+
+    /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+    result = g_regex_match_full (missed_call_regex, urc, -1, 0, 0, &match_info, &error);
+    g_assert_no_error (error);
+    g_assert (result);
+
+    result = mm_simtech_parse_missed_call_urc (match_info, &details, &error);
+    g_assert_no_error (error);
+    g_assert (result);
+
+    g_assert_cmpstr (expected_details, ==, details);
+    g_free (details);
+
+    g_match_info_free (match_info);
+    g_regex_unref (missed_call_regex);
+}
+
+static void
+test_missed_call_urc (void)
+{
+    common_test_missed_call_urc ("\r\nMISSED_CALL: 11:01AM 07712345678\r\n", "11:01AM 07712345678");
+}
+
+/*****************************************************************************/
+
+static void
+common_test_cring_urc (const gchar *urc,
+                       const gchar *expected_type)
+{
+    GError     *error = NULL;
+    GRegex     *cring_regex = NULL;
+    GMatchInfo *match_info = NULL;
+    gchar      *type;
+    gboolean    result;
+
+    cring_regex = mm_simtech_get_cring_urc_regex ();
+
+    /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+    result = g_regex_match_full (cring_regex, urc, -1, 0, 0, &match_info, &error);
+    g_assert_no_error (error);
+    g_assert (result);
+
+    type = g_match_info_fetch (match_info, 1);
+    g_assert (type);
+
+    g_assert_cmpstr (type, ==, expected_type);
+
+    g_match_info_free (match_info);
+    g_regex_unref (cring_regex);
+    g_free (type);
+}
+
+static void
+test_cring_urc_two_crs (void)
+{
+    common_test_cring_urc ("\r\r\n+CRING: VOICE\r\r\n", "VOICE");
+}
+
+static void
+test_cring_urc_one_cr (void)
+{
+    common_test_cring_urc ("\r\n+CRING: VOICE\r\n", "VOICE");
+}
+
+/*****************************************************************************/
+
+static void
+common_test_rxdtmf_urc (const gchar *urc,
+                        const gchar *expected_str)
+{
+    GError     *error = NULL;
+    GRegex     *rxdtmf_regex = NULL;
+    GMatchInfo *match_info = NULL;
+    gchar      *type;
+    gboolean    result;
+
+    rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex ();
+
+    /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+    result = g_regex_match_full (rxdtmf_regex, urc, -1, 0, 0, &match_info, &error);
+    g_assert_no_error (error);
+    g_assert (result);
+
+    type = g_match_info_fetch (match_info, 1);
+    g_assert (type);
+
+    g_assert_cmpstr (type, ==, expected_str);
+
+    g_match_info_free (match_info);
+    g_regex_unref (rxdtmf_regex);
+    g_free (type);
+}
+
+static void
+test_rxdtmf_urc_two_crs (void)
+{
+    common_test_rxdtmf_urc ("\r\r\n+RXDTMF: 8\r\r\n", "8");
+    common_test_rxdtmf_urc ("\r\r\n+RXDTMF: *\r\r\n", "*");
+    common_test_rxdtmf_urc ("\r\r\n+RXDTMF: #\r\r\n", "#");
+    common_test_rxdtmf_urc ("\r\r\n+RXDTMF: A\r\r\n", "A");
+}
+
+static void
+test_rxdtmf_urc_one_cr (void)
+{
+    common_test_rxdtmf_urc ("\r\n+RXDTMF: 8\r\n", "8");
+    common_test_rxdtmf_urc ("\r\n+RXDTMF: *\r\n", "*");
+    common_test_rxdtmf_urc ("\r\n+RXDTMF: #\r\n", "#");
+    common_test_rxdtmf_urc ("\r\n+RXDTMF: A\r\n", "A");
+}
+
+/*****************************************************************************/
+
+void
+_mm_log (const char *loc,
+         const char *func,
+         guint32 level,
+         const char *fmt,
+         ...)
+{
+    va_list args;
+    gchar *msg;
+
+    if (!g_test_verbose ())
+        return;
+
+    va_start (args, fmt);
+    msg = g_strdup_vprintf (fmt, args);
+    va_end (args);
+    g_print ("%s\n", msg);
+    g_free (msg);
+}
+
+int main (int argc, char **argv)
+{
+    setlocale (LC_ALL, "");
+
+    g_test_init (&argc, &argv, NULL);
+
+    g_test_add_func ("/MM/simtech/clcc/urc/single",   test_clcc_urc_single);
+    g_test_add_func ("/MM/simtech/clcc/urc/multiple", test_clcc_urc_multiple);
+    g_test_add_func ("/MM/simtech/clcc/urc/complex",  test_clcc_urc_complex);
+
+    g_test_add_func ("/MM/simtech/voicecall/urc/begin",        test_voice_call_begin_urc);
+    g_test_add_func ("/MM/simtech/voicecall/urc/end",          test_voice_call_end_urc);
+    g_test_add_func ("/MM/simtech/voicecall/urc/end-duration", test_voice_call_end_duration_urc);
+
+    g_test_add_func ("/MM/simtech/missedcall/urc", test_missed_call_urc);
+
+    g_test_add_func ("/MM/simtech/cring/urc/two-crs", test_cring_urc_two_crs);
+    g_test_add_func ("/MM/simtech/cring/urc/one-cr", test_cring_urc_one_cr);
+
+    g_test_add_func ("/MM/simtech/rxdtmf/urc/two-crs", test_rxdtmf_urc_two_crs);
+    g_test_add_func ("/MM/simtech/rxdtmf/urc/one-cr", test_rxdtmf_urc_one_cr);
+
+    return g_test_run ();
+}
diff --git a/plugins/telit/tests/test-mm-modem-helpers-telit.c b/plugins/telit/tests/test-mm-modem-helpers-telit.c
index f95a97d..c9c30e5 100644
--- a/plugins/telit/tests/test-mm-modem-helpers-telit.c
+++ b/plugins/telit/tests/test-mm-modem-helpers-telit.c
@@ -553,17 +553,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/tests/test-helpers.c b/plugins/tests/test-helpers.c
index 5620c04..1fe2cdc 100644
--- a/plugins/tests/test-helpers.c
+++ b/plugins/tests/test-helpers.c
@@ -24,8 +24,8 @@
 
 void
 mm_test_helpers_compare_bands (GArray            *bands,
-			       const MMModemBand *expected_bands,
-			       guint              n_expected_bands)
+                               const MMModemBand *expected_bands,
+                               guint              n_expected_bands)
 {
     gchar  *bands_str;
     GArray *expected_bands_array;
diff --git a/plugins/tests/test-keyfiles.c b/plugins/tests/test-keyfiles.c
index 9604643..c1c740c 100644
--- a/plugins/tests/test-keyfiles.c
+++ b/plugins/tests/test-keyfiles.c
@@ -57,17 +57,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/tests/test-udev-rules.c b/plugins/tests/test-udev-rules.c
index 581adca..f6dc6f4 100644
--- a/plugins/tests/test-udev-rules.c
+++ b/plugins/tests/test-udev-rules.c
@@ -22,9 +22,6 @@
 #define _LIBMM_INSIDE_MM
 #include <libmm-glib.h>
 
-/* Define symbol to enable test message traces */
-#undef ENABLE_TEST_MESSAGE_TRACES
-
 #include "mm-kernel-device-generic-rules.h"
 #include "mm-log.h"
 
@@ -133,17 +130,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c b/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c
index bc05b7c..dcd4ab2 100644
--- a/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c
+++ b/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c
@@ -26,12 +26,6 @@
 #include "mm-modem-helpers-thuraya.h"
 #include "mm-log.h"
 
-#if defined ENABLE_TEST_MESSAGE_TRACES
-#define trace(message, ...) g_print (message, ##__VA_ARGS__)
-#else
-#define trace(...)
-#endif
-
 /*****************************************************************************/
 /* Test CPMS response */
 
@@ -62,7 +56,7 @@
     GArray *mem2 = NULL;
     GArray *mem3 = NULL;
 
-    trace ("\nTesting thuraya +CPMS=? response...\n");
+    g_debug ("Testing thuraya +CPMS=? response...");
 
     g_assert (mm_thuraya_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3));
     g_assert_cmpuint (mem1->len, ==, 5);
@@ -98,17 +92,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     va_start (args, fmt);
     msg = g_strdup_vprintf (fmt, args);
     va_end (args);
     g_print ("%s\n", msg);
     g_free (msg);
-#endif
 }
 
 #define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (GTestFixtureFunc) t, NULL)
diff --git a/plugins/ublox/mm-broadband-modem-ublox.c b/plugins/ublox/mm-broadband-modem-ublox.c
index 509c4c6..d27599b 100644
--- a/plugins/ublox/mm-broadband-modem-ublox.c
+++ b/plugins/ublox/mm-broadband-modem-ublox.c
@@ -1208,8 +1208,8 @@
 {
     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);
+    if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
+        mm_warn ("Couldn't setup parent voice unsolicited events: %s", error->message);
         g_error_free (error);
     }
 
diff --git a/plugins/ublox/tests/test-modem-helpers-ublox.c b/plugins/ublox/tests/test-modem-helpers-ublox.c
index 5fad8a7..17a4641 100644
--- a/plugins/ublox/tests/test-modem-helpers-ublox.c
+++ b/plugins/ublox/tests/test-modem-helpers-ublox.c
@@ -986,17 +986,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/plugins/x22x/77-mm-x22x-port-types.rules b/plugins/x22x/77-mm-x22x-port-types.rules
index 9ba0ae4..9d58796 100644
--- a/plugins/x22x/77-mm-x22x-port-types.rules
+++ b/plugins/x22x/77-mm-x22x-port-types.rules
@@ -40,6 +40,13 @@
 ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
 ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{ID_MM_X22X_TAGGED}="1"
 
+# Alcaltel X602D
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{ID_MM_X22X_TAGGED}="1"
+
 GOTO="mm_x22x_port_types_end"
 
 # Olivetti devices ---------------------------
diff --git a/plugins/x22x/mm-broadband-modem-x22x.c b/plugins/x22x/mm-broadband-modem-x22x.c
index ed9aec5..0d7794a 100644
--- a/plugins/x22x/mm-broadband-modem-x22x.c
+++ b/plugins/x22x/mm-broadband-modem-x22x.c
@@ -38,6 +38,13 @@
 G_DEFINE_TYPE_EXTENDED (MMBroadbandModemX22x, mm_broadband_modem_x22x, MM_TYPE_BROADBAND_MODEM, 0,
                         G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init))
 
+struct _MMBroadbandModemX22xPrivate {
+    GRegex *mode_regex;
+    GRegex *sysinfo_regex;
+    GRegex *specc_regex;
+    GRegex *sperror_regex;
+};
+
 /*****************************************************************************/
 /* Load supported modes (Modem interface) */
 
@@ -303,6 +310,52 @@
 }
 
 /*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+set_ignored_unsolicited_events_handlers (MMBroadbandModemX22x *self)
+{
+    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));
+
+    /* Enable/disable unsolicited events in given port */
+    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->mode_regex,
+            NULL, NULL, NULL);
+        mm_port_serial_at_add_unsolicited_msg_handler (
+            ports[i],
+            self->priv->sysinfo_regex,
+            NULL, NULL, NULL);
+        mm_port_serial_at_add_unsolicited_msg_handler (
+            ports[i],
+            self->priv->specc_regex,
+            NULL, NULL, NULL);
+        mm_port_serial_at_add_unsolicited_msg_handler (
+            ports[i],
+            self->priv->sperror_regex,
+            NULL, NULL, NULL);
+    }
+}
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+    /* Call parent's setup ports first always */
+    MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_x22x_parent_class)->setup_ports (self);
+
+    /* Unsolicited messages to always ignore */
+    set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_X22X (self));
+}
+
+/*****************************************************************************/
 
 MMBroadbandModemX22x *
 mm_broadband_modem_x22x_new (const gchar *device,
@@ -323,6 +376,30 @@
 static void
 mm_broadband_modem_x22x_init (MMBroadbandModemX22x *self)
 {
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                              MM_TYPE_BROADBAND_MODEM_X22X,
+                                              MMBroadbandModemX22xPrivate);
+
+    self->priv->mode_regex    = g_regex_new ("\\r\\n\\^MODE:.+\\r\\n",
+                                             G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+    self->priv->sysinfo_regex = g_regex_new ("\\r\\n\\^SYSINFO:.+\\r\\n",
+                                             G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+    self->priv->specc_regex   = g_regex_new ("\\r\\n\\+SPECC\\r\\n",
+                                             G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+    self->priv->sperror_regex = g_regex_new ("\\r\\n\\+SPERROR:.+\\r\\n",
+                                             G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+    MMBroadbandModemX22x *self = MM_BROADBAND_MODEM_X22X (object);
+
+    g_regex_unref (self->priv->mode_regex);
+    g_regex_unref (self->priv->sysinfo_regex);
+    g_regex_unref (self->priv->specc_regex);
+    g_regex_unref (self->priv->sperror_regex);
+    G_OBJECT_CLASS (mm_broadband_modem_x22x_parent_class)->finalize (object);
 }
 
 static void
@@ -343,4 +420,12 @@
 static void
 mm_broadband_modem_x22x_class_init (MMBroadbandModemX22xClass *klass)
 {
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMBroadbandModemX22xPrivate));
+
+    object_class->finalize = finalize;
+
+    broadband_modem_class->setup_ports = setup_ports;
 }
diff --git a/plugins/x22x/mm-broadband-modem-x22x.h b/plugins/x22x/mm-broadband-modem-x22x.h
index f61e301..74c2b48 100644
--- a/plugins/x22x/mm-broadband-modem-x22x.h
+++ b/plugins/x22x/mm-broadband-modem-x22x.h
@@ -29,9 +29,11 @@
 
 typedef struct _MMBroadbandModemX22x MMBroadbandModemX22x;
 typedef struct _MMBroadbandModemX22xClass MMBroadbandModemX22xClass;
+typedef struct _MMBroadbandModemX22xPrivate MMBroadbandModemX22xPrivate;
 
 struct _MMBroadbandModemX22x {
     MMBroadbandModem parent;
+    MMBroadbandModemX22xPrivate *priv;
 };
 
 struct _MMBroadbandModemX22xClass{
diff --git a/plugins/xmm/tests/test-modem-helpers-xmm.c b/plugins/xmm/tests/test-modem-helpers-xmm.c
index 28aea93..b4a8b2b 100644
--- a/plugins/xmm/tests/test-modem-helpers-xmm.c
+++ b/plugins/xmm/tests/test-modem-helpers-xmm.c
@@ -760,17 +760,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/po/cs.po b/po/cs.po
index 7427623..568e43f 100644
--- a/po/cs.po
+++ b/po/cs.po
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2017-10-22 11:32+0200\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2017-10-21 15:32+0200\n"
 "Last-Translator: Marek Černocký <marek@manet.cz>\n"
 "Language-Team: čeština <gnome-cs-list@gnome.org>\n"
@@ -69,22 +69,33 @@
 msgstr "Systémová zásada brání v hlasových hovorech."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Systémová zásada brání v dotazování na informace o síti a na služby, nebo "
+"brání v jejich využívání."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr ""
 "Povolovat sdělování a zobrazování geografické polohy a informací o pozici"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "Systémová zásada brání v povolení sdělování a v zobrazení informací o "
 "geografické poloze."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Dotazovat se na informace o síti a na služby a využívat je"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -92,12 +103,12 @@
 "Systémová zásada brání v dotazování na informace o síti a na služby, nebo "
 "brání v jejich využívání."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr ""
 "Dotazovat se na firmware a spravovat jej na mobilním širokopásmovém zařízení"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "Systémová zásada brání v dotázání na firmware nebo brání v jeho správě na "
diff --git a/po/da.po b/po/da.po
index 30c76d0..9473d0a 100644
--- a/po/da.po
+++ b/po/da.po
@@ -6,7 +6,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2019-02-11 17:22+0100\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2019-02-10 16:46+0200\n"
 "Last-Translator: scootergrisen\n"
 "Language-Team: Danish\n"
@@ -65,21 +65,32 @@
 msgstr "Systempolitikken forhindrer stemmeopkald."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Systempolitikken forhindrer forespørgsel eller anvendelse af "
+"netværksinformation og -tjenester."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr "Aktivér og vis information om geografisk placering og positition"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "Systempolitikken forhindrer aktivering og visning af information geografisk "
 "placering."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Forespørg og anvend netværksinformation og -tjenester"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -87,11 +98,11 @@
 "Systempolitikken forhindrer forespørgsel eller anvendelse af "
 "netværksinformation og -tjenester."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Forespørg og håndter firmware på en mobilt bredbånd-enhed"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "Systempolitikken forhindrer forespørgsel og håndtering af enhedens firmware."
diff --git a/po/de.po b/po/de.po
index 7fc9dc4..7e0281f 100644
--- a/po/de.po
+++ b/po/de.po
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2017-08-29 09:24+0200\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2014-01-06 21:23+0100\n"
 "Last-Translator: Mario Blättermann <mario.blaettermann@gmail.com>\n"
 "Language-Team: German <debian-l10n-german@lists.debian.org>\n"
@@ -70,23 +70,34 @@
 msgstr "Die Systemrichtlinien verhindern die Steuerung von ModemManager."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Die Systemrichtlinien verhindern die Abfrage der Netzwerkinformationen und -"
+"dienste."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr ""
 "Informationen zum geografischen Standort und Positionierung aktivieren und "
 "anzeigen"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "Die Systemrichtlinien verhindern das Aktivieren oder Ändern der "
 "Informationen zum geografischen Standort."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Netzwerkinformationen und -dienste abfragen und nutzen"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -94,11 +105,11 @@
 "Die Systemrichtlinien verhindern die Abfrage der Netzwerkinformationen und -"
 "dienste."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Firmware auf mobilen Breitbandgeräten abfragen und verwalten"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "Die Systemrichtlinien verhindern die Abfrage oder Verwaltung der Firmware "
diff --git a/po/fr.po b/po/fr.po
index 8408271..c86ecd1 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2018-09-13 09:33+0200\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2018-08-18 16:17+0200\n"
 "Last-Translator: Claude Paroz <claude@2xlibre.net>\n"
 "Language-Team: French <gnomefr@traduc.org>\n"
@@ -68,21 +68,32 @@
 msgstr "La politique système empêche les appels vocaux."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"La politique système empêche l’interrogation et l’utilisation des "
+"informations et des services du réseau."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr "Activer et voir les informations de position géographique"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "La politique système empêche d’activer ou de voir les informations de "
 "position géographique."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Interroger et utiliser les informations et services du réseau"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -90,11 +101,11 @@
 "La politique système empêche l’interrogation et l’utilisation des "
 "informations et des services du réseau."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Interroger et gérer le matériel d’un périphérique mobile à large bande"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "La politique système empêche l’interrogation et la gestion du matériel de ce "
diff --git a/po/fur.po b/po/fur.po
index 67e4961..536e482 100644
--- a/po/fur.po
+++ b/po/fur.po
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2018-06-19 17:58+0200\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2018-03-25 17:20+0200\n"
 "Last-Translator: Fabio Tomat <f.t.public@gmail.com>\n"
 "Language-Team: Friulian <f.t.public@gmail.com>\n"
@@ -69,22 +69,33 @@
 msgstr "La politiche dal sisteme e impedìs lis clamadis vocâls."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"La politiche dal sisteme e impedìs la interogazion e la utilizazion di "
+"informazions di rêt e servizis."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr ""
 "Abilite e viôt la posizion gjeografiche e lis informazions su la posizion"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "La politiche dal sisteme e impedìs di abilitâ o viodi lis informazions su la "
 "posizion gjeografiche."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Interoghe e dopre lis informazions di rêt e i servizis"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -92,12 +103,12 @@
 "La politiche dal sisteme e impedìs la interogazion e la utilizazion di "
 "informazions di rêt e servizis."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr ""
 "Interoghe e gjestìs il firmware suntun dispositîf a bande largje mobile"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "La politiche dal sisteme e impedìs di interogâ o gjestî il firmware di chest "
diff --git a/po/hu.po b/po/hu.po
index 53b9e9d..6b204c2 100644
--- a/po/hu.po
+++ b/po/hu.po
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: modemmanager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2017-09-27 21:46+0200\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2017-09-26 22:02+0000\n"
 "Last-Translator: Gabor Kelemen <kelemeng@openscope.org>\n"
 "Language-Team: Hungarian <hu@li.org>\n"
@@ -66,22 +66,33 @@
 msgstr "A rendszer házirendje nem teszi lehetővé a hívásokat."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"A rendszer házirendje nem teszi lehetővé a hálózati információk és "
+"szolgáltatások lekérdezését és használatát."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr ""
 "Földrajzi helyzetmeghatározás bekapcsolása és az információk megtekintése"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "A rendszer házirendje nem teszi lehetővé a földrajzi helyzetmeghatározás "
 "bekapcsolását vagy az információk megtekintését."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Hálózati információk és szolgáltatások lekérdezése és használata"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -89,11 +100,11 @@
 "A rendszer házirendje nem teszi lehetővé a hálózati információk és "
 "szolgáltatások lekérdezését és használatát."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Firmware lekérdezése és kezelése a mobil széles sávú eszközön"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "A rendszer lekérdezése és használata lehetővé a firmware lekérdezését és "
diff --git a/po/id.po b/po/id.po
index 9efd0cc..a6142a5 100644
--- a/po/id.po
+++ b/po/id.po
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2018-03-04 16:56+0100\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2018-03-04 20:31+0700\n"
 "Last-Translator: Andika Triwidada <andika@gmail.com>\n"
 "Language-Team: Indonesian <gnome-l10n-id@googlegroups.com>\n"
@@ -67,21 +67,32 @@
 msgstr "Kebijakan sistem mencegah panggilan suara."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Kebijakan sistem mencegah kueri atau pemanfaatan layanan dan informasi "
+"jaringan."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr "Fungsikan dan tilik lokasi geografis dan informasi posisi"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "Kebijakan sistem mencegah memfungsikan atau menilik informasi lokasi "
 "geografis."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Kueri dan manfaatkan layanan dan informasi jaringan"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -89,11 +100,11 @@
 "Kebijakan sistem mencegah kueri atau pemanfaatan layanan dan informasi "
 "jaringan."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Kueri dan kelola firmware pada suatu peranti data seluler"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr "Kebijakan sistem mencegah kueri atau pengelolaan firmware peranti ini."
 
diff --git a/po/it.po b/po/it.po
index b32735c..a15f503 100644
--- a/po/it.po
+++ b/po/it.po
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2018-09-13 09:33+0200\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2018-09-11 11:25+0200\n"
 "Last-Translator: Milo Casagrande <milo@milo.name>\n"
 "Language-Team: Italian <gnome-it-list@gnome.org>\n"
@@ -68,22 +68,33 @@
 msgstr "La politica di sistema impedisce di effettuare chiamate vocali."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"La politica di sistema impedisce di interrogare o di utilizzare le "
+"informazioni e i servizi della rete."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr ""
 "Abilita e visualizza informazioni di geolocalizzazione e posizionamento"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "La politica di sistema impedisce di abilitare o visualizzare informazioni di "
 "geolocalizzazione."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Interroga e utilizza informazioni e servizi della rete"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -91,12 +102,12 @@
 "La politica di sistema impedisce di interrogare o di utilizzare le "
 "informazioni e i servizi della rete."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr ""
 "Interroga e gestisce il firmware su un dispositivo mobile a banda larga"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "La politica di sistema impedisce di interrogare o gestire il firmware di "
diff --git a/po/lt.po b/po/lt.po
index ded731e..d238b02 100644
--- a/po/lt.po
+++ b/po/lt.po
@@ -7,7 +7,7 @@
 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"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2019-04-13 22:08+0300\n"
 "Last-Translator: \n"
 "Language-Team: \n"
@@ -72,20 +72,31 @@
 msgstr "Sistemos politika neleidžia balso skambučių."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Sistemos politika neleidžia užklausti ar panaudoti tinklo informacija ir "
+"paslaugas."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 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
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 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
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 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
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -93,13 +104,13 @@
 "Sistemos politika neleidžia užklausti ar panaudoti tinklo informacija ir "
 "paslaugas."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 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
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "Sistemos politika neleidžia užklausti ar tvarkyti šio įrenginio programinę "
diff --git a/po/pl.po b/po/pl.po
index f3c1803..928c378 100644
--- a/po/pl.po
+++ b/po/pl.po
@@ -1,15 +1,15 @@
 # Polish translation for ModemManager.
-# Copyright © 2017 the ModemManager authors.
+# Copyright © 2017-2019 the ModemManager authors.
 # This file is distributed under the same license as the ModemManager package.
-# Piotr Drąg <piotrdrag@gmail.com>, 2017.
-# Aviary.pl <community-poland@mozilla.org>, 2017.
+# Piotr Drąg <piotrdrag@gmail.com>, 2017-2019.
+# Aviary.pl <community-poland@mozilla.org>, 2017-2019.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: ModemManager\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2017-09-07 13:12+0200\n"
-"PO-Revision-Date: 2017-09-06 19:15+0200\n"
+"POT-Creation-Date: 2019-09-26 03:26+0000\n"
+"PO-Revision-Date: 2019-09-28 15:02+0200\n"
 "Last-Translator: Piotr Drąg <piotrdrag@gmail.com>\n"
 "Language-Team: Polish <community-poland@mozilla.org>\n"
 "Language: pl\n"
@@ -69,23 +69,32 @@
 msgstr "Ustawienia systemu uniemożliwiają dzwonienie."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr "Odpytywanie informacji o czasie sieciowym i strefie czasowej"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Ustawienia systemu uniemożliwiają odpytywanie informacji o czasie sieciowym."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr ""
 "Włączanie i wyświetlanie informacji o położeniu geograficznym "
 "i pozycjonowaniu"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "Ustawienia systemu uniemożliwiają włączanie lub wyświetlanie informacji "
 "o położeniu geograficznym."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Odpytywanie i używanie informacji i usług sieciowych"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -93,12 +102,12 @@
 "Ustawienia systemu uniemożliwiają odpytywanie lub używanie informacji "
 "i usług sieciowych."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr ""
 "Odpytywanie i zarządzanie oprogramowaniem sprzętowym urządzenia komórkowego"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "Ustawienia systemu uniemożliwiają odpytywanie lub zarządzanie "
diff --git a/po/pt_BR.po b/po/pt_BR.po
index a9d7446..d2860ac 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -6,7 +6,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2017-09-07 13:35+0200\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2017-09-05 21:49-0200\n"
 "Last-Translator: Rafael Fontenelle <rafaelff@gnome.org>\n"
 "Language-Team: Brazilian Portuguese <gnome-pt_br-list@gnome.org>\n"
@@ -67,21 +67,32 @@
 msgstr "A política de sistema impede chamadas de voz."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"A política de sistema impede de consultar ou utilizar serviços e informações "
+"de rede."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr "Habilitar e ver informações de posicionamento e localização geográfica"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "A política de sistema impede de habilitar ou ver informações de localização "
 "geográfica."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Consultar ou utilizar serviços e informações de rede."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -89,11 +100,11 @@
 "A política de sistema impede de consultar ou utilizar serviços e informações "
 "de rede."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Consultar e gerenciar firmware em um dispositivo de banda larga móvel"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "A política de sistema impede de consultar ou gerenciar o firmware do "
diff --git a/po/sk.po b/po/sk.po
index 46c858a..a8e342f 100644
--- a/po/sk.po
+++ b/po/sk.po
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2017-09-16 10:32-0700\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2017-09-16 08:51+0200\n"
 "Last-Translator: Dušan Kazik <prescott66@gmail.com>\n"
 "Language-Team: Slovak <gnome-sk-list@gnome.org>\n"
@@ -71,21 +71,32 @@
 msgstr "Politika systému zabraňuje hlasovým hovorom."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Politika systému zabraňuje požadovaniu, alebo spracovaniu sieťových "
+"informácií a službám."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr "Povolenie a zobrazenie geografickej polohy a informácií o pozícii"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "Politika systému zabraňuje povoleniu, alebo zobrazeniu informácií o "
 "geografickej polohe."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Požadovanie a spracovanie sieťových informácií a služieb"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -93,11 +104,11 @@
 "Politika systému zabraňuje požadovaniu, alebo spracovaniu sieťových "
 "informácií a službám."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Požadovanie a správa firmvéru mobilného širokopásmového zariadenia"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "Politika systému zabraňuje požadovaniu, alebo správe firmvéru tohto "
diff --git a/po/sv.po b/po/sv.po
index bff9c79..c6f0fcf 100644
--- a/po/sv.po
+++ b/po/sv.po
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: ModemManager master\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2017-12-05 10:49+0100\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2017-12-04 20:28+0100\n"
 "Last-Translator: Josef Andersson <l10nl18nsweja@gmail.com>\n"
 "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
@@ -68,21 +68,32 @@
 msgstr "En systempolicy förhindrar röstsamtal."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"En systempolicy förhindrar frågande och nyttjande av nätverksinformation och "
+"tjänster."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr "Aktivera och visa geografisk plats samt positioneringsinformation"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "En systempolicy förhindrar aktivering eller visning av geografisk "
 "platsinformation."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Fråga och nyttja nätverksinformation och tjänster"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -90,11 +101,11 @@
 "En systempolicy förhindrar frågande och nyttjande av nätverksinformation och "
 "tjänster."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Fråga och hantera fast programvara för en mobil bredbandsenhet"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "En systempolicy förhindrar att fråga och hantera denna enhets fasta "
diff --git a/po/tr.po b/po/tr.po
index 2f9ca15..85ce8ef 100644
--- a/po/tr.po
+++ b/po/tr.po
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2018-08-14 11:06+0200\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2018-06-23 18:53+0300\n"
 "Last-Translator: Emin Tufan Çetin <etcetin@gmail.com>\n"
 "Language-Team: Türkçe <gnome-turk@gnome.org>\n"
@@ -68,21 +68,32 @@
 msgstr "Sistem ilkesi sesli çağrıları engelliyor."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Sistem ilkesi ağ bilgisini ve hizmetleri sorgulamayı veya yararlanmayı "
+"engelliyor."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr "Coğrafi konum ve konumlandırma bilgisini etkinleştir ve gör"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "Sistem ilkesi coğrafi konum bilgisini etkinleştirmeyi ve göstermeyi "
 "engelliyor."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Ağ bilgisi ve hizmetleri sorgula ve yararlan"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -90,11 +101,11 @@
 "Sistem ilkesi ağ bilgisini ve hizmetleri sorgulamayı veya yararlanmayı "
 "engelliyor."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "Mobil geniş bant aygıtındaki donanım yazılımını sorgula ve yönet"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "Sistem ilkesi bu aygıtın donanım yazılımını sorgulamayı veya yönetmeyi "
diff --git a/po/uk.po b/po/uk.po
index 7fa3e58..f1412e0 100644
--- a/po/uk.po
+++ b/po/uk.po
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: Modem Manager\n"
 "Report-Msgid-Bugs-To: modemmanager-devel@lists.freedesktop.org\n"
-"POT-Creation-Date: 2017-11-12 13:40+0100\n"
+"POT-Creation-Date: 2019-09-25 12:48+0200\n"
 "PO-Revision-Date: 2017-11-11 18:33+0200\n"
 "Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n"
 "Language-Team: Ukrainian <kde-i18n-uk@kde.org>\n"
@@ -72,21 +72,32 @@
 msgstr "Правила системи перешкоджають голосовим викликам."
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr ""
+"Правила системи забороняють надсилання запитів і використання даних щодо "
+"мережі і служб."
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr ""
 "Увімкнути або переглянути дані щодо географічного розташування і позиціювання"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr ""
 "Правила системи забороняють вмикання або перегляд даних щодо розташування."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "Надіслати запит і використати дані щодо мережі і служби"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
@@ -94,13 +105,13 @@
 "Правила системи забороняють надсилання запитів і використання даних щодо "
 "мережі і служб."
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr ""
 "Опитування та керування мікропрограмою на пристрої мобільної широкосмугової "
 "мережі"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr ""
 "Правила системи перешкоджають опитуванню або керування мікропрограмою цього "
diff --git a/po/zh_CN.po b/po/zh_CN.po
index 4a998d2..1b576a2 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -8,7 +8,7 @@
 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"
+"POT-Creation-Date: 2019-09-25 12:48+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"
@@ -62,29 +62,38 @@
 msgstr "系统策略禁止语音呼叫。"
 
 #: data/org.freedesktop.ModemManager1.policy.in.in:58
+msgid "Query network time and timezone information"
+msgstr ""
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#, fuzzy
+msgid "System policy prevents querying network time information."
+msgstr "系统策略禁止查询和利用网络信息和服务。"
+
+#: data/org.freedesktop.ModemManager1.policy.in.in:67
 msgid "Enable and view geographic location and positioning information"
 msgstr "启用和查看地理位置和定位信息"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:59
+#: data/org.freedesktop.ModemManager1.policy.in.in:68
 msgid ""
 "System policy prevents enabling or viewing geographic location information."
 msgstr "系统策略禁止启用和查看地理位置和定位信息。"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:67
+#: data/org.freedesktop.ModemManager1.policy.in.in:76
 msgid "Query and utilize network information and services"
 msgstr "查询和利用网络信息和服务"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:68
+#: data/org.freedesktop.ModemManager1.policy.in.in:77
 msgid ""
 "System policy prevents querying or utilizing network information and "
 "services."
 msgstr "系统策略禁止查询和利用网络信息和服务。"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:76
+#: data/org.freedesktop.ModemManager1.policy.in.in:85
 msgid "Query and manage firmware on a mobile broadband device"
 msgstr "在移动宽带设备上查询和管理固件"
 
-#: data/org.freedesktop.ModemManager1.policy.in.in:77
+#: data/org.freedesktop.ModemManager1.policy.in.in:86
 msgid "System policy prevents querying or managing this device's firmware."
 msgstr "系统策略禁止在移动宽带设备上查询和管理固件。"
 
diff --git a/src/main.c b/src/main.c
index 96190b8..7a6b4b1 100644
--- a/src/main.c
+++ b/src/main.c
@@ -130,6 +130,28 @@
     g_main_loop_quit (loop);
 }
 
+static void
+register_dbus_errors (void)
+{
+  static volatile guint32 aux = 0;
+
+  if (aux)
+      return;
+
+  /* Register all known own errors */
+  aux |= MM_CORE_ERROR;
+  aux |= MM_MOBILE_EQUIPMENT_ERROR;
+  aux |= MM_CONNECTION_ERROR;
+  aux |= MM_SERIAL_ERROR;
+  aux |= MM_MESSAGE_ERROR;
+  aux |= MM_CDMA_ACTIVATION_ERROR;
+
+  /* We no longer use MM_CORE_ERROR_CANCELLED in the daemon, we rely on
+   * G_IO_ERROR_CANCELLED internally */
+  g_dbus_error_unregister_error (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, MM_CORE_ERROR_DBUS_PREFIX ".Cancelled");
+  g_dbus_error_register_error   (G_IO_ERROR,    G_IO_ERROR_CANCELLED,    MM_CORE_ERROR_DBUS_PREFIX ".Cancelled");
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -154,6 +176,9 @@
     g_unix_signal_add (SIGTERM, quit_cb, NULL);
     g_unix_signal_add (SIGINT, quit_cb, NULL);
 
+    /* Early register all known errors */
+    register_dbus_errors ();
+
     mm_info ("ModemManager (version " MM_DIST_VERSION ") starting in %s bus...",
              mm_context_get_test_session () ? "session" : "system");
 
diff --git a/src/mm-auth-provider.h b/src/mm-auth-provider.h
index e9c2cba..0f1270c 100644
--- a/src/mm-auth-provider.h
+++ b/src/mm-auth-provider.h
@@ -34,6 +34,7 @@
 #define MM_AUTHORIZATION_VOICE           "org.freedesktop.ModemManager1.Voice"
 #define MM_AUTHORIZATION_USSD            "org.freedesktop.ModemManager1.USSD"
 #define MM_AUTHORIZATION_LOCATION        "org.freedesktop.ModemManager1.Location"
+#define MM_AUTHORIZATION_TIME            "org.freedesktop.ModemManager1.Time"
 #define MM_AUTHORIZATION_FIRMWARE        "org.freedesktop.ModemManager1.Firmware"
 
 typedef struct _MMAuthProvider MMAuthProvider;
diff --git a/src/mm-base-bearer.c b/src/mm-base-bearer.c
index 9808eb4..222e123 100644
--- a/src/mm-base-bearer.c
+++ b/src/mm-base-bearer.c
@@ -724,8 +724,7 @@
         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)
-            || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
             /* Will launch disconnection */
             launch_disconnect = TRUE;
         } else
@@ -735,10 +734,8 @@
     else if (g_cancellable_is_cancelled (self->priv->connect_cancellable)) {
         mm_dbg ("Connected bearer '%s', but need to disconnect", self->priv->path);
         mm_bearer_connect_result_unref (result);
-        error = g_error_new (
-            MM_CORE_ERROR,
-            MM_CORE_ERROR_CANCELLED,
-            "Bearer got connected, but had to disconnect after cancellation request");
+        error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                             "Bearer got connected, but had to disconnect after cancellation request");
         launch_disconnect = TRUE;
     }
     else {
@@ -930,6 +927,8 @@
                   MM_BASE_BEARER_MODEM, &ctx->modem,
                   NULL);
 
+    mm_dbg ("User request to connect bearer '%s'", self->priv->path);
+
     mm_base_modem_authorize (ctx->modem,
                              invocation,
                              MM_AUTHORIZATION_DEVICE_CONTROL,
@@ -1121,6 +1120,8 @@
                   MM_BASE_BEARER_MODEM, &ctx->modem,
                   NULL);
 
+    mm_dbg ("User request to disconnect bearer '%s'", self->priv->path);
+
     mm_base_modem_authorize (ctx->modem,
                              invocation,
                              MM_AUTHORIZATION_DEVICE_CONTROL,
diff --git a/src/mm-base-call.c b/src/mm-base-call.c
index 0d09a09..c01ef46 100644
--- a/src/mm-base-call.c
+++ b/src/mm-base-call.c
@@ -34,6 +34,7 @@
 #include "mm-base-modem.h"
 #include "mm-log.h"
 #include "mm-modem-helpers.h"
+#include "mm-error-helpers.h"
 
 G_DEFINE_TYPE (MMBaseCall, mm_base_call, MM_GDBUS_TYPE_CALL_SKELETON)
 
@@ -69,6 +70,11 @@
 
     /* Ongoing call index */
     guint index;
+
+    /* Start cancellable, used when the call state transition to
+     * 'terminated' is coming asynchronously (e.g. via in-call state
+     * update notifications) */
+    GCancellable *start_cancellable;
 };
 
 /*****************************************************************************/
@@ -148,8 +154,19 @@
 {
     GError *error = NULL;
 
+    g_clear_object (&ctx->self->priv->start_cancellable);
+
     if (!MM_BASE_CALL_GET_CLASS (self)->start_finish (self, res, &error)) {
         mm_warn ("Couldn't start call : '%s'", error->message);
+
+        /* When cancelled via the start cancellable, it's because we got an early in-call error
+         * before the call attempt was reported as started. */
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
+            g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED)) {
+            g_clear_error (&error);
+            error = mm_connection_error_for_code (MM_CONNECTION_ERROR_NO_DIALTONE);
+        }
+
         /* Convert errors into call state updates */
         if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_DIALTONE))
             mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_ERROR);
@@ -159,6 +176,7 @@
             mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_REFUSED_OR_BUSY);
         else
             mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_UNKNOWN);
+
         g_dbus_method_invocation_take_error (ctx->invocation, error);
         handle_start_context_free (ctx);
         return;
@@ -188,6 +206,7 @@
     GError *error = NULL;
 
     if (!mm_base_modem_authorize_finish (modem, res, &error)) {
+        mm_base_call_change_state (ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_UNKNOWN);
         g_dbus_method_invocation_take_error (ctx->invocation, error);
         handle_start_context_free (ctx);
         return;
@@ -207,9 +226,18 @@
 
     mm_info ("user request to start call");
 
+    /* Disallow non-emergency calls when in emergency-only state */
+    if (!mm_iface_modem_voice_authorize_outgoing_call (MM_IFACE_MODEM_VOICE (modem), ctx->self, &error)) {
+        mm_base_call_change_state (ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_UNKNOWN);
+        g_dbus_method_invocation_take_error (ctx->invocation, error);
+        handle_start_context_free (ctx);
+        return;
+    }
+
     /* Check if we do support doing it */
     if (!MM_BASE_CALL_GET_CLASS (ctx->self)->start ||
         !MM_BASE_CALL_GET_CLASS (ctx->self)->start_finish) {
+        mm_base_call_change_state (ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_UNKNOWN);
         g_dbus_method_invocation_return_error (ctx->invocation,
                                                MM_CORE_ERROR,
                                                MM_CORE_ERROR_UNSUPPORTED,
@@ -220,7 +248,12 @@
 
     mm_base_call_change_state (ctx->self, MM_CALL_STATE_DIALING, MM_CALL_STATE_REASON_OUTGOING_STARTED);
 
+    /* Setup start cancellable to get notified of termination asynchronously */
+    g_assert (!ctx->self->priv->start_cancellable);
+    ctx->self->priv->start_cancellable = g_cancellable_new ();
+
     MM_BASE_CALL_GET_CLASS (ctx->self)->start (ctx->self,
+                                               ctx->self->priv->start_cancellable,
                                                (GAsyncReadyCallback)handle_start_ready,
                                                ctx);
 }
@@ -970,6 +1003,8 @@
             g_source_remove (self->priv->incoming_timeout);
             self->priv->incoming_timeout = 0;
         }
+        /* cancel start if ongoing */
+        g_cancellable_cancel (self->priv->start_cancellable);
     }
 
     mm_gdbus_call_set_state (MM_GDBUS_CALL (self), new_state);
@@ -1021,6 +1056,7 @@
 
 static void
 call_start (MMBaseCall *self,
+            GCancellable *cancellable,
             GAsyncReadyCallback callback,
             gpointer user_data)
 {
@@ -1030,12 +1066,15 @@
     task = g_task_new (self, NULL, callback, user_data);
 
     cmd = g_strdup_printf ("ATD%s;", mm_gdbus_call_get_number (MM_GDBUS_CALL (self)));
-    mm_base_modem_at_command (self->priv->modem,
-                              cmd,
-                              90,
-                              FALSE,
-                              (GAsyncReadyCallback)call_start_ready,
-                              task);
+    mm_base_modem_at_command_full (self->priv->modem,
+                                   mm_base_modem_peek_port_primary (self->priv->modem),
+                                   cmd,
+                                   90,
+                                   FALSE, /* no cached */
+                                   FALSE, /* no raw */
+                                   cancellable,
+                                   (GAsyncReadyCallback)call_start_ready,
+                                   task);
     g_free (cmd);
 }
 
@@ -1400,6 +1439,7 @@
 {
     MMBaseCall *self = MM_BASE_CALL (object);
 
+    g_assert (!self->priv->start_cancellable);
     g_free (self->priv->path);
 
     G_OBJECT_CLASS (mm_base_call_parent_class)->finalize (object);
diff --git a/src/mm-base-call.h b/src/mm-base-call.h
index 973bcaf..8d6944f 100644
--- a/src/mm-base-call.h
+++ b/src/mm-base-call.h
@@ -54,6 +54,7 @@
 
     /* Start the call */
     void     (* start)        (MMBaseCall *self,
+                               GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data);
     gboolean (* start_finish) (MMBaseCall *self,
diff --git a/src/mm-base-modem-at.c b/src/mm-base-modem-at.c
index b4e0660..e1537af 100644
--- a/src/mm-base-modem-at.c
+++ b/src/mm-base-modem-at.c
@@ -168,9 +168,7 @@
 
     /* Cancelled? */
     if (g_cancellable_is_cancelled (ctx->cancellable)) {
-        g_simple_async_result_set_error (ctx->simple,
-                                         MM_CORE_ERROR,
-                                         MM_CORE_ERROR_CANCELLED,
+        g_simple_async_result_set_error (ctx->simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
                                          "AT sequence was cancelled");
         if (error)
             g_error_free (error);
@@ -483,9 +481,7 @@
 
     /* Cancelled? */
     if (g_cancellable_is_cancelled (ctx->cancellable)) {
-        g_simple_async_result_set_error (ctx->result,
-                                         MM_CORE_ERROR,
-                                         MM_CORE_ERROR_CANCELLED,
+        g_simple_async_result_set_error (ctx->result, G_IO_ERROR, G_IO_ERROR_CANCELLED,
                                          "AT command was cancelled");
         if (error)
             g_error_free (error);
diff --git a/src/mm-base-modem.c b/src/mm-base-modem.c
index 3b9dc7d..2b96f1a 100644
--- a/src/mm-base-modem.c
+++ b/src/mm-base-modem.c
@@ -95,6 +95,9 @@
     MMPortSerialAt *gps_control;
     MMPortSerialGps *gps;
 
+    /* Some audio-capable devices will have a port for audio specifically */
+    MMPortSerial *audio;
+
     /* Support for parallel enable/disable operations */
     GList *enable_tasks;
     GList *disable_tasks;
@@ -232,6 +235,9 @@
         } else if (ptype == MM_PORT_TYPE_GPS) {
             /* Raw GPS port */
             port = MM_PORT (mm_port_serial_gps_new (name));
+        } else if (ptype == MM_PORT_TYPE_AUDIO) {
+            /* Generic audio port */
+            port = MM_PORT (mm_port_serial_new (name, ptype));
         } else {
             g_set_error (error,
                          MM_CORE_ERROR,
@@ -646,6 +652,22 @@
     return self->priv->gps;
 }
 
+MMPortSerial *
+mm_base_modem_get_port_audio (MMBaseModem *self)
+{
+    g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
+
+    return (self->priv->audio ? g_object_ref (self->priv->audio) : NULL);
+}
+
+MMPortSerial *
+mm_base_modem_peek_port_audio (MMBaseModem *self)
+{
+    g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);
+
+    return self->priv->audio;
+}
+
 #if defined WITH_QMI
 
 MMPortQmi *
@@ -929,6 +951,9 @@
         case MM_PORT_TYPE_GPS:
             port_infos[i].type = MM_MODEM_PORT_TYPE_GPS;
             break;
+        case MM_PORT_TYPE_AUDIO:
+            port_infos[i].type = MM_MODEM_PORT_TYPE_AUDIO;
+            break;
         case MM_PORT_TYPE_QMI:
             port_infos[i].type = MM_MODEM_PORT_TYPE_QMI;
             break;
@@ -1039,6 +1064,7 @@
     MMPortSerialQcdm *qcdm = NULL;
     MMPortSerialAt *gps_control = NULL;
     MMPortSerialGps *gps = NULL;
+    MMPortSerial *audio = NULL;
     MMPort *data_primary = NULL;
     GList *data = NULL;
 #if defined WITH_QMI
@@ -1129,6 +1155,12 @@
                 gps = MM_PORT_SERIAL_GPS (candidate);
             break;
 
+        case MM_PORT_TYPE_AUDIO:
+            g_assert (MM_IS_PORT_SERIAL (candidate));
+            if (!audio)
+                audio = MM_PORT_SERIAL (candidate);
+            break;
+
 #if defined WITH_QMI
         case MM_PORT_TYPE_QMI:
             if (!qmi_primary)
@@ -1246,6 +1278,7 @@
     log_port (self, MM_PORT (qcdm),         "qcdm");
     log_port (self, MM_PORT (gps_control),  "gps (control)");
     log_port (self, MM_PORT (gps),          "gps (nmea)");
+    log_port (self, MM_PORT (audio),        "audio");
 #if defined WITH_QMI
     log_port (self, MM_PORT (qmi_primary),  "qmi (primary)");
     for (l = qmi; l; l = g_list_next (l))
@@ -1576,6 +1609,7 @@
     g_clear_object (&self->priv->qcdm);
     g_clear_object (&self->priv->gps_control);
     g_clear_object (&self->priv->gps);
+    g_clear_object (&self->priv->audio);
 #if defined WITH_QMI
     /* We need to close the QMI port cleanly when disposing the modem object,
      * otherwise the allocated CIDs will be kept allocated, and if we end up
diff --git a/src/mm-base-modem.h b/src/mm-base-modem.h
index db3d54d..45a9ecc 100644
--- a/src/mm-base-modem.h
+++ b/src/mm-base-modem.h
@@ -121,6 +121,7 @@
 MMPortSerialQcdm *mm_base_modem_peek_port_qcdm         (MMBaseModem *self);
 MMPortSerialAt   *mm_base_modem_peek_port_gps_control  (MMBaseModem *self);
 MMPortSerialGps  *mm_base_modem_peek_port_gps          (MMBaseModem *self);
+MMPortSerial     *mm_base_modem_peek_port_audio        (MMBaseModem *self);
 #if defined WITH_QMI
 MMPortQmi        *mm_base_modem_peek_port_qmi          (MMBaseModem *self);
 MMPortQmi        *mm_base_modem_peek_port_qmi_for_data (MMBaseModem *self, MMPort *data, GError **error);
@@ -138,6 +139,7 @@
 MMPortSerialQcdm *mm_base_modem_get_port_qcdm         (MMBaseModem *self);
 MMPortSerialAt   *mm_base_modem_get_port_gps_control  (MMBaseModem *self);
 MMPortSerialGps  *mm_base_modem_get_port_gps          (MMBaseModem *self);
+MMPortSerial     *mm_base_modem_get_port_audio        (MMBaseModem *self);
 #if defined WITH_QMI
 MMPortQmi        *mm_base_modem_get_port_qmi          (MMBaseModem *self);
 MMPortQmi        *mm_base_modem_get_port_qmi_for_data (MMBaseModem *self, MMPort *data, GError **error);
diff --git a/src/mm-base-sim.c b/src/mm-base-sim.c
index 1cb2459..73ed088 100644
--- a/src/mm-base-sim.c
+++ b/src/mm-base-sim.c
@@ -953,6 +953,28 @@
 
 /*****************************************************************************/
 
+gboolean
+mm_base_sim_is_emergency_number (MMBaseSim   *self,
+                                 const gchar *number)
+{
+    const gchar *const *emergency_numbers;
+    guint               i;
+
+    emergency_numbers = mm_gdbus_sim_get_emergency_numbers (MM_GDBUS_SIM (self));
+
+    if (!emergency_numbers)
+        return FALSE;
+
+    for (i = 0; emergency_numbers[i]; i++) {
+        if (g_strcmp0 (number, emergency_numbers[i]) == 0)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+/*****************************************************************************/
+
 #undef STR_REPLY_READY_FN
 #define STR_REPLY_READY_FN(NAME)                                        \
     static void                                                         \
@@ -973,7 +995,84 @@
     }
 
 /*****************************************************************************/
-/* SIM IDENTIFIER */
+/* Emergency numbers */
+
+static GStrv
+parse_emergency_numbers (const gchar  *response,
+                         GError      **error)
+{
+    guint  sw1 = 0;
+    guint  sw2 = 0;
+    gchar *hex = 0;
+    GStrv  ret;
+
+    if (!mm_3gpp_parse_crsm_response (response, &sw1, &sw2, &hex, error))
+        return NULL;
+
+    if ((sw1 == 0x90 && sw2 == 0x00) ||
+        (sw1 == 0x91) ||
+        (sw1 == 0x92) ||
+        (sw1 == 0x9f)) {
+        ret = mm_3gpp_parse_emergency_numbers (hex, error);
+        g_free (hex);
+        return ret;
+    }
+
+    g_free (hex);
+    g_set_error (error,
+                 MM_CORE_ERROR,
+                 MM_CORE_ERROR_FAILED,
+                 "SIM failed to handle CRSM request (sw1 %d sw2 %d)",
+                 sw1, sw2);
+    return NULL;
+}
+
+static GStrv
+load_emergency_numbers_finish (MMBaseSim     *self,
+                               GAsyncResult  *res,
+                               GError       **error)
+{
+    gchar *result;
+    GStrv  emergency_numbers;
+    guint  i;
+
+    result = g_task_propagate_pointer (G_TASK (res), error);
+    if (!result)
+        return NULL;
+
+    emergency_numbers = parse_emergency_numbers (result, error);
+    g_free (result);
+
+    if (!emergency_numbers)
+        return NULL;
+
+    for (i = 0; emergency_numbers[i]; i++)
+        mm_dbg ("loaded emergency number: %s", emergency_numbers[i]);
+
+    return emergency_numbers;
+}
+
+STR_REPLY_READY_FN (load_emergency_numbers)
+
+static void
+load_emergency_numbers (MMBaseSim           *self,
+                        GAsyncReadyCallback  callback,
+                        gpointer             user_data)
+{
+    mm_dbg ("loading emergency numbers...");
+
+    /* READ BINARY of EF_ECC (Emergency Call Codes) ETSI TS 51.011 section 10.3.27 */
+    mm_base_modem_at_command (
+        self->priv->modem,
+        "+CRSM=176,28599,0,0,15",
+        20,
+        FALSE,
+        (GAsyncReadyCallback)load_emergency_numbers_command_ready,
+        g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* ICCID */
 
 static gchar *
 parse_iccid (const gchar *response,
@@ -1341,6 +1440,7 @@
     INITIALIZATION_STEP_IMSI,
     INITIALIZATION_STEP_OPERATOR_ID,
     INITIALIZATION_STEP_OPERATOR_NAME,
+    INITIALIZATION_STEP_EMERGENCY_NUMBERS,
     INITIALIZATION_STEP_LAST
 } InitializationStep;
 
@@ -1414,6 +1514,32 @@
     interface_initialization_step (task);
 }
 
+static void
+init_load_emergency_numbers_ready (MMBaseSim    *self,
+                                   GAsyncResult *res,
+                                   GTask        *task)
+{
+    InitAsyncContext *ctx;
+    GError           *error = NULL;
+    GStrv             str_list;
+
+    str_list = MM_BASE_SIM_GET_CLASS (self)->load_emergency_numbers_finish (self, res, &error);
+    if (error) {
+        mm_warn ("couldn't load list of Emergency Numbers: '%s'", error->message);
+        g_error_free (error);
+    }
+
+    if (str_list) {
+        mm_gdbus_sim_set_emergency_numbers (MM_GDBUS_SIM (self), (const gchar *const *) str_list);
+        g_strfreev (str_list);
+    }
+
+    /* Go on to next step */
+    ctx = g_task_get_task_data (task);
+    ctx->step++;
+    interface_initialization_step (task);
+}
+
 #undef STR_REPLY_READY_FN
 #define STR_REPLY_READY_FN(NAME,DISPLAY)                                \
     static void                                                         \
@@ -1527,6 +1653,22 @@
         /* Fall down to next step */
         ctx->step++;
 
+    case INITIALIZATION_STEP_EMERGENCY_NUMBERS:
+        /* Emergency Numbers are meant to be loaded only once during the whole
+         * lifetime of the modem. Therefore, if we already have them loaded,
+         * don't try to load them again. */
+        if (mm_gdbus_sim_get_emergency_numbers (MM_GDBUS_SIM (self)) == NULL &&
+            MM_BASE_SIM_GET_CLASS (self)->load_emergency_numbers &&
+            MM_BASE_SIM_GET_CLASS (self)->load_emergency_numbers_finish) {
+            MM_BASE_SIM_GET_CLASS (self)->load_emergency_numbers (
+                self,
+                (GAsyncReadyCallback)init_load_emergency_numbers_ready,
+                task);
+            return;
+        }
+        /* Fall down to next step */
+        ctx->step++;
+
     case INITIALIZATION_STEP_LAST:
         /* We are done without errors! */
         g_task_return_boolean (task, TRUE);
@@ -1741,6 +1883,8 @@
     klass->load_operator_identifier_finish = load_operator_identifier_finish;
     klass->load_operator_name = load_operator_name;
     klass->load_operator_name_finish = load_operator_name_finish;
+    klass->load_emergency_numbers = load_emergency_numbers;
+    klass->load_emergency_numbers_finish = load_emergency_numbers_finish;
     klass->send_pin = send_pin;
     klass->send_pin_finish = common_send_pin_puk_finish;
     klass->send_puk = send_puk;
diff --git a/src/mm-base-sim.h b/src/mm-base-sim.h
index 5e141f3..afafa37 100644
--- a/src/mm-base-sim.h
+++ b/src/mm-base-sim.h
@@ -83,6 +83,14 @@
                                            GAsyncResult *res,
                                            GError **error);
 
+    /* Load emergency numbers (async) */
+    void  (* load_emergency_numbers)        (MMBaseSim *self,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data);
+    GStrv (* load_emergency_numbers_finish) (MMBaseSim *self,
+                                             GAsyncResult *res,
+                                             GError **error);
+
     /* Change PIN (async) */
     void     (* change_pin)        (MMBaseSim *self,
                                     const gchar *old_pin,
@@ -172,4 +180,7 @@
                                                      GAsyncResult *res,
                                                      GError **error);
 
+gboolean     mm_base_sim_is_emergency_number (MMBaseSim   *self,
+                                              const gchar *number);
+
 #endif /* MM_BASE_SIM_H */
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index 10d831a..7e5f665 100644
--- a/src/mm-broadband-modem-mbim.c
+++ b/src/mm-broadband-modem-mbim.c
@@ -3309,7 +3309,7 @@
             self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ? "yes" : "no",
             self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no",
             self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_USSD ? "yes" : "no",
-            self->priv->setup_flags & MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_LTE_ATTACH_STATUS ? "yes" : "no");
+            self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_STATUS ? "yes" : "no");
 
     if (setup) {
         /* Don't re-enable it if already there */
@@ -4521,23 +4521,23 @@
         break;
 
     case MBIM_USSD_RESPONSE_TERMINATED_BY_NETWORK:
-        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "USSD terminated by network");
+        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, "USSD terminated by network");
         break;
 
     case MBIM_USSD_RESPONSE_OTHER_LOCAL_CLIENT:
-        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Another ongoing USSD operation is in progress");
+        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_RETRY, "Another ongoing USSD operation is in progress");
         break;
 
     case MBIM_USSD_RESPONSE_OPERATION_NOT_SUPPORTED:
-        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Operation not supported");
+        error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, "Operation not supported");
         break;
 
     case MBIM_USSD_RESPONSE_NETWORK_TIMEOUT:
-        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Network timeout");
+        error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, "Network timeout");
         break;
 
     default:
-        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Unknown USSD response (%u)", ussd_response);
+        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unknown USSD response (%u)", ussd_response);
         break;
     }
 
@@ -4851,7 +4851,7 @@
         task = self->priv->pending_ussd_action;
         self->priv->pending_ussd_action = NULL;
 
-        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED,
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
                                  "USSD session was cancelled");
         g_object_unref (task);
     }
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index 6329c21..30f3da8 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -7053,14 +7053,18 @@
 }
 
 typedef struct {
-    QmiClientDms *client;
-    GList *pairs;
-    GList *l;
+    QmiClientDms         *client;
+    GList                *pairs;
+    FirmwarePair         *current_pair;
+    MMFirmwareProperties *current_firmware;
+    gboolean              skip_image_info;
 } FirmwareListPreloadContext;
 
 static void
 firmware_list_preload_context_free (FirmwareListPreloadContext *ctx)
 {
+    g_clear_object (&ctx->current_firmware);
+    g_clear_pointer (&ctx->current_pair, (GDestroyNotify)firmware_pair_free);
     g_list_free_full (ctx->pairs, (GDestroyNotify)firmware_pair_free);
     g_object_unref (ctx->client);
     g_slice_free (FirmwareListPreloadContext, ctx);
@@ -7074,150 +7078,201 @@
     return g_task_propagate_boolean (G_TASK (res), error);
 }
 
+static void
+store_preloaded_firmware_image_info (MMBroadbandModemQmi  *self,
+                                     MMFirmwareProperties *firmware,
+                                     gboolean              running)
+{
+    self->priv->firmware_list = g_list_append (self->priv->firmware_list, g_object_ref (firmware));
+
+    /* If this is is also the running image, keep an extra reference to it */
+    if (running) {
+        if (self->priv->current_firmware)
+            mm_warn ("A running firmware is already set (%s), not setting '%s'",
+                     mm_firmware_properties_get_unique_id (self->priv->current_firmware),
+                     mm_firmware_properties_get_unique_id (firmware));
+        else
+            self->priv->current_firmware = g_object_ref (firmware);
+    }
+}
+
 static void get_next_image_info (GTask *task);
 
 static void
 get_pri_image_info_ready (QmiClientDms *client,
                           GAsyncResult *res,
-                          GTask *task)
+                          GTask        *task)
 {
-    MMBroadbandModemQmi *self;
-    FirmwareListPreloadContext *ctx;
+    MMBroadbandModemQmi                   *self;
+    FirmwareListPreloadContext            *ctx;
     QmiMessageDmsGetStoredImageInfoOutput *output;
-    GError *error = NULL;
-    FirmwarePair *current;
+    GError                                *error = NULL;
 
     self = g_task_get_source_object (task);
-    ctx = g_task_get_task_data (task);
-    current = (FirmwarePair *)ctx->l->data;
+    ctx  = g_task_get_task_data (task);
+
+    g_assert (ctx->current_pair);
+    g_assert (ctx->current_firmware);
 
     output = qmi_client_dms_get_stored_image_info_finish (client, res, &error);
-    if (!output ||
-        !qmi_message_dms_get_stored_image_info_output_get_result (output, &error)) {
-        mm_warn ("Couldn't get detailed info for PRI image with build ID '%s': %s",
-                 current->build_id,
-                 error->message);
+    if (!output || !qmi_message_dms_get_stored_image_info_output_get_result (output, &error)) {
+        if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND))
+            ctx->skip_image_info = TRUE;
+        else
+            mm_dbg ("couldn't get detailed info for PRI image with build ID '%s': %s",
+                    ctx->current_pair->build_id, error->message);
         g_error_free (error);
-    } else {
-        gchar *unique_id_str;
-        MMFirmwareProperties *firmware;
+        goto out;
+    }
 
-        firmware = mm_firmware_properties_new (MM_FIRMWARE_IMAGE_TYPE_GOBI,
-                                               current->build_id);
+    /* Boot version (optional) */
+    {
+        guint16 boot_major_version;
+        guint16 boot_minor_version;
 
-        unique_id_str = mm_utils_bin2hexstr ((const guint8 *)current->pri_unique_id->data,
-                                             current->pri_unique_id->len);
-        mm_firmware_properties_set_gobi_pri_unique_id (firmware, unique_id_str);
-        g_free (unique_id_str);
+        if (qmi_message_dms_get_stored_image_info_output_get_boot_version (
+                output,
+                &boot_major_version,
+                &boot_minor_version,
+                NULL)) {
+            gchar *aux;
 
-        unique_id_str = mm_utils_bin2hexstr ((const guint8 *)current->modem_unique_id->data,
-                                             current->modem_unique_id->len);
-        mm_firmware_properties_set_gobi_modem_unique_id (firmware, unique_id_str);
-        g_free (unique_id_str);
-
-        /* Boot version (optional) */
-        {
-            guint16 boot_major_version;
-            guint16 boot_minor_version;
-
-            if (qmi_message_dms_get_stored_image_info_output_get_boot_version (
-                    output,
-                    &boot_major_version,
-                    &boot_minor_version,
-                    NULL)) {
-                gchar *aux;
-
-                aux = g_strdup_printf ("%u.%u", boot_major_version, boot_minor_version);
-                mm_firmware_properties_set_gobi_boot_version (firmware, aux);
-                g_free (aux);
-            }
-        }
-
-        /* PRI version (optional) */
-        {
-            guint32 pri_version;
-            const gchar *pri_info;
-
-            if (qmi_message_dms_get_stored_image_info_output_get_pri_version (
-                    output,
-                    &pri_version,
-                    &pri_info,
-                    NULL)) {
-                gchar *aux;
-
-                aux = g_strdup_printf ("%u", pri_version);
-                mm_firmware_properties_set_gobi_pri_version (firmware, aux);
-                g_free (aux);
-
-                mm_firmware_properties_set_gobi_pri_info (firmware, pri_info);
-            }
-        }
-
-        /* Add firmware image to our internal list */
-        self->priv->firmware_list = g_list_append (self->priv->firmware_list,
-                                                   firmware);
-
-        /* If this is is also the current image running, keep it */
-        if (current->current) {
-            if (self->priv->current_firmware)
-                mm_warn ("A current firmware is already set (%s), not setting '%s' as current",
-                         mm_firmware_properties_get_unique_id (self->priv->current_firmware),
-                         current->build_id);
-            else
-                self->priv->current_firmware = g_object_ref (firmware);
-
+            aux = g_strdup_printf ("%u.%u", boot_major_version, boot_minor_version);
+            mm_firmware_properties_set_gobi_boot_version (ctx->current_firmware, aux);
+            g_free (aux);
         }
     }
 
-    if (output)
-        qmi_message_dms_get_stored_image_info_output_unref (output);
+    /* PRI version (optional) */
+    {
+        guint32 pri_version;
+        const gchar *pri_info;
+
+        if (qmi_message_dms_get_stored_image_info_output_get_pri_version (
+                output,
+                &pri_version,
+                &pri_info,
+                NULL)) {
+            gchar *aux;
+
+            aux = g_strdup_printf ("%u", pri_version);
+            mm_firmware_properties_set_gobi_pri_version (ctx->current_firmware, aux);
+            g_free (aux);
+
+            mm_firmware_properties_set_gobi_pri_info (ctx->current_firmware, pri_info);
+        }
+    }
+
+out:
+
+    /* We're done with this image */
+    store_preloaded_firmware_image_info (self, ctx->current_firmware, ctx->current_pair->current);
+    g_clear_object (&ctx->current_firmware);
+    g_clear_pointer (&ctx->current_pair, (GDestroyNotify)firmware_pair_free);
 
     /* Go on to the next one */
-    ctx->l = g_list_next (ctx->l);
     get_next_image_info (task);
+
+    if (output)
+        qmi_message_dms_get_stored_image_info_output_unref (output);
+}
+
+static MMFirmwareProperties *
+create_firmware_properties_from_pair (FirmwarePair  *pair,
+                                      GError       **error)
+{
+    gchar                *pri_unique_id_str = NULL;
+    gchar                *modem_unique_id_str = NULL;
+    gchar                *firmware_unique_id_str = NULL;
+    MMFirmwareProperties *firmware = NULL;
+
+    /* If the string is ASCII, use it without converting to HEX */
+
+    pri_unique_id_str = mm_qmi_unique_id_to_firmware_unique_id (pair->pri_unique_id, error);
+    if (!pri_unique_id_str)
+        goto out;
+
+    modem_unique_id_str = mm_qmi_unique_id_to_firmware_unique_id (pair->modem_unique_id, error);
+    if (!modem_unique_id_str)
+        goto out;
+
+    /* We will always append the PRI unique ID to the build id to form the unique id
+     * used by the API, because it may happen that a device holds multiple PRI images
+     * for the same build ID.
+     *
+     * E.g. we could have a single modem image (e.g. 02.14.03.00) and then two or more
+     * different PRI images with the same build ID (e.g. 02.14.03.00_VODAFONE) but
+     * different unique IDs (e.g. 000.008_000 and 000.016_000).
+     */
+    firmware_unique_id_str = g_strdup_printf ("%s_%s", pair->build_id, pri_unique_id_str);
+
+    firmware = mm_firmware_properties_new (MM_FIRMWARE_IMAGE_TYPE_GOBI, firmware_unique_id_str);
+    mm_firmware_properties_set_gobi_pri_unique_id (firmware, pri_unique_id_str);
+    mm_firmware_properties_set_gobi_modem_unique_id (firmware, modem_unique_id_str);
+
+out:
+    g_free (firmware_unique_id_str);
+    g_free (pri_unique_id_str);
+    g_free (modem_unique_id_str);
+
+    return firmware;
 }
 
 static void
 get_next_image_info (GTask *task)
 {
-    MMBroadbandModemQmi *self;
+    MMBroadbandModemQmi        *self;
     FirmwareListPreloadContext *ctx;
-    QmiMessageDmsGetStoredImageInfoInputImage image_id;
-    QmiMessageDmsGetStoredImageInfoInput *input;
-    FirmwarePair *current;
+    GError                     *error = NULL;
 
     self = g_task_get_source_object (task);
     ctx = g_task_get_task_data (task);
 
-    if (!ctx->l) {
-        /* We're done */
-
-        if (!self->priv->firmware_list) {
-            mm_warn ("No valid firmware images listed. "
-                     "Assuming firmware unsupported.");
-            g_task_return_boolean (task, FALSE);
-        } else
-            g_task_return_boolean (task, TRUE);
-
+    if (!ctx->pairs) {
+        g_task_return_boolean (task, TRUE);
         g_object_unref (task);
         return;
     }
 
-    current = (FirmwarePair *)ctx->l->data;
+    /* Take next pair to process from list head */
+    ctx->current_pair = (FirmwarePair *)ctx->pairs->data;
+    ctx->pairs = g_list_delete_link (ctx->pairs, ctx->pairs);
 
-    /* Query PRI image info */
-    image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI;
-    image_id.unique_id = current->pri_unique_id;
-    image_id.build_id = current->build_id;
-    input = qmi_message_dms_get_stored_image_info_input_new ();
-    qmi_message_dms_get_stored_image_info_input_set_image (input, &image_id, NULL);
-    qmi_client_dms_get_stored_image_info (ctx->client,
-                                          input,
-                                          10,
-                                          NULL,
-                                          (GAsyncReadyCallback)get_pri_image_info_ready,
-                                          task);
-    qmi_message_dms_get_stored_image_info_input_unref (input);
+    /* Build firmware properties */
+    ctx->current_firmware = create_firmware_properties_from_pair (ctx->current_pair, &error);
+    if (!ctx->current_firmware) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    /* Now, load additional optional information for the PRI image */
+    if (!ctx->skip_image_info) {
+        QmiMessageDmsGetStoredImageInfoInputImage  image_id;
+        QmiMessageDmsGetStoredImageInfoInput      *input;
+
+        image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI;
+        image_id.unique_id = ctx->current_pair->pri_unique_id;
+        image_id.build_id = ctx->current_pair->build_id;
+        input = qmi_message_dms_get_stored_image_info_input_new ();
+        qmi_message_dms_get_stored_image_info_input_set_image (input, &image_id, NULL);
+        qmi_client_dms_get_stored_image_info (ctx->client,
+                                              input,
+                                              10,
+                                              NULL,
+                                              (GAsyncReadyCallback)get_pri_image_info_ready,
+                                              task);
+        qmi_message_dms_get_stored_image_info_input_unref (input);
+        return;
+    }
+
+    /* If we shouldn't be loading additional image info, we're done with this image */
+    store_preloaded_firmware_image_info (self, ctx->current_firmware, ctx->current_pair->current);
+    g_clear_object (&ctx->current_firmware);
+    g_clear_pointer (&ctx->current_pair, (GDestroyNotify)firmware_pair_free);
+
+    /* Go on to the next one */
+    get_next_image_info (task);
 }
 
 static gboolean
@@ -7242,85 +7297,15 @@
     return strncmp (pri_id, modem_id, modem_id_len - 1) == 0;
 }
 
-static void
-list_stored_images_ready (QmiClientDms *client,
-                          GAsyncResult *res,
-                          GTask *task)
+static GList *
+find_image_pairs (QmiMessageDmsListStoredImagesOutputListImage  *image_pri,
+                  QmiMessageDmsListStoredImagesOutputListImage  *image_modem,
+                  GError                                       **error)
 {
-    FirmwareListPreloadContext *ctx;
-    GArray *array;
-    gint pri_id;
-    gint modem_id;
-    guint i;
-    guint j;
-    QmiMessageDmsListStoredImagesOutputListImage *image_pri;
-    QmiMessageDmsListStoredImagesOutputListImage *image_modem;
-    QmiMessageDmsListStoredImagesOutput *output;
+    guint  i, j;
+    GList *pairs = NULL;
 
-    output = qmi_client_dms_list_stored_images_finish (client, res, NULL);
-    if (!output ||
-        !qmi_message_dms_list_stored_images_output_get_result (output, NULL)) {
-        /* Assume firmware unsupported */
-        g_task_return_boolean (task, FALSE);
-        g_object_unref (task);
-        if (output)
-            qmi_message_dms_list_stored_images_output_unref (output);
-        return;
-    }
-
-    qmi_message_dms_list_stored_images_output_get_list (
-        output,
-        &array,
-        NULL);
-
-    /* Find which index corresponds to each image type */
-    pri_id = -1;
-    modem_id = -1;
-    for (i = 0; i < array->len; i++) {
-        QmiMessageDmsListStoredImagesOutputListImage *image;
-
-        image = &g_array_index (array,
-                                QmiMessageDmsListStoredImagesOutputListImage,
-                                i);
-
-        switch (image->type) {
-        case QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI:
-            if (pri_id != -1)
-                mm_warn ("Multiple array elements found with PRI type");
-            else
-                pri_id = (gint)i;
-            break;
-        case QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM:
-            if (modem_id != -1)
-                mm_warn ("Multiple array elements found with MODEM type");
-            else
-                modem_id = (gint)i;
-            break;
-        default:
-            break;
-        }
-    }
-
-    if (pri_id < 0 || modem_id < 0) {
-        mm_dbg ("We need both PRI (%s) and MODEM (%s) images",
-                pri_id < 0 ? "not found" : "found",
-                modem_id < 0 ? "not found" : "found");
-        g_task_return_boolean (task, FALSE);
-        g_object_unref (task);
-        qmi_message_dms_list_stored_images_output_unref (output);
-        return;
-    }
-
-    ctx = g_task_get_task_data (task);
-
-    /* Loop PRI images and try to find a pairing MODEM image with same boot ID */
-    image_pri = &g_array_index (array,
-                                QmiMessageDmsListStoredImagesOutputListImage,
-                                pri_id);
-    image_modem = &g_array_index (array,
-                                  QmiMessageDmsListStoredImagesOutputListImage,
-                                  modem_id);
-
+    /* Loop PRI images and try to find a pairing MODEM image with same build ID */
     for (i = 0; i < image_pri->sublist->len; i++) {
         QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement *subimage_pri;
 
@@ -7342,8 +7327,15 @@
                 pair->build_id = g_strdup (subimage_pri->build_id);
                 pair->modem_unique_id = g_array_ref (subimage_modem->unique_id);
                 pair->pri_unique_id = g_array_ref (subimage_pri->unique_id);
+
+                /* We're using the PRI 'index_of_running_image' only as source to select
+                 * which is the current running firmware. This avoids issues with the wrong
+                 * 'index_of_running_image' reported for the MODEM images, see:
+                 *   https://forum.sierrawireless.com/t/mc74xx-wrong-running-image-in-qmi-get-stored-images/8998
+                 */
                 pair->current = (image_pri->index_of_running_image == i ? TRUE : FALSE);
-                ctx->pairs = g_list_append (ctx->pairs, pair);
+
+                pairs = g_list_append (pairs, pair);
                 break;
             }
         }
@@ -7352,19 +7344,110 @@
             mm_dbg ("Pairing for PRI image with build ID '%s' not found", subimage_pri->build_id);
     }
 
-    if (!ctx->pairs) {
-        mm_dbg ("No valid PRI+MODEM pairs found");
-        g_task_return_boolean (task, FALSE);
-        g_object_unref (task);
-        qmi_message_dms_list_stored_images_output_unref (output);
-        return;
+    if (!pairs)
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, "No valid PRI+MODEM pairs found");
+
+    return pairs;
+}
+
+static gboolean
+find_image_type_indices (GArray                                        *array,
+                         QmiMessageDmsListStoredImagesOutputListImage **image_pri,
+                         QmiMessageDmsListStoredImagesOutputListImage **image_modem,
+                         GError                                       **error)
+{
+    guint i;
+
+    g_assert (array);
+    g_assert (image_pri);
+    g_assert (image_modem);
+
+    *image_pri = NULL;
+    *image_modem = NULL;
+
+    /* The MODEM image list is usually given before the PRI image list, but
+     * let's not assume that. Try to find both lists and report at which index
+     * we can find each. */
+
+    for (i = 0; i < array->len; i++) {
+        QmiMessageDmsListStoredImagesOutputListImage *image;
+
+        image = &g_array_index (array, QmiMessageDmsListStoredImagesOutputListImage, i);
+        switch (image->type) {
+        case QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI:
+            if (*image_pri != NULL)
+                mm_dbg ("Multiple array elements found with PRI type: ignoring additional list at index %u", i);
+            else
+                *image_pri = image;
+            break;
+        case QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM:
+            if (*image_modem != NULL)
+                mm_dbg ("Multiple array elements found with MODEM type: ignoring additional list at index %u", i);
+            else
+                *image_modem = image;
+            break;
+        default:
+            break;
+        }
     }
 
-    /* Firmware is supported; now keep on loading info for each image and cache it */
-    qmi_message_dms_list_stored_images_output_unref (output);
+    if (!(*image_pri) || !(*image_modem)) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+                     "Missing image list: pri list %s, modem list %s",
+                     !(*image_pri) ? "not found" : "found",
+                     !(*image_modem) ? "not found" : "found");
+        return FALSE;
+    }
 
-    ctx->l = ctx->pairs;
+    return TRUE;
+}
+
+static void
+list_stored_images_ready (QmiClientDms *client,
+                          GAsyncResult *res,
+                          GTask        *task)
+{
+    FirmwareListPreloadContext                   *ctx;
+    GArray                                       *array;
+    QmiMessageDmsListStoredImagesOutputListImage *image_pri;
+    QmiMessageDmsListStoredImagesOutputListImage *image_modem;
+    QmiMessageDmsListStoredImagesOutput          *output;
+    GError                                       *error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    /* Read array from output */
+    output = qmi_client_dms_list_stored_images_finish (client, res, &error);
+    if (!output ||
+        !qmi_message_dms_list_stored_images_output_get_result (output, &error) ||
+        !qmi_message_dms_list_stored_images_output_get_list (output, &array, &error)) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        goto out;
+    }
+
+    /* Find which index corresponds to each image type */
+    if (!find_image_type_indices (array, &image_pri, &image_modem, &error)) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        goto out;
+    }
+
+    /* Build firmware PRI+MODEM pair list */
+    ctx->pairs = find_image_pairs (image_pri, image_modem, &error);
+    if (!ctx->pairs) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        goto out;
+    }
+
+    /* Now keep on loading info for each image and cache it */
     get_next_image_info (task);
+
+out:
+
+    if (output)
+        qmi_message_dms_list_stored_images_output_unref (output);
 }
 
 static void
@@ -7612,21 +7695,21 @@
 
 static void
 firmware_change_current (MMIfaceModemFirmware *_self,
-                         const gchar *unique_id,
-                         GAsyncReadyCallback callback,
-                         gpointer user_data)
+                         const gchar          *unique_id,
+                         GAsyncReadyCallback   callback,
+                         gpointer              user_data)
 {
-    MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self);
-    QmiMessageDmsSetFirmwarePreferenceInput *input;
-    FirmwareChangeCurrentContext *ctx;
-    GTask *task;
-    QmiClient *client = NULL;
-    GArray *array;
-    QmiMessageDmsSetFirmwarePreferenceInputListImage modem_image_id;
-    QmiMessageDmsSetFirmwarePreferenceInputListImage pri_image_id;
-    guint8 *tmp;
-    gsize tmp_len;
+    MMBroadbandModemQmi                              *self;
+    GTask                                            *task;
+    FirmwareChangeCurrentContext                     *ctx;
+    GError                                           *error = NULL;
+    QmiClient                                        *client = NULL;
+    GArray                                           *array;
+    QmiMessageDmsSetFirmwarePreferenceInput          *input = NULL;
+    QmiMessageDmsSetFirmwarePreferenceInputListImage  modem_image_id = { 0 };
+    QmiMessageDmsSetFirmwarePreferenceInputListImage  pri_image_id   = { 0 };
 
+    self = MM_BROADBAND_MODEM_QMI (_self);
     if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
                                       QMI_SERVICE_DMS, &client,
                                       callback, user_data))
@@ -7679,22 +7762,26 @@
     }
 
     /* Modem image ID */
-    tmp_len = 0;
-    tmp = (guint8 *)mm_utils_hexstr2bin (mm_firmware_properties_get_gobi_modem_unique_id (ctx->firmware), &tmp_len);
     modem_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM;
     modem_image_id.build_id = (gchar *)mm_firmware_properties_get_unique_id (ctx->firmware);
-    modem_image_id.unique_id = g_array_sized_new (FALSE, FALSE, sizeof (guint8), tmp_len);
-    g_array_insert_vals (modem_image_id.unique_id, 0, tmp, tmp_len);
-    g_free (tmp);
+    modem_image_id.unique_id = mm_firmware_unique_id_to_qmi_unique_id (mm_firmware_properties_get_gobi_modem_unique_id (ctx->firmware), &error);
+    if (!modem_image_id.unique_id) {
+        g_prefix_error (&error, "Couldn't build modem image unique id: ");
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        goto out;
+    }
 
     /* PRI image ID */
-    tmp_len = 0;
-    tmp = (guint8 *)mm_utils_hexstr2bin (mm_firmware_properties_get_gobi_pri_unique_id (ctx->firmware), &tmp_len);
     pri_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI;
     pri_image_id.build_id = (gchar *)mm_firmware_properties_get_unique_id (ctx->firmware);
-    pri_image_id.unique_id = g_array_sized_new (FALSE, FALSE, sizeof (guint8), tmp_len);
-    g_array_insert_vals (pri_image_id.unique_id, 0, tmp, tmp_len);
-    g_free (tmp);
+    pri_image_id.unique_id = mm_firmware_unique_id_to_qmi_unique_id (mm_firmware_properties_get_gobi_pri_unique_id (ctx->firmware), &error);
+    if (!pri_image_id.unique_id) {
+        g_prefix_error (&error, "Couldn't build PRI image unique id: ");
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        goto out;
+    }
 
     mm_dbg ("Changing Gobi firmware to MODEM '%s' and PRI '%s' with Build ID '%s'...",
             mm_firmware_properties_get_gobi_modem_unique_id (ctx->firmware),
@@ -7715,9 +7802,14 @@
         NULL,
         (GAsyncReadyCallback)firmware_select_stored_image_ready,
         task);
-    g_array_unref (modem_image_id.unique_id);
-    g_array_unref (pri_image_id.unique_id);
-    qmi_message_dms_set_firmware_preference_input_unref (input);
+
+out:
+    if (modem_image_id.unique_id)
+        g_array_unref (modem_image_id.unique_id);
+    if (pri_image_id.unique_id)
+        g_array_unref (pri_image_id.unique_id);
+    if (input)
+        qmi_message_dms_set_firmware_preference_input_unref (input);
 }
 
 /*****************************************************************************/
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 3cbf513..9df2ee1 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -121,9 +121,11 @@
     PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
     PROP_MODEM_SIM_HOT_SWAP_CONFIGURED,
     PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
+    PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
     PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
     PROP_MODEM_CARRIER_CONFIG_MAPPING,
     PROP_FLOW_CONTROL,
+    PROP_INDICATORS_DISABLED,
     PROP_LAST
 };
 
@@ -139,10 +141,12 @@
     /* Broadband modem specific implementation */
     PortsContext *enabled_ports_ctx;
     PortsContext *sim_hot_swap_ports_ctx;
+    PortsContext *in_call_ports_ctx;
     gboolean modem_init_run;
     gboolean sim_hot_swap_supported;
     gboolean sim_hot_swap_configured;
     gboolean periodic_signal_check_disabled;
+    gboolean periodic_access_tech_check_disabled;
 
     /*<--- Modem interface --->*/
     /* Properties */
@@ -153,6 +157,7 @@
     gchar *carrier_config_mapping;
     /* Implementation helpers */
     MMModemCharset modem_current_charset;
+    gboolean modem_cind_disabled;
     gboolean modem_cind_support_checked;
     gboolean modem_cind_supported;
     guint modem_cind_indicator_signal_quality;
@@ -248,6 +253,119 @@
 };
 
 /*****************************************************************************/
+/* Generic ports open/close context */
+
+struct _PortsContext {
+    volatile gint     ref_count;
+    MMPortSerialAt   *primary;
+    gboolean          primary_open;
+    MMPortSerialAt   *secondary;
+    gboolean          secondary_open;
+    MMPortSerialQcdm *qcdm;
+    gboolean          qcdm_open;
+};
+
+static PortsContext *
+ports_context_ref (PortsContext *ctx)
+{
+    g_atomic_int_inc (&ctx->ref_count);
+    return ctx;
+}
+
+static void
+ports_context_unref (PortsContext *ctx)
+{
+    if (g_atomic_int_dec_and_test (&ctx->ref_count)) {
+        if (ctx->primary) {
+            if (ctx->primary_open)
+                mm_port_serial_close (MM_PORT_SERIAL (ctx->primary));
+            g_object_unref (ctx->primary);
+        }
+        if (ctx->secondary) {
+            if (ctx->secondary_open)
+                mm_port_serial_close (MM_PORT_SERIAL (ctx->secondary));
+            g_object_unref (ctx->secondary);
+        }
+        if (ctx->qcdm) {
+            if (ctx->qcdm_open)
+                mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm));
+            g_object_unref (ctx->qcdm);
+        }
+        g_free (ctx);
+    }
+}
+
+static gboolean
+ports_context_open (MMBroadbandModem  *self,
+                    PortsContext      *ctx,
+                    gboolean           disable_at_init_sequence,
+                    gboolean           with_at_secondary,
+                    gboolean           with_qcdm,
+                    GError           **error)
+{
+    /* Open primary */
+    ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+    if (!ctx->primary) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_FAILED,
+                     "Couldn't get primary port");
+        return FALSE;
+    }
+    /* If we'll need to run modem initialization, disable port init sequence */
+    if (disable_at_init_sequence)
+        g_object_set (ctx->primary,
+                      MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE,
+                      NULL);
+    if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->primary), error)) {
+        g_prefix_error (error, "Couldn't open primary port: ");
+        return FALSE;
+    }
+    ctx->primary_open = TRUE;
+
+    /* Open secondary (optional) */
+    if (with_at_secondary) {
+        ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+        if (ctx->secondary) {
+            /* If we'll need to run modem initialization, disable port init sequence */
+            if (disable_at_init_sequence)
+                g_object_set (ctx->secondary,
+                              MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE,
+                              NULL);
+            if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->secondary), error)) {
+                g_prefix_error (error, "Couldn't open secondary port: ");
+                return FALSE;
+            }
+            ctx->secondary_open = TRUE;
+        }
+    }
+
+    /* Open qcdm (optional) */
+    if (with_qcdm) {
+        ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
+        if (ctx->qcdm) {
+            if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->qcdm), error)) {
+                g_prefix_error (error, "Couldn't open QCDM port: ");
+                return FALSE;
+            }
+            ctx->qcdm_open = TRUE;
+        }
+    }
+
+    return TRUE;
+}
+
+static PortsContext *
+ports_context_new (void)
+{
+    PortsContext *ctx;
+
+    ctx = g_new0 (PortsContext, 1);
+    ctx->ref_count = 1;
+    return ctx;
+}
+
+/*****************************************************************************/
 
 static gboolean
 response_processor_string_ignore_at_errors (MMBaseModem *self,
@@ -2205,7 +2323,8 @@
     /* Check whether we can get a non-connected AT port */
     ctx->at_port = (MMPortSerial *)mm_base_modem_get_best_at_port (MM_BASE_MODEM (self), &error);
     if (ctx->at_port) {
-        if (self->priv->modem_cind_supported &&
+        if (!self->priv->modem_cind_disabled &&
+            self->priv->modem_cind_supported &&
             CIND_INDICATOR_IS_VALID (self->priv->modem_cind_indicator_signal_quality))
             signal_quality_cind (task);
         else
@@ -3187,7 +3306,7 @@
     self = g_task_get_source_object (task);
 
     /* Check support for +CIEV indications, managed with +CIND/+CMER */
-    if (!self->priv->modem_cind_support_checked) {
+    if (!self->priv->modem_cind_disabled && !self->priv->modem_cind_support_checked) {
         mm_dbg ("Checking indicator support...");
         self->priv->modem_cind_support_checked = TRUE;
         mm_base_modem_at_command (MM_BASE_MODEM (self),
@@ -3236,7 +3355,7 @@
 
     task = g_task_new (self, NULL, callback, user_data);
 
-    if (self->priv->modem_cind_support_checked && self->priv->modem_cind_supported)
+    if (!self->priv->modem_cind_disabled && self->priv->modem_cind_support_checked && self->priv->modem_cind_supported)
         set_ciev_unsolicited_events_handlers (self, FALSE);
 
     if (self->priv->modem_cgerep_supported)
@@ -3316,14 +3435,14 @@
     ctx = g_task_get_task_data (task);
 
     /* CMER on primary port */
-    if (!ctx->cmer_primary_done && ctx->cmer_command && ctx->primary) {
+    if (!ctx->cmer_primary_done && ctx->cmer_command && ctx->primary && !self->priv->modem_cind_disabled) {
         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) {
+    else if (!ctx->cmer_secondary_done && ctx->cmer_command && ctx->secondary && !self->priv->modem_cind_disabled) {
         mm_dbg ("%s +CIND event reporting in secondary port...", ctx->enable ? "Enabling" : "Disabling");
         ctx->cmer_secondary_done = TRUE;
         command = ctx->cmer_command;
@@ -5294,7 +5413,7 @@
         task = self->priv->pending_ussd_action;
         self->priv->pending_ussd_action = NULL;
 
-        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED,
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
                                  "USSD session was cancelled");
         g_object_unref (task);
     }
@@ -5701,13 +5820,13 @@
     case 2:
         /* Response to the user's request? */
         if (task)
-            error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "USSD terminated by network");
+            error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, "USSD terminated by network");
         break;
 
     case 4:
         /* Response to the user's request? */
         if (task)
-            error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Operation not supported");
+            error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, "Operation not supported");
         break;
 
     default:
@@ -7205,6 +7324,20 @@
 }
 
 static void
+ignore_sim_related_errors (GError **error)
+{
+    g_assert (error && *error);
+    if (g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) ||
+        g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)          ||
+        g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK)          ||
+        g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE)      ||
+        g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY)         ||
+        g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG)) {
+        g_clear_error (error);
+    }
+}
+
+static void
 clcc_format_check_ready (MMBroadbandModem *self,
                          GAsyncResult     *res,
                          GTask            *task)
@@ -7231,16 +7364,25 @@
 
     mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
     if (error) {
-        g_task_return_error (task, error);
-        g_object_unref (task);
-        return;
+        /* Ignore some errors that the module may return when there is no SIM inserted or
+         * if the SIM is PIN-locked. We do need the voice interface exposed even in those
+         * cases, in order to support emergency calls */
+        ignore_sim_related_errors (&error);
+        if (error) {
+            g_task_return_error (task, error);
+            g_object_unref (task);
+            return;
+        }
     }
 
     /* Also check if +CLCC is supported */
     mm_base_modem_at_command (MM_BASE_MODEM (self),
                               "+CLCC=?",
                               3,
-                              TRUE,
+                              /* Do NOT cache as the reply may be different if PIN locked
+                               * or unlocked. E.g. we may not support +CLCC for emergency
+                               * voice calls. */
+                              FALSE,
                               (GAsyncReadyCallback)clcc_format_check_ready,
                               task);
 }
@@ -7260,7 +7402,7 @@
     mm_base_modem_at_command (MM_BASE_MODEM (self),
                               "H",
                               3,
-                              TRUE,
+                              FALSE,
                               (GAsyncReadyCallback)ath_format_check_ready,
                               task);
 }
@@ -7353,21 +7495,19 @@
 }
 
 static void
-set_voice_in_call_unsolicited_events_handlers (MMIfaceModemVoice   *self,
-                                               gboolean             enable,
-                                               GAsyncReadyCallback  callback,
-                                               gpointer             user_data)
+set_voice_in_call_unsolicited_events_handlers (MMBroadbandModem *self,
+                                               PortsContext     *ports_ctx,
+                                               gboolean          enable)
 {
     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);
+                                       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));
+    ports[0] = MM_PORT_SERIAL_AT (ports_ctx->primary);
+    ports[1] = MM_PORT_SERIAL_AT (ports_ctx->secondary);
 
     /* Enable unsolicited events in given port */
     for (i = 0; i < 2; i++) {
@@ -7386,26 +7526,60 @@
     }
 
     g_regex_unref (in_call_event_regex);
+}
+
+static void
+modem_voice_setup_in_call_unsolicited_events (MMIfaceModemVoice   *_self,
+                                              GAsyncReadyCallback  callback,
+                                              gpointer             user_data)
+{
+    MMBroadbandModem *self;
+    GTask            *task;
+    GError           *error = NULL;
+
+    self = MM_BROADBAND_MODEM (_self);
+    if (!self->priv->in_call_ports_ctx)  {
+        PortsContext *ctx;
+
+        mm_dbg ("Setting up in-call ports context");
+        ctx = ports_context_new ();
+        if (!ports_context_open (self, ctx, FALSE, FALSE, FALSE, &error)) {
+            ports_context_unref (ctx);
+            g_prefix_error (&error, "Couldn't open ports in-call: ");
+        } else {
+            set_voice_in_call_unsolicited_events_handlers (self, ctx, TRUE);
+            self->priv->in_call_ports_ctx = ctx;
+        }
+    } else
+        mm_dbg ("In-call ports context already set up");
 
     task = g_task_new (self, NULL, callback, user_data);
-    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);
 }
 
 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,
+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);
+    MMBroadbandModem *self;
+    GTask            *task;
+
+    self = MM_BROADBAND_MODEM (_self);
+    if (self->priv->in_call_ports_ctx)  {
+        mm_dbg ("Cleaning up in-call ports context");
+        set_voice_in_call_unsolicited_events_handlers (self, self->priv->in_call_ports_ctx, FALSE);
+        g_clear_pointer (&self->priv->in_call_ports_ctx, (GDestroyNotify) ports_context_unref);
+    } else
+        mm_dbg ("In-call ports context already cleaned up");
+
+    task = g_task_new (self, NULL, callback, user_data);
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
 }
 
 /*****************************************************************************/
@@ -9773,50 +9947,6 @@
 }
 
 /*****************************************************************************/
-/* Generic ports open/close context */
-
-struct _PortsContext {
-    volatile gint ref_count;
-
-    MMPortSerialAt *primary;
-    gboolean primary_open;
-    MMPortSerialAt *secondary;
-    gboolean secondary_open;
-    MMPortSerialQcdm *qcdm;
-    gboolean qcdm_open;
-};
-
-static PortsContext *
-ports_context_ref (PortsContext *ctx)
-{
-    g_atomic_int_inc (&ctx->ref_count);
-    return ctx;
-}
-
-static void
-ports_context_unref (PortsContext *ctx)
-{
-    if (g_atomic_int_dec_and_test (&ctx->ref_count)) {
-        if (ctx->primary) {
-            if (ctx->primary_open)
-                mm_port_serial_close (MM_PORT_SERIAL (ctx->primary));
-            g_object_unref (ctx->primary);
-        }
-        if (ctx->secondary) {
-            if (ctx->secondary_open)
-                mm_port_serial_close (MM_PORT_SERIAL (ctx->secondary));
-            g_object_unref (ctx->secondary);
-        }
-        if (ctx->qcdm) {
-            if (ctx->qcdm_open)
-                mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm));
-            g_object_unref (ctx->qcdm);
-        }
-        g_free (ctx);
-    }
-}
-
-/*****************************************************************************/
 /* Initialization started/stopped */
 
 static gboolean
@@ -9864,17 +9994,6 @@
 
     ctx->primary_open = TRUE;
 
-    /* Try to disable echo */
-    mm_base_modem_at_command_full (MM_BASE_MODEM (self),
-                                   ctx->primary,
-                                   "E0", 3,
-                                   FALSE, FALSE, NULL, NULL, NULL);
-    /* Try to get extended errors */
-    mm_base_modem_at_command_full (MM_BASE_MODEM (self),
-                                   ctx->primary,
-                                   "+CMEE=1", 3,
-                                   FALSE, FALSE, NULL, NULL, NULL);
-
     return TRUE;
 }
 
@@ -9889,10 +10008,9 @@
 
     task = g_task_new (self, NULL, callback, user_data);
 
-    ctx = g_new0 (PortsContext, 1);
-    ctx->ref_count = 1;
-
-    if (!open_ports_initialization (self, ctx, &error)) {
+    /* Open ports for initialization, just the primary AT port */
+    ctx = ports_context_new ();
+    if (!ports_context_open (self, ctx, FALSE, FALSE, FALSE, &error)) {
         ports_context_unref (ctx);
         g_prefix_error (&error, "Couldn't open ports during modem initialization: ");
         g_task_return_error (task, error);
@@ -9981,6 +10099,7 @@
     ctx = g_task_get_task_data (task);
 
     /* Reset init sequence enabled flags and run them explicitly */
+    g_assert (ctx->modem_init_required);
     g_object_set (ctx->ports->primary,
                   MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, TRUE,
                   NULL);
@@ -10053,64 +10172,6 @@
     g_object_unref (task);
 }
 
-static gboolean
-open_ports_enabling (MMBroadbandModem *self,
-                     PortsContext *ctx,
-                     gboolean modem_init_required,
-                     GError **error)
-{
-    /* Open primary */
-    ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
-    if (!ctx->primary) {
-        g_set_error (error,
-                     MM_CORE_ERROR,
-                     MM_CORE_ERROR_FAILED,
-                     "Couldn't get primary port");
-        return FALSE;
-    }
-
-    /* If we'll need to run modem initialization, disable port init sequence */
-    if (modem_init_required)
-        g_object_set (ctx->primary,
-                      MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE,
-                      NULL);
-
-
-    if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->primary), error)) {
-        g_prefix_error (error, "Couldn't open primary port: ");
-        return FALSE;
-    }
-
-    ctx->primary_open = TRUE;
-
-    /* Open secondary (optional) */
-    ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
-    if (ctx->secondary) {
-        /* If we'll need to run modem initialization, disable port init sequence */
-        if (modem_init_required)
-            g_object_set (ctx->secondary,
-                          MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE,
-                          NULL);
-        if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->secondary), error)) {
-            g_prefix_error (error, "Couldn't open secondary port: ");
-            return FALSE;
-        }
-        ctx->secondary_open = TRUE;
-    }
-
-    /* Open qcdm (optional) */
-    ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
-    if (ctx->qcdm) {
-        if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->qcdm), error)) {
-            g_prefix_error (error, "Couldn't open QCDM port: ");
-            return FALSE;
-        }
-        ctx->qcdm_open = TRUE;
-    }
-
-    return TRUE;
-}
-
 static void
 enabling_started (MMBroadbandModem *self,
                   GAsyncReadyCallback callback,
@@ -10121,8 +10182,7 @@
     GTask *task;
 
     ctx = g_slice_new0 (EnablingStartedContext);
-    ctx->ports = g_new0 (PortsContext, 1);
-    ctx->ports->ref_count = 1;
+    ctx->ports = ports_context_new ();
 
     /* Skip modem initialization if the device was hotplugged OR if we already
      * did it (i.e. don't reinitialize if the modem got disabled and enabled
@@ -10141,8 +10201,8 @@
     task = g_task_new (self, NULL, callback, user_data);
     g_task_set_task_data (task, ctx, (GDestroyNotify)enabling_started_context_free);
 
-    /* Enabling */
-    if (!open_ports_enabling (self, ctx->ports, ctx->modem_init_required, &error)) {
+    /* Open ports for enabling, including secondary AT port and QCDM if available */
+    if (!ports_context_open (self, ctx->ports, ctx->modem_init_required, TRUE, TRUE, &error)) {
         g_prefix_error (&error, "Couldn't open ports during modem enabling: ");
         g_task_return_error (task, error);
         g_object_unref (task);
@@ -10219,11 +10279,11 @@
     DISABLING_STEP_DISCONNECT_BEARERS,
     DISABLING_STEP_IFACE_SIMPLE,
     DISABLING_STEP_IFACE_FIRMWARE,
+    DISABLING_STEP_IFACE_VOICE,
     DISABLING_STEP_IFACE_SIGNAL,
     DISABLING_STEP_IFACE_OMA,
     DISABLING_STEP_IFACE_TIME,
     DISABLING_STEP_IFACE_MESSAGING,
-    DISABLING_STEP_IFACE_VOICE,
     DISABLING_STEP_IFACE_LOCATION,
     DISABLING_STEP_IFACE_CDMA,
     DISABLING_STEP_IFACE_3GPP_USSD,
@@ -10423,6 +10483,18 @@
         /* Fall down to next step */
         ctx->step++;
 
+    case DISABLING_STEP_IFACE_VOICE:
+        if (ctx->self->priv->modem_voice_dbus_skeleton) {
+            mm_dbg ("Modem has voice capabilities, disabling the Voice interface...");
+            /* Disabling the Modem Voice interface */
+            mm_iface_modem_voice_disable (MM_IFACE_MODEM_VOICE (ctx->self),
+                                          (GAsyncReadyCallback)iface_modem_voice_disable_ready,
+                                          task);
+            return;
+        }
+        /* Fall down to next step */
+        ctx->step++;
+
     case DISABLING_STEP_IFACE_SIGNAL:
         if (ctx->self->priv->modem_signal_dbus_skeleton) {
             mm_dbg ("Modem has extended signal reporting capabilities, disabling the Signal interface...");
@@ -10471,18 +10543,6 @@
         /* Fall down to next step */
         ctx->step++;
 
-    case DISABLING_STEP_IFACE_VOICE:
-        if (ctx->self->priv->modem_voice_dbus_skeleton) {
-            mm_dbg ("Modem has voice capabilities, disabling the Voice interface...");
-            /* Disabling the Modem Voice interface */
-            mm_iface_modem_voice_disable (MM_IFACE_MODEM_VOICE (ctx->self),
-                                          (GAsyncReadyCallback)iface_modem_voice_disable_ready,
-                                          task);
-            return;
-        }
-        /* Fall down to next step */
-        ctx->step++;
-
     case DISABLING_STEP_IFACE_LOCATION:
         if (ctx->self->priv->modem_location_dbus_skeleton) {
             mm_dbg ("Modem has location capabilities, disabling the Location interface...");
@@ -10586,10 +10646,10 @@
     ENABLING_STEP_IFACE_CDMA,
     ENABLING_STEP_IFACE_LOCATION,
     ENABLING_STEP_IFACE_MESSAGING,
-    ENABLING_STEP_IFACE_VOICE,
     ENABLING_STEP_IFACE_TIME,
     ENABLING_STEP_IFACE_SIGNAL,
     ENABLING_STEP_IFACE_OMA,
+    ENABLING_STEP_IFACE_VOICE,
     ENABLING_STEP_IFACE_FIRMWARE,
     ENABLING_STEP_IFACE_SIMPLE,
     ENABLING_STEP_LAST,
@@ -10833,19 +10893,6 @@
         /* Fall down to next step */
         ctx->step++;
 
-    case ENABLING_STEP_IFACE_VOICE:
-        if (ctx->self->priv->modem_voice_dbus_skeleton) {
-            mm_dbg ("Modem has voice capabilities, enabling the Voice interface...");
-            /* Enabling the Modem Voice interface */
-            mm_iface_modem_voice_enable (MM_IFACE_MODEM_VOICE (ctx->self),
-                                         g_task_get_cancellable (task),
-                                         (GAsyncReadyCallback)iface_modem_voice_enable_ready,
-                                         task);
-            return;
-        }
-        /* Fall down to next step */
-        ctx->step++;
-
     case ENABLING_STEP_IFACE_TIME:
         if (ctx->self->priv->modem_time_dbus_skeleton) {
             mm_dbg ("Modem has time capabilities, enabling the Time interface...");
@@ -10885,6 +10932,19 @@
         /* Fall down to next step */
         ctx->step++;
 
+    case ENABLING_STEP_IFACE_VOICE:
+        if (ctx->self->priv->modem_voice_dbus_skeleton) {
+            mm_dbg ("Modem has voice capabilities, enabling the Voice interface...");
+            /* Enabling the Modem Voice interface */
+            mm_iface_modem_voice_enable (MM_IFACE_MODEM_VOICE (ctx->self),
+                                         g_task_get_cancellable (task),
+                                         (GAsyncReadyCallback)iface_modem_voice_enable_ready,
+                                         task);
+            return;
+        }
+        /* Fall down to next step */
+        ctx->step++;
+
     case ENABLING_STEP_IFACE_FIRMWARE:
         /* Fall down to next step */
         ctx->step++;
@@ -10991,10 +11051,11 @@
     INITIALIZE_STEP_IFACE_CDMA,
     INITIALIZE_STEP_IFACE_LOCATION,
     INITIALIZE_STEP_IFACE_MESSAGING,
-    INITIALIZE_STEP_IFACE_VOICE,
     INITIALIZE_STEP_IFACE_TIME,
     INITIALIZE_STEP_IFACE_SIGNAL,
     INITIALIZE_STEP_IFACE_OMA,
+    INITIALIZE_STEP_FALLBACK_LIMITED,
+    INITIALIZE_STEP_IFACE_VOICE,
     INITIALIZE_STEP_IFACE_FIRMWARE,
     INITIALIZE_STEP_SIM_HOT_SWAP,
     INITIALIZE_STEP_IFACE_SIMPLE,
@@ -11101,9 +11162,9 @@
 
         mm_iface_modem_update_failed_state (MM_IFACE_MODEM (self), failed_reason);
 
-        /* Jump to the firmware step. We allow firmware switching even in failed
-         * state */
-        ctx->step = INITIALIZE_STEP_IFACE_FIRMWARE;
+        /* Jump to the fallback step when on failure, we will allow some additional
+         * interfaces even in failed state. */
+        ctx->step = INITIALIZE_STEP_FALLBACK_LIMITED;
         initialize_step (task);
         return;
     }
@@ -11116,9 +11177,9 @@
      * the initialization sequence. Instead, we will re-initialize once
      * we are unlocked. */
     if (ctx->self->priv->modem_state == MM_MODEM_STATE_LOCKED) {
-        /* Jump to the Firmware interface. We do allow modems to export
-         * both the Firmware and Simple interfaces when locked. */
-        ctx->step = INITIALIZE_STEP_IFACE_FIRMWARE;
+        /* Jump to the fallback step when locked, we will allow some additional
+         * interfaces even in locked state. */
+        ctx->step = INITIALIZE_STEP_FALLBACK_LIMITED;
         initialize_step (task);
         return;
     }
@@ -11286,14 +11347,6 @@
                                              task);
         return;
 
-    case INITIALIZE_STEP_IFACE_VOICE:
-        /* Initialize the Voice interface */
-        mm_iface_modem_voice_initialize (MM_IFACE_MODEM_VOICE (ctx->self),
-                                         g_task_get_cancellable (task),
-                                         (GAsyncReadyCallback)iface_modem_voice_initialize_ready,
-                                         task);
-        return;
-
     case INITIALIZE_STEP_IFACE_TIME:
         /* Initialize the Time interface */
         mm_iface_modem_time_initialize (MM_IFACE_MODEM_TIME (ctx->self),
@@ -11318,6 +11371,20 @@
                                        task);
         return;
 
+    case INITIALIZE_STEP_FALLBACK_LIMITED:
+        /* All the initialization steps after this one will be run both on
+         * successful and locked/failed initializations.
+         * Fall down to next step */
+        ctx->step++;
+
+    case INITIALIZE_STEP_IFACE_VOICE:
+        /* Initialize the Voice interface */
+        mm_iface_modem_voice_initialize (MM_IFACE_MODEM_VOICE (ctx->self),
+                                         g_task_get_cancellable (task),
+                                         (GAsyncReadyCallback)iface_modem_voice_initialize_ready,
+                                         task);
+        return;
+
     case INITIALIZE_STEP_IFACE_FIRMWARE:
         /* Initialize the Firmware interface */
         mm_iface_modem_firmware_initialize (MM_IFACE_MODEM_FIRMWARE (ctx->self),
@@ -11347,11 +11414,8 @@
                     GError *error = NULL;
 
                     mm_dbg ("Creating ports context for SIM hot swap");
-
-                    ports = g_new0 (PortsContext, 1);
-                    ports->ref_count = 1;
-
-                    if (!open_ports_enabling (ctx->self, ports, FALSE, &error)) {
+                    ports = ports_context_new ();
+                    if (!ports_context_open (ctx->self, ports, FALSE, FALSE, FALSE, &error)) {
                         mm_warn ("Couldn't open ports during Modem SIM hot swap enabling: %s", error? error->message : "unknown reason");
                         g_error_free (error);
                     } else {
@@ -11420,9 +11484,11 @@
                                      "Modem is unusable, "
                                      "cannot fully initialize");
 sim_hot_swap_enabled:
-                /* Ensure we only leave the Modem, OMA, and Firmware interfaces
+                /* Ensure we only leave the Modem and Firmware interfaces
                  * around.  A failure could be caused by firmware issues, which
-                 * a firmware update, switch, or provisioning could fix.
+                 * a firmware update, switch, or provisioning could fix. We also
+                 * leave the Voice interface around so that we can attempt
+                 * emergency voice calls.
                  */
                 mm_iface_modem_3gpp_shutdown (MM_IFACE_MODEM_3GPP (ctx->self));
                 mm_iface_modem_3gpp_ussd_shutdown (MM_IFACE_MODEM_3GPP_USSD (ctx->self));
@@ -11430,7 +11496,6 @@
                 mm_iface_modem_location_shutdown (MM_IFACE_MODEM_LOCATION (ctx->self));
                 mm_iface_modem_signal_shutdown (MM_IFACE_MODEM_SIGNAL (ctx->self));
                 mm_iface_modem_messaging_shutdown (MM_IFACE_MODEM_MESSAGING (ctx->self));
-                mm_iface_modem_voice_shutdown (MM_IFACE_MODEM_VOICE (ctx->self));
                 mm_iface_modem_time_shutdown (MM_IFACE_MODEM_TIME (ctx->self));
                 mm_iface_modem_simple_shutdown (MM_IFACE_MODEM_SIMPLE (ctx->self));
             }
@@ -11760,6 +11825,9 @@
     case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
         self->priv->periodic_signal_check_disabled = g_value_get_boolean (value);
         break;
+    case PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED:
+        self->priv->periodic_access_tech_check_disabled = g_value_get_boolean (value);
+        break;
     case PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED:
         self->priv->periodic_call_list_check_disabled = g_value_get_boolean (value);
         break;
@@ -11769,6 +11837,9 @@
     case PROP_FLOW_CONTROL:
         self->priv->flow_control = g_value_get_flags (value);
         break;
+    case PROP_INDICATORS_DISABLED:
+        self->priv->modem_cind_disabled = g_value_get_boolean (value);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -11886,6 +11957,9 @@
     case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
         g_value_set_boolean (value, self->priv->periodic_signal_check_disabled);
         break;
+    case PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED:
+        g_value_set_boolean (value, self->priv->periodic_access_tech_check_disabled);
+        break;
     case PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED:
         g_value_set_boolean (value, self->priv->periodic_call_list_check_disabled);
         break;
@@ -11895,6 +11969,9 @@
     case PROP_FLOW_CONTROL:
         g_value_set_flags (value, self->priv->flow_control);
         break;
+    case PROP_INDICATORS_DISABLED:
+        g_value_set_boolean (value, self->priv->modem_cind_disabled);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -11925,6 +12002,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_access_tech_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;
@@ -11943,6 +12021,9 @@
     if (self->priv->sim_hot_swap_ports_ctx)
         ports_context_unref (self->priv->sim_hot_swap_ports_ctx);
 
+    if (self->priv->in_call_ports_ctx)
+        ports_context_unref (self->priv->in_call_ports_ctx);
+
     if (self->priv->modem_3gpp_registration_regex)
         mm_3gpp_creg_regex_destroy (self->priv->modem_3gpp_registration_regex);
 
@@ -12006,6 +12087,11 @@
         g_clear_object (&self->priv->modem_simple_dbus_skeleton);
     }
 
+    if (self->priv->modem_firmware_dbus_skeleton) {
+        mm_iface_modem_firmware_shutdown (MM_IFACE_MODEM_FIRMWARE (object));
+        g_clear_object (&self->priv->modem_firmware_dbus_skeleton);
+    }
+
     g_clear_object (&self->priv->modem_3gpp_initial_eps_bearer);
     g_clear_object (&self->priv->modem_sim);
     g_clear_object (&self->priv->modem_bearer_list);
@@ -12454,6 +12540,10 @@
                                       MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED);
 
     g_object_class_override_property (object_class,
+                                      PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
+                                      MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED);
+
+    g_object_class_override_property (object_class,
                                       PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
                                       MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED);
 
@@ -12469,4 +12559,12 @@
                             MM_FLOW_CONTROL_NONE,
                             G_PARAM_READWRITE);
     g_object_class_install_property (object_class, PROP_FLOW_CONTROL, properties[PROP_FLOW_CONTROL]);
+
+    properties[PROP_INDICATORS_DISABLED] =
+        g_param_spec_boolean (MM_BROADBAND_MODEM_INDICATORS_DISABLED,
+                              "Disable indicators",
+                              "Avoid explicitly setting up +CIND URCs",
+                              FALSE,
+                              G_PARAM_READWRITE);
+    g_object_class_install_property (object_class, PROP_INDICATORS_DISABLED, properties[PROP_INDICATORS_DISABLED]);
 }
diff --git a/src/mm-broadband-modem.h b/src/mm-broadband-modem.h
index 4625b22..8042f50 100644
--- a/src/mm-broadband-modem.h
+++ b/src/mm-broadband-modem.h
@@ -39,7 +39,8 @@
 typedef struct _MMBroadbandModemClass MMBroadbandModemClass;
 typedef struct _MMBroadbandModemPrivate MMBroadbandModemPrivate;
 
-#define MM_BROADBAND_MODEM_FLOW_CONTROL "broadband-modem-flow-control"
+#define MM_BROADBAND_MODEM_FLOW_CONTROL        "broadband-modem-flow-control"
+#define MM_BROADBAND_MODEM_INDICATORS_DISABLED "broadband-modem-indicators-disabled"
 
 struct _MMBroadbandModem {
     MMBaseModem parent;
diff --git a/src/mm-iface-modem-firmware.c b/src/mm-iface-modem-firmware.c
index a3e68c5..f65b775 100644
--- a/src/mm-iface-modem-firmware.c
+++ b/src/mm-iface-modem-firmware.c
@@ -157,7 +157,7 @@
 
     mm_base_modem_authorize (MM_BASE_MODEM (self),
                              invocation,
-                             MM_AUTHORIZATION_DEVICE_CONTROL,
+                             MM_AUTHORIZATION_FIRMWARE,
                              (GAsyncReadyCallback)list_auth_ready,
                              ctx);
 
@@ -244,7 +244,7 @@
 
     mm_base_modem_authorize (MM_BASE_MODEM (self),
                              invocation,
-                             MM_AUTHORIZATION_DEVICE_CONTROL,
+                             MM_AUTHORIZATION_FIRMWARE,
                              (GAsyncReadyCallback)select_auth_ready,
                              ctx);
 
diff --git a/src/mm-iface-modem-simple.c b/src/mm-iface-modem-simple.c
index ecf0b68..893c470 100644
--- a/src/mm-iface-modem-simple.c
+++ b/src/mm-iface-modem-simple.c
@@ -29,6 +29,40 @@
 #include "mm-log.h"
 
 /*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "iface-modem-simple-private-tag"
+static GQuark private_quark;
+
+typedef struct {
+    GCancellable *ongoing_connect;
+} Private;
+
+static void
+private_free (Private *priv)
+{
+    g_assert (!priv->ongoing_connect);
+    g_slice_free (Private, priv);
+}
+
+static Private *
+get_private (MMIfaceModemSimple *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);
+        g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+    }
+
+    return priv;
+}
+
+/*****************************************************************************/
 /* Register in either a CDMA or a 3GPP network (or both) */
 
 typedef struct {
@@ -198,16 +232,18 @@
 
     /* Results to set */
     MMBaseBearer *bearer;
+
+    /* Cancellation */
+    GCancellable *cancellable;
 } ConnectionContext;
 
 static void
 connection_context_free (ConnectionContext *ctx)
 {
+    g_clear_object (&ctx->cancellable);
+    g_clear_object (&ctx->properties);
+    g_clear_object (&ctx->bearer);
     g_variant_unref (ctx->dictionary);
-    if (ctx->properties)
-        g_object_unref (ctx->properties);
-    if (ctx->bearer)
-        g_object_unref (ctx->bearer);
     g_object_unref (ctx->skeleton);
     g_object_unref (ctx->invocation);
     g_object_unref (ctx->self);
@@ -436,9 +472,58 @@
         ctx->found = g_object_ref (bearer);
 }
 
+static gboolean
+completed_if_cancelled (ConnectionContext *ctx)
+{
+    /* Do nothing if not cancelled */
+    if (!g_cancellable_is_cancelled (ctx->cancellable))
+        return FALSE;
+
+    /* Otherwise cancellable is valid and it's cancelled */
+    g_dbus_method_invocation_return_error (
+        ctx->invocation, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+        "Connection attempt cancelled");
+    connection_context_free (ctx);
+    return TRUE;
+}
+
+static void
+cleanup_cancellation (ConnectionContext *ctx)
+{
+    Private *priv;
+
+    priv = get_private (ctx->self);
+    g_clear_object (&priv->ongoing_connect);
+    g_clear_object (&ctx->cancellable);
+}
+
+static gboolean
+setup_cancellation (ConnectionContext  *ctx,
+                    GError            **error)
+{
+    Private *priv;
+
+    /* Only one connection attempt by Simple.Connect() may be handled at a time */
+    priv = get_private (ctx->self);
+    if (priv->ongoing_connect) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS,
+                     "Connection request forbidden: operation already in progress");
+        return FALSE;
+    }
+
+    g_assert (!ctx->cancellable);
+    ctx->cancellable = g_cancellable_new ();
+    priv->ongoing_connect = g_object_ref (ctx->cancellable);
+    return TRUE;
+}
+
 static void
 connection_step (ConnectionContext *ctx)
 {
+    /* Early abort if operation is cancelled */
+    if (completed_if_cancelled (ctx))
+        return;
+
     switch (ctx->step) {
     case CONNECTION_STEP_FIRST:
         /* Fall down to next step */
@@ -588,6 +673,10 @@
         mm_info ("Simple connect state (%d/%d): Connect",
                  ctx->step, CONNECTION_STEP_LAST);
 
+        /* At this point, we can cleanup the cancellation point in the Simple interface,
+         * because the bearer connection has its own cancellation setup. */
+        cleanup_cancellation (ctx);
+
         /* Wait... if we're already using an existing bearer, we need to check if it is
          * already connected; and if so, just don't do anything else */
         if (mm_base_bearer_get_status (ctx->bearer) != MM_BEARER_STATUS_CONNECTED) {
@@ -639,6 +728,12 @@
         return;
     }
 
+    if (!setup_cancellation (ctx, &error)) {
+        g_dbus_method_invocation_take_error (ctx->invocation, error);
+        connection_context_free (ctx);
+        return;
+    }
+
     /* We may be able to skip some steps, so check that before doing anything */
     g_object_get (self,
                   MM_IFACE_MODEM_STATE, &current,
@@ -736,6 +831,8 @@
     ctx->self = g_object_ref (self);
     ctx->dictionary = g_variant_ref (dictionary);
 
+    mm_dbg ("User request to connect modem");
+
     mm_base_modem_authorize (MM_BASE_MODEM (self),
                              invocation,
                              MM_AUTHORIZATION_DEVICE_CONTROL,
@@ -824,6 +921,7 @@
 {
     GError *error = NULL;
     MMBearerList *list = NULL;
+    Private *priv;
 
     if (!mm_base_modem_authorize_finish (self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -831,6 +929,14 @@
         return;
     }
 
+    /* If not disconnecting a specific bearer, also cancel any ongoing
+     * connection attempt. */
+    priv = get_private (MM_IFACE_MODEM_SIMPLE (self));
+    if (!ctx->bearer_path && priv->ongoing_connect) {
+        g_cancellable_cancel (priv->ongoing_connect);
+        g_clear_object (&priv->ongoing_connect);
+    }
+
     g_object_get (self,
                   MM_IFACE_MODEM_BEARER_LIST, &list,
                   NULL);
@@ -878,11 +984,18 @@
     ctx->self = g_object_ref (self);
     ctx->invocation = g_object_ref (invocation);
 
-    if (bearer_path &&
-        bearer_path[0] == '/' &&
-        bearer_path[1]) {
-        ctx->bearer_path = g_strdup (ctx->bearer_path);
-    }
+    /* The Disconnect() method expects a valid object path given in bearer path,
+     * it cannot be NULL, so we assume we get '/' when we're asked to disconnect
+     * all connected bearers, as that is what mm_modem_simple_disconnect() does
+     * when a NULL bearer path is given to that method.
+     *
+     * We will detect the '/' string and set the bearer path as NULL in the
+     * context if so, and otherwise use the given input string as path */
+    if (g_strcmp0 (bearer_path, "/") != 0) {
+        mm_dbg ("User request to disconnect modem (bearer '%s')", bearer_path);
+        ctx->bearer_path = g_strdup (bearer_path);
+    } else
+        mm_dbg ("User request to disconnect modem (all bearers)");
 
     mm_base_modem_authorize (MM_BASE_MODEM (self),
                              invocation,
diff --git a/src/mm-iface-modem-time.c b/src/mm-iface-modem-time.c
index b8d35bf..1ed4b90 100644
--- a/src/mm-iface-modem-time.c
+++ b/src/mm-iface-modem-time.c
@@ -55,42 +55,34 @@
 }
 
 static void
-load_network_time_ready (MMIfaceModemTime *self,
-                         GAsyncResult *res,
+load_network_time_ready (MMIfaceModemTime            *self,
+                         GAsyncResult                *res,
                          HandleGetNetworkTimeContext *ctx)
 {
     gchar *time_str;
     GError *error = NULL;
 
-    time_str = MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_time_finish (self,
-                                                                                   res,
-                                                                                   &error);
+    time_str = MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_time_finish (self, res, &error);
     if (error)
         g_dbus_method_invocation_take_error (ctx->invocation, error);
     else
-        mm_gdbus_modem_time_complete_get_network_time (ctx->skeleton,
-                                                       ctx->invocation,
-                                                       time_str);
+        mm_gdbus_modem_time_complete_get_network_time (ctx->skeleton, ctx->invocation, time_str);
     g_free (time_str);
     handle_get_network_time_context_free (ctx);
 }
 
-static gboolean
-handle_get_network_time (MmGdbusModemTime *skeleton,
-                         GDBusMethodInvocation *invocation,
-                         MMIfaceModemTime *self)
+static void
+handle_get_network_time_auth_ready (MMBaseModem                 *self,
+                                    GAsyncResult                *res,
+                                    HandleGetNetworkTimeContext *ctx)
 {
-    HandleGetNetworkTimeContext *ctx;
-    MMModemState state;
+    MMModemState  state;
+    GError       *error = NULL;
 
-    if (!MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_time ||
-        !MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_time_finish) {
-        g_dbus_method_invocation_return_error (invocation,
-                                               MM_CORE_ERROR,
-                                               MM_CORE_ERROR_UNSUPPORTED,
-                                               "Cannot load network time: "
-                                               "operation not supported");
-        return TRUE;
+    if (!mm_base_modem_authorize_finish (self, res, &error)) {
+        g_dbus_method_invocation_take_error (ctx->invocation, error);
+        handle_get_network_time_context_free (ctx);
+        return;
     }
 
     state = MM_MODEM_STATE_UNKNOWN;
@@ -99,23 +91,49 @@
                   NULL);
     /* If we're not yet registered, we cannot get the network time */
     if (state < MM_MODEM_STATE_REGISTERED) {
-        g_dbus_method_invocation_return_error (invocation,
+        g_dbus_method_invocation_return_error (ctx->invocation,
                                                MM_CORE_ERROR,
                                                MM_CORE_ERROR_WRONG_STATE,
                                                "Cannot load network time: "
                                                "not registered yet");
-        return TRUE;
+        handle_get_network_time_context_free (ctx);
+        return;
     }
 
+    if (!MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_time ||
+        !MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_time_finish) {
+        g_dbus_method_invocation_return_error (ctx->invocation,
+                                               MM_CORE_ERROR,
+                                               MM_CORE_ERROR_UNSUPPORTED,
+                                               "Cannot load network time: "
+                                               "operation not supported");
+        handle_get_network_time_context_free (ctx);
+        return;
+    }
+
+    MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_time (
+        ctx->self,
+        (GAsyncReadyCallback)load_network_time_ready,
+        ctx);
+}
+
+static gboolean
+handle_get_network_time (MmGdbusModemTime      *skeleton,
+                         GDBusMethodInvocation *invocation,
+                         MMIfaceModemTime      *self)
+{
+    HandleGetNetworkTimeContext *ctx;
+
     ctx = g_new (HandleGetNetworkTimeContext, 1);
     ctx->invocation = g_object_ref (invocation);
     ctx->skeleton = g_object_ref (skeleton);
     ctx->self = g_object_ref (self);
 
-    MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_time (
-        self,
-        (GAsyncReadyCallback)load_network_time_ready,
-        ctx);
+    mm_base_modem_authorize (MM_BASE_MODEM (self),
+                             invocation,
+                             MM_AUTHORIZATION_TIME,
+                             (GAsyncReadyCallback)handle_get_network_time_auth_ready,
+                             ctx);
     return TRUE;
 }
 
diff --git a/src/mm-iface-modem-voice.c b/src/mm-iface-modem-voice.c
index 1c7af9c..ff685aa 100644
--- a/src/mm-iface-modem-voice.c
+++ b/src/mm-iface-modem-voice.c
@@ -23,13 +23,9 @@
 #include "mm-call-list.h"
 #include "mm-log.h"
 
-#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;
 
@@ -43,6 +39,109 @@
 
 /*****************************************************************************/
 
+gboolean
+mm_iface_modem_voice_authorize_outgoing_call (MMIfaceModemVoice  *self,
+                                              MMBaseCall         *call,
+                                              GError            **error)
+{
+    MmGdbusModemVoice *skeleton = NULL;
+    MMBaseSim         *sim = NULL;
+    gboolean           emergency_only = FALSE;
+    gboolean           call_allowed = FALSE;
+    GError            *inner_error = NULL;
+    guint              i;
+    const gchar       *number;
+
+    static const gchar *always_valid_emergency_numbers[] = { "112", "911" };
+    static const gchar *no_sim_valid_emergency_numbers[] = { "000", "08", "110", "999", "118", "119" };
+
+    g_assert (mm_base_call_get_direction (call) == MM_CALL_DIRECTION_OUTGOING);
+    number = mm_base_call_get_number (call);
+    g_assert (number);
+
+    g_object_get (self,
+                  MM_IFACE_MODEM_VOICE_DBUS_SKELETON, &skeleton,
+                  NULL);
+
+    if (!skeleton) {
+        g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                     "voice operations unsupported");
+        goto out;
+    }
+
+    g_object_get (skeleton,
+                  "emergency-only", &emergency_only,
+                  NULL);
+
+    /* Identification of emergency numbers. 3GPP TS 22.101
+     *
+     *   a) 112 and 911 shall always be available.
+     *   b) Any emergency call number stored on a SIM/USIM when the SIM/USIM is
+     *      present.
+     *   c) 000, 08, 110, 999, 118 and 119 when a SIM/USIM is not present.
+     *   d) Additional emergency call numbers that may have been downloaded by
+     *      the serving network when the SIM/USIM is present.
+     *
+     * In ModemManager we're not flagging any call as being "emergency" or
+     * "normal", but we can right away limit non-emergency calls if we're in
+     * "emergency-only" mode.
+     */
+
+    /* If we're not in emergency mode, the call (emergency or normal) is always allowed */
+    if (!emergency_only) {
+        mm_dbg ("voice call to %s allowed", number);
+        call_allowed = TRUE;
+        goto out;
+    }
+
+    for (i = 0; i < G_N_ELEMENTS (always_valid_emergency_numbers); i++) {
+        if (g_strcmp0 (number, always_valid_emergency_numbers[i]) == 0) {
+            mm_dbg ("voice call to %s allowed: emergency call number always valid", number);
+            call_allowed = TRUE;
+            goto out;
+        }
+    }
+
+    /* Check if we have a SIM */
+    g_object_get (self,
+                  MM_IFACE_MODEM_SIM, &sim,
+                  NULL);
+    if (!sim) {
+        /* If no SIM available, some additional numbers may be valid emergency numbers */
+        for (i = 0; i < G_N_ELEMENTS (no_sim_valid_emergency_numbers); i++) {
+            if (g_strcmp0 (number, no_sim_valid_emergency_numbers[i]) == 0) {
+                mm_dbg ("voice call to %s allowed: emergency call number valid when no SIM", number);
+                call_allowed = TRUE;
+                goto out;
+            }
+        }
+
+        mm_dbg ("voice call to %s NOT allowed: not a valid emergency call number when no SIM", number);
+        goto out;
+    }
+
+    /* Check if the number is programmed in EF_ECC */
+    if (mm_base_sim_is_emergency_number (sim, number)) {
+        mm_dbg ("voice call to %s allowed: emergency call number programmed in the SIM", number);
+        call_allowed = TRUE;
+    } else
+        mm_dbg ("voice call to %s NOT allowed: not a valid emergency call number programmed in the SIM", number);
+
+ out:
+
+    if (inner_error)
+        g_propagate_error (error, inner_error);
+    else if (!call_allowed)
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED,
+                     "only emergency calls allowed");
+
+    g_clear_object (&skeleton);
+    g_clear_object (&sim);
+    return call_allowed;
+}
+
+/*****************************************************************************/
+
 /* 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);
@@ -355,6 +454,11 @@
     for (l = ctx.call_info_list; l; l = g_list_next (l)) {
         MMCallInfo *call_info = (MMCallInfo *)(l->data);
 
+        /* Ignore unknown terminated calls, because these be due to an already
+         * processed event. */
+        if (call_info->state == MM_CALL_STATE_TERMINATED)
+            continue;
+
         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",
@@ -464,7 +568,6 @@
                           GAsyncResult *res,
                           HandleDeleteContext *ctx)
 {
-    MMModemState modem_state = MM_MODEM_STATE_UNKNOWN;
     MMCallList *list = NULL;
     GError *error = NULL;
 
@@ -475,19 +578,6 @@
     }
 
     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 delete call: device not yet enabled");
-        handle_delete_context_free (ctx);
-        return;
-    }
-
-    g_object_get (self,
                   MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
                   NULL);
     if (!list) {
@@ -554,7 +644,6 @@
                           GAsyncResult *res,
                           HandleCreateContext *ctx)
 {
-    MMModemState modem_state = MM_MODEM_STATE_UNKNOWN;
     MMCallList *list = NULL;
     GError *error = NULL;
     MMCallProperties *properties;
@@ -566,19 +655,6 @@
         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 create CALL: device not yet enabled");
-        handle_create_context_free (ctx);
-        return;
-    }
-
     /* Parse input properties */
     properties = mm_call_properties_new_from_dictionary (ctx->dictionary, &error);
     if (!properties) {
@@ -656,21 +732,6 @@
 {
     GStrv paths;
     MMCallList *list = NULL;
-    MMModemState modem_state;
-
-    modem_state = MM_MODEM_STATE_UNKNOWN;
-    g_object_get (self,
-                  MM_IFACE_MODEM_STATE, &modem_state,
-                  NULL);
-
-    if (modem_state < MM_MODEM_STATE_ENABLED) {
-        g_dbus_method_invocation_return_error (invocation,
-                                               MM_CORE_ERROR,
-                                               MM_CORE_ERROR_WRONG_STATE,
-                                               "Cannot list CALL messages: "
-                                               "device not yet enabled");
-        return TRUE;
-    }
 
     g_object_get (self,
                   MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
@@ -762,9 +823,8 @@
                                    GAsyncResult               *res,
                                    HandleHoldAndAcceptContext *ctx)
 {
-    MMModemState  modem_state = MM_MODEM_STATE_UNKNOWN;
-    GError       *error = NULL;
-    MMCallList   *list = NULL;
+    GError     *error = NULL;
+    MMCallList *list = NULL;
 
     if (!mm_base_modem_authorize_finish (self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -772,19 +832,6 @@
         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,
@@ -904,9 +951,8 @@
                                      GAsyncResult                 *res,
                                      HandleHangupAndAcceptContext *ctx)
 {
-    MMModemState  modem_state = MM_MODEM_STATE_UNKNOWN;
-    GError       *error = NULL;
-    MMCallList   *list = NULL;
+    GError     *error = NULL;
+    MMCallList *list = NULL;
 
     if (!mm_base_modem_authorize_finish (self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -914,19 +960,6 @@
         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,
@@ -1059,9 +1092,8 @@
                               GAsyncResult           *res,
                               HandleHangupAllContext *ctx)
 {
-    MMModemState  modem_state = MM_MODEM_STATE_UNKNOWN;
-    GError       *error = NULL;
-    MMCallList   *list = NULL;
+    GError     *error = NULL;
+    MMCallList *list = NULL;
 
     if (!mm_base_modem_authorize_finish (self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -1069,19 +1101,6 @@
         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,
@@ -1190,9 +1209,8 @@
                             GAsyncResult          *res,
                             HandleTransferContext *ctx)
 {
-    MMModemState  modem_state = MM_MODEM_STATE_UNKNOWN;
-    GError       *error = NULL;
-    MMCallList   *list = NULL;
+    GError     *error = NULL;
+    MMCallList *list = NULL;
 
     if (!mm_base_modem_authorize_finish (self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -1200,19 +1218,6 @@
         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,
@@ -1302,8 +1307,7 @@
                                       GAsyncResult                  *res,
                                       HandleCallWaitingSetupContext *ctx)
 {
-    MMModemState  modem_state = MM_MODEM_STATE_UNKNOWN;
-    GError       *error = NULL;
+    GError *error = NULL;
 
     if (!mm_base_modem_authorize_finish (self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -1311,19 +1315,6 @@
         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,
@@ -1403,8 +1394,7 @@
                                       GAsyncResult                  *res,
                                       HandleCallWaitingQueryContext *ctx)
 {
-    MMModemState  modem_state = MM_MODEM_STATE_UNKNOWN;
-    GError       *error = NULL;
+    GError *error = NULL;
 
     if (!mm_base_modem_authorize_finish (self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -1412,19 +1402,6 @@
         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,
@@ -2165,7 +2142,7 @@
 
     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))
+        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
             mm_warn ("Cannot cleanup in-call modem state: %s", error->message);
         g_clear_error (&error);
     } else {
@@ -2189,7 +2166,7 @@
 
     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))
+        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
             mm_warn ("Cannot setup in-call modem state: %s", error->message);
         g_clear_error (&error);
     } else {
@@ -2600,11 +2577,6 @@
         ctx->step++;
 
     case DISABLING_STEP_LAST:
-        /* Clear CALL list */
-        g_object_set (self,
-                      MM_IFACE_MODEM_VOICE_CALL_LIST, NULL,
-                      NULL);
-
         /* We are done without errors! */
         g_task_return_boolean (task, TRUE);
         g_object_unref (task);
@@ -2734,55 +2706,12 @@
     ctx = g_task_get_task_data (task);
 
     switch (ctx->step) {
-    case ENABLING_STEP_FIRST: {
-        MMCallList *list;
-
-        list = mm_call_list_new (MM_BASE_MODEM (self));
-        g_object_set (self,
-                      MM_IFACE_MODEM_VOICE_CALL_LIST, list,
-                      NULL);
-
-        /* Connect to list's signals */
-        g_signal_connect (list,
-                          MM_CALL_ADDED,
-                          G_CALLBACK (call_added),
-                          ctx->skeleton);
-        g_signal_connect (list,
-                          MM_CALL_DELETED,
-                          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);
-
+    case ENABLING_STEP_FIRST:
         /* Fall down to next step */
         ctx->step++;
-    }
 
     case ENABLING_STEP_SETUP_UNSOLICITED_EVENTS:
-        /* Allow setting up unsolicited events */
+        /* Allow setting up unsolicited events to get notified of incoming calls */
         if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_unsolicited_events &&
             MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_unsolicited_events_finish) {
             MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->setup_unsolicited_events (
@@ -2795,7 +2724,7 @@
         ctx->step++;
 
     case ENABLING_STEP_ENABLE_UNSOLICITED_EVENTS:
-        /* Allow setting up unsolicited events */
+        /* Allow setting up unsolicited events to get notified of incoming calls */
         if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->enable_unsolicited_events &&
             MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->enable_unsolicited_events_finish) {
             MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->enable_unsolicited_events (
@@ -2855,7 +2784,7 @@
 typedef enum {
     INITIALIZATION_STEP_FIRST,
     INITIALIZATION_STEP_CHECK_SUPPORT,
-    INITIALIZATION_STEP_FAIL_IF_UNSUPPORTED,
+    INITIALIZATION_STEP_SETUP_CALL_LIST,
     INITIALIZATION_STEP_LAST
 } InitializationStep;
 
@@ -2879,19 +2808,12 @@
     InitializationContext *ctx;
     GError *error = NULL;
 
-    if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support_finish (self,
-                                                                          res,
-                                                                          &error)) {
-        if (error) {
-            /* This error shouldn't be treated as critical */
-            mm_dbg ("Voice support check failed: '%s'", error->message);
-            g_error_free (error);
-        }
-    } else {
-        /* Voice is supported! */
-        g_object_set_qdata (G_OBJECT (self),
-                            supported_quark,
-                            GUINT_TO_POINTER (TRUE));
+    if (!MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support_finish (self, res, &error)) {
+        mm_dbg ("Voice support check failed: '%s'", error->message);
+        g_error_free (error);
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Voice not supported");
+        g_object_unref (task);
+        return;
     }
 
     /* Go on to next step */
@@ -2917,56 +2839,83 @@
 
     switch (ctx->step) {
     case INITIALIZATION_STEP_FIRST:
-        /* Setup quarks if we didn't do it before */
-        if (G_UNLIKELY (!support_checked_quark))
-            support_checked_quark = (g_quark_from_static_string (
-                                         SUPPORT_CHECKED_TAG));
-        if (G_UNLIKELY (!supported_quark))
-            supported_quark = (g_quark_from_static_string (
-                                   SUPPORTED_TAG));
-
         /* Fall down to next step */
         ctx->step++;
 
     case INITIALIZATION_STEP_CHECK_SUPPORT:
-        if (!GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (self),
-                                                   support_checked_quark))) {
-            /* Set the checked flag so that we don't run it again */
-            g_object_set_qdata (G_OBJECT (self),
-                                support_checked_quark,
-                                GUINT_TO_POINTER (TRUE));
-            /* Initially, assume we don't support it */
-            g_object_set_qdata (G_OBJECT (self),
-                                supported_quark,
-                                GUINT_TO_POINTER (FALSE));
-
-            if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support &&
-                MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support_finish) {
-                MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support (
-                    self,
-                    (GAsyncReadyCallback)check_support_ready,
-                    task);
-                return;
-            }
-
-            /* If there is no implementation to check support, assume we DON'T
-             * support it. */
-        }
-        /* Fall down to next step */
-        ctx->step++;
-
-    case INITIALIZATION_STEP_FAIL_IF_UNSUPPORTED:
-        if (!GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (self),
-                                                   supported_quark))) {
-            g_task_return_new_error (task,
-                                     MM_CORE_ERROR,
-                                     MM_CORE_ERROR_UNSUPPORTED,
-                                     "Voice not supported");
-            g_object_unref (task);
+        /* Always check voice support when we run initialization, because
+         * the support may be different before and after SIM-PIN unlock. */
+        if (MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support &&
+            MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support_finish) {
+            MM_IFACE_MODEM_VOICE_GET_INTERFACE (self)->check_support (
+                self,
+                (GAsyncReadyCallback)check_support_ready,
+                task);
             return;
         }
+
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Voice not supported");
+        g_object_unref (task);
+        return;
+
+    case INITIALIZATION_STEP_SETUP_CALL_LIST: {
+        MMCallList *list = NULL;
+
+        g_object_get (self,
+                      MM_IFACE_MODEM_VOICE_CALL_LIST, &list,
+                      NULL);
+
+        /* Create a new call list if not already available (this initialization
+         * may be called multiple times) */
+        if (!list) {
+            list = mm_call_list_new (MM_BASE_MODEM (self));
+            g_object_set (self,
+                          MM_IFACE_MODEM_VOICE_CALL_LIST, list,
+                          NULL);
+
+            /* Connect to list's signals */
+            g_signal_connect (list,
+                              MM_CALL_ADDED,
+                              G_CALLBACK (call_added),
+                              ctx->skeleton);
+            g_signal_connect (list,
+                              MM_CALL_DELETED,
+                              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;
+
+            /* Cleanup any previously configured handler, before checking if we need to
+             * add a new one, because the PERIODIC_CALL_LIST_CHECK_DISABLED flag may
+             * change before and after SIM-PIN unlock */
+            g_signal_handlers_disconnect_by_func (list, G_CALLBACK (setup_call_list_polling), self);
+
+            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 */
         ctx->step++;
+    }
 
     case INITIALIZATION_STEP_LAST:
         /* Setup all method handlers */
@@ -3002,6 +2951,20 @@
     return g_task_propagate_boolean (G_TASK (res), error);
 }
 
+static gboolean
+modem_state_to_emergency_only (GBinding     *binding,
+                               const GValue *from_value,
+                               GValue       *to_value)
+{
+    MMModemState state;
+
+    /* If the modem is REGISTERED, we allow any kind of call, otherwise
+     * only emergency calls */
+    state = g_value_get_enum (from_value);
+    g_value_set_boolean (to_value, (state < MM_MODEM_STATE_REGISTERED));
+    return TRUE;
+}
+
 void
 mm_iface_modem_voice_initialize (MMIfaceModemVoice *self,
                                  GCancellable *cancellable,
@@ -3022,6 +2985,12 @@
         g_object_set (self,
                       MM_IFACE_MODEM_VOICE_DBUS_SKELETON, skeleton,
                       NULL);
+
+        g_object_bind_property_full (self, MM_IFACE_MODEM_STATE,
+                                     skeleton, "emergency-only",
+                                     G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE,
+                                     (GBindingTransformFunc) modem_state_to_emergency_only,
+                                     NULL, NULL, NULL);
     }
 
     /* Perform async initialization here */
diff --git a/src/mm-iface-modem-voice.h b/src/mm-iface-modem-voice.h
index 7f793dc..2a9e52e 100644
--- a/src/mm-iface-modem-voice.h
+++ b/src/mm-iface-modem-voice.h
@@ -243,6 +243,11 @@
                                          guint              index,
                                          const gchar       *dtmf);
 
+/* Authorize outgoing call based on modem status and ECC list */
+gboolean mm_iface_modem_voice_authorize_outgoing_call (MMIfaceModemVoice  *self,
+                                                       MMBaseCall         *call,
+                                                       GError            **error);
+
 /* Join/Leave multiparty calls
  *
  * These actions are provided in the Call API, but implemented in the
diff --git a/src/mm-iface-modem.c b/src/mm-iface-modem.c
index c284524..4247a7e 100644
--- a/src/mm-iface-modem.c
+++ b/src/mm-iface-modem.c
@@ -315,8 +315,8 @@
         /* For several kinds of errors, just return them directly */
         if (error->domain == MM_SERIAL_ERROR ||
             g_error_matches (error,
-                             MM_CORE_ERROR,
-                             MM_CORE_ERROR_CANCELLED) ||
+                             G_IO_ERROR,
+                             G_IO_ERROR_CANCELLED) ||
             g_error_matches (error,
                              MM_MOBILE_EQUIPMENT_ERROR,
                              MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) ||
@@ -1142,18 +1142,24 @@
 
 typedef struct {
     gboolean enabled;
-    guint    interval;
-    guint    initial_retries;
     guint    timeout_source;
 
+    /* We first attempt an initial loading, and once it's done we
+     * setup polling */
+    guint    initial_retries;
+    gboolean initial_check_done;
+
     /* Values polled in this iteration */
     guint                   signal_quality;
     MMModemAccessTechnology access_technologies;
     guint                   access_technologies_mask;
 
-    /* If both these are unset we'll automatically stop polling */
+    /* If both signal and access tech polling are either unsupported
+     * or disabled, we'll automatically stop polling */
     gboolean signal_quality_polling_supported;
+    gboolean signal_quality_polling_disabled;
     gboolean access_technology_polling_supported;
+    gboolean access_technology_polling_disabled;
 
     /* Steps triggered when polling active */
     SignalCheckStep running_step;
@@ -1194,6 +1200,12 @@
         ctx->signal_quality_polling_supported = (MM_IFACE_MODEM_GET_INTERFACE (self)->load_signal_quality &&
                                                  MM_IFACE_MODEM_GET_INTERFACE (self)->load_signal_quality_finish);
 
+        /* Get plugin-specific setup for the polling logic */
+        g_object_get (self,
+                      MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,      &ctx->signal_quality_polling_disabled,
+                      MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED, &ctx->access_technology_polling_disabled,
+                      NULL);
+
         g_object_set_qdata_full (G_OBJECT (self), signal_check_context_quark,
                                  ctx, (GDestroyNotify) signal_check_context_free);
     }
@@ -1226,7 +1238,9 @@
         if (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {
             mm_dbg ("Polling to refresh access technologies is unsupported");
             ctx->access_technology_polling_supported = FALSE;
-        } else
+        }
+        /* Ignore logging any message if the error is in 'in-progress' */
+        else if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS))
             mm_dbg ("Couldn't refresh access technologies: '%s'", error->message);
         g_error_free (error);
     }
@@ -1254,7 +1268,9 @@
         if (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {
             mm_dbg ("Polling to refresh signal quality is unsupported");
             ctx->signal_quality_polling_supported = FALSE;
-        } else
+        }
+        /* Ignore logging any message if the error is in 'in-progress' */
+        else if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS))
             mm_dbg ("Couldn't refresh signal quality: '%s'", error->message);
         g_error_free (error);
     }
@@ -1270,7 +1286,6 @@
 static void
 peridic_signal_check_step (MMIfaceModem *self)
 {
-    gboolean periodic_signal_check_disabled = FALSE;
     SignalCheckContext *ctx;
 
     ctx = get_signal_check_context (self);
@@ -1284,7 +1299,8 @@
         ctx->running_step++;
 
     case SIGNAL_CHECK_STEP_SIGNAL_QUALITY:
-        if (ctx->enabled && ctx->signal_quality_polling_supported) {
+        if (ctx->enabled && ctx->signal_quality_polling_supported &&
+            (!ctx->initial_check_done || !ctx->signal_quality_polling_disabled)) {
             MM_IFACE_MODEM_GET_INTERFACE (self)->load_signal_quality (
                 self, (GAsyncReadyCallback)signal_quality_check_ready, NULL);
             return;
@@ -1293,7 +1309,8 @@
         ctx->running_step++;
 
     case SIGNAL_CHECK_STEP_ACCESS_TECHNOLOGIES:
-        if (ctx->enabled && ctx->access_technology_polling_supported) {
+        if (ctx->enabled && ctx->access_technology_polling_supported &&
+            (!ctx->initial_check_done || !ctx->access_technology_polling_disabled)) {
             MM_IFACE_MODEM_GET_INTERFACE (self)->load_access_technologies (
                 self, (GAsyncReadyCallback)access_technologies_check_ready, NULL);
             return;
@@ -1308,7 +1325,7 @@
         /* If we have been disabled while we were running the steps, we don't
          * do anything else. */
         if (!ctx->enabled) {
-            mm_dbg ("Periodic signal checks not rescheduled: disabled");
+            mm_dbg ("Periodic signal quality and access technology checks not rescheduled: disabled");
             return;
         }
 
@@ -1317,45 +1334,38 @@
          * quality and access technology values. As soon as we get them, OR if
          * we made too many retries at a high frequency, we fallback to the
          * slower polling. */
-        if (ctx->interval == SIGNAL_CHECK_INITIAL_TIMEOUT_SEC) {
+        if (!ctx->initial_check_done) {
             gboolean signal_quality_ready;
             gboolean access_technology_ready;
-            gboolean initial_check_done;
 
             /* Signal quality is ready if unsupported or if we got a valid
              * value reported */
             signal_quality_ready = (!ctx->signal_quality_polling_supported || (ctx->signal_quality != 0));
+
             /* Access technology is ready if unsupported or if we got a valid
              * value reported */
             access_technology_ready = (!ctx->access_technology_polling_supported ||
                                        ((ctx->access_technologies & ctx->access_technologies_mask) != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN));
 
-            initial_check_done = ((signal_quality_ready && access_technology_ready) ||
-                                  (--ctx->initial_retries == 0));
-            if (initial_check_done) {
-                /* After the initial check is done, check if periodic signal
-                 * check is disabled. */
-                g_object_get (self,
-                              MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
-                              &periodic_signal_check_disabled,
-                              NULL);
-                ctx->interval = SIGNAL_CHECK_TIMEOUT_SEC;
-            }
+            ctx->initial_check_done = ((signal_quality_ready && access_technology_ready) || (--ctx->initial_retries == 0));
         }
 
-        /* If both tasks are unsupported, implicitly disable. Do NOT clear the
-         * values, because if we're told they are unsupported it may be that
-         * they're really updated via unsolicited messages. */
-        if (!ctx->access_technology_polling_supported &&
-            (!ctx->signal_quality_polling_supported || periodic_signal_check_disabled)) {
-            mm_dbg ("Periodic signal and access technologies checks not supported");
+        /* After running the initial check, if both signal quality and access tech
+         * loading are either disabled or unsupported, we'll stop polling completely,
+         * because they may be loaded asynchronously by unsolicited messages */
+        if (ctx->initial_check_done &&
+            (!ctx->signal_quality_polling_supported    || ctx->signal_quality_polling_disabled) &&
+            (!ctx->access_technology_polling_supported || ctx->access_technology_polling_disabled)) {
+            mm_dbg ("Periodic signal quality and access technology checks not rescheduled: unneeded or unsupported");
             periodic_signal_check_disable (self, FALSE);
             return;
         }
 
-        mm_dbg ("Periodic signal quality checks scheduled in %ds", ctx->interval);
+        mm_dbg ("Periodic signal quality and access technology checks scheduled");
         g_assert (!ctx->timeout_source);
-        ctx->timeout_source = g_timeout_add_seconds (ctx->interval, (GSourceFunc) periodic_signal_check_cb, self);
+        ctx->timeout_source = g_timeout_add_seconds (ctx->initial_check_done ? SIGNAL_CHECK_TIMEOUT_SEC : SIGNAL_CHECK_INITIAL_TIMEOUT_SEC,
+                                                     (GSourceFunc) periodic_signal_check_cb,
+                                                     self);
         return;
     }
 }
@@ -1410,8 +1420,8 @@
 
     /* Reset refresh rate and initial retries when we're asked to refresh signal
      * so that we poll at a higher frequency */
-    ctx->interval        = SIGNAL_CHECK_INITIAL_TIMEOUT_SEC;
-    ctx->initial_retries = SIGNAL_CHECK_INITIAL_RETRIES;
+    ctx->initial_retries    = SIGNAL_CHECK_INITIAL_RETRIES;
+    ctx->initial_check_done = FALSE;
 
     /* Start sequence */
     periodic_signal_check_cb (self);
@@ -1465,7 +1475,7 @@
         ctx->enabled = TRUE;
     }
 
-    /* And refresh, which will trigger the first check at high frequency*/
+    /* And refresh, which will trigger the first check at high frequency */
     mm_iface_modem_refresh_signal (self);
 }
 
@@ -3316,8 +3326,8 @@
          * state. */
         if (error->domain == MM_SERIAL_ERROR ||
             g_error_matches (error,
-                             MM_CORE_ERROR,
-                             MM_CORE_ERROR_CANCELLED)) {
+                             G_IO_ERROR,
+                             G_IO_ERROR_CANCELLED)) {
             ctx->saved_error = error;
             ctx->step = UPDATE_LOCK_INFO_CONTEXT_STEP_LAST;
             update_lock_info_context_step (task);
@@ -5691,10 +5701,18 @@
     g_object_interface_install_property
         (g_iface,
          g_param_spec_boolean (MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
-                               "Periodic signal check disabled",
-                               "Whether periodic signal check is disabled.",
+                               "Periodic signal quality check disabled",
+                               "Whether periodic signal quality check is disabled.",
                                FALSE,
-                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+                               G_PARAM_READWRITE));
+
+    g_object_interface_install_property
+        (g_iface,
+         g_param_spec_boolean (MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
+                               "Periodic access technology check disabled",
+                               "Whether periodic access technology check is disabled.",
+                               FALSE,
+                               G_PARAM_READWRITE));
 
     g_object_interface_install_property
         (g_iface,
diff --git a/src/mm-iface-modem.h b/src/mm-iface-modem.h
index 9df3903..c4aba7a 100644
--- a/src/mm-iface-modem.h
+++ b/src/mm-iface-modem.h
@@ -38,8 +38,9 @@
 #define MM_IFACE_MODEM_BEARER_LIST             "iface-modem-bearer-list"
 #define MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED  "iface-modem-sim-hot-swap-supported"
 #define MM_IFACE_MODEM_SIM_HOT_SWAP_CONFIGURED "iface-modem-sim-hot-swap-configured"
-#define MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED "iface-modem-periodic-signal-check-disabled"
 #define MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING  "iface-modem-carrier-config-mapping"
+#define MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED      "iface-modem-periodic-signal-check-disabled"
+#define MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED "iface-modem-periodic-access-tech-check-disabled"
 
 typedef struct _MMIfaceModem MMIfaceModem;
 
diff --git a/src/mm-modem-helpers-qmi.c b/src/mm-modem-helpers-qmi.c
index ef6e048..c8f5e0b 100644
--- a/src/mm-modem-helpers-qmi.c
+++ b/src/mm-modem-helpers-qmi.c
@@ -20,6 +20,7 @@
 #include <mm-errors-types.h>
 
 #include "mm-modem-helpers-qmi.h"
+#include "mm-modem-helpers.h"
 #include "mm-enums-types.h"
 #include "mm-log.h"
 
@@ -1588,6 +1589,8 @@
     }
 }
 
+/*****************************************************************************/
+
 gboolean
 mm_error_from_qmi_loc_indication_status (QmiLocIndicationStatus   status,
                                          GError                 **error)
@@ -1618,3 +1621,121 @@
         return FALSE;
     }
 }
+
+/*****************************************************************************/
+/* Convert between firmware unique ID (string) and QMI unique ID (16 bytes)
+ *
+ * The unique ID coming in the QMI message is a fixed-size 16 byte array, and its
+ * format really depends on the manufacturer. But, if the manufacturer is nice enough
+ * to use ASCII for this field, just use it ourselves as well, no need to obfuscate
+ * the information we expose in our interfaces.
+ *
+ * We also need to do the conversion in the other way around, because when
+ * selecting a new image to run we need to provide the QMI unique ID.
+ */
+
+#define EXPECTED_QMI_UNIQUE_ID_LENGTH 16
+
+gchar *
+mm_qmi_unique_id_to_firmware_unique_id (GArray  *qmi_unique_id,
+                                        GError **error)
+{
+    gint     i;
+    gboolean expect_nul_byte = FALSE;
+
+    if (qmi_unique_id->len != EXPECTED_QMI_UNIQUE_ID_LENGTH) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                     "unexpected QMI unique ID length: %u (expected: %u)",
+                     qmi_unique_id->len, EXPECTED_QMI_UNIQUE_ID_LENGTH);
+        return NULL;
+    }
+
+    for (i = 0; i < qmi_unique_id->len; i++) {
+        guint8 val;
+
+        val = g_array_index (qmi_unique_id, guint8, i);
+
+        /* Check for ASCII chars */
+        if (g_ascii_isprint ((gchar) val)) {
+            /* Halt iteration if we found an ASCII char after a NUL byte */
+            if (expect_nul_byte)
+                break;
+
+            /* good char */
+            continue;
+        }
+
+        /* Allow NUL bytes at the end of the array */
+        if (val == '\0' && i > 0) {
+            if (!expect_nul_byte)
+                expect_nul_byte = TRUE;
+            continue;
+        }
+
+        /* Halt iteration, not something we can build as ASCII */
+        break;
+    }
+
+    if (i != qmi_unique_id->len)
+        return mm_utils_bin2hexstr ((const guint8 *)qmi_unique_id->data, qmi_unique_id->len);
+
+    return g_strndup ((const gchar *)qmi_unique_id->data, qmi_unique_id->len);
+}
+
+GArray *
+mm_firmware_unique_id_to_qmi_unique_id (const gchar  *unique_id,
+                                        GError      **error)
+{
+    guint   len;
+    GArray *qmi_unique_id;
+
+    len = strlen (unique_id);
+
+    /* The length will be exactly EXPECTED_QMI_UNIQUE_ID_LENGTH*2 if given in HEX */
+    if (len == (2 * EXPECTED_QMI_UNIQUE_ID_LENGTH)) {
+        guint8 *tmp;
+        gsize   tmp_len;
+        guint   i;
+
+        for (i = 0; i < len; i++) {
+            if (!g_ascii_isxdigit (unique_id[i])) {
+                g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                             "Unexpected character found in unique id (not HEX): %c", unique_id[i]);
+                return NULL;
+            }
+        }
+
+        tmp_len = 0;
+        tmp = (guint8 *) mm_utils_hexstr2bin (unique_id, &tmp_len);
+        g_assert (tmp_len == EXPECTED_QMI_UNIQUE_ID_LENGTH);
+
+        qmi_unique_id = g_array_sized_new (FALSE, FALSE, sizeof (guint8), tmp_len);
+        g_array_insert_vals (qmi_unique_id, 0, tmp, tmp_len);
+        g_free (tmp);
+        return qmi_unique_id;
+    }
+
+    /* The length will be EXPECTED_QMI_UNIQUE_ID_LENGTH or less if given in ASCII */
+    if (len > 0 && len <= EXPECTED_QMI_UNIQUE_ID_LENGTH) {
+        guint i;
+
+        for (i = 0; i < len; i++) {
+            if (!g_ascii_isprint (unique_id[i])) {
+                g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                             "Unexpected character found in unique id (not ASCII): %c", unique_id[i]);
+                return NULL;
+            }
+        }
+
+        qmi_unique_id = g_array_sized_new (FALSE, FALSE, sizeof (guint8), EXPECTED_QMI_UNIQUE_ID_LENGTH);
+        g_array_set_size (qmi_unique_id, EXPECTED_QMI_UNIQUE_ID_LENGTH);
+        memcpy (&qmi_unique_id->data[0], unique_id, len);
+        if (len < EXPECTED_QMI_UNIQUE_ID_LENGTH)
+            memset (&qmi_unique_id->data[len], 0, EXPECTED_QMI_UNIQUE_ID_LENGTH - len);
+        return qmi_unique_id;
+    }
+
+    g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                 "Unexpected unique id length: %u", len);
+    return NULL;
+}
diff --git a/src/mm-modem-helpers-qmi.h b/src/mm-modem-helpers-qmi.h
index a51c4f6..5c0200e 100644
--- a/src/mm-modem-helpers-qmi.h
+++ b/src/mm-modem-helpers-qmi.h
@@ -138,4 +138,12 @@
 
 MMModemCapability mm_modem_capability_from_qmi_capabilities_context (MMQmiCapabilitiesContext *ctx);
 
+/*****************************************************************************/
+/* QMI unique id manipulation */
+
+gchar  *mm_qmi_unique_id_to_firmware_unique_id (GArray       *qmi_unique_id,
+                                                GError      **error);
+GArray *mm_firmware_unique_id_to_qmi_unique_id (const gchar  *unique_id,
+                                                GError      **error);
+
 #endif  /* MM_MODEM_HELPERS_QMI_H */
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index f7115cb..ec07aad 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -606,6 +606,15 @@
         [3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */
         [4] = MM_CALL_STATE_RINGING_IN,  /* Incoming (MTC) */
         [5] = MM_CALL_STATE_WAITING,     /* Waiting  (MTC) */
+
+        /* This next call state number isn't defined by 3GPP, because it
+         * doesn't make sense to have it when reporting a full list of calls
+         * via +CLCC (i.e. the absence of the call would mean it's terminated).
+         * But, this value may be used by other implementations (e.g. SimTech
+         * plugin) to report that a call is terminated even when the full
+         * call list isn't being reported. So, let's support it in the generic,
+         * parser, even if not strictly standard. */
+        [6] = MM_CALL_STATE_TERMINATED,
     };
 
     g_assert (out_list);
@@ -4344,6 +4353,70 @@
 }
 
 /*************************************************************************/
+/* Emergency numbers (+CRSM output) */
+
+GStrv
+mm_3gpp_parse_emergency_numbers (const char *raw, GError **error)
+{
+    gsize      rawlen;
+    guint8    *bin;
+    gsize      binlen;
+    gsize      max_items;
+    GPtrArray *out;
+    guint      i;
+
+    /* The emergency call code is of a variable length with a maximum length of
+     * 6 digits. Each emergency call code is coded on three bytes, with each
+     * digit within the code being coded on four bits. If a code of less that 6
+     * digits is chosen, then the unused nibbles shall be set to 'F'. */
+
+    rawlen = strlen (raw);
+    if (!rawlen) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                     "empty emergency numbers list");
+        return NULL;
+    }
+
+    if (rawlen % 6 != 0) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                     "invalid raw emergency numbers list length: %" G_GSIZE_FORMAT, rawlen);
+        return NULL;
+    }
+
+    bin = (guint8 *) mm_utils_hexstr2bin (raw, &binlen);
+    if (!bin) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                     "invalid raw emergency numbers list contents: %s", raw);
+        return NULL;
+    }
+
+    max_items = binlen / 3;
+    out = g_ptr_array_sized_new (max_items + 1);
+
+    for (i = 0; i < max_items; i++) {
+        gchar *number;
+
+        number = mm_bcd_to_string (&bin[i*3], 3);
+        if (number && number[0])
+            g_ptr_array_add (out, number);
+        else
+            g_free (number);
+    }
+
+    g_free (bin);
+
+    if (!out->len) {
+        g_ptr_array_unref (out);
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+                     "uninitialized emergency numbers list");
+        return NULL;
+    }
+
+    g_ptr_array_add (out, NULL);
+    return (GStrv) g_ptr_array_free (out, FALSE);
+}
+
+/*************************************************************************/
 
 gboolean
 mm_cdma_parse_spservice_read_response (const gchar *reply,
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
index 73a13dd..28a6999 100644
--- a/src/mm-modem-helpers.h
+++ b/src/mm-modem-helpers.h
@@ -445,6 +445,8 @@
 gboolean mm_3gpp_rssnr_level_to_rssnr (gint     rssnr_level,
                                        gdouble *out_rssnr);
 
+GStrv mm_3gpp_parse_emergency_numbers (const char *raw, GError **error);
+
 /*****************************************************************************/
 /* CDMA specific helpers and utilities */
 /*****************************************************************************/
diff --git a/src/mm-port-probe.c b/src/mm-port-probe.c
index c02f0b2..c0b5b45 100644
--- a/src/mm-port-probe.c
+++ b/src/mm-port-probe.c
@@ -93,6 +93,7 @@
     /* From udev tags */
     gboolean is_ignored;
     gboolean is_gps;
+    gboolean is_audio;
     gboolean maybe_at_primary;
     gboolean maybe_at_secondary;
     gboolean maybe_at_ppp;
@@ -1477,6 +1478,15 @@
         mm_port_probe_set_result_qcdm (self, FALSE);
     }
 
+    /* If this is a port flagged as an audio port, don't do any AT or QCDM probing */
+    if (self->priv->is_audio) {
+        mm_dbg ("(%s/%s) audio port detected",
+                mm_kernel_device_get_subsystem (self->priv->port),
+                mm_kernel_device_get_name (self->priv->port));
+        mm_port_probe_set_result_at (self, FALSE);
+        mm_port_probe_set_result_qcdm (self, FALSE);
+    }
+
     /* If this is a port flagged as being an AT port, don't do any QCDM probing */
     if (self->priv->maybe_at_primary || self->priv->maybe_at_secondary || self->priv->maybe_at_ppp) {
         mm_dbg ("(%s/%s) no QCDM probing in possible AT port",
@@ -1711,6 +1721,9 @@
     if (self->priv->is_gps)
         return MM_PORT_TYPE_GPS;
 
+    if (self->priv->is_audio)
+        return MM_PORT_TYPE_AUDIO;
+
     return MM_PORT_TYPE_UNKNOWN;
 }
 
@@ -1900,6 +1913,7 @@
         self->priv->port = g_value_dup_object (value);
         self->priv->is_ignored = mm_kernel_device_get_property_as_boolean (self->priv->port, ID_MM_PORT_IGNORE);
         self->priv->is_gps = mm_kernel_device_get_property_as_boolean (self->priv->port, ID_MM_PORT_TYPE_GPS);
+        self->priv->is_audio = mm_kernel_device_get_property_as_boolean (self->priv->port, ID_MM_PORT_TYPE_AUDIO);
         self->priv->maybe_at_primary = mm_kernel_device_get_property_as_boolean (self->priv->port, ID_MM_PORT_TYPE_AT_PRIMARY);
         self->priv->maybe_at_secondary = mm_kernel_device_get_property_as_boolean (self->priv->port, ID_MM_PORT_TYPE_AT_SECONDARY);
         self->priv->maybe_at_ppp = mm_kernel_device_get_property_as_boolean (self->priv->port, ID_MM_PORT_TYPE_AT_PPP);
diff --git a/src/mm-port-serial.c b/src/mm-port-serial.c
index 052d0dd..3101ce6 100644
--- a/src/mm-port-serial.c
+++ b/src/mm-port-serial.c
@@ -815,8 +815,7 @@
     /* FIXME: This is not completely correct - if the response finally arrives and there's
      * some other command waiting for response right now, the other command will
      * get the output of the cancelled command. Not sure what to do here. */
-    error = g_error_new_literal (MM_CORE_ERROR,
-                                 MM_CORE_ERROR_CANCELLED,
+    error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED,
                                  "Waiting for the reply cancelled");
     /* Note: may complete last operation and unref the MMPortSerial */
     port_serial_got_response (self, NULL, error);
@@ -1566,10 +1565,7 @@
     task = self->priv->reopen_task;
     self->priv->reopen_task = NULL;
 
-    g_task_return_new_error (task,
-                             MM_CORE_ERROR,
-                             MM_CORE_ERROR_CANCELLED,
-                             "Reopen cancelled");
+    g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Reopen cancelled");
     g_object_unref (task);
 }
 
@@ -1740,7 +1736,7 @@
 static gboolean
 flash_cancel_cb (GTask *task)
 {
-    g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Flash cancelled");
+    g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Flash cancelled");
     g_object_unref (task);
     return G_SOURCE_REMOVE;
 }
diff --git a/src/mm-port.h b/src/mm-port.h
index 8046329..33b07d9 100644
--- a/src/mm-port.h
+++ b/src/mm-port.h
@@ -41,7 +41,8 @@
     MM_PORT_TYPE_GPS,
     MM_PORT_TYPE_QMI,
     MM_PORT_TYPE_MBIM,
-    MM_PORT_TYPE_LAST = MM_PORT_TYPE_MBIM /*< skip >*/
+    MM_PORT_TYPE_AUDIO,
+    MM_PORT_TYPE_LAST = MM_PORT_TYPE_AUDIO /*< skip >*/
 } MMPortType;
 
 #define MM_TYPE_PORT            (mm_port_get_type ())
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c
index 9629fab..d1a78d4 100644
--- a/src/mm-shared-qmi.c
+++ b/src/mm-shared-qmi.c
@@ -1000,7 +1000,7 @@
      * switching only when switching GSM/UMTS+CDMA/EVDO multimode devices, and only if
      * we have support for the commands doing it.
      */
-    if (priv->feature_nas_technology_preference == FEATURE_SUPPORTED || priv->feature_nas_system_selection_preference == FEATURE_UNKNOWN) {
+    if (priv->feature_nas_technology_preference == FEATURE_SUPPORTED || priv->feature_nas_system_selection_preference == FEATURE_SUPPORTED) {
         if (mask == (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)) {
             /* Multimode GSM/UMTS+CDMA/EVDO device switched to GSM/UMTS only */
             single = MM_MODEM_CAPABILITY_GSM_UMTS;
diff --git a/src/mm-sms-part-3gpp.c b/src/mm-sms-part-3gpp.c
index 7e3f537..d977989 100644
--- a/src/mm-sms-part-3gpp.c
+++ b/src/mm-sms-part-3gpp.c
@@ -156,25 +156,26 @@
     return utf8;
 }
 
-static char *
+static gchar *
 sms_decode_timestamp (const guint8 *timestamp)
 {
-    /* YYMMDDHHMMSS+ZZ */
-    char *timestr;
-    int quarters, hours;
+    /* ISO8601 format: YYYY-MM-DDTHH:MM:SS+HHMM */
+    guint year, month, day, hour, minute, second;
+    gint quarters, offset_minutes;
 
-    timestr = g_malloc0 (16);
-    sms_semi_octets_to_bcd_string (timestr, timestamp, 6);
+    year = 2000 + ((timestamp[0] & 0xf) * 10) + ((timestamp[0] >> 4) & 0xf);
+    month = ((timestamp[1] & 0xf) * 10) + ((timestamp[1] >> 4) & 0xf);
+    day = ((timestamp[2] & 0xf) * 10) + ((timestamp[2] >> 4) & 0xf);
+    hour = ((timestamp[3] & 0xf) * 10) + ((timestamp[3] >> 4) & 0xf);
+    minute = ((timestamp[4] & 0xf) * 10) + ((timestamp[4] >> 4) & 0xf);
+    second = ((timestamp[5] & 0xf) * 10) + ((timestamp[5] >> 4) & 0xf);
     quarters = ((timestamp[6] & 0x7) * 10) + ((timestamp[6] >> 4) & 0xf);
-    hours = quarters / 4;
+    offset_minutes = quarters * 15;
     if (timestamp[6] & 0x08)
-        timestr[12] = '-';
-    else
-        timestr[12] = '+';
-    timestr[13] = (hours / 10) + '0';
-    timestr[14] = (hours % 10) + '0';
-    /* TODO(njw): Change timestamp rep to something that includes quarter-hours */
-    return timestr;
+        offset_minutes = -1 * offset_minutes;
+
+    return mm_new_iso8601_time (year, month, day, hour,
+                                minute, second, TRUE, offset_minutes);
 }
 
 static MMSmsEncoding
diff --git a/src/tests/test-at-serial-port.c b/src/tests/test-at-serial-port.c
index 2173f20..5aacba2 100644
--- a/src/tests/test-at-serial-port.c
+++ b/src/tests/test-at-serial-port.c
@@ -71,17 +71,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/src/tests/test-charsets.c b/src/tests/test-charsets.c
index e7b47da..8c34af5 100644
--- a/src/tests/test-charsets.c
+++ b/src/tests/test-charsets.c
@@ -20,12 +20,6 @@
 #include "mm-modem-helpers.h"
 #include "mm-log.h"
 
-#if defined ENABLE_TEST_MESSAGE_TRACES
-#define trace(message, ...) g_print (message, ##__VA_ARGS__)
-#else
-#define trace(...)
-#endif
-
 static void
 test_gsm7_default_chars (void)
 {
@@ -400,7 +394,7 @@
     guint i;
 
     for (i = 0; i < G_N_ELEMENTS (charset_can_convert_to_test); i++) {
-        trace ("testing charset conversion: '%s'\n", charset_can_convert_to_test[i].utf8);
+        g_debug ("testing charset conversion: '%s'", charset_can_convert_to_test[i].utf8);
         g_assert (mm_charset_can_convert_to (charset_can_convert_to_test[i].utf8, MM_MODEM_CHARSET_GSM)     == charset_can_convert_to_test[i].to_gsm);
         g_assert (mm_charset_can_convert_to (charset_can_convert_to_test[i].utf8, MM_MODEM_CHARSET_IRA)     == charset_can_convert_to_test[i].to_ira);
         g_assert (mm_charset_can_convert_to (charset_can_convert_to_test[i].utf8, MM_MODEM_CHARSET_8859_1)  == charset_can_convert_to_test[i].to_8859_1);
@@ -417,17 +411,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/src/tests/test-modem-helpers-qmi.c b/src/tests/test-modem-helpers-qmi.c
index f85430c..d6b491c 100644
--- a/src/tests/test-modem-helpers-qmi.c
+++ b/src/tests/test-modem-helpers-qmi.c
@@ -316,17 +316,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
index 18c7fdb..0e63e41 100644
--- a/src/tests/test-modem-helpers.c
+++ b/src/tests/test-modem-helpers.c
@@ -24,12 +24,6 @@
 #include "mm-modem-helpers.h"
 #include "mm-log.h"
 
-#if defined ENABLE_TEST_MESSAGE_TRACES
-#define trace(message, ...) g_print (message, ##__VA_ARGS__)
-#else
-#define trace(...)
-#endif
-
 #define g_assert_cmpfloat_tolerance(val1, val2, tolerance)  \
     g_assert_cmpfloat (fabs (val1 - val2), <, tolerance)
 
@@ -489,7 +483,7 @@
     GError *error = NULL;
     GList *results;
 
-    trace ("\nTesting %s +COPS response...\n", desc);
+    g_debug ("Testing %s +COPS response...", desc);
 
     results = mm_3gpp_parse_cops_test_response (reply, &error);
     g_assert (results);
@@ -1091,17 +1085,17 @@
     g_assert (data);
     g_assert (result);
 
-    trace ("\nTesting '%s' +C%sREG %s response...\n",
-           test,
-           result->cgreg ? "G" : "",
-           solicited ? "solicited" : "unsolicited");
+    g_debug ("Testing '%s' +C%sREG %s response...",
+             test,
+             result->cgreg ? "G" : "",
+             solicited ? "solicited" : "unsolicited");
 
     array = solicited ? data->solicited_creg : data->unsolicited_creg;
     for (i = 0; i < array->len; i++) {
         GRegex *r = g_ptr_array_index (array, i);
 
         if (g_regex_match (r, reply, 0, &info)) {
-            trace ("  matched with %d\n", i);
+            g_debug ("  matched with %d", i);
             regex_num = i + 1;
             break;
         }
@@ -1109,9 +1103,9 @@
         info = NULL;
     }
 
-    trace ("  regex_num (%u) == result->regex_num (%u)\n",
-           regex_num,
-           result->regex_num);
+    g_debug ("  regex_num (%u) == result->regex_num (%u)",
+             regex_num,
+             result->regex_num);
 
     g_assert (info != NULL);
     g_assert_cmpuint (regex_num, ==, result->regex_num);
@@ -1124,8 +1118,8 @@
     g_assert (lac == result->lac);
     g_assert (ci == result->ci);
 
-    trace ("  access_tech (%d) == result->act (%d)\n",
-           access_tech, result->act);
+    g_debug ("  access_tech (%d) == result->act (%d)",
+             access_tech, result->act);
     g_assert_cmpuint (access_tech, ==, result->act);
     g_assert_cmpuint (cgreg, ==, result->cgreg);
     g_assert_cmpuint (cereg, ==, result->cereg);
@@ -1881,7 +1875,7 @@
     DevidItem *item = (DevidItem *) d;
     char *devid;
 
-    trace ("%s... ", item->desc);
+    g_debug ("%s... ", item->desc);
     devid = mm_create_device_identifier (item->vid,
                                          item->pid,
                                          item->ati,
@@ -1990,7 +1984,7 @@
     GError *error = NULL;
     GHashTable *results;
 
-    trace ("\nTesting %s +CIND response...\n", desc);
+    g_debug ("Testing %s +CIND response...", desc);
 
     results = mm_3gpp_parse_cind_test_response (reply, &error);
     g_assert (results);
@@ -2133,7 +2127,7 @@
         type = mm_3gpp_parse_cgev_indication_action (test->str);
         g_assert_cmpuint (type, ==, test->expected_type);
 
-        g_print ("[%u] type: %u\n", i, type);
+        g_debug ("[%u] type: %u", i, type);
 
         switch (type) {
         case MM_3GPP_CGEV_NW_DETACH:
@@ -2145,7 +2139,7 @@
         case MM_3GPP_CGEV_ME_DEACT_PRIMARY: {
             guint cid;
 
-            g_print ("[%u] parsing as primary\n", i);
+            g_debug ("[%u] parsing as primary", i);
             ret = mm_3gpp_parse_cgev_indication_primary (test->str, type, &cid, &error);
             g_assert_no_error (error);
             g_assert (ret);
@@ -2160,7 +2154,7 @@
             guint cid;
             guint event_type;
 
-            g_print ("[%u] parsing as secondary\n", i);
+            g_debug ("[%u] parsing as secondary", i);
             ret = mm_3gpp_parse_cgev_indication_secondary (test->str, type, &p_cid, &cid, &event_type, &error);
             g_assert_no_error (error);
             g_assert (ret);
@@ -2177,7 +2171,7 @@
             gchar *pdp_addr;
             guint  cid;
 
-            g_print ("[%u] parsing as pdp\n", i);
+            g_debug ("[%u] parsing as pdp", i);
             ret = mm_3gpp_parse_cgev_indication_pdp (test->str, type, &pdp_type, &pdp_addr, &cid, &error);
             g_assert_no_error (error);
             g_assert (ret);
@@ -2409,7 +2403,7 @@
     GError *error = NULL;
     GList *results;
 
-    trace ("\nTesting %s +CGDCONT test response...\n", desc);
+    g_debug ("Testing %s +CGDCONT test response...", desc);
 
     results = mm_3gpp_parse_cgdcont_test_response (reply, &error);
     g_assert (results);
@@ -2537,7 +2531,7 @@
     GError *error = NULL;
     GList *results;
 
-    trace ("\nTesting %s +CGDCONT response...\n", desc);
+    g_debug ("Testing %s +CGDCONT response...", desc);
 
     results = mm_3gpp_parse_cgdcont_read_response (reply, &error);
     g_assert (results);
@@ -2607,7 +2601,7 @@
     GError *error = NULL;
     GList *results;
 
-    trace ("\nTesting %s +CGACT response...\n", desc);
+    g_debug ("Testing %s +CGACT response...", desc);
 
     results = mm_3gpp_parse_cgact_read_response (reply, &error);
     g_assert_no_error (error);
@@ -2872,7 +2866,7 @@
     GArray *mem2 = NULL;
     GArray *mem3 = NULL;
 
-    trace ("\nTesting Cinterion +CPMS=? response...\n");
+    g_debug ("Testing Cinterion +CPMS=? response...");
 
     g_assert (mm_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3));
     g_assert_cmpuint (mem1->len, ==, 2);
@@ -2900,7 +2894,7 @@
     GArray *mem2 = NULL;
     GArray *mem3 = NULL;
 
-    trace ("\nTesting Huawei MU609 +CPMS=? response...\n");
+    g_debug ("Testing Huawei MU609 +CPMS=? response...");
 
     g_assert (mm_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3));
     g_assert_cmpuint (mem1->len, ==, 1);
@@ -2924,7 +2918,7 @@
     GArray *mem2 = NULL;
     GArray *mem3 = NULL;
 
-    trace ("\nTesting Nokia C6 response...\n");
+    g_debug ("Testing Nokia C6 response...");
 
     g_assert (mm_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3));
     g_assert_cmpuint (mem1->len, ==, 0);
@@ -2949,7 +2943,7 @@
     GArray *mem2 = NULL;
     GArray *mem3 = NULL;
 
-    trace ("\nTesting mixed +CPMS=? response...\n");
+    g_debug ("Testing mixed +CPMS=? response...");
 
     g_assert (mm_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3));
     g_assert_cmpuint (mem1->len, ==, 2);
@@ -2974,7 +2968,7 @@
     GArray *mem2 = NULL;
     GArray *mem3 = NULL;
 
-    trace ("\nTesting mixed +CPMS=? response with spaces...\n");
+    g_debug ("Testing mixed +CPMS=? response with spaces...");
 
     g_assert (mm_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3));
     g_assert_cmpuint (mem1->len, ==, 2);
@@ -3003,7 +2997,7 @@
     GArray *mem2 = NULL;
     GArray *mem3 = NULL;
 
-    trace ("\nTesting mixed +CPMS=? response...\n");
+    g_debug ("Testing mixed +CPMS=? response...");
 
     g_assert (mm_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3));
     g_assert_cmpuint (mem1->len, ==, 0);
@@ -3060,7 +3054,7 @@
     GStrv results;
     guint i;
 
-    trace ("\nTesting +CNUM response (%s)...\n", desc);
+    g_debug ("Testing +CNUM response (%s)...", desc);
 
     results = mm_3gpp_parse_cnum_exec_response (reply);
     g_assert (results);
@@ -3161,19 +3155,19 @@
     GError *error = NULL;
 
     if (expected_mcc) {
-        trace ("\nParsing Operator ID '%s' "
-               "(%" G_GUINT16_FORMAT ", %" G_GUINT16_FORMAT  ")...\n",
-               operator_id, expected_mcc, expected_mnc);
+        g_debug ("Parsing Operator ID '%s' "
+                 "(%" G_GUINT16_FORMAT ", %" G_GUINT16_FORMAT  ")...",
+                 operator_id, expected_mcc, expected_mnc);
         result = mm_3gpp_parse_operator_id (operator_id, &mcc, &mnc, &error);
     } else {
-        trace ("\nValidating Operator ID '%s'...\n", operator_id);
+        g_debug ("Validating Operator ID '%s'...", operator_id);
         result = mm_3gpp_parse_operator_id (operator_id, NULL, NULL, &error);
     }
 
     if (error)
-        trace ("\tGot %s error: %s...\n",
-               expected_success ? "unexpected" : "expected",
-               error->message);
+        g_debug ("Got %s error: %s...",
+                 expected_success ? "unexpected" : "expected",
+                 error->message);
 
     g_assert (result == expected_success);
 
@@ -3193,7 +3187,6 @@
 static void
 test_parse_operator_id (void *f, gpointer d)
 {
-    trace ("\n");
     /* Valid MCC+MNC(2) */
     common_parse_operator_id ("41201",  TRUE, 412, 1);
     common_parse_operator_id ("41201",  TRUE, 0, 0);
@@ -4257,7 +4250,7 @@
     g_assert_no_error (error);
     g_assert (result);
 
-    g_print ("found %u calls\n", g_list_length (call_info_list));
+    g_debug ("found %u calls", g_list_length (call_info_list));
 
     if (expected_call_info_list) {
         g_assert (call_info_list);
@@ -4270,7 +4263,7 @@
         gboolean                   found = FALSE;
         guint                      i;
 
-        g_print ("call at index %u: direction %s, state %s, number %s\n",
+        g_debug ("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),
@@ -4345,6 +4338,55 @@
 }
 
 /*****************************************************************************/
+/* Test +CRSM EF_ECC read data parsing */
+
+#define MAX_EMERGENCY_NUMBERS 5
+typedef struct {
+    const gchar *raw;
+    guint        n_numbers;
+    gchar       *numbers[MAX_EMERGENCY_NUMBERS];
+} EmergencyNumbersTest;
+
+static const EmergencyNumbersTest emergency_numbers_tests[] = {
+   { "",                                           0                                          },
+   { "FFF",                                        0                                          },
+   { "FFFFFF" "FFFFFF" "FFFFFF" "FFFFFF" "FFFFFF", 0                                          },
+   { "00F0FF" "11F2FF" "88F8FF",                   3, { "000", "112", "888" }                 },
+   { "00F0FF" "11F2FF" "88F8FF" "FFFFFF" "FFFFFF", 3, { "000", "112", "888" }                 },
+   { "00F0FF" "11F2FF" "88F8FF" "214365" "08FFFF", 5, { "000", "112", "888", "123456", "80" } },
+};
+
+static void
+test_emergency_numbers (void)
+{
+    guint i;
+
+    for (i = 0; i < G_N_ELEMENTS (emergency_numbers_tests); i++) {
+        GStrv   numbers;
+        GError *error = NULL;
+        guint   j;
+
+        g_debug ("  testing %s...", emergency_numbers_tests[i].raw);
+
+        numbers = mm_3gpp_parse_emergency_numbers (emergency_numbers_tests[i].raw, &error);
+        if (!emergency_numbers_tests[i].n_numbers) {
+            g_assert (error);
+            g_assert (!numbers);
+            continue;
+        }
+
+        g_assert_no_error (error);
+        g_assert (numbers);
+
+        g_assert_cmpuint (emergency_numbers_tests[i].n_numbers, ==, g_strv_length (numbers));
+        for (j = 0; j < emergency_numbers_tests[i].n_numbers; j++)
+            g_assert_cmpstr (emergency_numbers_tests[i].numbers[j], ==, numbers[j]);
+
+        g_strfreev (numbers);
+    }
+}
+
+/*****************************************************************************/
 
 typedef struct {
     gchar *str;
@@ -4432,17 +4474,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     va_start (args, fmt);
     msg = g_strdup_vprintf (fmt, args);
     va_end (args);
     g_print ("%s\n", msg);
     g_free (msg);
-#endif
 }
 
 #define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (GTestFixtureFunc) t, NULL)
@@ -4666,6 +4708,8 @@
     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_emergency_numbers, 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/src/tests/test-qcdm-serial-port.c b/src/tests/test-qcdm-serial-port.c
index ac73a5b..e689905 100644
--- a/src/tests/test-qcdm-serial-port.c
+++ b/src/tests/test-qcdm-serial-port.c
@@ -444,17 +444,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     va_start (args, fmt);
     msg = g_strdup_vprintf (fmt, args);
     va_end (args);
     g_print ("%s\n", msg);
     g_free (msg);
-#endif
 }
 
 typedef void (*TCFunc) (TestData *, gconstpointer);
diff --git a/src/tests/test-sms-part-3gpp.c b/src/tests/test-sms-part-3gpp.c
index dcf4cc8..d915240 100644
--- a/src/tests/test-sms-part-3gpp.c
+++ b/src/tests/test-sms-part-3gpp.c
@@ -26,23 +26,6 @@
 #include "mm-sms-part-3gpp.h"
 #include "mm-log.h"
 
-/* If defined will print debugging traces */
-#ifdef TEST_SMS_PART_ENABLE_TRACE
-#define trace_pdu(pdu, pdu_len) do {      \
-        guint i;                          \
-                                          \
-        g_print ("\n        ");           \
-        for (i = 0; i < len; i++) {       \
-            g_print ("  0x%02X", pdu[i]); \
-            if (((i + 1) % 12) == 0)      \
-                g_print ("\n        ");   \
-        }                                 \
-        g_print ("\n");                   \
-    } while (0)
-#else
-#define trace_pdu(...)
-#endif
-
 /********************* PDU PARSER TESTS *********************/
 
 static void
@@ -136,7 +119,7 @@
         pdu, sizeof (pdu),
         "+12404492164", /* smsc */
         "+16175927198", /* number */
-        "110228115050-05", /* timestamp */
+        "2011-02-28T11:50:50-05:00", /* timestamp */
         FALSE,
         "Here's a longer message [{with some extended characters}] "
         "thrown in, such as £ and ΩΠΨ and §¿ as well.", /* text */
@@ -157,7 +140,7 @@
         pdu, sizeof (pdu),
         "+79037011111", /* smsc */
         "InternetSMS", /* number */
-        "110329192004+04", /* timestamp */
+        "2011-03-29T19:20:04+04:00", /* timestamp */
         FALSE,
         "тест", /* text */
         NULL, 0);
@@ -177,7 +160,7 @@
         pdu, sizeof (pdu),
         "+12345678901", /* smsc */
         "+18005551212", /* number */
-        "110101123456+00", /* timestamp */
+        "2011-01-01T12:34:56+00:00", /* timestamp */
         FALSE,
         "hellohello", /* text */
         NULL, 0);
@@ -198,7 +181,7 @@
         pdu, sizeof (pdu),
         "+12345678901", /* smsc */
         "+18005551212", /* number */
-        "110101123456+00", /* timestamp */
+        "2011-01-01T12:34:56+00:00", /* timestamp */
         FALSE,
         "hellohello", /* text */
         NULL, 0);
@@ -219,7 +202,7 @@
         pdu, sizeof (pdu),
         "+12345678901", /* smsc */
         "+18005551212", /* number */
-        "110101123456+00", /* timestamp */
+        "2011-01-01T12:34:56+00:00", /* timestamp */
         FALSE,
         "hellohello", /* text */
         NULL, 0);
@@ -240,7 +223,7 @@
         pdu, sizeof (pdu),
         "+12345678901", /* smsc */
         "18005551212", /* number, no plus */
-        "110101123456+00", /* timestamp */
+        "2011-01-01T12:34:56+00:00", /* timestamp */
         FALSE,
         "hellohello", /* text */
         NULL, 0);
@@ -262,7 +245,7 @@
         pdu, sizeof (pdu),
         "+12345678901", /* smsc */
         "+18005551212", /* number */
-        "110101123456+00", /* timestamp */
+        "2011-01-01T12:34:56+00:00", /* timestamp */
         FALSE,
         NULL, /* text */
         expected_data, /* data */
@@ -310,7 +293,7 @@
         pdu, sizeof (pdu),
         "+33609001390", /* smsc */
         "1800", /* number */
-        "110624130815+02", /* timestamp */
+        "2011-06-24T13:08:15+02:00", /* timestamp */
         FALSE,
         "Info SFR - Confidentiel, à ne jamais transmettre -\r\n"
         "Voici votre nouveau mot de passe : sw2ced pour gérer "
@@ -334,7 +317,7 @@
         pdu, sizeof (pdu),
         "+12345678901", /* smsc */
         "+18005551212", /* number */
-        "110101123456+00", /* timestamp */
+        "2011-01-01T12:34:56+00:00", /* timestamp */
         FALSE,
         NULL, /* text */
         expected_data, /* data */
@@ -380,7 +363,7 @@
         hexpdu,
         "+31653131316", /* smsc */
         "1002", /* number */
-        "110629233219+02", /* timestamp */
+        "2011-06-29T23:32:19+02:00", /* timestamp */
         TRUE,
         "Welkom, bel om uw Voicemail te beluisteren naar +31612001233"
         " (PrePay: *100*1233#). Voicemail ontvangen is altijd gratis."
@@ -405,7 +388,7 @@
         hexpdu1,
         "+12063130025", /* smsc */
         "+16175046925", /* number */
-        "120425195650-04", /* timestamp */
+        "2012-04-25T19:56:50-04:00", /* timestamp */
         TRUE, /* multipart! */
         "This is a very long test designed to exercise multi part capability. It should "
         "show up as one message, not as two, as the underlying encoding represents ", /* text */
@@ -415,7 +398,7 @@
         hexpdu2,
         "+12063130026", /* smsc */
         "+16175046925", /* number */
-        "120425195651-04", /* timestamp */
+        "2012-04-25T19:56:51-04:00", /* timestamp */
         TRUE, /* multipart! */
         "that the parts are related to one another. ", /* text */
         NULL, 0);
@@ -448,7 +431,7 @@
         hexpdu1,
         "+34656000311", /* smsc */
         "639337937", /* number */
-        "120911074036+02", /* timestamp */
+        "2012-09-11T07:40:36+02:00", /* timestamp */
         FALSE, /* multipart! */
         NULL, /* text */
         NULL, 0);
@@ -509,6 +492,21 @@
 /********************* PDU CREATOR TESTS *********************/
 
 static void
+trace_pdu (const guint8 *pdu,
+           guint         len)
+{
+    guint i;
+
+    g_print ("n        ");
+    for (i = 0; i < len; i++) {
+        g_print ("  0x%02X", pdu[i]);
+        if (((i + 1) % 12) == 0)
+            g_print ("n        ");
+    }
+    g_print ("n");
+}
+
+static void
 common_test_create_pdu (const gchar *smsc,
                         const gchar *number,
                         const gchar *text,
@@ -549,7 +547,8 @@
                                            &error);
     mm_sms_part_free (part);
 
-    trace_pdu (pdu, len);
+    if (g_test_verbose ())
+        trace_pdu (pdu, len);
 
     g_assert_no_error (error);
     g_assert (pdu != NULL);
@@ -848,17 +847,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/src/tests/test-sms-part-cdma.c b/src/tests/test-sms-part-cdma.c
index 91b7c3c..e155926 100644
--- a/src/tests/test-sms-part-cdma.c
+++ b/src/tests/test-sms-part-cdma.c
@@ -25,23 +25,6 @@
 #include "mm-sms-part-cdma.h"
 #include "mm-log.h"
 
-/* If defined will print debugging traces */
-#ifdef TEST_SMS_PART_ENABLE_TRACE
-#define trace_pdu(pdu, pdu_len) do {      \
-        guint i;                          \
-                                          \
-        g_print ("\n        ");           \
-        for (i = 0; i < len; i++) {       \
-            g_print ("  0x%02X", pdu[i]); \
-            if (((i + 1) % 12) == 0)      \
-                g_print ("\n        ");   \
-        }                                 \
-        g_print ("\n");                   \
-    } while (0)
-#else
-#define trace_pdu(...)
-#endif
-
 /********************* PDU PARSER TESTS *********************/
 
 static void
@@ -370,6 +353,21 @@
 /********************* PDU CREATOR TESTS *********************/
 
 static void
+trace_pdu (const guint8 *pdu,
+           guint         len)
+{
+    guint i;
+
+    g_print ("n        ");
+    for (i = 0; i < len; i++) {
+        g_print ("  0x%02X", pdu[i]);
+        if (((i + 1) % 12) == 0)
+            g_print ("n        ");
+    }
+    g_print ("n");
+}
+
+static void
 common_test_create_pdu (MMSmsCdmaTeleserviceId teleservice_id,
                         const gchar *number,
                         const gchar *text,
@@ -401,7 +399,8 @@
     pdu = mm_sms_part_cdma_get_submit_pdu (part, &len, &error);
     mm_sms_part_free (part);
 
-    trace_pdu (pdu, len);
+    if (g_test_verbose ())
+        trace_pdu (pdu, len);
 
     g_assert_no_error (error);
     g_assert (pdu != NULL);
@@ -512,17 +511,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)
diff --git a/src/tests/test-udev-rules.c b/src/tests/test-udev-rules.c
index 07e9b8d..3398e41 100644
--- a/src/tests/test-udev-rules.c
+++ b/src/tests/test-udev-rules.c
@@ -22,9 +22,6 @@
 #define _LIBMM_INSIDE_MM
 #include <libmm-glib.h>
 
-/* Define symbol to enable test message traces */
-#undef ENABLE_TEST_MESSAGE_TRACES
-
 #include "mm-kernel-device-generic-rules.h"
 #include "mm-log.h"
 
@@ -53,17 +50,17 @@
          const char *fmt,
          ...)
 {
-#if defined ENABLE_TEST_MESSAGE_TRACES
-    /* Dummy log function */
     va_list args;
     gchar *msg;
 
+    if (!g_test_verbose ())
+        return;
+
     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)