[gnome-settings-daemon] Re-write wwan plugin



commit 00a872cae6f6204291d9fbb8ac3d2d88cdc830f0
Author: Mohammed Sadiq <sadiq sadiqpk org>
Date:   Tue Mar 31 16:09:30 2020 +0530

    Re-write wwan plugin
    
    This uses cc-wwan-device.c copied from gnome-control-center without
    any modification.
    
    The following features/fixes are also implemented:
    
    * Handle multiple devices
    * Handle PUK unlocking
    * Close prompt if the device got removed
    * Fix showing the wrong unlock count

 plugins/wwan/cc-wwan-device.c         | 1341 +++++++++++++++++++++++++++++++++
 plugins/wwan/cc-wwan-device.h         |  152 ++++
 plugins/wwan/cc-wwan-errors-private.h |  104 +++
 plugins/wwan/gsd-wwan-device.c        |  237 ------
 plugins/wwan/gsd-wwan-device.h        |   38 -
 plugins/wwan/gsd-wwan-manager.c       |  528 +++++++++++--
 plugins/wwan/gsd-wwan-pinentry.c      |  296 --------
 plugins/wwan/gsd-wwan-pinentry.h      |   28 -
 plugins/wwan/meson.build              |    5 +-
 9 files changed, 2082 insertions(+), 647 deletions(-)
---
diff --git a/plugins/wwan/cc-wwan-device.c b/plugins/wwan/cc-wwan-device.c
new file mode 100644
index 00000000..7ecfee14
--- /dev/null
+++ b/plugins/wwan/cc-wwan-device.c
@@ -0,0 +1,1341 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device.c
+ *
+ * Copyright 2019-2020 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ *   Mohammed Sadiq <sadiq sadiqpk org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-device"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+#include <polkit/polkit.h>
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+# include <NetworkManager.h>
+# include <nma-mobile-providers.h>
+#endif
+
+#include "cc-wwan-errors-private.h"
+#include "cc-wwan-device.h"
+
+/**
+ * @short_description: Device Object
+ * @include: "cc-wwan-device.h"
+ */
+
+struct _CcWwanDevice
+{
+  GObject      parent_instance;
+
+  MMObject    *mm_object;
+  MMModem     *modem;
+  MMSim       *sim;
+  MMModem3gpp *modem_3gpp;
+
+  const char  *operator_code; /* MCCMNC */
+  GError      *error;
+
+  /* Building with NetworkManager is optional,
+   * so #NMclient type can’t be used here.
+   */
+  GObject      *nm_client; /* An #NMClient */
+  CcWwanData   *wwan_data;
+
+  gulong      modem_3gpp_id;
+  gulong      modem_3gpp_locks_id;
+
+  /* Enabled locks like PIN, PIN2, PUK, etc. */
+  MMModem3gppFacility locks;
+
+  CcWwanState  registration_state;
+  gboolean     network_is_manual;
+};
+
+G_DEFINE_TYPE (CcWwanDevice, cc_wwan_device, G_TYPE_OBJECT)
+
+
+enum {
+  PROP_0,
+  PROP_OPERATOR_NAME,
+  PROP_ENABLED_LOCKS,
+  PROP_ERROR,
+  PROP_HAS_DATA,
+  PROP_NETWORK_MODE,
+  PROP_REGISTRATION_STATE,
+  PROP_SIGNAL,
+  PROP_UNLOCK_REQUIRED,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+cc_wwan_device_state_changed_cb (CcWwanDevice *self)
+{
+  MMModem3gppRegistrationState state;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_OPERATOR_NAME]);
+
+  state = mm_modem_3gpp_get_registration_state (self->modem_3gpp);
+
+  switch (state)
+    {
+    case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_UNKNOWN;
+      break;
+
+    case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_DENIED;
+      break;
+
+    case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_IDLE;
+      break;
+
+    case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_SEARCHING;
+      break;
+
+    case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_ROAMING;
+      break;
+
+    default:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_REGISTERED;
+      break;
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REGISTRATION_STATE]);
+}
+
+static void
+cc_wwan_device_locks_changed_cb (CcWwanDevice *self)
+{
+  self->locks = mm_modem_3gpp_get_enabled_facility_locks (self->modem_3gpp);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED_LOCKS]);
+}
+
+static void
+cc_wwan_device_3gpp_changed_cb (CcWwanDevice *self)
+{
+  gulong handler_id = 0;
+
+  if (self->modem_3gpp_id)
+    g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_id);
+  self->modem_3gpp_id = 0;
+
+  if (self->modem_3gpp_locks_id)
+    g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_locks_id);
+  self->modem_3gpp_locks_id = 0;
+
+  g_clear_object (&self->modem_3gpp);
+  self->modem_3gpp = mm_object_get_modem_3gpp (self->mm_object);
+
+  if (self->modem_3gpp)
+    {
+      handler_id = g_signal_connect_object (self->modem_3gpp, "notify::registration-state",
+                                            G_CALLBACK (cc_wwan_device_state_changed_cb),
+                                            self, G_CONNECT_SWAPPED);
+      self->modem_3gpp_id = handler_id;
+
+      handler_id = g_signal_connect_object (self->modem_3gpp, "notify::enabled-facility-locks",
+                                            G_CALLBACK (cc_wwan_device_locks_changed_cb),
+                                            self, G_CONNECT_SWAPPED);
+      self->modem_3gpp_locks_id = handler_id;
+      cc_wwan_device_locks_changed_cb (self);
+      cc_wwan_device_state_changed_cb (self);
+    }
+}
+
+static void
+cc_wwan_device_signal_quality_changed_cb (CcWwanDevice *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIGNAL]);
+}
+
+static void
+cc_wwan_device_mode_changed_cb (CcWwanDevice *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NETWORK_MODE]);
+}
+
+static void
+cc_wwan_device_emit_data_changed (CcWwanDevice *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_DATA]);
+}
+
+static void
+cc_wwan_device_unlock_required_cb (CcWwanDevice *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UNLOCK_REQUIRED]);
+}
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+static void
+cc_wwan_device_nm_changed_cb (CcWwanDevice *self,
+                              GParamSpec   *pspec,
+                              NMClient     *client)
+{
+  gboolean nm_is_running;
+
+  nm_is_running = nm_client_get_nm_running (client);
+
+  if (!nm_is_running && self->wwan_data != NULL)
+    {
+      g_clear_object (&self->wwan_data);
+      cc_wwan_device_emit_data_changed (self);
+    }
+}
+
+static void
+cc_wwan_device_nm_device_added_cb (CcWwanDevice *self,
+                                   NMDevice     *nm_device)
+{
+  if (!NM_IS_DEVICE_MODEM (nm_device))
+    return;
+
+  if(!self->sim || !cc_wwan_device_is_nm_device (self, G_OBJECT (nm_device)))
+    return;
+
+  self->wwan_data = cc_wwan_data_new (self->mm_object,
+                                      NM_CLIENT (self->nm_client));
+
+  if (self->wwan_data)
+    {
+      g_signal_connect_object (self->wwan_data, "notify::enabled",
+                               G_CALLBACK (cc_wwan_device_emit_data_changed),
+                               self, G_CONNECT_SWAPPED);
+      cc_wwan_device_emit_data_changed (self);
+    }
+}
+#endif
+
+static void
+cc_wwan_device_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  CcWwanDevice *self = (CcWwanDevice *)object;
+  MMModemMode allowed, preferred;
+
+  switch (prop_id)
+    {
+    case PROP_OPERATOR_NAME:
+      g_value_set_string (value, cc_wwan_device_get_operator_name (self));
+      break;
+
+    case PROP_ERROR:
+      g_value_set_boolean (value, self->error != NULL);
+      break;
+
+    case PROP_HAS_DATA:
+      g_value_set_boolean (value, self->wwan_data != NULL);
+      break;
+
+    case PROP_ENABLED_LOCKS:
+      g_value_set_int (value, self->locks);
+      break;
+
+    case PROP_NETWORK_MODE:
+      if (cc_wwan_device_get_current_mode (self, &allowed, &preferred))
+        g_value_take_string (value, cc_wwan_device_get_string_from_mode (self, allowed, preferred));
+      break;
+
+    case PROP_REGISTRATION_STATE:
+      g_value_set_int (value, self->registration_state);
+      break;
+
+    case PROP_UNLOCK_REQUIRED:
+      g_value_set_int (value, cc_wwan_device_get_lock (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_device_dispose (GObject *object)
+{
+  CcWwanDevice *self = (CcWwanDevice *)object;
+
+  g_clear_error (&self->error);
+  g_clear_object (&self->modem);
+  g_clear_object (&self->mm_object);
+  g_clear_object (&self->sim);
+  g_clear_object (&self->modem_3gpp);
+
+  g_clear_object (&self->nm_client);
+  g_clear_object (&self->wwan_data);
+
+  G_OBJECT_CLASS (cc_wwan_device_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_device_class_init (CcWwanDeviceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = cc_wwan_device_get_property;
+  object_class->dispose = cc_wwan_device_dispose;
+
+  properties[PROP_OPERATOR_NAME] =
+    g_param_spec_string ("operator-name",
+                         "Operator Name",
+                         "Operator Name the device is connected to",
+                         NULL,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_ENABLED_LOCKS] =
+    g_param_spec_int ("enabled-locks",
+                      "Enabled Locks",
+                      "Locks Enabled in Modem",
+                      MM_MODEM_3GPP_FACILITY_NONE,
+                      MM_MODEM_3GPP_FACILITY_CORP_PERS,
+                      MM_MODEM_3GPP_FACILITY_NONE,
+                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_ERROR] =
+    g_param_spec_boolean ("error",
+                          "Error",
+                          "Set if some Error occurs",
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_HAS_DATA] =
+    g_param_spec_boolean ("has-data",
+                          "has-data",
+                          "Data for the device",
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_NETWORK_MODE] =
+    g_param_spec_string ("network-mode",
+                         "Network Mode",
+                         "A String representing preferred network mode",
+                         NULL,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_REGISTRATION_STATE] =
+    g_param_spec_int ("registration-state",
+                      "Registration State",
+                      "The current network registration state",
+                      CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+                      CC_WWAN_REGISTRATION_STATE_DENIED,
+                      CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_UNLOCK_REQUIRED] =
+    g_param_spec_int ("unlock-required",
+                      "Unlock Required",
+                      "The Modem lock status changed",
+                      MM_MODEM_LOCK_UNKNOWN,
+                      MM_MODEM_LOCK_PH_NETSUB_PUK,
+                      MM_MODEM_LOCK_UNKNOWN,
+                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_SIGNAL] =
+    g_param_spec_int ("signal",
+                      "Signal",
+                      "Get Device Signal",
+                      0, 100, 0,
+                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+cc_wwan_device_init (CcWwanDevice *self)
+{
+}
+
+/**
+ * cc_wwan_device_new:
+ * @mm_object: (transfer full): An #MMObject
+ *
+ * Create a new device representing the given
+ * @mm_object.
+ *
+ * Returns: A #CcWwanDevice
+ */
+CcWwanDevice *
+cc_wwan_device_new (MMObject *mm_object,
+                    GObject  *nm_client)
+{
+  CcWwanDevice *self;
+
+  g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL);
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+  g_return_val_if_fail (NM_IS_CLIENT (nm_client), NULL);
+#else
+  g_return_val_if_fail (!nm_client, NULL);
+#endif
+
+  self = g_object_new (CC_TYPE_WWAN_DEVICE, NULL);
+
+  self->mm_object = g_object_ref (mm_object);
+  self->modem = mm_object_get_modem (mm_object);
+  self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL);
+  g_set_object (&self->nm_client, nm_client);
+  if (self->sim)
+    {
+      self->operator_code = mm_sim_get_operator_identifier (self->sim);
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+      self->wwan_data = cc_wwan_data_new (mm_object,
+                                          NM_CLIENT (self->nm_client));
+#endif
+    }
+
+  g_signal_connect_object (self->mm_object, "notify::unlock-required",
+                           G_CALLBACK (cc_wwan_device_unlock_required_cb),
+                           self, G_CONNECT_SWAPPED);
+  if (self->wwan_data)
+    g_signal_connect_object (self->wwan_data, "notify::enabled",
+                             G_CALLBACK (cc_wwan_device_emit_data_changed),
+                             self, G_CONNECT_SWAPPED);
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+  g_signal_connect_object (self->nm_client, "notify::nm-running" ,
+                           G_CALLBACK (cc_wwan_device_nm_changed_cb), self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->nm_client, "device-added",
+                           G_CALLBACK (cc_wwan_device_nm_device_added_cb),
+                           self, G_CONNECT_SWAPPED);
+#endif
+
+  g_signal_connect_object (self->mm_object, "notify::modem3gpp",
+                           G_CALLBACK (cc_wwan_device_3gpp_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->modem, "notify::signal-quality",
+                           G_CALLBACK (cc_wwan_device_signal_quality_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  cc_wwan_device_3gpp_changed_cb (self);
+  g_signal_connect_object (self->modem, "notify::current-modes",
+                           G_CALLBACK (cc_wwan_device_mode_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  return self;
+}
+
+gboolean
+cc_wwan_device_has_sim (CcWwanDevice *self)
+{
+  MMModemStateFailedReason state_reason;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+  state_reason = mm_modem_get_state_failed_reason (self->modem);
+
+  if (state_reason == MM_MODEM_STATE_FAILED_REASON_SIM_MISSING)
+    return FALSE;
+
+  return TRUE;
+}
+
+/**
+ * cc_wwan_device_get_lock:
+ * @self: a #CcWwanDevice
+ *
+ * Get the active device lock that is required to
+ * be unlocked for accessing device features.
+ *
+ * Returns: %TRUE if PIN enabled, %FALSE otherwise.
+ */
+MMModemLock
+cc_wwan_device_get_lock (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), MM_MODEM_LOCK_UNKNOWN);
+
+  return mm_modem_get_unlock_required (self->modem);
+}
+
+
+/**
+ * cc_wwan_device_get_sim_lock:
+ * @self: a #CcWwanDevice
+ *
+ * Get if SIM lock with PIN is enabled.  SIM PIN
+ * enabled doesn’t mean that SIM is locked.
+ * See cc_wwan_device_get_lock().
+ *
+ * Returns: %TRUE if PIN enabled, %FALSE otherwise.
+ */
+gboolean
+cc_wwan_device_get_sim_lock (CcWwanDevice *self)
+{
+  gboolean sim_lock;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+  sim_lock = self->locks & MM_MODEM_3GPP_FACILITY_SIM;
+
+  return !!sim_lock;
+}
+
+guint
+cc_wwan_device_get_unlock_retries (CcWwanDevice *self,
+                                   MMModemLock   lock)
+{
+  MMUnlockRetries *retries;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0);
+
+  retries = mm_modem_peek_unlock_retries (self->modem);
+
+  return mm_unlock_retries_get (retries, lock);
+}
+
+static void
+cc_wwan_device_pin_sent_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_send_pin_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_send_pin (CcWwanDevice        *self,
+                         const gchar         *pin,
+                         GCancellable        *cancellable,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (MM_IS_SIM (self->sim));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (pin && *pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_send_pin (self->sim, pin, cancellable,
+                   cc_wwan_device_pin_sent_cb,
+                   g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_send_pin_finish (CcWwanDevice  *self,
+                                GAsyncResult  *result,
+                                GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_puk_sent_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_send_puk_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_send_puk (CcWwanDevice        *self,
+                         const gchar         *puk,
+                         const gchar         *pin,
+                         GCancellable        *cancellable,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (MM_IS_SIM (self->sim));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (puk && *puk);
+  g_return_if_fail (pin && *pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_send_puk (self->sim, puk, pin, cancellable,
+                   cc_wwan_device_puk_sent_cb,
+                   g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_send_puk_finish (CcWwanDevice  *self,
+                                GAsyncResult  *result,
+                                GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_enable_pin_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_enable_pin_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_enable_pin (CcWwanDevice        *self,
+                           const gchar         *pin,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (pin && *pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_enable_pin (self->sim, pin, cancellable,
+                     cc_wwan_device_enable_pin_cb,
+                     g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_enable_pin_finish (CcWwanDevice  *self,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_disable_pin_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_disable_pin_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_disable_pin (CcWwanDevice        *self,
+                            const gchar         *pin,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (pin && *pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_disable_pin (self->sim, pin, cancellable,
+                      cc_wwan_device_disable_pin_cb,
+                      g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_disable_pin_finish (CcWwanDevice *self,
+                                   GAsyncResult *result,
+                                   GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_change_pin_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_change_pin_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_change_pin (CcWwanDevice        *self,
+                           const gchar         *old_pin,
+                           const gchar         *new_pin,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (old_pin && *old_pin);
+  g_return_if_fail (new_pin && *new_pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_change_pin (self->sim, old_pin, new_pin, cancellable,
+                     cc_wwan_device_change_pin_cb,
+                     g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_change_pin_finish (CcWwanDevice  *self,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_network_mode_set_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMModem *modem = (MMModem *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_modem_set_current_modes_finish (modem, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_warning ("Error: %s", error->message);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * cc_wwan_device_set_network_mode:
+ * @self: a #CcWwanDevice
+ * @allowed: The allowed #MMModemModes
+ * @preferred: The preferred #MMModemMode
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: (nullable): a #GAsyncReadyCallback or %NULL
+ * @user_data: (nullable): closure data for @callback
+ *
+ * Asynchronously set preferred network mode.
+ *
+ * Call @cc_wwan_device_set_current_mode_finish()
+ * in @callback to get the result of operation.
+ */
+void
+cc_wwan_device_set_current_mode (CcWwanDevice        *self,
+                                 MMModemMode          allowed,
+                                 MMModemMode          preferred,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  GPermission *permission;
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  permission = polkit_permission_new_sync ("org.freedesktop.ModemManager1.Device.Control",
+                                           NULL, cancellable, &error);
+  g_task_set_task_data (task, permission, g_object_unref);
+
+  if (error)
+    g_warning ("error: %s", error->message);
+
+  if (error)
+    g_task_return_error (task, g_steal_pointer (&error));
+  else if (!g_permission_get_allowed (permission))
+    {
+      error = g_error_new (G_IO_ERROR,
+                           G_IO_ERROR_PERMISSION_DENIED,
+                           "Access Denied");
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    mm_modem_set_current_modes (self->modem, allowed, preferred,
+                                cancellable, cc_wwan_device_network_mode_set_cb,
+                                g_steal_pointer (&task));
+}
+
+/**
+ * cc_wwan_device_set_current_mode_finish:
+ * @self: a #CcWwanDevice
+ * @result: a #GAsyncResult
+ * @error: a location for #GError or %NULL
+ *
+ * Get the status whether setting network mode
+ * succeeded
+ *
+ * Returns: %TRUE if network mode was successfully set,
+ * %FALSE otherwise.
+ */
+gboolean
+cc_wwan_device_set_current_mode_finish (CcWwanDevice  *self,
+                                        GAsyncResult  *result,
+                                        GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+cc_wwan_device_get_current_mode (CcWwanDevice *self,
+                                 MMModemMode  *allowed,
+                                 MMModemMode  *preferred)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+  return mm_modem_get_current_modes (self->modem, allowed, preferred);
+}
+
+gboolean
+cc_wwan_device_is_auto_network (CcWwanDevice *self)
+{
+  /*
+   * XXX: ModemManager Doesn’t have a true API to check
+   * if registration is automatic or manual.  So Let’s
+   * do some guess work.
+   */
+  if (self->registration_state == CC_WWAN_REGISTRATION_STATE_DENIED)
+    return FALSE;
+
+  return !self->network_is_manual;
+}
+
+CcWwanState
+cc_wwan_device_get_network_state (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0);
+
+  return self->registration_state;
+}
+
+gboolean
+cc_wwan_device_get_supported_modes (CcWwanDevice *self,
+                                    MMModemMode  *allowed,
+                                    MMModemMode  *preferred)
+{
+  g_autofree MMModemModeCombination *modes = NULL;
+  guint n_modes, i;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+  if (!mm_modem_get_supported_modes (self->modem, &modes, &n_modes))
+    return FALSE;
+
+  if (allowed)
+    *allowed = 0;
+  if (preferred)
+    *preferred = 0;
+
+  for (i = 0; i < n_modes; i++)
+    {
+      if (allowed)
+        *allowed = *allowed | modes[i].allowed;
+      if (preferred)
+        *preferred = *preferred | modes[i].preferred;
+    }
+
+  return TRUE;
+}
+
+#define APPEND_MODE_TO_STRING(_str, _now, _preferred, _mode_str) do { \
+    if (_str->len > 0)                                                \
+      g_string_append (_str, ", ");                                   \
+    g_string_append (_str, _mode_str);                                \
+    if (_preferred == _now)                                           \
+      g_string_append (_str, _(" (Preferred)"));                      \
+  } while (0)
+
+gchar *
+cc_wwan_device_get_string_from_mode (CcWwanDevice *self,
+                                     MMModemMode   allowed,
+                                     MMModemMode   preferred)
+{
+  GString *str;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+  g_return_val_if_fail (allowed != 0, NULL);
+
+  str = g_string_sized_new (10);
+
+  if (allowed & MM_MODEM_MODE_2G)
+    APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_2G, preferred, "2G");
+  if (allowed & MM_MODEM_MODE_3G)
+    APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_3G, preferred, "3G");
+  if (allowed & MM_MODEM_MODE_4G)
+    APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_4G, preferred, "4G");
+
+  if (allowed == MM_MODEM_MODE_2G ||
+      allowed == MM_MODEM_MODE_3G ||
+      allowed == MM_MODEM_MODE_4G)
+    g_string_append (str, _(" Only"));
+
+  if (str->len == 0)
+    return g_string_free (str, TRUE);
+  else
+    return g_string_free (str, FALSE);
+}
+#undef APPEND_MODE_TO_STRING
+
+static void
+wwan_network_list_free (GList *network_list)
+{
+  g_list_free_full (network_list, (GDestroyNotify)mm_modem_3gpp_network_free);
+}
+
+static void
+cc_wwan_device_scan_complete_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  MMModem3gpp *modem_3gpp = (MMModem3gpp *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  GList *network_list;
+
+  network_list = mm_modem_3gpp_scan_finish (modem_3gpp, result, &error);
+
+  if (error)
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_pointer (task, network_list, (GDestroyNotify)wwan_network_list_free);
+}
+
+void
+cc_wwan_device_scan_networks (CcWwanDevice        *self,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_modem_3gpp_scan (self->modem_3gpp, cancellable,
+                      cc_wwan_device_scan_complete_cb,
+                      g_steal_pointer (&task));
+}
+
+GList *
+cc_wwan_device_scan_networks_finish (CcWwanDevice  *self,
+                                     GAsyncResult  *result,
+                                     GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_register_network_complete_cb (GObject      *object,
+                                             GAsyncResult *result,
+                                             gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMModem3gpp *modem_3gpp = (MMModem3gpp *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_modem_3gpp_register_finish (modem_3gpp, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_warning ("Error: %s", error->message);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_register_network (CcWwanDevice        *self,
+                                 const gchar         *network_id,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  if (network_id && *network_id)
+    self->network_is_manual = TRUE;
+  else
+    self->network_is_manual = FALSE;
+
+  mm_modem_3gpp_register (self->modem_3gpp, network_id, cancellable,
+                          cc_wwan_device_register_network_complete_cb,
+                          g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_register_network_finish (CcWwanDevice *self,
+                                        GAsyncResult *result,
+                                        GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * cc_wwan_device_get_operator_name:
+ * @self: a #CcWwanDevice
+ *
+ * Get the human readable network operator name
+ * currently the device is connected to.
+ *
+ * Returns: (nullable): The operator name or %NULL
+ */
+const gchar *
+cc_wwan_device_get_operator_name (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  if (!self->modem_3gpp)
+    return NULL;
+
+  return mm_modem_3gpp_get_operator_name (self->modem_3gpp);
+}
+
+gchar *
+cc_wwan_device_dup_sim_identifier (CcWwanDevice *self)
+{
+  char *identifier;
+
+  identifier = mm_sim_dup_operator_name (self->sim);
+  if (identifier)
+    return identifier;
+
+  identifier = mm_sim_dup_operator_identifier (self->sim);
+  if (identifier)
+    return identifier;
+
+  identifier = mm_sim_dup_identifier (self->sim);
+  if (identifier)
+    return identifier;
+
+  return g_strdup ("");
+}
+
+gchar *
+cc_wwan_device_dup_network_type_string (CcWwanDevice *self)
+{
+  MMModemAccessTechnology type;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  type = mm_modem_get_access_technologies (self->modem);
+
+  return mm_modem_access_technology_build_string_from_mask (type);
+}
+
+gchar *
+cc_wwan_device_dup_signal_string (CcWwanDevice *self)
+{
+  MMModemSignal *modem_signal;
+  MMSignal *signal;
+  GString *str;
+  gdouble value;
+  gboolean recent;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  modem_signal = mm_object_peek_modem_signal (self->mm_object);
+
+  if (!modem_signal)
+    return g_strdup_printf ("%d%%", mm_modem_get_signal_quality (self->modem, &recent));
+
+  str = g_string_new ("");
+
+  /* Adapted from ModemManager mmcli-modem-signal.c */
+  signal = mm_modem_signal_peek_cdma (modem_signal);
+  if (signal)
+    {
+      if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rssi: %.2g dBm ", value);
+      if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "ecio: %.2g dBm ", value);
+    }
+
+  signal = mm_modem_signal_peek_evdo (modem_signal);
+  if (signal)
+    {
+      if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rssi: %.2g dBm ", value);
+      if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "ecio: %.2g dBm ", value);
+      if ((value = mm_signal_get_sinr (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "sinr: %.2g dB ", value);
+      if ((value = mm_signal_get_io (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "io: %.2g dBm ", value);
+    }
+
+  signal = mm_modem_signal_peek_gsm (modem_signal);
+  if (signal)
+    if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+      g_string_append_printf (str, "rssi: %.2g dBm ", value);
+
+  signal = mm_modem_signal_peek_umts (modem_signal);
+  if (signal)
+    {
+      if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rssi: %.2g dBm ", value);
+      if ((value = mm_signal_get_rscp (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rscp: %.2g dBm ", value);
+      if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "ecio: %.2g dBm ", value);
+    }
+
+  signal = mm_modem_signal_peek_lte (modem_signal);
+  if (signal)
+    {
+      if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rssi: %.2g dBm ", value);
+      if ((value = mm_signal_get_rsrq (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rsrq: %.2g dB ", value);
+      if ((value = mm_signal_get_rsrp (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rsrp: %.2g dBm ", value);
+      if ((value = mm_signal_get_snr (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "snr: %.2g dB ", value);
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+const gchar *
+cc_wwan_device_get_manufacturer (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return mm_modem_get_manufacturer (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_model (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return mm_modem_get_model (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_firmware_version (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return mm_modem_get_revision (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_identifier (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return mm_modem_get_equipment_identifier (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_simple_error (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  if (!self->error)
+    return NULL;
+
+  return cc_wwan_error_get_message (self->error);
+}
+
+gboolean
+cc_wwan_device_is_nm_device (CcWwanDevice *self,
+                             GObject      *nm_device)
+{
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+  g_return_val_if_fail (NM_IS_DEVICE (nm_device), FALSE);
+
+  return g_str_equal (mm_modem_get_primary_port (self->modem),
+                      nm_device_get_iface (NM_DEVICE (nm_device)));
+#else
+  return FALSE;
+#endif
+}
+
+const gchar *
+cc_wwan_device_get_path (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), "");
+
+  return mm_object_get_path (self->mm_object);
+}
+
+CcWwanData *
+cc_wwan_device_get_data (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return self->wwan_data;
+}
+
+gboolean
+cc_wwan_device_pin_valid (const gchar *password,
+                          MMModemLock  lock)
+{
+  size_t len;
+
+  g_return_val_if_fail (lock == MM_MODEM_LOCK_SIM_PIN  ||
+                        lock == MM_MODEM_LOCK_SIM_PIN2 ||
+                        lock == MM_MODEM_LOCK_SIM_PUK  ||
+                        lock == MM_MODEM_LOCK_SIM_PUK2, FALSE);
+  if (!password)
+    return FALSE;
+
+  len = strlen (password);
+
+  if (len < 4 || len > 8)
+    return FALSE;
+
+  if (strspn (password, "0123456789") != len)
+    return FALSE;
+
+  /*
+   * XXX: Can PUK code be something other than 8 digits?
+   * 3GPP standard seems mum on this
+   */
+  if (lock == MM_MODEM_LOCK_SIM_PUK ||
+      lock == MM_MODEM_LOCK_SIM_PUK2)
+    if (len != 8)
+      return FALSE;
+
+  return TRUE;
+}
diff --git a/plugins/wwan/cc-wwan-device.h b/plugins/wwan/cc-wwan-device.h
new file mode 100644
index 00000000..add27d3d
--- /dev/null
+++ b/plugins/wwan/cc-wwan-device.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device.h
+ *
+ * Copyright 2019-2020 Purism SPC
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ *   Mohammed Sadiq <sadiq sadiqpk org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <libmm-glib.h>
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+# include "cc-wwan-data.h"
+#endif
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+  CC_WWAN_REGISTRATION_STATE_IDLE,
+  CC_WWAN_REGISTRATION_STATE_REGISTERED,
+  CC_WWAN_REGISTRATION_STATE_ROAMING,
+  CC_WWAN_REGISTRATION_STATE_SEARCHING,
+  CC_WWAN_REGISTRATION_STATE_DENIED
+} CcWwanState;
+
+typedef struct _CcWwanData CcWwanData;
+
+#define CC_TYPE_WWAN_DEVICE (cc_wwan_device_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDevice, cc_wwan_device, CC, WWAN_DEVICE, GObject)
+
+CcWwanDevice  *cc_wwan_device_new                (MMObject            *mm_object,
+                                                  GObject             *nm_client);
+gboolean       cc_wwan_device_has_sim            (CcWwanDevice        *self);
+MMModemLock    cc_wwan_device_get_lock           (CcWwanDevice        *self);
+gboolean       cc_wwan_device_get_sim_lock       (CcWwanDevice        *self);
+guint          cc_wwan_device_get_unlock_retries (CcWwanDevice        *self,
+                                                  MMModemLock          lock);
+void           cc_wwan_device_enable_pin         (CcWwanDevice        *self,
+                                                  const gchar         *pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_enable_pin_finish  (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+void           cc_wwan_device_disable_pin        (CcWwanDevice        *self,
+                                                  const gchar         *pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_disable_pin_finish (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+void           cc_wwan_device_send_pin           (CcWwanDevice        *self,
+                                                  const gchar         *pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_send_pin_finish    (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+void          cc_wwan_device_send_puk            (CcWwanDevice        *self,
+                                                  const gchar         *puk,
+                                                  const gchar         *pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean      cc_wwan_device_send_puk_finish     (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+void           cc_wwan_device_change_pin         (CcWwanDevice        *self,
+                                                  const gchar         *old_pin,
+                                                  const gchar         *new_pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_change_pin_finish  (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+const gchar   *cc_wwan_device_get_operator_name  (CcWwanDevice        *self);
+gchar         *cc_wwan_device_dup_sim_identifier (CcWwanDevice        *self);
+gchar         *cc_wwan_device_dup_network_type_string (CcWwanDevice   *self);
+gchar         *cc_wwan_device_dup_signal_string  (CcWwanDevice        *self);
+const gchar   *cc_wwan_device_get_manufacturer   (CcWwanDevice        *self);
+const gchar   *cc_wwan_device_get_model          (CcWwanDevice        *self);
+const gchar   *cc_wwan_device_get_firmware_version (CcWwanDevice      *self);
+const gchar   *cc_wwan_device_get_identifier     (CcWwanDevice        *self);
+gboolean       cc_wwan_device_get_current_mode   (CcWwanDevice        *self,
+                                                  MMModemMode         *allowed,
+                                                  MMModemMode         *preferred);
+gboolean       cc_wwan_device_is_auto_network    (CcWwanDevice        *self);
+CcWwanState    cc_wwan_device_get_network_state  (CcWwanDevice        *self);
+gboolean       cc_wwan_device_get_supported_modes (CcWwanDevice       *self,
+                                                   MMModemMode        *allowed,
+                                                   MMModemMode        *preferred);
+void           cc_wwan_device_set_current_mode   (CcWwanDevice        *self,
+                                                  MMModemMode          allowed,
+                                                  MMModemMode          preferred,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_set_current_mode_finish (CcWwanDevice        *self,
+                                                       GAsyncResult        *result,
+                                                       GError             **error);
+gchar         *cc_wwan_device_get_string_from_mode    (CcWwanDevice        *self,
+                                                       MMModemMode          allowed,
+                                                       MMModemMode          preferred);
+void           cc_wwan_device_scan_networks           (CcWwanDevice        *self,
+                                                       GCancellable        *cancellable,
+                                                       GAsyncReadyCallback  callback,
+                                                       gpointer             user_data);
+GList         *cc_wwan_device_scan_networks_finish    (CcWwanDevice        *self,
+                                                       GAsyncResult        *result,
+                                                       GError             **error);
+void           cc_wwan_device_register_network        (CcWwanDevice        *self,
+                                                       const gchar         *network_id,
+                                                       GCancellable        *cancellable,
+                                                       GAsyncReadyCallback  callback,
+                                                       gpointer             user_data);
+gboolean       cc_wwan_device_register_network_finish (CcWwanDevice        *self,
+                                                       GAsyncResult        *result,
+                                                       GError             **error);
+const gchar   *cc_wwan_device_get_simple_error        (CcWwanDevice        *self);
+GSList        *cc_wwan_device_get_apn_list            (CcWwanDevice        *self);
+gboolean       cc_wwan_device_is_nm_device            (CcWwanDevice        *self,
+                                                       GObject             *nm_device);
+const gchar   *cc_wwan_device_get_path                (CcWwanDevice        *self);
+CcWwanData    *cc_wwan_device_get_data                (CcWwanDevice        *self);
+gboolean       cc_wwan_device_pin_valid               (const gchar         *password,
+                                                       MMModemLock          lock);
+
+G_END_DECLS
diff --git a/plugins/wwan/cc-wwan-errors-private.h b/plugins/wwan/cc-wwan-errors-private.h
new file mode 100644
index 00000000..761b82f3
--- /dev/null
+++ b/plugins/wwan/cc-wwan-errors-private.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-errors-private.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * Modified from mm-error-helpers.c from ModemManager
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ *   Mohammed Sadiq <sadiq sadiqpk org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <libmm-glib.h>
+
+typedef struct {
+  guint code;
+  const gchar *message;
+} ErrorTable;
+
+
+static ErrorTable me_errors[] = {
+  { MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE,                      N_("Phone failure") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION,                      N_("No connection to phone") },
+  { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED,                      N_("Phone-adaptor link reserved") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED,                        N_("Operation not allowed") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED,                      N_("Operation not supported") },
+  { MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN,                         N_("PH-SIM PIN required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN,                        N_("PH-FSIM PIN required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK,                        N_("PH-FSIM PUK required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED,                   N_("SIM not inserted") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN,                            N_("SIM PIN required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK,                            N_("SIM PUK required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE,                        N_("SIM failure") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY,                           N_("SIM busy") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG,                          N_("SIM wrong") },
+  { MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD,                 N_("Incorrect password") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2,                           N_("SIM PIN2 required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2,                           N_("SIM PUK2 required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL,                        N_("Memory full") },
+  { MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX,                      N_("Invalid index") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NOT_FOUND,                          N_("Not found") },
+  { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE,                     N_("Memory failure") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK,                         N_("No network service") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,                    N_("Network timeout") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED,                N_("Network not allowed - emergency calls 
only") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN,                        N_("Network personalization PIN required") 
},
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK,                        N_("Network personalization PUK required") 
},
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN,                 N_("Network subset personalization PIN 
required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK,                 N_("Network subset personalization PUK 
required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN,                        N_("Service provider personalization PIN 
required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK,                        N_("Service provider personalization PUK 
required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN,                           N_("Corporate personalization PIN 
required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK,                           N_("Corporate personalization PUK 
required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN,                            N_("Unknown error") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_MS,                    N_("Illegal MS") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_ME,                    N_("Illegal ME") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_NOT_ALLOWED,           N_("GPRS services not allowed") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PLMN_NOT_ALLOWED,              N_("PLMN not allowed") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_LOCATION_NOT_ALLOWED,          N_("Location area not allowed") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ROAMING_NOT_ALLOWED,           N_("Roaming not allowed in this location 
area") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUPPORTED,  N_("Service option not supported") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUBSCRIBED, N_("Requested service option not 
subscribed") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_OUT_OF_ORDER,   N_("Service option temporarily out of 
order") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN,                       N_("Unspecified GPRS error") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PDP_AUTH_FAILURE,              N_("PDP authentication failure") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS,          N_("Invalid mobile class") },
+};
+
+static inline const gchar *
+cc_wwan_error_get_message (GError *error)
+{
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return _("Action Cancelled");
+
+ if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED))
+   return _("Access denied");
+
+  if (error->domain != MM_MOBILE_EQUIPMENT_ERROR)
+    return error->message;
+
+  for (guint i = 0; i < G_N_ELEMENTS (me_errors); i++)
+    if (me_errors[i].code == error->code)
+      return _(me_errors[i].message);
+
+  return _("Unknown Error");
+}
diff --git a/plugins/wwan/gsd-wwan-manager.c b/plugins/wwan/gsd-wwan-manager.c
index 317218e5..42109ef8 100644
--- a/plugins/wwan/gsd-wwan-manager.c
+++ b/plugins/wwan/gsd-wwan-manager.c
@@ -30,10 +30,13 @@
 
 #include <libmm-glib.h>
 
+#define GCR_API_SUBJECT_TO_CHANGE
+#include <gcr/gcr-base.h>
+
 #include "gnome-settings-profile.h"
-#include "gsd-wwan-device.h"
+#include "cc-wwan-device.h"
+#include "cc-wwan-errors-private.h"
 #include "gsd-wwan-manager.h"
-#include "gsd-wwan-pinentry.h"
 
 
 struct _GsdWwanManager
@@ -44,7 +47,16 @@ struct _GsdWwanManager
         gboolean   unlock;
         GSettings *settings;
 
+        /* List of all devices not in ‘devices_to_unlock’ */
         GPtrArray *devices;
+        GPtrArray *devices_to_unlock;
+
+        /* Currently shown prompt and device being unlocked */
+        GcrPrompt    *prompt;
+        CcWwanDevice *unlocking_device;
+        GCancellable *cancellable;
+        char         *puk_code;      /* Used only for PUK unlock */
+        guint         prompt_timeout_id;
 
         MMManager *mm1;
         gboolean  mm1_running;
@@ -65,54 +77,460 @@ G_DEFINE_TYPE (GsdWwanManager, gsd_wwan_manager, G_TYPE_OBJECT)
 /* The plugin's manager object */
 static gpointer manager_object = NULL;
 
+static void wwan_manager_ensure_unlocking   (GsdWwanManager *self);
+static void wwan_manager_unlock_device      (CcWwanDevice   *device,
+                                             gpointer        user_data);
+static void wwan_manager_unlock_required_cb (GsdWwanManager *self,
+                                             GParamSpec     *pspec,
+                                             CcWwanDevice   *device);
 
 static void
-unlock_sim_cb (GsdWwanManager *self, GsdWwanDevice *device)
+manager_unlock_prompt_new (GsdWwanManager *self,
+                           CcWwanDevice   *device,
+                           MMModemLock     lock,
+                           const char     *msg,
+                           gboolean        new_password)
 {
-        g_return_if_fail (GSD_IS_WWAN_MANAGER (self));
-        g_return_if_fail (GSD_IS_WWAN_DEVICE (device));
+        g_autoptr(GError) error = NULL;
+        g_autofree gchar *identifier = NULL;
+        g_autofree gchar *description = NULL;
+        g_autofree gchar *warning = NULL;
+        const gchar *message = NULL;
+        guint retries;
+
+        identifier = cc_wwan_device_dup_sim_identifier (device);
+        g_debug ("Creating new PIN/PUK dialog for SIM %s", identifier);
+
+        if (!self->prompt)
+                self->prompt = gcr_system_prompt_open (-1, self->cancellable, &error);
+
+        if (!self->prompt) {
+                if (error->code == GCR_SYSTEM_PROMPT_IN_PROGRESS)
+                        g_warning ("Another Gcr system prompt is already in progress.");
+                else
+                        g_warning ("Couldn't create prompt for SIM Code entry: %s", error->message);
+                return;
+        }
 
-        if (!self->unlock)
+        /* Set up the dialog  */
+        if (new_password) {
+                gcr_prompt_set_title (self->prompt, _("New PIN for SIM"));
+                gcr_prompt_set_continue_label (self->prompt, _("Set"));
+        } else {
+                gcr_prompt_set_title (self->prompt, _("Unlock SIM card"));
+                gcr_prompt_set_continue_label (self->prompt, _("Unlock"));
+        }
+
+        gcr_prompt_set_cancel_label (self->prompt, _("Cancel"));
+        gcr_prompt_set_password_new (self->prompt, new_password);
+
+        if (lock == MM_MODEM_LOCK_SIM_PIN) {
+                if (new_password) {
+                        description = g_strdup_printf (_("Please provide a new PIN for SIM card %s"),
+                                                       identifier);
+                        message = _("Enter a New PIN to unlock your SIM card");
+                } else {
+                        description = g_strdup_printf (_("Please provide the PIN for SIM card %s"),
+                                                       identifier);
+                        message = _("Enter PIN to unlock your SIM card");
+                }
+        } else if (lock == MM_MODEM_LOCK_SIM_PUK) {
+                description = g_strdup_printf (_("Please provide the PUK for SIM card %s"),
+                                               identifier);
+                message = _("Enter PUK to unlock your SIM card");
+        } else {
+                g_warning ("Unsupported lock type: %u", lock);
+                g_clear_object (&self->prompt);
                 return;
+        }
+
+        gcr_prompt_set_description (self->prompt, description);
+        gcr_prompt_set_message (self->prompt, message);
+
+        if (!new_password)
+                retries = cc_wwan_device_get_unlock_retries (device, lock);
+
+        if (!new_password && retries != MM_UNLOCK_RETRIES_UNKNOWN) {
+                if (msg) {
+                        /* msg is already localised */
+                        warning = g_strdup_printf (ngettext ("%2$s. You have %1$u try left",
+                                                             "%2$s. You have %1$u tries left", retries),
+                                                   retries, msg);
+                } else {
+                        warning = g_strdup_printf (ngettext ("You have %u try left",
+                                                             "You have %u tries left", retries),
+                                                   retries);
+                }
+        } else if (msg) {
+                warning = g_strdup (msg);
+        }
+
+        gcr_prompt_set_warning (self->prompt, warning);
+
+        /* TODO */
+        /* if (lock == MM_MODEM_LOCK_SIM_PIN) */
+        /*         gcr_prompt_set_choice_label (prompt, _("Automatically unlock this SIM card")); */
+}
+
+static gboolean
+unlock_device (gpointer user_data)
+{
+        GsdWwanManager *self;
+        CcWwanDevice *device;
+        g_autoptr(GTask) task = user_data;
+        MMModemLock lock;
+
+        g_assert (G_IS_TASK (task));
+
+        self = g_task_get_task_data (task);
+        device = g_task_get_source_object (task);
+
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+        g_assert (CC_IS_WWAN_DEVICE (device));
+
+        self->prompt_timeout_id = 0;
+
+        if (g_task_return_error_if_cancelled (task))
+                return G_SOURCE_REMOVE;
+
+        lock = cc_wwan_device_get_lock (device);
+
+        if (lock != MM_MODEM_LOCK_SIM_PIN &&
+            lock != MM_MODEM_LOCK_SIM_PUK) {
+                g_cancellable_cancel (g_task_get_cancellable (task));
+                g_task_return_error_if_cancelled (task);
+                return G_SOURCE_REMOVE;
+        }
 
-        gsd_wwan_pinentry_unlock_sim (device, NULL);
+        wwan_manager_unlock_device (device, g_steal_pointer (&task));
+
+        return G_SOURCE_REMOVE;
 }
 
+static void
+wwan_manager_password_sent_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+        GsdWwanManager *self;
+        CcWwanDevice *device = (CcWwanDevice *)object;
+        g_autoptr(GTask) task = user_data;
+        g_autoptr(GError) error = NULL;
+        gboolean ret;
+
+        g_assert (CC_IS_WWAN_DEVICE (device));
+        g_assert (G_IS_TASK (task));
+
+        self = g_task_get_task_data (task);
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+
+        if (self->puk_code)
+                ret = cc_wwan_device_send_puk_finish (device, result, &error);
+        else
+                ret = cc_wwan_device_send_pin_finish (device, result, &error);
+
+        g_clear_pointer (&self->puk_code, gcr_secure_memory_free);
+
+        /* Ask again if a failable error occured */
+        if (error &&
+            (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, 
MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD) ||
+             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_UNKNOWN))) {
+                g_object_set_data (G_OBJECT (task), "error", (gpointer)cc_wwan_error_get_message (error));
+                /* ModemManager updates the lock status after some delay.  Wait around 250 milliseconds
+                 * so that the values are updated.
+                 */
+                self->prompt_timeout_id = g_timeout_add (250, unlock_device, g_steal_pointer (&task));
+
+                return;
+        }
+
+        if (ret)
+                g_task_return_boolean (task, TRUE);
+        else
+                g_task_return_error (task, error);
+
+}
 
 static gboolean
-device_match_by_object (GsdWwanDevice *device, GDBusObject *object)
+wwan_manager_unlock_device_finish (CcWwanDevice  *self,
+                                   GAsyncResult  *result,
+                                   GError       **error)
 {
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static const char *
+wwan_manager_show_prompt (GsdWwanManager *self,
+                          CcWwanDevice   *device,
+                          GTask          *task)
+{
+        g_autoptr(GError) error = NULL;
+        const char *code;
+
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+        g_assert (CC_IS_WWAN_DEVICE (device));
+        g_assert (G_IS_TASK (task));
+
+        if (!self->prompt) {
+                g_task_return_new_error (task,
+                                         G_IO_ERROR,
+                                         G_IO_ERROR_FAILED,
+                                         "Failed to create a new prompt");
+                return NULL;
+        }
+
+        g_set_object (&self->unlocking_device, device);
+
+        /* Irritate user if an empty password is provided */
+        do {
+                code = gcr_prompt_password_run (self->prompt, self->cancellable, &error);
+        } while (code && !*code);
+
+        if (error) {
+                g_task_return_error (task, g_steal_pointer (&error));
+                return NULL;
+        }
+
+        /* User cancelled the dialog */
+        if (!code) {
+                g_cancellable_cancel (g_task_get_cancellable (task));
+                g_task_return_error_if_cancelled (task);
+                return NULL;
+        }
+
+        return code;
+}
+
+static void
+wwan_manager_unlock_device (CcWwanDevice *device,
+                            gpointer      user_data)
+{
+        GsdWwanManager *self;
+        g_autoptr(GTask) task = user_data;
+        GCancellable *cancellable;
+        const char *code, *error_msg;
+        MMModemLock lock;
+
+        g_assert (CC_IS_WWAN_DEVICE (device));
+        g_assert (G_IS_TASK (task));
+
+        self = g_task_get_task_data (task);
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+
+        error_msg = g_object_get_data (G_OBJECT (task), "error");
+        lock = cc_wwan_device_get_lock (device);
+        manager_unlock_prompt_new (self, device, lock, error_msg, FALSE);
+        g_object_set_data (G_OBJECT (task), "error", NULL);
+
+        code = wwan_manager_show_prompt (self, device, task);
+        if (!code)
+                return;
+
+        if (lock == MM_MODEM_LOCK_SIM_PUK) {
+                gcr_secure_memory_free (self->puk_code);
+                self->puk_code = gcr_secure_memory_strdup (code);
+
+                manager_unlock_prompt_new (self, device, MM_MODEM_LOCK_SIM_PIN, NULL, TRUE);
+                code = wwan_manager_show_prompt (self, device, task);
+                if (!code)
+                        return;
+        }
+
+        cancellable = g_task_get_cancellable (task);
+
+        if (lock == MM_MODEM_LOCK_SIM_PIN)
+                cc_wwan_device_send_pin (device, code, cancellable,
+                                         wwan_manager_password_sent_cb,
+                                         g_steal_pointer (&task));
+        else if (lock == MM_MODEM_LOCK_SIM_PUK)
+                cc_wwan_device_send_puk (device, self->puk_code, code, cancellable,
+                                         wwan_manager_password_sent_cb,
+                                         g_steal_pointer (&task));
+}
+
+static void
+wwan_manager_unlock_device_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+        g_autoptr(GsdWwanManager) self = user_data;
+        CcWwanDevice *device = (CcWwanDevice *)object;
+        g_autoptr(GError) error = NULL;
+
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+        g_assert (CC_IS_WWAN_DEVICE (device));
+        g_assert (G_IS_TASK (result));
+
+        wwan_manager_unlock_device_finish (device, result, &error);
+
+        /* Move the device from devices to unlock to the list of devices */
+        if (g_ptr_array_remove (self->devices_to_unlock, device))
+                g_ptr_array_add (self->devices, g_object_ref (device));
+
+        g_clear_pointer (&self->puk_code, gcr_secure_memory_free);
+        g_clear_object (&self->prompt);
+        g_clear_object (&self->cancellable);
+        g_clear_object (&self->unlocking_device);
+        g_clear_handle_id (&self->prompt_timeout_id, g_source_remove);
+
+        /* Unlock the next device */
+        if (self->devices_to_unlock->len)
+                wwan_manager_unlock_required_cb (self, NULL, self->devices_to_unlock->pdata[0]);
+
+        if (error)
+                g_debug ("Error unlocking device: %s", error->message);
+
+        if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                g_warning ("Error unlocking device: %s", error->message);
+}
+
+static void
+wwan_manager_unlock_required_cb (GsdWwanManager *self,
+                                 GParamSpec     *pspec,
+                                 CcWwanDevice   *device)
+{
+        MMModemLock lock;
+
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+        g_assert (CC_IS_WWAN_DEVICE (device));
+
+        lock = cc_wwan_device_get_lock (device);
+
+        if (lock != MM_MODEM_LOCK_SIM_PIN &&
+            lock != MM_MODEM_LOCK_SIM_PUK) {
+                g_object_ref (device);
+
+                /* Move the device from devices to unlock to the list of devices */
+                if (g_ptr_array_remove (self->devices_to_unlock, device))
+                        g_ptr_array_add (self->devices, device);
+
+                /* If the device is the device being unlocked, cancel the process */
+                if (device == self->unlocking_device)
+                        g_cancellable_cancel (self->cancellable);
+        } else if (lock == MM_MODEM_LOCK_SIM_PIN ||
+                   lock == MM_MODEM_LOCK_SIM_PUK) {
+                g_object_ref (device);
+
+                /* Move the device to devices to unlock from the list of devices */
+                if (g_ptr_array_remove (self->devices, device)) {
+                        g_ptr_array_add (self->devices_to_unlock, device);
+                        wwan_manager_ensure_unlocking (self);
+                }
+        }
+}
+
+
+static gboolean
+device_match_by_object (CcWwanDevice *device, GDBusObject *object)
+{
+        const char *device_path, *object_path;
+
+        g_return_val_if_fail (G_IS_DBUS_OBJECT (object), FALSE);
+        g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), FALSE);
+
+        device_path = cc_wwan_device_get_path (device);
+        object_path = mm_object_get_path (MM_OBJECT (object));
+
+        return g_strcmp0 (device_path, object_path) == 0;
+}
+
+/*
+ * @array: (out) (nullable):
+ * @index: (out) (nullable):
+ *
+ * Returns: %TRUE if found.  %FALSE otherwise
+ */
+static gboolean
+wwan_manager_find_match (GsdWwanManager  *self,
+                         GDBusObject     *object,
+                         GPtrArray      **array,
+                         guint           *index)
+{
+        GPtrArray *devices = NULL;
+        guint i = 0;
+
         g_return_val_if_fail (G_IS_DBUS_OBJECT (object), FALSE);
-        g_return_val_if_fail (GSD_IS_WWAN_DEVICE (device), FALSE);
 
-        return object == G_DBUS_OBJECT (gsd_wwan_device_get_mm_object (device));
+        if (g_ptr_array_find_with_equal_func (self->devices,
+                                              object,
+                                              (GEqualFunc) device_match_by_object,
+                                              &i))
+                devices = self->devices;
+        else if (g_ptr_array_find_with_equal_func (self->devices_to_unlock,
+                                                     object,
+                                                     (GEqualFunc) device_match_by_object,
+                                                     &i))
+                devices = self->devices_to_unlock;
+
+        if (index && i >= 0)
+                *index = i;
+        if (array)
+                *array = devices;
+
+        if (devices)
+                return TRUE;
+
+        return FALSE;
 }
 
 
+static void
+wwan_manager_ensure_unlocking (GsdWwanManager *self)
+{
+        CcWwanDevice *device;
+        GTask *task;
+
+        g_assert (GSD_WWAN_MANAGER (self));
+
+        if (!self->unlock || self->unlocking_device)
+                return;
+
+        if (self->devices_to_unlock->len == 0)
+                return;
+
+        g_warn_if_fail (!self->cancellable);
+        g_clear_object (&self->cancellable);
+
+        device = self->devices_to_unlock->pdata[0];
+        self->cancellable = g_cancellable_new ();
+        task = g_task_new (device, self->cancellable,
+                           wwan_manager_unlock_device_cb,
+                           g_object_ref (self));
+        g_task_set_task_data (task, g_object_ref (self), g_object_unref);
+
+        wwan_manager_unlock_device (device, task);
+}
+
 static void
 gsd_wwan_manager_cache_mm_object (GsdWwanManager *self, MMObject *obj)
 {
         const gchar *modem_object_path;
-        GsdWwanDevice *device;
+        CcWwanDevice *wwan_device;
+        MMModemLock lock;
 
         modem_object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (obj));
         g_return_if_fail (modem_object_path);
 
-        if (g_ptr_array_find_with_equal_func (self->devices,
-                                              obj,
-                                              (GEqualFunc) device_match_by_object,
-                                              NULL)) {
-                g_debug("Device %s already tracked", modem_object_path);
+        /* This shouldn’t happen, so warn and return if this happen. */
+        if (wwan_manager_find_match (self, G_DBUS_OBJECT (obj), NULL, NULL)) {
+                g_warning("Device %s already tracked", modem_object_path);
                 return;
         }
 
         g_debug ("Tracking device at: %s", modem_object_path);
-        device = gsd_wwan_device_new (MM_OBJECT (obj));
-        g_signal_connect_swapped (device,
-                                  "sim-needs-unlock",
-                                  G_CALLBACK (unlock_sim_cb),
-                                  self);
-        g_ptr_array_add (self->devices, device);
+        wwan_device = cc_wwan_device_new (MM_OBJECT (obj), NULL);
+        lock = cc_wwan_device_get_lock (wwan_device);
+        if (lock == MM_MODEM_LOCK_SIM_PIN ||
+            lock == MM_MODEM_LOCK_SIM_PUK)
+                g_ptr_array_add (self->devices_to_unlock, wwan_device);
+        else
+                g_ptr_array_add (self->devices, wwan_device);
+
+        g_signal_connect_object (wwan_device, "notify::unlock-required",
+                                 G_CALLBACK (wwan_manager_unlock_required_cb),
+                                 self, G_CONNECT_SWAPPED);
+        wwan_manager_ensure_unlocking (self);
 }
 
 
@@ -127,19 +545,26 @@ object_added_cb (GsdWwanManager *self, GDBusObject *object, GDBusObjectManager *
 
 
 static void
-object_removed_cb (GsdWwanManager *self, GDBusObject *object, GDBusObjectManager *obj_manager)
+object_removed_cb (GsdWwanManager     *self,
+                   GDBusObject        *object,
+                   GDBusObjectManager *obj_manager)
 {
+        CcWwanDevice *device;
+        GPtrArray *devices;
         guint index;
 
         g_return_if_fail (GSD_IS_WWAN_MANAGER (self));
         g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER (obj_manager));
 
-        if (g_ptr_array_find_with_equal_func (self->devices,
-                                              object,
-                                              (GEqualFunc) device_match_by_object,
-                                              &index)) {
-                g_ptr_array_remove_index_fast (self->devices, index);
-        }
+        if (!wwan_manager_find_match (self, object, &devices, &index))
+                g_return_if_reached ();
+
+        device = g_ptr_array_index (devices, index);
+
+        g_ptr_array_remove_index (devices, index);
+
+        if (device == self->unlocking_device)
+                g_cancellable_cancel (self->cancellable);
 }
 
 
@@ -154,9 +579,13 @@ mm1_name_owner_changed_cb (GDBusObjectManagerClient *client, GParamSpec *pspec,
 
         if (!self->mm1_running) {
                 /* Drop all devices when MM goes away */
-                if (self->devices->len) {
-                        g_ptr_array_set_size (self->devices, 0);
-                }
+                g_ptr_array_set_size (self->devices, 0);
+                g_ptr_array_set_size (self->devices_to_unlock, 0);
+
+                g_clear_object (&self->prompt);
+                g_clear_pointer (&self->puk_code, gcr_secure_memory_free);
+                g_clear_object (&self->unlocking_device);
+
                 return;
         }
 }
@@ -269,14 +698,6 @@ gsd_wwan_manager_stop (GsdWwanManager *self)
 }
 
 
-static void
-unlock_all (GsdWwanDevice *device, GsdWwanManager *self)
-{
-        if (gsd_wwan_device_needs_unlock (device))
-                unlock_sim_cb (self, device);
-}
-
-
 static void
 gsd_wwan_manager_set_unlock_sim (GsdWwanManager *self, gboolean unlock)
 {
@@ -285,11 +706,17 @@ gsd_wwan_manager_set_unlock_sim (GsdWwanManager *self, gboolean unlock)
 
         self->unlock = unlock;
 
-        if (self->unlock) {
-                g_ptr_array_foreach (self->devices,
-                                     (GFunc) unlock_all,
-                                     self);
-        }
+        /*
+         * XXX: Should the devices in ‘self->devices’ be moved to
+         * ‘self->devices_to_unlock’ if required?  Otherwise, no prompt
+         * will be shown for devices the user explicitly cancelled
+         * unlock prompt.
+         */
+        /* Unlock the first device if no device is being unlocked.  Unlocking
+         * the rest will be handled appropriately after this is finished. */
+        if (self->unlock && self->devices_to_unlock->len > 0 && !self->unlocking_device)
+                wwan_manager_unlock_required_cb (self, NULL,
+                                                 self->devices_to_unlock->pdata[0]);
 
         g_object_notify_by_pspec (G_OBJECT (self), props[PROP_UNLOCK_SIM]);
 }
@@ -340,7 +767,17 @@ gsd_wwan_manager_dispose (GObject *object)
                 self->mm1_running = FALSE;
                 g_clear_object (&self->mm1);
         }
+
+        if (self->cancellable)
+                g_cancellable_cancel (self->cancellable);
+        g_clear_object (&self->cancellable);
+        g_clear_handle_id (&self->prompt_timeout_id, g_source_remove);
+        g_clear_object (&self->unlocking_device);
+        g_clear_pointer (&self->puk_code, gcr_secure_memory_free);
+        g_clear_object (&self->prompt);
+
         g_clear_pointer (&self->devices, g_ptr_array_unref);
+        g_clear_pointer (&self->devices_to_unlock, g_ptr_array_unref);
         g_clear_object (&self->settings);
 
         G_OBJECT_CLASS (gsd_wwan_manager_parent_class)->dispose (object);
@@ -370,6 +807,7 @@ static void
 gsd_wwan_manager_init (GsdWwanManager *self)
 {
         self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+        self->devices_to_unlock = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
 }
 
 
diff --git a/plugins/wwan/meson.build b/plugins/wwan/meson.build
index 81869b9f..3f117fb3 100644
--- a/plugins/wwan/meson.build
+++ b/plugins/wwan/meson.build
@@ -1,11 +1,10 @@
 sources = files(
-  'gsd-wwan-device.c',
+  'cc-wwan-device.c',
   'gsd-wwan-manager.c',
-  'gsd-wwan-pinentry.c',
   'main.c'
 )
 
-deps = plugins_deps + [gio_dep, gcr_base_dep, mm_glib_dep]
+deps = plugins_deps + [gio_dep, gcr_base_dep, mm_glib_dep, polkit_gobject_dep]
 
 cflags += ['-DGNOMECC_DATA_DIR="@0@"'.format(gsd_pkgdatadir)]
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]