[gnome-control-center] wwan: Add new panel for modem management



commit dc840f0aec346f3fb297789eb1641255574c47a4
Author: Mohammed Sadiq <sadiq sadiqpk org>
Date:   Fri Oct 4 22:16:23 2019 +0530

    wwan: Add new panel for modem management
    
    The panel supports 2G/3G/4G GSM/LTE modems. CDMA2000 Modems are not supported.
    If a supported modem is present, the panel will be shown and the modem will be
    handled, else, network-panel shall manage the modem as it did in the past.
    
    If more than one modem with data enabled is present, the user is allowed to set
    priority of one SIM over the other (the priority is for SIM, not modem).
    
    Fixes https://gitlab.gnome.org/GNOME/gnome-control-center/issues/132

 .gitlab-ci.yml                             |    4 +
 meson.build                                |    4 +
 panels/meson.build                         |    3 +-
 panels/wwan/cc-wwan-apn-dialog.c           |  424 ++++++++
 panels/wwan/cc-wwan-apn-dialog.h           |   40 +
 panels/wwan/cc-wwan-apn-dialog.ui          |  249 +++++
 panels/wwan/cc-wwan-data.c                 | 1446 ++++++++++++++++++++++++++++
 panels/wwan/cc-wwan-data.h                 |   93 ++
 panels/wwan/cc-wwan-details-dialog.c       |  257 +++++
 panels/wwan/cc-wwan-details-dialog.h       |   40 +
 panels/wwan/cc-wwan-details-dialog.ui      |  320 ++++++
 panels/wwan/cc-wwan-device-page.c          |  634 ++++++++++++
 panels/wwan/cc-wwan-device-page.h          |   42 +
 panels/wwan/cc-wwan-device-page.ui         |  270 ++++++
 panels/wwan/cc-wwan-device.c               | 1355 ++++++++++++++++++++++++++
 panels/wwan/cc-wwan-device.h               |  152 +++
 panels/wwan/cc-wwan-errors-private.h       |  104 ++
 panels/wwan/cc-wwan-mode-dialog.c          |  327 +++++++
 panels/wwan/cc-wwan-mode-dialog.h          |   40 +
 panels/wwan/cc-wwan-mode-dialog.ui         |   57 ++
 panels/wwan/cc-wwan-network-dialog.c       |  443 +++++++++
 panels/wwan/cc-wwan-network-dialog.h       |   40 +
 panels/wwan/cc-wwan-network-dialog.ui      |  188 ++++
 panels/wwan/cc-wwan-panel.c                |  929 ++++++++++++++++++
 panels/wwan/cc-wwan-panel.h                |   36 +
 panels/wwan/cc-wwan-panel.ui               |  336 +++++++
 panels/wwan/cc-wwan-sim-lock-dialog.c      |  310 ++++++
 panels/wwan/cc-wwan-sim-lock-dialog.h      |   40 +
 panels/wwan/cc-wwan-sim-lock-dialog.ui     |  306 ++++++
 panels/wwan/gnome-wwan-panel.desktop.in.in |   16 +
 panels/wwan/meson.build                    |   61 ++
 panels/wwan/wwan.gresource.xml             |   12 +
 shell/cc-panel-list.c                      |    1 +
 shell/cc-panel-loader.c                    |    9 +
 34 files changed, 8587 insertions(+), 1 deletion(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3a002b59e..aa8874c04 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -139,6 +139,7 @@ build:
   stage: build
 
   script:
+    - dnf -y install gcr-devel
     - *environment_information
     - *build_procedure
 
@@ -171,6 +172,7 @@ test:
     - build
 
   script:
+    - dnf -y install gcr-devel
     - *environment_information
     - *run_tests
 
@@ -199,6 +201,7 @@ coverage:
     - master@GNOME/gnome-control-center
 
   script:
+    - dnf -y install gcr-devel
     - *environment_information
     - *build_procedure
     - *run_tests
@@ -314,6 +317,7 @@ flatpak:
   stage: manual
   when: manual
   script:
+    - dnf -y install gcr-devel
     - *environment_information
     - *build_procedure
     - *run_tests
diff --git a/meson.build b/meson.build
index 91fa7f52c..42a9536b4 100644
--- a/meson.build
+++ b/meson.build
@@ -228,6 +228,10 @@ config_h.set('BUILD_NETWORK', host_is_linux,
              description: 'Define to 1 to build the Network panel')
 config_h.set('HAVE_NETWORK_MANAGER', host_is_linux,
              description: 'Define to 1 if NetworkManager is available')
+config_h.set('BUILD_WWAN', host_is_linux,
+             description: 'Define to 1 to build the WWan panel')
+config_h.set('HAVE_WWAN', host_is_linux,
+             description: 'Define to 1 if WWan is available')
 
 if host_is_linux_not_s390
   # gnome-bluetooth
diff --git a/panels/meson.build b/panels/meson.build
index 1318904ae..f603db919 100644
--- a/panels/meson.build
+++ b/panels/meson.build
@@ -27,7 +27,8 @@ panels = [
   'sound',
   'universal-access',
   'usage',
-  'user-accounts'
+  'user-accounts',
+  'wwan',
 ]
 
 if host_is_linux
diff --git a/panels/wwan/cc-wwan-apn-dialog.c b/panels/wwan/cc-wwan-apn-dialog.c
new file mode 100644
index 000000000..bc5fde283
--- /dev/null
+++ b/panels/wwan/cc-wwan-apn-dialog.c
@@ -0,0 +1,424 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-apn-dialog.c
+ *
+ * Copyright 2019 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-apn-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "cc-wwan-device.h"
+#include "cc-wwan-data.h"
+#include "list-box-helper.h"
+#include "cc-wwan-apn-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: Dialog to manage Internet Access Points
+ */
+
+struct _CcWwanApnDialog
+{
+  GtkDialog          parent_instance;
+
+  GtkButton         *add_button;
+  GtkButton         *back_button;
+  GtkButton         *save_button;
+  GtkEntry          *apn_entry;
+  GtkEntry          *name_entry;
+  GtkEntry          *password_entry;
+  GtkEntry          *username_entry;
+  GtkGrid           *apn_edit_view;
+  GtkListBox        *apn_list;
+  GtkRadioButton    *apn_radio_button;
+  GtkScrolledWindow *apn_list_view;
+  GtkStack          *apn_settings_stack;
+
+  CcWwanData        *wwan_data;
+  CcWwanDataApn     *apn_to_save;   /* The APN currently being edited */
+  CcWwanDevice      *device;
+
+  gboolean           enable_data;
+  gboolean           enable_roaming;
+};
+
+G_DEFINE_TYPE (CcWwanApnDialog, cc_wwan_apn_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+#define CC_TYPE_WWAN_APN_ROW (cc_wwan_apn_row_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanApnRow, cc_wwan_apn_row, CC, WWAN_APN_ROW, GtkListBoxRow)
+
+struct _CcWwanApnRow
+{
+  GtkListBoxRow   parent_instance;
+  GtkRadioButton *radio_button;
+  CcWwanDataApn  *apn;
+};
+
+G_DEFINE_TYPE (CcWwanApnRow, cc_wwan_apn_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_wwan_apn_row_finalize (GObject *object)
+{
+  CcWwanApnRow *row = (CcWwanApnRow *)object;
+
+  g_clear_object (&row->apn);
+
+  G_OBJECT_CLASS (cc_wwan_apn_row_parent_class)->finalize (object);
+}
+
+static void
+cc_wwan_apn_row_class_init (CcWwanApnRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = cc_wwan_apn_row_finalize;
+}
+
+static void
+cc_wwan_apn_row_init (CcWwanApnRow *row)
+{
+}
+
+static void
+cc_wwan_apn_back_clicked_cb (CcWwanApnDialog *self)
+{
+  GtkWidget *view;
+
+  view = gtk_stack_get_visible_child (self->apn_settings_stack);
+
+  if (view == GTK_WIDGET (self->apn_edit_view))
+    {
+      gtk_widget_hide (GTK_WIDGET (self->save_button));
+      gtk_widget_show (GTK_WIDGET (self->add_button));
+      gtk_stack_set_visible_child (self->apn_settings_stack,
+                                   GTK_WIDGET (self->apn_list_view));
+    }
+  else
+    {
+      gtk_widget_hide (GTK_WIDGET (self));
+    }
+}
+
+static void
+cc_wwan_apn_add_clicked_cb (CcWwanApnDialog *self)
+{
+  gtk_entry_set_text (self->name_entry, "");
+  gtk_entry_set_text (self->apn_entry, "");
+  gtk_entry_set_text (self->username_entry, "");
+  gtk_entry_set_text (self->password_entry, "");
+
+  gtk_widget_hide (GTK_WIDGET (self->add_button));
+  gtk_widget_show (GTK_WIDGET (self->save_button));
+  self->apn_to_save = NULL;
+  gtk_stack_set_visible_child (self->apn_settings_stack,
+                               GTK_WIDGET (self->apn_edit_view));
+}
+
+static void
+cc_wwan_apn_save_clicked_cb (CcWwanApnDialog *self)
+{
+  const gchar *name, *apn_name;
+  CcWwanDataApn *apn;
+
+  apn = self->apn_to_save;
+  self->apn_to_save = NULL;
+
+  name = gtk_entry_get_text (self->name_entry);
+  apn_name = gtk_entry_get_text (self->apn_entry);
+
+  if (!apn)
+    apn = cc_wwan_data_apn_new ();
+
+  cc_wwan_data_apn_set_name (apn, name);
+  cc_wwan_data_apn_set_apn (apn, apn_name);
+  cc_wwan_data_apn_set_username (apn, gtk_entry_get_text (self->username_entry));
+  cc_wwan_data_apn_set_password (apn, gtk_entry_get_text (self->password_entry));
+
+  cc_wwan_data_save_apn (self->wwan_data, apn, NULL, NULL, NULL);
+
+  gtk_widget_hide (GTK_WIDGET (self->save_button));
+  gtk_stack_set_visible_child (self->apn_settings_stack,
+                               GTK_WIDGET (self->apn_list_view));
+}
+
+static void
+cc_wwan_apn_entry_changed_cb (CcWwanApnDialog *self)
+{
+  GtkWidget *widget;
+  const gchar *str;
+  gboolean valid_name, valid_apn;
+
+  widget = GTK_WIDGET (self->name_entry);
+  str = gtk_entry_get_text (self->name_entry);
+  valid_name = str && *str;
+
+  if (valid_name)
+    gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "error");
+  else
+    gtk_style_context_add_class (gtk_widget_get_style_context (widget), "error");
+
+  widget = GTK_WIDGET (self->apn_entry);
+  str = gtk_entry_get_text (self->apn_entry);
+  valid_apn = str && *str;
+
+  if (valid_apn)
+    gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "error");
+  else
+    gtk_style_context_add_class (gtk_widget_get_style_context (widget), "error");
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->save_button), valid_name && valid_apn);
+}
+
+static void
+cc_wwan_apn_activated_cb (CcWwanApnDialog *self,
+                          CcWwanApnRow    *row)
+{
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->radio_button), TRUE);
+}
+
+static void
+cc_wwan_apn_changed_cb (CcWwanApnDialog *self,
+                        GtkWidget       *widget)
+{
+  CcWwanApnRow *row;
+
+  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+    return;
+
+  widget = gtk_widget_get_ancestor (widget, CC_TYPE_WWAN_APN_ROW);
+  row = CC_WWAN_APN_ROW (widget);
+
+  if (cc_wwan_data_set_default_apn (self->wwan_data, row->apn))
+    cc_wwan_data_save_settings (self->wwan_data, NULL, NULL, NULL);
+}
+
+static void
+cc_wwan_apn_edit_clicked_cb (CcWwanApnDialog *self,
+                             GtkButton       *button)
+{
+  CcWwanDataApn *apn;
+  CcWwanApnRow *row;
+  GtkWidget *widget;
+
+  widget = gtk_widget_get_ancestor (GTK_WIDGET (button), CC_TYPE_WWAN_APN_ROW);
+  row = CC_WWAN_APN_ROW (widget);
+  apn = row->apn;
+  self->apn_to_save = apn;
+
+  gtk_widget_show (GTK_WIDGET (self->save_button));
+  gtk_widget_hide (GTK_WIDGET (self->add_button));
+
+  gtk_entry_set_text (self->name_entry, cc_wwan_data_apn_get_name (apn));
+  gtk_entry_set_text (self->apn_entry, cc_wwan_data_apn_get_apn (apn));
+  gtk_entry_set_text (self->username_entry, cc_wwan_data_apn_get_username (apn));
+  gtk_entry_set_text (self->password_entry, cc_wwan_data_apn_get_password (apn));
+
+  gtk_stack_set_visible_child (self->apn_settings_stack,
+                               GTK_WIDGET (self->apn_edit_view));
+}
+
+static GtkWidget *
+cc_wwan_apn_dialog_row_new (CcWwanDataApn   *apn,
+                            CcWwanApnDialog *self)
+{
+  CcWwanApnRow *row;
+  GtkWidget *grid, *name_label, *apn_label, *radio, *edit_button;
+  GtkStyleContext *context;
+
+  row = g_object_new (CC_TYPE_WWAN_APN_ROW, NULL);
+
+  grid = g_object_new (GTK_TYPE_GRID,
+                       "margin-top", 6,
+                       "margin-bottom", 6,
+                       "margin-start", 6,
+                       "margin-end", 6,
+                       NULL);
+
+  radio = gtk_radio_button_new_from_widget (self->apn_radio_button);
+  row->radio_button = GTK_RADIO_BUTTON (radio);
+  gtk_widget_set_margin_end (radio, 12);
+  gtk_grid_attach (GTK_GRID (grid), radio, 0, 0, 1, 2);
+  row->apn = g_object_ref (apn);
+
+  if (cc_wwan_data_get_default_apn (self->wwan_data) == apn)
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE);
+  g_signal_connect_object (radio, "toggled",
+                           G_CALLBACK (cc_wwan_apn_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  name_label = gtk_label_new (cc_wwan_data_apn_get_name (apn));
+  gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+  gtk_widget_set_hexpand (name_label, TRUE);
+  gtk_grid_attach (GTK_GRID (grid), name_label, 1, 0, 1, 1);
+
+  apn_label = gtk_label_new (cc_wwan_data_apn_get_apn (apn));
+  gtk_widget_set_halign (apn_label, GTK_ALIGN_START);
+  context = gtk_widget_get_style_context (apn_label);
+  gtk_style_context_add_class (context, "dim-label");
+  gtk_grid_attach (GTK_GRID (grid), apn_label, 1, 1, 1, 1);
+
+  edit_button = gtk_button_new_from_icon_name ("emblem-system-symbolic",
+                                               GTK_ICON_SIZE_BUTTON);
+  g_signal_connect_object (edit_button, "clicked",
+                           G_CALLBACK (cc_wwan_apn_edit_clicked_cb),
+                           self, G_CONNECT_SWAPPED);
+  gtk_grid_attach (GTK_GRID (grid), edit_button, 2, 0, 1, 2);
+
+  gtk_container_add (GTK_CONTAINER (row), grid);
+  gtk_widget_show_all (GTK_WIDGET (row));
+
+  return GTK_WIDGET (row);
+}
+
+static void
+cc_wwan_apn_dialog_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  CcWwanApnDialog *self = (CcWwanApnDialog *)object;
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      self->device = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_apn_dialog_constructed (GObject *object)
+{
+  CcWwanApnDialog *self = (CcWwanApnDialog *)object;
+
+  G_OBJECT_CLASS (cc_wwan_apn_dialog_parent_class)->constructed (object);
+
+  self->wwan_data = cc_wwan_device_get_data (self->device);
+
+  gtk_list_box_bind_model (self->apn_list,
+                           cc_wwan_data_get_apn_list (self->wwan_data),
+                           (GtkListBoxCreateWidgetFunc)cc_wwan_apn_dialog_row_new,
+                           self, NULL);
+}
+
+static void
+cc_wwan_apn_dialog_dispose (GObject *object)
+{
+  CcWwanApnDialog *self = (CcWwanApnDialog *)object;
+
+  g_clear_object (&self->device);
+
+  G_OBJECT_CLASS (cc_wwan_apn_dialog_parent_class)->dispose (object);
+}
+
+
+static void
+cc_wwan_apn_dialog_show (GtkWidget *widget)
+{
+  CcWwanApnDialog *self = (CcWwanApnDialog *)widget;
+
+  gtk_widget_show (GTK_WIDGET (self->add_button));
+  gtk_widget_hide (GTK_WIDGET (self->save_button));
+  gtk_stack_set_visible_child (self->apn_settings_stack,
+                               GTK_WIDGET (self->apn_list_view));
+
+  GTK_WIDGET_CLASS (cc_wwan_apn_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wwan_apn_dialog_class_init (CcWwanApnDialogClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = cc_wwan_apn_dialog_set_property;
+  object_class->constructed  = cc_wwan_apn_dialog_constructed;
+  object_class->dispose = cc_wwan_apn_dialog_dispose;
+
+  widget_class->show = cc_wwan_apn_dialog_show;
+
+  properties[PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "The WWAN Device",
+                         CC_TYPE_WWAN_DEVICE,
+                         G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/control-center/wwan/cc-wwan-apn-dialog.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, add_button);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_edit_view);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_entry);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_list);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_list_view);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_radio_button);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, apn_settings_stack);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, back_button);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, name_entry);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, password_entry);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, save_button);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanApnDialog, username_entry);
+
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_back_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_add_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_save_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_entry_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_apn_activated_cb);
+}
+
+static void
+cc_wwan_apn_dialog_init (CcWwanApnDialog *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcWwanApnDialog *
+cc_wwan_apn_dialog_new (GtkWindow    *parent_window,
+                        CcWwanDevice *device)
+{
+  g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+  return g_object_new (CC_TYPE_WWAN_APN_DIALOG,
+                       "transient-for", parent_window,
+                       "use-header-bar", 1,
+                       "device", device,
+                       NULL);
+}
diff --git a/panels/wwan/cc-wwan-apn-dialog.h b/panels/wwan/cc-wwan-apn-dialog.h
new file mode 100644
index 000000000..0e9885836
--- /dev/null
+++ b/panels/wwan/cc-wwan-apn-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-apn-dialog.h
+ *
+ * Copyright 2019 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 <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_APN_DIALOG (cc_wwan_apn_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanApnDialog, cc_wwan_apn_dialog, CC, WWAN_APN_DIALOG, GtkDialog)
+
+CcWwanApnDialog *cc_wwan_apn_dialog_new   (GtkWindow       *parent_window,
+                                           CcWwanDevice    *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-apn-dialog.ui b/panels/wwan/cc-wwan-apn-dialog.ui
new file mode 100644
index 000000000..fb8432bc6
--- /dev/null
+++ b/panels/wwan/cc-wwan-apn-dialog.ui
@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcWwanApnDialog" parent="GtkDialog">
+    <property name="default-height">480</property>
+    <property name="default-width">360</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="visible">1</property>
+        <property name="title" translatable="yes">Access Points</property>
+
+        <!-- Back button -->
+        <child>
+          <object class="GtkButton" id="back_button">
+            <property name="visible">1</property>
+            <property name="use-underline">1</property>
+            <signal name="clicked" handler="cc_wwan_apn_back_clicked_cb" swapped="yes" />
+            <child internal-child="accessible">
+              <object class="AtkObject">
+                <property name="accessible-name" translatable="yes">Back</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">1</property>
+                <property name="icon-name">go-previous-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">start</property>
+          </packing>
+        </child>
+
+        <!-- Add button -->
+        <child>
+          <object class="GtkButton" id="add_button">
+            <property name="visible">1</property>
+            <property name="use-underline">1</property>
+            <signal name="clicked" handler="cc_wwan_apn_add_clicked_cb" swapped="yes" />
+            <child internal-child="accessible">
+              <object class="AtkObject">
+                <property name="accessible-name" translatable="yes">Add</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">1</property>
+                <property name="icon-name">list-add-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+
+        <!-- Save button -->
+        <child>
+          <object class="GtkButton" id="save_button">
+            <property name="visible">0</property>
+            <property name="use-underline">1</property>
+            <property name="label" translatable="yes">_Save</property>
+            <signal name="clicked" handler="cc_wwan_apn_save_clicked_cb" swapped="yes" />
+            <style>
+              <class name="default" />
+            </style>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+
+      </object>
+    </child>
+
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">1</property>
+        <property name="border-width">0</property>
+        <property name="width-request">340</property>
+        <property name="height-request">360</property>
+
+        <child>
+          <object class="HdyClamp">
+            <property name="visible">1</property>
+            <property name="margin-top">32</property>
+            <property name="margin-bottom">32</property>
+
+            <child>
+              <object class="GtkStack" id="apn_settings_stack">
+                <property name="visible">1</property>
+                <property name="transition-type">slide-left-right</property>
+
+                <!-- Access Point List -->
+                <child>
+                  <object class="GtkScrolledWindow" id="apn_list_view">
+                    <property name="visible">1</property>
+                    <property name="margin-start">18</property>
+                    <property name="margin-end">18</property>
+                    <child>
+                      <object class="GtkListBox" id="apn_list">
+                        <property name="visible">1</property>
+                        <property name="valign">start</property>
+                        <property name="selection-mode">none</property>
+                        <signal name="row-activated" handler="cc_wwan_apn_activated_cb" swapped="yes" />
+                        <style>
+                          <class name="content" />
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+
+                <child>
+                  <object class="GtkGrid" id="apn_edit_view">
+                    <property name="visible">1</property>
+                    <property name="orientation">vertical</property>
+                    <property name="expand">1</property>
+                    <property name="margin-start">18</property>
+                    <property name="margin-end">18</property>
+                    <property name="row-spacing">6</property>
+                    <property name="column-spacing">12</property>
+
+                    <!-- Name -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="halign">end</property>
+                        <property name="valign">center</property>
+                        <property name="label" translatable="yes">Name</property>
+                        <style>
+                           <class name="dim-label" />
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="name_entry">
+                        <property name="visible">1</property>
+                        <property name="hexpand">1</property>
+                        <signal name="changed" handler="cc_wwan_apn_entry_changed_cb" swapped="yes" />
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+
+                    <!-- APN -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="halign">end</property>
+                        <property name="valign">center</property>
+                        <property name="label" translatable="yes">APN</property>
+                        <style>
+                          <class name="dim-label" />
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="apn_entry">
+                        <property name="visible">1</property>
+                        <property name="margin-bottom">12</property>
+                        <signal name="changed" handler="cc_wwan_apn_entry_changed_cb" swapped="yes" />
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+
+                    <!-- Username -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="halign">end</property>
+                        <property name="valign">center</property>
+                        <property name="label" translatable="yes">Username</property>
+                        <style>
+                          <class name="dim-label" />
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="username_entry">
+                        <property name="visible">1</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">2</property>
+                      </packing>
+                    </child>
+
+                    <!-- Password -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="halign">end</property>
+                        <property name="valign">center</property>
+                        <property name="label" translatable="yes">Passsword</property>
+                        <style>
+                          <class name="dim-label" />
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="password_entry">
+                        <property name="visible">1</property>
+                        <property name="margin-bottom">12</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">3</property>
+                      </packing>
+                    </child>
+
+                  </object>
+                </child>
+
+              </object> <!-- ./GtkStack apn_settings_stack -->
+            </child>
+          </object>
+        </child>
+      </object>
+    </child> <!-- ./internal-child -->
+
+  </template>
+
+  <!-- A simple hack to create a radio button group -->
+  <object class="GtkRadioButton" id="apn_radio_button" />
+</interface>
diff --git a/panels/wwan/cc-wwan-data.c b/panels/wwan/cc-wwan-data.c
new file mode 100644
index 000000000..0be8f3403
--- /dev/null
+++ b/panels/wwan/cc-wwan-data.c
@@ -0,0 +1,1446 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-data.c
+ *
+ * Copyright 2019 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-data"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <glib/gi18n.h>
+#include <nma-mobile-providers.h>
+
+#include "cc-wwan-data.h"
+
+/**
+ * @short_description: Device Internet Data Object
+ * @include: "cc-wwan-device-data.h"
+ *
+ * #CcWwanData represents the data object of the given
+ * #CcWwanDevice.  Please note that while #CcWWanDevice
+ * is bound to the hardware device, #CcWwanData may also
+ * depend on the inserted SIM (if supported). So the state
+ * of #CcWwanData changes when SIM is changed.
+ */
+
+/* Priority for connections. larger the number, lower the priority */
+#define CC_WWAN_DNS_PRIORITY_LOW  (20)
+#define CC_WWAN_DNS_PRIORITY_HIGH (15)
+
+/* These are to be set as route metric */
+#define CC_WWAN_ROUTE_PRIORITY_LOW  (1050)
+#define CC_WWAN_ROUTE_PRIORITY_HIGH (1040)
+
+struct _CcWwanData
+{
+  GObject      parent_instance;
+
+  MMObject    *mm_object;
+  MMModem     *modem;
+  MMSim       *sim;
+  gchar       *sim_id;
+
+  gchar       *operator_code; /* MCCMNC */
+  GError      *error;
+
+  NMClient           *nm_client;
+  NMDevice           *nm_device;
+  NMAMobileProvidersDatabase *apn_db;
+  NMAMobileProvider  *apn_provider;
+  CcWwanDataApn      *default_apn;
+  CcWwanDataApn      *old_default_apn;
+  GListStore         *apn_list;
+  NMActiveConnection *active_connection;
+
+  gint     priority;
+  gboolean data_enabled; /* autoconnect enabled */
+  gboolean home_only;    /* Data roaming */
+};
+
+G_DEFINE_TYPE (CcWwanData, cc_wwan_data, G_TYPE_OBJECT)
+
+/*
+ * Default Access Point Settings Logic:
+ * For a provided SIM, all the APNs available from NetworkManager
+ * that matches the given SIM identifier (ICCID, available via
+ * mm_sim_get_identifier() or similar gdbus API) is loaded for
+ * the Device (In NetworkManager, it is saved as ‘sim-id’, if
+ * present).  At a time, only one connection will be bound to
+ * a device.  If there are more than one match, the item with
+ * the highest ‘route-metric’ is taken.  If more matches are
+ * still available, the first item is chosen.
+ *
+ * Populating All available APNs:
+ * All Possible APNs for the given sim are populated the following
+ * way (A list of all the following avoiding duplicates)
+ * 1. The above mentioned “Default Access Point Settings Logic”
+ * 2. Get All saved Network Manager connections with the
+ *    provided MCCMNC of the given SIM
+ * 3. Get All possible APNs for the MCCMNC from mobile-provider-info
+ *
+ * Testing if data is enabled:
+ * Check if any of the items from step 1 have ‘autoconnect’ set
+ *
+ * Checking/Setting current SIM for data (in case of multiple SIM):
+ * Since other networks (like wifi, ethernet) should have higher
+ * priorities we use a negative number for priority.
+ * 1. All APNs by default have priority CC_WWAN_APN_PRIORITY_LOW
+ * 2. APN of selected SIM for active data have priority of
+ *    CC_WWAN_APN_PRIORITY_HIGH
+ *
+ * XXX: Since users may create custom APNs via nmtui or like tools
+ * we may have to check if there are some inconsistencies with APNs
+ * available in NetworkManager, and ask user if they have to reset
+ * the APNs that have invalid settings (basically, we care only APNs
+ * that are set to have ‘autoconnect’ enabled, and all we need is to
+ * disable autoconnect). We won’t interfere CDMA/EVDO networks.
+ */
+struct _CcWwanDataApn {
+  GObject parent_instance;
+
+  /* Set if the APN is from the mobile-provider-info database */
+  NMAMobileAccessMethod *access_method;
+
+  /* Set if the APN is saved in NetworkManager */
+  NMConnection *nm_connection;
+  NMRemoteConnection *remote_connection;
+
+  gboolean modified;
+};
+
+G_DEFINE_TYPE (CcWwanDataApn, cc_wwan_data_apn, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_ERROR,
+  PROP_ENABLED,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+wwan_data_apn_reset (CcWwanDataApn *apn)
+{
+  if (!apn)
+    return;
+
+  g_clear_object (&apn->nm_connection);
+  g_clear_object (&apn->remote_connection);
+}
+
+static NMConnection *
+wwan_data_get_nm_connection (CcWwanDataApn *apn)
+{
+  NMConnection *connection;
+  NMSetting *setting;
+  g_autofree gchar *uuid = NULL;
+
+  if (apn->nm_connection)
+    return apn->nm_connection;
+
+  if (apn->remote_connection)
+    return NM_CONNECTION (apn->remote_connection);
+
+  connection = nm_simple_connection_new ();
+  apn->nm_connection = connection;
+
+  setting = nm_setting_connection_new ();
+  uuid = nm_utils_uuid_generate ();
+  g_object_set (setting,
+                NM_SETTING_CONNECTION_UUID, uuid,
+                NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME,
+                NULL);
+  nm_connection_add_setting (connection, setting);
+
+  setting = nm_setting_serial_new ();
+  nm_connection_add_setting (connection, setting);
+
+  setting = nm_setting_ip4_config_new ();
+  g_object_set (setting, NM_SETTING_IP_CONFIG_METHOD, "auto", NULL);
+  nm_connection_add_setting (connection, setting);
+
+  nm_connection_add_setting (connection, nm_setting_gsm_new ());
+  nm_connection_add_setting (connection, nm_setting_ppp_new ());
+
+  return apn->nm_connection;
+}
+
+static gboolean
+wwan_data_apn_are_same (NMRemoteConnection    *remote_connection,
+                        NMAMobileAccessMethod *access_method)
+{
+  NMConnection *connection;
+  NMSetting *setting;
+
+  if (!remote_connection)
+    return FALSE;
+
+  connection = NM_CONNECTION (remote_connection);
+  setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+
+  if (g_strcmp0 (nma_mobile_access_method_get_3gpp_apn (access_method),
+                 nm_setting_gsm_get_apn (NM_SETTING_GSM (setting))) != 0)
+    return FALSE;
+
+  if (g_strcmp0 (nma_mobile_access_method_get_username (access_method),
+                 nm_setting_gsm_get_username (NM_SETTING_GSM (setting))) != 0)
+    return FALSE;
+
+  if (g_strcmp0 (nma_mobile_access_method_get_password (access_method),
+                 nm_setting_gsm_get_password (NM_SETTING_GSM (setting))) != 0)
+    return FALSE;
+
+  return TRUE;
+}
+
+static CcWwanDataApn *
+wwan_data_find_matching_apn (CcWwanData            *self,
+                             NMAMobileAccessMethod *access_method)
+{
+  CcWwanDataApn *apn;
+  guint i, n_items;
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->apn_list));
+
+  for (i = 0; i < n_items; i++)
+    {
+      apn = g_list_model_get_item (G_LIST_MODEL (self->apn_list), i);
+
+      if (apn->access_method == access_method)
+        return apn;
+
+      if (wwan_data_apn_are_same (apn->remote_connection,
+                                  access_method))
+        return apn;
+
+      g_object_unref (apn);
+    }
+
+  return NULL;
+}
+
+static gboolean
+wwan_data_nma_method_is_mms (NMAMobileAccessMethod *method)
+{
+  const char *str;
+
+  str = nma_mobile_access_method_get_3gpp_apn (method);
+  if (str && strcasestr (str, "mms"))
+    return TRUE;
+
+  str = nma_mobile_access_method_get_name (method);
+  if (str && strcasestr (str, "mms"))
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+wwan_data_update_apn_list_db (CcWwanData *self)
+{
+  GSList *apn_methods = NULL, *l;
+  g_autoptr(GError) error = NULL;
+  guint i = 0;
+
+  if (!self->sim || !self->operator_code)
+    return;
+
+  if (!self->apn_list)
+    self->apn_list = g_list_store_new (CC_TYPE_WWAN_DATA_APN);
+
+  if (!self->apn_db)
+    self->apn_db = nma_mobile_providers_database_new_sync (NULL, NULL, NULL, &error);
+
+  if (error)
+    {
+      g_warning ("%s", error->message);
+      return;
+    }
+
+  if (!self->apn_provider)
+    self->apn_provider = nma_mobile_providers_database_lookup_3gpp_mcc_mnc (self->apn_db,
+                                                                            self->operator_code);
+
+  if (self->apn_provider)
+    apn_methods = nma_mobile_provider_get_methods (self->apn_provider);
+
+  for (l = apn_methods; l; l = l->next, i++)
+    {
+      g_autoptr(CcWwanDataApn) apn = NULL;
+
+      /* We don’t list MMS APNs */
+      if (wwan_data_nma_method_is_mms (l->data))
+        continue;
+
+      apn = wwan_data_find_matching_apn (self, l->data);
+
+      /* Prepend the item in order */
+      if (!apn)
+        {
+          apn = cc_wwan_data_apn_new ();
+          g_list_store_insert (self->apn_list, i, apn);
+        }
+
+      apn->access_method = l->data;
+    }
+}
+
+static void
+wwan_data_update_apn_list (CcWwanData *self)
+{
+  const GPtrArray *nm_connections;
+  guint i;
+
+  if (self->apn_list || !self->sim)
+    return;
+
+  if (!self->apn_list)
+    self->apn_list = g_list_store_new (CC_TYPE_WWAN_DATA_APN);
+
+  if (self->nm_device)
+    {
+      nm_connections = nm_device_get_available_connections (self->nm_device);
+
+      for (i = 0; i < nm_connections->len; i++)
+        {
+          g_autoptr(CcWwanDataApn) apn = NULL;
+
+          apn = cc_wwan_data_apn_new ();
+          apn->remote_connection = g_object_ref (nm_connections->pdata[i]);
+          g_list_store_append (self->apn_list, apn);
+
+          /* Load the default APN */
+          if (!self->default_apn && self->sim_id)
+            {
+              NMSettingConnection *connection_setting;
+              NMSettingIPConfig *ip_setting;
+              NMSettingGsm *setting;
+              NMConnection *connection;
+              const gchar *sim_id;
+
+              connection = NM_CONNECTION (apn->remote_connection);
+              setting = nm_connection_get_setting_gsm (connection);
+              connection_setting = nm_connection_get_setting_connection (connection);
+              sim_id = nm_setting_gsm_get_sim_id (setting);
+
+              if (sim_id && *sim_id && g_str_equal (sim_id, self->sim_id))
+                {
+                  self->default_apn = apn;
+                  self->home_only = nm_setting_gsm_get_home_only (setting);
+                  self->data_enabled = nm_setting_connection_get_autoconnect (connection_setting);
+
+                  /* If any of the APN has a high priority, the device have high priority */
+                  ip_setting = nm_connection_get_setting_ip4_config (connection);
+                  if (nm_setting_ip_config_get_route_metric (ip_setting) == CC_WWAN_ROUTE_PRIORITY_HIGH)
+                    self->priority = CC_WWAN_APN_PRIORITY_HIGH;
+                }
+            }
+        }
+    }
+}
+
+static void
+wwan_device_state_changed_cb (CcWwanData *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED]);
+}
+
+static void
+cc_wwan_data_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  CcWwanData *self = (CcWwanData *)object;
+
+  switch (prop_id)
+    {
+    case PROP_ERROR:
+      g_value_set_boolean (value, self->error != NULL);
+      break;
+
+    case PROP_ENABLED:
+      g_value_set_boolean (value, cc_wwan_data_get_enabled (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_data_dispose (GObject *object)
+{
+  CcWwanData *self = (CcWwanData *)object;
+
+  g_clear_pointer (&self->sim_id, g_free);
+  g_clear_pointer (&self->operator_code, g_free);
+  g_clear_error (&self->error);
+  g_clear_object (&self->apn_list);
+  g_clear_object (&self->modem);
+  g_clear_object (&self->mm_object);
+  g_clear_object (&self->nm_client);
+  g_clear_object (&self->active_connection);
+  g_clear_object (&self->apn_db);
+
+  G_OBJECT_CLASS (cc_wwan_data_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_data_class_init (CcWwanDataClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = cc_wwan_data_get_property;
+  object_class->dispose = cc_wwan_data_dispose;
+
+  properties[PROP_ERROR] =
+    g_param_spec_boolean ("error",
+                          "Error",
+                          "Set if some Error occurs",
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_ENABLED] =
+    g_param_spec_boolean ("enabled",
+                          "Enabled",
+                          "Get if the data is enabled",
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+cc_wwan_data_init (CcWwanData *self)
+{
+  self->home_only = TRUE;
+  self->priority = CC_WWAN_APN_PRIORITY_LOW;
+}
+
+/**
+ * cc_wwan_data_new:
+ * @mm_object: An #MMObject
+ * @nm_client: An #NMClient
+ *
+ * Create a new device data representing the given
+ * @mm_object. If @mm_object isn’t a 3G/CDMA/LTE
+ * modem, %NULL will be returned
+ *
+ * Returns: A #CcWwanData or %NULL.
+ */
+CcWwanData *
+cc_wwan_data_new (MMObject *mm_object,
+                  NMClient *nm_client)
+{
+  CcWwanData *self;
+  NMDevice *nm_device = NULL;
+  g_autoptr(MMModem) modem = NULL;
+  NMDeviceModemCapabilities capabilities = 0;
+
+  g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL);
+  g_return_val_if_fail (NM_CLIENT (nm_client), NULL);
+
+  modem = mm_object_get_modem (mm_object);
+
+  if (modem)
+    nm_device = nm_client_get_device_by_iface (nm_client,
+                                               mm_modem_get_primary_port (modem));
+
+  if (NM_IS_DEVICE_MODEM (nm_device))
+    capabilities = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (nm_device));
+
+  if (!(capabilities & (NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS
+                        | NM_DEVICE_MODEM_CAPABILITY_LTE)))
+    return NULL;
+
+  self = g_object_new (CC_TYPE_WWAN_DATA, NULL);
+
+  self->nm_client = g_object_ref (nm_client);
+  self->mm_object = g_object_ref (mm_object);
+  self->modem = g_steal_pointer (&modem);
+  self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL);
+  self->sim_id = mm_sim_dup_identifier (self->sim);
+  self->operator_code = mm_sim_dup_operator_identifier (self->sim);
+  self->nm_device = g_object_ref (nm_device);
+  self->active_connection = nm_device_get_active_connection (nm_device);
+
+  if (!self->operator_code)
+    {
+      MMModem3gpp *modem_3gpp;
+
+      modem_3gpp = mm_object_peek_modem_3gpp (mm_object);
+      if (modem_3gpp)
+        self->operator_code = mm_modem_3gpp_dup_operator_code (modem_3gpp);
+    }
+
+  if (self->active_connection)
+    g_object_ref (self->active_connection);
+
+  g_signal_connect_object (self->nm_device, "notify::state",
+                           G_CALLBACK (wwan_device_state_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  wwan_data_update_apn_list (self);
+  wwan_data_update_apn_list_db (self);
+
+  return self;
+}
+
+GError *
+cc_wwan_data_get_error (CcWwanData *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+
+  return self->error;
+}
+
+const gchar *
+cc_wwan_data_get_simple_html_error (CcWwanData *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+
+  if (!self->error)
+    return NULL;
+
+  if (g_error_matches (self->error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return _("Operation Cancelled");
+
+  if (g_error_matches (self->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED))
+    return _("<b>Error:</b> Access denied changing settings");
+
+  if (self->error->domain == MM_MOBILE_EQUIPMENT_ERROR)
+    return _("<b>Error:</b> Mobile Equipment Error");
+
+  return NULL;
+}
+
+GListModel *
+cc_wwan_data_get_apn_list (CcWwanData *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+
+  if (!self->apn_list)
+    wwan_data_update_apn_list (self);
+
+  return G_LIST_MODEL (self->apn_list);
+}
+
+static gboolean
+wwan_data_apn_is_new (CcWwanDataApn *apn)
+{
+  return apn->remote_connection == NULL;
+}
+
+static void
+wwan_data_update_apn (CcWwanData    *self,
+                      CcWwanDataApn *apn,
+                      NMConnection  *connection)
+{
+  NMSetting *setting;
+  const gchar *name, *username, *password, *apn_name;
+  gint dns_priority, route_metric;
+
+  setting = NM_SETTING (nm_connection_get_setting_connection (connection));
+
+  g_object_set (setting,
+                NM_SETTING_CONNECTION_AUTOCONNECT, self->data_enabled,
+                NULL);
+
+  setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+
+  g_object_set (setting,
+                NM_SETTING_GSM_HOME_ONLY, self->home_only,
+                NULL);
+
+  setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection));
+  if (self->priority == CC_WWAN_APN_PRIORITY_HIGH &&
+      self->default_apn == apn)
+    {
+      dns_priority = CC_WWAN_DNS_PRIORITY_HIGH;
+      route_metric = CC_WWAN_ROUTE_PRIORITY_HIGH;
+    }
+  else
+    {
+      dns_priority = CC_WWAN_DNS_PRIORITY_LOW;
+      route_metric = CC_WWAN_ROUTE_PRIORITY_LOW;
+    }
+
+  g_object_set (setting,
+                NM_SETTING_IP_CONFIG_DNS_PRIORITY, dns_priority,
+                NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)route_metric,
+                NULL);
+
+  if (apn->access_method && !apn->remote_connection)
+    {
+      name = nma_mobile_access_method_get_name (apn->access_method);
+      username = nma_mobile_access_method_get_username (apn->access_method);
+      password = nma_mobile_access_method_get_password (apn->access_method);
+      apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method);
+    }
+  else
+    {
+      return;
+    }
+
+  setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+  g_object_set (setting,
+                NM_SETTING_GSM_USERNAME, username,
+                NM_SETTING_GSM_PASSWORD, password,
+                NM_SETTING_GSM_APN, apn_name,
+                NULL);
+
+  setting = NM_SETTING (nm_connection_get_setting_connection (connection));
+
+  g_object_set (setting,
+                NM_SETTING_CONNECTION_ID, name,
+                NULL);
+}
+
+static gint
+wwan_data_get_apn_index (CcWwanData    *self,
+                         CcWwanDataApn *apn)
+{
+  GListModel *model;
+  guint i, n_items;
+
+  model = G_LIST_MODEL (self->apn_list);
+  n_items = g_list_model_get_n_items (model);
+
+  for (i = 0; i < n_items; i++)
+    {
+      g_autoptr(CcWwanDataApn) cached_apn = NULL;
+
+      cached_apn = g_list_model_get_item (model, i);
+
+      if (apn == cached_apn)
+        return i;
+    }
+
+  return -1;
+}
+
+static void
+cc_wwan_data_connection_updated_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  CcWwanData *self;
+  CcWwanDataApn *apn;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  self = g_task_get_source_object (G_TASK (task));
+  apn = g_task_get_task_data (G_TASK (task));
+
+  nm_remote_connection_commit_changes_finish (apn->remote_connection,
+                                              result, &error);
+  if (!error)
+    {
+      guint apn_index;
+      apn_index = wwan_data_get_apn_index (self, apn);
+
+      if (apn_index >= 0)
+        g_list_model_items_changed (G_LIST_MODEL (self->apn_list),
+                                    apn_index, 1, 1);
+      else
+        g_warning ("APN ‘%s’ not in APN list",
+                   cc_wwan_data_apn_get_name (apn));
+
+      apn->modified = FALSE;
+      g_task_return_boolean (task, TRUE);
+    }
+  else
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+}
+
+static void
+cc_wwan_data_new_connection_added_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  CcWwanData *self;
+  CcWwanDataApn *apn;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  self = g_task_get_source_object (G_TASK (task));
+  apn = g_task_get_task_data (G_TASK (task));
+  apn->remote_connection = nm_client_add_connection_finish (self->nm_client,
+                                                            result, &error);
+  if (!error)
+    {
+      apn->modified = FALSE;
+
+      /* If APN has access method, it’s already on the list */
+      if (!apn->access_method)
+        {
+          g_list_store_append (self->apn_list, apn);
+          g_object_unref (apn);
+        }
+
+      g_task_return_pointer (task, apn, NULL);
+    }
+  else
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+}
+
+void
+cc_wwan_data_save_apn (CcWwanData          *self,
+                       CcWwanDataApn       *apn,
+                       GCancellable        *cancellable,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data)
+{
+  NMConnection *connection = NULL;
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DATA (self));
+  g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_task_data (task, apn, NULL);
+
+  connection = wwan_data_get_nm_connection (apn);
+
+  /* If the item has a remote connection, it should already be saved.
+   * We should save it again only if it got modified */
+  if (apn->remote_connection && !apn->modified)
+    {
+      g_task_return_pointer (task, apn, NULL);
+      return;
+    }
+
+  wwan_data_update_apn (self, apn, connection);
+  if (wwan_data_apn_is_new (apn))
+    {
+      nm_client_add_connection_async (self->nm_client, apn->nm_connection,
+                                      TRUE, cancellable,
+                                      cc_wwan_data_new_connection_added_cb,
+                                      g_steal_pointer (&task));
+    }
+  else
+    {
+      nm_remote_connection_commit_changes_async (apn->remote_connection, TRUE,
+                                                 cancellable,
+                                                 cc_wwan_data_connection_updated_cb,
+                                                 g_steal_pointer (&task));
+    }
+}
+
+CcWwanDataApn *
+cc_wwan_data_save_apn_finish (CcWwanData    *self,
+                              GAsyncResult  *result,
+                              GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+cc_wwan_data_activated_cb (GObject      *object,
+                           GAsyncResult *result,
+                           gpointer      user_data)
+{
+  CcWwanData *self;
+  NMActiveConnection *connection;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  self = g_task_get_source_object (G_TASK (task));
+  connection = nm_client_activate_connection_finish (self->nm_client,
+                                                     result, &error);
+  if (connection)
+    {
+      g_set_object (&self->active_connection, connection);
+      g_task_return_boolean (task, TRUE);
+    }
+  else
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+
+  if (error)
+    g_warning ("Error: %s", error->message);
+}
+
+static void
+cc_wwan_data_settings_saved_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+  CcWwanData *self;
+  GCancellable *cancellable;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  self = g_task_get_source_object (G_TASK (task));
+  cancellable = g_task_get_cancellable (G_TASK (task));
+
+  if (!cc_wwan_data_save_apn_finish (self, result, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  self->default_apn->modified = FALSE;
+
+  if (self->data_enabled)
+    {
+      nm_client_activate_connection_async (self->nm_client,
+                                           NM_CONNECTION (self->default_apn->remote_connection),
+                                           self->nm_device,
+                                           NULL, cancellable,
+                                           cc_wwan_data_activated_cb,
+                                           g_steal_pointer (&task));
+    }
+  else
+    {
+      if (nm_device_disconnect (self->nm_device, cancellable, &error))
+        {
+          g_clear_object (&self->active_connection);
+          g_task_return_boolean (task, TRUE);
+        }
+      else
+        {
+          g_task_return_error (task, g_steal_pointer (&error));
+        }
+    }
+}
+
+/**
+ * cc_wwan_data_save_settings:
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: a #GAsyncReadyCallback, or %NULL
+ * @user_data: closure data for @callback
+ *
+ * Save default settings to disk and apply changes.
+ * If the default APN has data enabled, the data is
+ * activated after the settings are saved.
+ *
+ * It’s a programmer error to call this function without
+ * a default APN set.
+ * Finish with cc_wwan_data_save_settings_finish().
+ */
+void
+cc_wwan_data_save_settings (CcWwanData          *self,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  NMConnection *connection;
+  NMSetting *setting;
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DATA (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (self->default_apn != NULL);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  /* Reset old settings to default value */
+  if (self->old_default_apn && self->old_default_apn->remote_connection)
+    {
+      connection = NM_CONNECTION (self->old_default_apn->remote_connection);
+
+      setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+      g_object_set (G_OBJECT (setting),
+                    NM_SETTING_GSM_HOME_ONLY, TRUE,
+                    NM_SETTING_GSM_SIM_ID, NULL,
+                    NULL);
+
+      setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection));
+      g_object_set (setting,
+                    NM_SETTING_IP_CONFIG_DNS_PRIORITY, CC_WWAN_DNS_PRIORITY_LOW,
+                    NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)CC_WWAN_ROUTE_PRIORITY_LOW,
+                    NULL);
+
+      setting = NM_SETTING (nm_connection_get_setting_connection (connection));
+      g_object_set (G_OBJECT (setting),
+                    NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+                    NULL);
+
+      nm_remote_connection_commit_changes (NM_REMOTE_CONNECTION (connection),
+                                           TRUE, cancellable, NULL);
+      self->old_default_apn->modified = FALSE;
+      self->old_default_apn = NULL;
+    }
+
+  self->default_apn->modified = TRUE;
+  connection = wwan_data_get_nm_connection (self->default_apn);
+
+  setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+  g_object_set (G_OBJECT (setting),
+                NM_SETTING_GSM_HOME_ONLY, self->home_only,
+                NM_SETTING_GSM_SIM_ID, self->sim_id,
+                NULL);
+
+  cc_wwan_data_save_apn (self, self->default_apn, cancellable,
+                         cc_wwan_data_settings_saved_cb,
+                         g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_data_save_settings_finish (CcWwanData    *self,
+                                   GAsyncResult  *result,
+                                   GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+cc_wwan_data_delete_apn (CcWwanData     *self,
+                         CcWwanDataApn  *apn,
+                         GCancellable   *cancellable,
+                         GError        **error)
+{
+  NMRemoteConnection *connection = NULL;
+  gboolean ret = FALSE;
+  gint apn_index;
+
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+  g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), FALSE);
+  g_return_val_if_fail (error != NULL, FALSE);
+
+  apn_index = wwan_data_get_apn_index (self, apn);
+  if (apn_index == -1)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                   "APN not found for the connection");
+      return FALSE;
+    }
+
+  connection = g_steal_pointer (&apn->remote_connection);
+  wwan_data_apn_reset (apn);
+
+  if (connection)
+    ret = nm_remote_connection_delete (connection, cancellable, error);
+
+  if (!ret)
+    {
+      apn->remote_connection = connection;
+      *error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                            "Deleting APN from NetworkManager failed");
+      return ret;
+    }
+
+  g_object_unref (connection);
+
+  /* We remove the item only if it's not in the mobile provider database */
+  if (!apn->access_method)
+    {
+      if (self->default_apn == apn)
+        self->default_apn = NULL;
+
+      g_list_store_remove (self->apn_list, apn_index);
+
+      return TRUE;
+    }
+
+  *error = g_error_new (G_IO_ERROR, G_IO_ERROR_READ_ONLY,
+                        "Deleting APN from NetworkManager failed");
+  return FALSE;
+}
+
+CcWwanDataApn *
+cc_wwan_data_get_default_apn (CcWwanData *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL);
+
+  return self->default_apn;
+}
+
+gboolean
+cc_wwan_data_set_default_apn (CcWwanData    *self,
+                              CcWwanDataApn *apn)
+{
+  NMConnection *connection;
+  NMSetting *setting;
+
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+  g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), FALSE);
+  g_return_val_if_fail (self->sim_id != NULL, FALSE);
+
+  if (self->default_apn == apn)
+    return FALSE;
+
+  /*
+   * APNs are bound to the SIM, not the modem device.
+   * This will let the APN work if the same SIM inserted
+   * in a different device, and not enable data if a
+   * different SIM is inserted to the modem.
+   */
+  apn->modified = TRUE;
+  self->old_default_apn = self->default_apn;
+  self->default_apn = apn;
+  connection = wwan_data_get_nm_connection (apn);
+  setting = NM_SETTING (nm_connection_get_setting_gsm (connection));
+  g_object_set (G_OBJECT (setting),
+                NM_SETTING_GSM_SIM_ID, self->sim_id, NULL);
+
+  return TRUE;
+}
+
+gboolean
+cc_wwan_data_get_enabled (CcWwanData *self)
+{
+  NMSettingConnection *setting;
+  NMConnection *connection;
+  NMDeviceState state;
+
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+
+  state = nm_device_get_state (self->nm_device);
+
+  if (state == NM_DEVICE_STATE_DISCONNECTED ||
+      state == NM_DEVICE_STATE_DEACTIVATING)
+    if (nm_device_get_state_reason (self->nm_device) == NM_DEVICE_STATE_REASON_USER_REQUESTED)
+      return FALSE;
+
+  if (nm_device_get_active_connection (self->nm_device) != NULL)
+    return TRUE;
+
+  if (!self->default_apn || !self->default_apn->remote_connection)
+    return FALSE;
+
+  connection = NM_CONNECTION (self->default_apn->remote_connection);
+  setting = nm_connection_get_setting_connection (connection);
+
+  return nm_setting_connection_get_autoconnect (setting);
+}
+
+/**
+ * cc_wwan_data_set_enabled:
+ * @self: A #CcWwanData
+ * @enable_data: whether to enable data
+ *
+ * Enable data for the device.  The settings is
+ * saved to disk only after a default APN is set.
+ *
+ * If the data is enabled, the device will automatically
+ * turn data on everytime the same SIM is available.
+ * The data set is bound to the SIM, not the modem device.
+ *
+ * Use @cc_wwan_data_save_apn() with the default APN
+ * to save the changes and really enable/disable data.
+ */
+void
+cc_wwan_data_set_enabled (CcWwanData *self,
+                          gboolean    enable_data)
+{
+  g_return_if_fail (CC_IS_WWAN_DATA (self));
+
+  self->data_enabled = !!enable_data;
+
+  if (self->default_apn)
+    self->default_apn->modified = TRUE;
+}
+
+gboolean
+cc_wwan_data_get_roaming_enabled (CcWwanData *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE);
+
+  if (!self->default_apn)
+    return FALSE;
+
+  return !self->home_only;
+}
+
+/**
+ * cc_wwan_data_apn_set_roaming_enabled:
+ * @self: A #CcWwanData
+ * @enable_roaming: whether to enable roaming or not
+ *
+ * Enable roaming for the device.  The settings is
+ * saved to disk only after a default APN is set.
+ *
+ * Use @cc_wwan_data_save_apn() with the default APN
+ * to save the changes and really enable/disable data.
+ */
+void
+cc_wwan_data_set_roaming_enabled (CcWwanData *self,
+                                  gboolean    enable_roaming)
+{
+  g_return_if_fail (CC_IS_WWAN_DATA (self));
+
+  self->home_only = !enable_roaming;
+
+  if (self->default_apn)
+    self->default_apn->modified = TRUE;
+}
+
+static void
+cc_wwan_data_apn_finalize (GObject *object)
+{
+  CcWwanDataApn *apn = CC_WWAN_DATA_APN (object);
+
+  wwan_data_apn_reset (apn);
+  g_clear_pointer (&apn->access_method,
+                   nma_mobile_access_method_unref);
+
+  G_OBJECT_CLASS (cc_wwan_data_parent_class)->finalize (object);
+}
+
+static void
+cc_wwan_data_apn_class_init (CcWwanDataApnClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = cc_wwan_data_apn_finalize;
+}
+
+static void
+cc_wwan_data_apn_init (CcWwanDataApn *apn)
+{
+}
+
+CcWwanDataApn *
+cc_wwan_data_apn_new (void)
+{
+  return g_object_new (CC_TYPE_WWAN_DATA_APN, NULL);
+}
+
+/**
+ * cc_wwan_data_apn_get_name:
+ * @apn: A #CcWwanDataApn
+ *
+ * Get the Name of @apn
+ *
+ * Returns: (transfer none): The Name of @apn
+ */
+const gchar *
+cc_wwan_data_apn_get_name (CcWwanDataApn *apn)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), "");
+
+  if (apn->remote_connection)
+    return nm_connection_get_id (NM_CONNECTION (apn->remote_connection));
+
+  if (apn->access_method)
+    return nma_mobile_access_method_get_name (apn->access_method);
+
+  return "";
+}
+
+/**
+ * cc_wwan_data_apn_set_name:
+ * @apn: A #CcWwanDataApn
+ * @name: The name to be given for APN, should not
+ * be empty
+ *
+ * Set the name of @apn to be @name.
+ *
+ * @apn is only modified, use @cc_wwan_data_save_apn()
+ * to save the changes.
+ */
+void
+cc_wwan_data_apn_set_name (CcWwanDataApn *apn,
+                           const gchar   *name)
+{
+  NMConnection *connection;
+  NMSettingConnection *setting;
+
+  g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+  g_return_if_fail (name != NULL);
+  g_return_if_fail (*name != '\0');
+
+  if (g_str_equal (cc_wwan_data_apn_get_name (apn), name))
+    return;
+
+  apn->modified = TRUE;
+  connection = wwan_data_get_nm_connection (apn);
+  setting = nm_connection_get_setting_connection (connection);
+  g_object_set (G_OBJECT (setting),
+                NM_SETTING_CONNECTION_ID, name,
+                NULL);
+}
+
+/**
+ * cc_wwan_data_apn_get_apn:
+ * @apn: A #CcWwanDataApn
+ *
+ * Get the APN of @apn
+ *
+ * Returns: (transfer none): The APN of @apn
+ */
+const gchar *
+cc_wwan_data_apn_get_apn (CcWwanDataApn *apn)
+{
+  const gchar *apn_name = "";
+
+  g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), "");
+
+  if (apn->remote_connection)
+    {
+      NMSettingGsm *setting;
+
+      setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection));
+      apn_name = nm_setting_gsm_get_apn (setting);
+    }
+  else if (apn->access_method)
+    {
+      apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method);
+    }
+
+  return apn_name ? apn_name : "";
+}
+
+/**
+ * cc_wwan_data_apn_set_apn:
+ * @apn: A #CcWwanDataApn
+ * @apn_name: The apn to be used, should not be
+ * empty
+ *
+ * Set the APN of @apn to @apn_name. @apn_name is
+ * usually a URL like “example.com” or a simple string
+ * like “internet”
+ *
+ * @apn is only modified, use @cc_wwan_data_save_apn()
+ * to save the changes.
+ */
+void
+cc_wwan_data_apn_set_apn (CcWwanDataApn *apn,
+                          const gchar   *apn_name)
+{
+  NMConnection *connection;
+  NMSettingGsm *setting;
+
+  g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+  g_return_if_fail (apn_name != NULL);
+  g_return_if_fail (*apn_name != '\0');
+
+  if (g_str_equal (cc_wwan_data_apn_get_apn (apn), apn_name))
+    return;
+
+  apn->modified = TRUE;
+  connection = wwan_data_get_nm_connection (apn);
+  setting = nm_connection_get_setting_gsm (connection);
+  g_object_set (G_OBJECT (setting),
+                NM_SETTING_GSM_APN, apn_name,
+                NULL);
+}
+
+/**
+ * cc_wwan_data_apn_get_username:
+ * @apn: A #CcWwanDataApn
+ *
+ * Get the Username of @apn
+ *
+ * Returns: (transfer none): The Username of @apn
+ */
+const gchar *
+cc_wwan_data_apn_get_username (CcWwanDataApn *apn)
+{
+  const gchar *username = "";
+
+  g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), "");
+
+  if (apn->remote_connection)
+    {
+      NMSettingGsm *setting;
+
+      setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection));
+      username = nm_setting_gsm_get_username (setting);
+    }
+  else if (apn->access_method)
+    {
+      username = nma_mobile_access_method_get_username (apn->access_method);
+    }
+
+  return username ? username : "";
+}
+
+/**
+ * cc_wwan_data_apn_set_username:
+ * @apn: A #CcWwanDataAPN
+ * @username: The username to be used
+ *
+ * Set the Username of @apn to @username.
+ *
+ * @apn is only modified, use @cc_wwan_data_save_apn()
+ * to save the changes.
+ */
+void
+cc_wwan_data_apn_set_username (CcWwanDataApn *apn,
+                               const gchar   *username)
+{
+  NMConnection *connection;
+  NMSettingGsm *setting;
+
+  g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+
+  if (username && !*username)
+    username = NULL;
+
+  if (g_strcmp0 (cc_wwan_data_apn_get_username (apn), username) == 0)
+    return;
+
+  apn->modified = TRUE;
+  connection = wwan_data_get_nm_connection (apn);
+  setting = nm_connection_get_setting_gsm (connection);
+  g_object_set (G_OBJECT (setting),
+                NM_SETTING_GSM_USERNAME, username,
+                NULL);
+}
+
+/**
+ * cc_wwan_data_apn_get_password:
+ * @apn: A #CcWwanDataApn
+ *
+ * Get the Password of @apn
+ *
+ * Returns: (transfer none): The Password of @apn
+ */
+const gchar *
+cc_wwan_data_apn_get_password (CcWwanDataApn *apn)
+{
+  const gchar *password = "";
+
+  g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), "");
+
+  if (NM_IS_REMOTE_CONNECTION (apn->remote_connection))
+    {
+      g_autoptr(GVariant) secrets = NULL;
+      g_autoptr(GError) error = NULL;
+
+      secrets = nm_remote_connection_get_secrets (NM_REMOTE_CONNECTION (apn->remote_connection),
+                                                  "gsm", NULL, &error);
+
+      if (!error)
+        nm_connection_update_secrets (NM_CONNECTION (apn->remote_connection),
+                                      "gsm", secrets, &error);
+
+      if (error)
+        {
+          g_warning ("Error: %s", error->message);
+          return "";
+        }
+    }
+
+  if (apn->remote_connection)
+    {
+      NMSettingGsm *setting;
+
+      setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection));
+      password = nm_setting_gsm_get_password (setting);
+    }
+  else if (apn->access_method)
+    {
+      password = nma_mobile_access_method_get_password (apn->access_method);
+    }
+
+  return password ? password : "";
+
+  if (apn->remote_connection)
+    nm_connection_clear_secrets (NM_CONNECTION (apn->remote_connection));
+}
+
+/**
+ * cc_wwan_data_apn_set_password:
+ * @apn: A #CcWwanDataApn
+ * @password: The password to be used
+ *
+ * Set the Password of @apn to @password.
+ *
+ * @apn is only modified, use @cc_wwan_data_save_apn()
+ * to save the changes.
+ */
+void
+cc_wwan_data_apn_set_password (CcWwanDataApn *apn,
+                               const gchar   *password)
+{
+  NMConnection *connection;
+  NMSettingGsm *setting;
+
+  g_return_if_fail (CC_IS_WWAN_DATA_APN (apn));
+
+  if (password && !*password)
+    password = NULL;
+
+  if (g_strcmp0 (cc_wwan_data_apn_get_password (apn), password) == 0)
+    return;
+
+  apn->modified = TRUE;
+  connection = wwan_data_get_nm_connection (apn);
+  setting = nm_connection_get_setting_gsm (connection);
+  g_object_set (G_OBJECT (setting),
+                NM_SETTING_GSM_PASSWORD, password,
+                NULL);
+}
+
+gint
+cc_wwan_data_get_priority (CcWwanData *self)
+{
+  CcWwanDataApn *apn;
+  NMSettingIPConfig *setting;
+
+  g_return_val_if_fail (CC_IS_WWAN_DATA (self),
+                        CC_WWAN_APN_PRIORITY_LOW);
+
+  apn = self->default_apn;
+
+  if (!apn || !apn->remote_connection)
+    return CC_WWAN_APN_PRIORITY_LOW;
+
+  setting = nm_connection_get_setting_ip4_config (NM_CONNECTION (apn->remote_connection));
+
+  /* Lower the number, higher the priority */
+  if (nm_setting_ip_config_get_route_metric (setting) <= CC_WWAN_ROUTE_PRIORITY_HIGH)
+    return CC_WWAN_APN_PRIORITY_HIGH;
+  else
+    return CC_WWAN_APN_PRIORITY_LOW;
+}
+
+void
+cc_wwan_data_set_priority (CcWwanData *self,
+                           int         priority)
+{
+  g_return_if_fail (CC_IS_WWAN_DATA (self));
+  g_return_if_fail (priority == CC_WWAN_APN_PRIORITY_LOW ||
+                    priority == CC_WWAN_APN_PRIORITY_HIGH);
+
+  if (self->priority == priority)
+    return;
+
+  self->priority = priority;
+
+  if (self->default_apn)
+    self->default_apn->modified = TRUE;
+}
diff --git a/panels/wwan/cc-wwan-data.h b/panels/wwan/cc-wwan-data.h
new file mode 100644
index 000000000..9572b862d
--- /dev/null
+++ b/panels/wwan/cc-wwan-data.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-data.h
+ *
+ * Copyright 2019 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>
+#include <NetworkManager.h>
+
+G_BEGIN_DECLS
+
+#define CC_WWAN_APN_PRIORITY_LOW  (1)
+#define CC_WWAN_APN_PRIORITY_HIGH (2)
+
+#define CC_TYPE_WWAN_DATA_APN (cc_wwan_data_apn_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDataApn, cc_wwan_data_apn, CC, WWAN_DATA_APN, GObject)
+
+#define CC_TYPE_WWAN_DATA (cc_wwan_data_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanData, cc_wwan_data, CC, WWAN_DATA, GObject)
+
+CcWwanData    *cc_wwan_data_new                   (MMObject             *mm_object,
+                                                   NMClient             *nm_client);
+GError        *cc_wwan_data_get_error             (CcWwanData           *self);
+const gchar   *cc_wwan_data_get_simple_html_error (CcWwanData           *self);
+GListModel    *cc_wwan_data_get_apn_list          (CcWwanData           *self);
+void           cc_wwan_data_save_apn              (CcWwanData           *self,
+                                                   CcWwanDataApn        *apn,
+                                                   GCancellable         *cancellable,
+                                                   GAsyncReadyCallback   callback,
+                                                   gpointer              user_data);
+CcWwanDataApn *cc_wwan_data_save_apn_finish       (CcWwanData           *self,
+                                                   GAsyncResult         *result,
+                                                   GError              **error);
+void           cc_wwan_data_save_settings         (CcWwanData          *self,
+                                                   GCancellable        *cancellable,
+                                                   GAsyncReadyCallback  callback,
+                                                   gpointer             user_data);
+gboolean       cc_wwan_data_save_settings_finish  (CcWwanData          *self,
+                                                   GAsyncResult        *result,
+                                                   GError             **error);
+gboolean       cc_wwan_data_delete_apn            (CcWwanData          *self,
+                                                   CcWwanDataApn       *apn,
+                                                   GCancellable        *cancellable,
+                                                   GError             **error);
+gboolean       cc_wwan_data_set_default_apn       (CcWwanData          *self,
+                                                   CcWwanDataApn       *apn);
+CcWwanDataApn *cc_wwan_data_get_default_apn       (CcWwanData          *self);
+gboolean       cc_wwan_data_get_enabled           (CcWwanData          *self);
+void           cc_wwan_data_set_enabled           (CcWwanData          *self,
+                                                   gboolean             enabled);
+gboolean       cc_wwan_data_get_roaming_enabled   (CcWwanData          *self);
+void           cc_wwan_data_set_roaming_enabled   (CcWwanData          *self,
+                                                   gboolean             enable_roaming);
+
+CcWwanDataApn *cc_wwan_data_apn_new          (void);
+const gchar   *cc_wwan_data_apn_get_name     (CcWwanDataApn *apn);
+void           cc_wwan_data_apn_set_name     (CcWwanDataApn *apn,
+                                              const gchar   *name);
+const gchar   *cc_wwan_data_apn_get_apn      (CcWwanDataApn *apn);
+void           cc_wwan_data_apn_set_apn      (CcWwanDataApn *apn,
+                                              const gchar   *apn_name);
+const gchar   *cc_wwan_data_apn_get_username (CcWwanDataApn *apn);
+void           cc_wwan_data_apn_set_username (CcWwanDataApn *apn,
+                                              const gchar   *username);
+const gchar   *cc_wwan_data_apn_get_password (CcWwanDataApn *apn);
+void           cc_wwan_data_apn_set_password (CcWwanDataApn *apn,
+                                              const gchar   *password);
+gint           cc_wwan_data_get_priority     (CcWwanData    *self);
+void           cc_wwan_data_set_priority     (CcWwanData    *self,
+                                              int            priority);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-details-dialog.c b/panels/wwan/cc-wwan-details-dialog.c
new file mode 100644
index 000000000..59e8dc361
--- /dev/null
+++ b/panels/wwan/cc-wwan-details-dialog.c
@@ -0,0 +1,257 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-network-dialog.c
+ *
+ * Copyright 2019 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-details-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "list-box-helper.h"
+#include "cc-wwan-details-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: Dialog to Show Device Details
+ */
+
+struct _CcWwanDetailsDialog
+{
+  GtkDialog     parent_instance;
+
+  GtkLabel     *device_identifier;
+  GtkLabel     *device_model;
+  GtkLabel     *firmware_version;
+  GtkLabel     *identifier_label;
+  GtkLabel     *manufacturer;
+  GtkLabel     *network_status;
+  GtkLabel     *network_type;
+  GtkLabel     *operator_name;
+  GtkLabel     *own_numbers;
+  GtkLabel     *signal_strength;
+
+  CcWwanDevice *device;
+};
+
+G_DEFINE_TYPE (CcWwanDetailsDialog, cc_wwan_details_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+cc_wwan_details_update_network_status (CcWwanDetailsDialog *self)
+{
+  CcWwanState state;
+
+  g_assert (CC_IS_WWAN_DETAILS_DIALOG (self));
+
+  state = cc_wwan_device_get_network_state (self->device);
+
+  switch (state)
+    {
+    case CC_WWAN_REGISTRATION_STATE_IDLE:
+      gtk_label_set_label (self->network_status, _("Not Registered"));
+      break;
+
+    case CC_WWAN_REGISTRATION_STATE_REGISTERED:
+      gtk_label_set_label (self->network_status, _("Registered"));
+      break;
+
+    case CC_WWAN_REGISTRATION_STATE_ROAMING:
+      gtk_label_set_label (self->network_status, _("Roaming"));
+      break;
+
+    case CC_WWAN_REGISTRATION_STATE_SEARCHING:
+      gtk_label_set_label (self->network_status, _("Searching"));
+      break;
+
+    case CC_WWAN_REGISTRATION_STATE_DENIED:
+      gtk_label_set_label (self->network_status, _("Denied"));
+      break;
+
+    default:
+      gtk_label_set_label (self->network_status, _("Unknown"));
+      break;
+    }
+}
+
+static void
+cc_wwan_details_signal_changed_cb (CcWwanDetailsDialog *self)
+{
+  g_autofree gchar *network_type_string = NULL;
+  g_autofree gchar *signal_string = NULL;
+  const gchar *operator_name;
+
+  g_assert (CC_IS_WWAN_DETAILS_DIALOG (self));
+
+  operator_name = cc_wwan_device_get_operator_name (self->device);
+  if (operator_name)
+    gtk_label_set_label (self->operator_name, operator_name);
+
+  network_type_string = cc_wwan_device_dup_network_type_string (self->device);
+  if (network_type_string)
+    gtk_label_set_label (self->network_type, network_type_string);
+
+  signal_string = cc_wwan_device_dup_signal_string (self->device);
+  if (signal_string)
+    gtk_label_set_label (self->signal_strength, signal_string);
+
+  cc_wwan_details_update_network_status (self);
+}
+
+static void
+cc_wwan_details_update_hardware_details (CcWwanDetailsDialog *self)
+{
+  const gchar *str;
+
+  g_assert (CC_IS_WWAN_DETAILS_DIALOG (self));
+
+  str = cc_wwan_device_get_manufacturer (self->device);
+  if (str)
+    gtk_label_set_label (self->manufacturer, str);
+
+  str = cc_wwan_device_get_model (self->device);
+  if (str)
+    gtk_label_set_label (self->device_model, str);
+
+  str = cc_wwan_device_get_firmware_version (self->device);
+  if (str)
+    gtk_label_set_label (self->firmware_version, str);
+
+  str = cc_wwan_device_get_identifier (self->device);
+  if (str)
+    gtk_label_set_label (self->device_identifier, str);
+}
+
+static void
+cc_wwan_details_dialog_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      self->device = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_details_dialog_constructed (GObject *object)
+{
+  CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object);
+  g_autofree char *numbers = NULL;
+
+  G_OBJECT_CLASS (cc_wwan_details_dialog_parent_class)->constructed (object);
+
+  g_signal_connect_object (self->device, "notify::signal",
+                           G_CALLBACK (cc_wwan_details_signal_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  numbers = cc_wwan_device_dup_own_numbers (self->device);
+  gtk_widget_set_visible (GTK_WIDGET (self->own_numbers), !!numbers);
+
+  if (numbers)
+    gtk_label_set_text (self->own_numbers, numbers);
+
+  cc_wwan_details_signal_changed_cb (self);
+  cc_wwan_details_update_hardware_details (self);
+}
+
+static void
+cc_wwan_details_dialog_dispose (GObject *object)
+{
+  CcWwanDetailsDialog *self = CC_WWAN_DETAILS_DIALOG (object);
+
+  g_clear_object (&self->device);
+
+  G_OBJECT_CLASS (cc_wwan_details_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_details_dialog_class_init (CcWwanDetailsDialogClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = cc_wwan_details_dialog_set_property;
+  object_class->constructed  = cc_wwan_details_dialog_constructed;
+  object_class->dispose = cc_wwan_details_dialog_dispose;
+
+  properties[PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "The WWAN Device",
+                         CC_TYPE_WWAN_DEVICE,
+                         G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/control-center/wwan/cc-wwan-details-dialog.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, device_identifier);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, device_model);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, firmware_version);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, identifier_label);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, manufacturer);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, network_status);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, network_type);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, operator_name);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, own_numbers);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDetailsDialog, signal_strength);
+}
+
+static void
+cc_wwan_details_dialog_init (CcWwanDetailsDialog *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcWwanDetailsDialog *
+cc_wwan_details_dialog_new (GtkWindow    *parent_window,
+                            CcWwanDevice *device)
+{
+  g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+  return g_object_new (CC_TYPE_WWAN_DETAILS_DIALOG,
+                       "transient-for", parent_window,
+                       "use-header-bar", 1,
+                       "device", device,
+                       NULL);
+}
diff --git a/panels/wwan/cc-wwan-details-dialog.h b/panels/wwan/cc-wwan-details-dialog.h
new file mode 100644
index 000000000..7e7812cde
--- /dev/null
+++ b/panels/wwan/cc-wwan-details-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-details-dialog.h
+ *
+ * Copyright 2019 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 <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_DETAILS_DIALOG (cc_wwan_details_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDetailsDialog, cc_wwan_details_dialog, CC, WWAN_DETAILS_DIALOG, GtkDialog)
+
+CcWwanDetailsDialog *cc_wwan_details_dialog_new (GtkWindow    *parent_window,
+                                                 CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-details-dialog.ui b/panels/wwan/cc-wwan-details-dialog.ui
new file mode 100644
index 000000000..042d3ee33
--- /dev/null
+++ b/panels/wwan/cc-wwan-details-dialog.ui
@@ -0,0 +1,320 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcWwanDetailsDialog" parent="GtkDialog">
+    <property name="title" translatable="yes">Modem Details</property>
+    <property name="default-height">480</property>
+    <property name="default-width">360</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">1</property>
+        <property name="border-width">0</property>
+        <property name="width-request">340</property>
+        <property name="height-request">360</property>
+        <child>
+          <object class="HdyClamp">
+            <property name="visible">1</property>
+            <property name="margin-top">32</property>
+            <property name="margin-bottom">32</property>
+            <property name="margin-start">18</property>
+            <property name="margin-end">18</property>
+
+            <child>
+              <object class="GtkBox">
+                <property name="visible">1</property>
+                <property name="orientation">vertical</property>
+
+                <!-- Modem Status Title -->
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">1</property>
+                    <property name="margin-bottom">12</property>
+                    <property name="label" translatable="yes">Modem Status</property>
+                    <property name="xalign">0.0</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+
+                <!-- Modem Status Content -->
+                <child>
+                  <object class="GtkGrid">
+                    <property name="visible">1</property>
+                    <property name="row-spacing">9</property>
+                    <property name="column-spacing">6</property>
+                    <property name="margin-bottom">24</property>
+
+                    <!-- Carrier -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">Carrier</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="operator_name">
+                        <property name="visible">1</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+
+                    <!-- Network Type -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">Network Type</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="network_type">
+                        <property name="visible">1</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+
+                    <!-- Signal Strength -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">Signal Strength</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="signal_strength">
+                        <property name="visible">1</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">2</property>
+                      </packing>
+                    </child>
+
+                    <!-- Network Status -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">Network Status</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="network_status">
+                        <property name="visible">1</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">3</property>
+                      </packing>
+                    </child>
+
+                    <!-- Own Numbers -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible" bind-source="own_numbers"
+                                  bind-property="visible" bind-flags="sync-create"/>
+                        <property name="label" translatable="yes">Own Number</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">4</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="own_numbers">
+                        <property name="visible">1</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">4</property>
+                      </packing>
+                    </child>
+
+                <!-- Device Details Title -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="margin-bottom">12</property>
+                        <property name="label" translatable="yes">Device Details</property>
+                        <property name="xalign">0.0</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">5</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+
+                    <!-- Device Details Content -->
+                    <!-- Manufacturer -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">Manufacturer</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">6</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="manufacturer">
+                        <property name="visible">1</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">6</property>
+                      </packing>
+                    </child>
+
+                    <!-- Model -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">Model</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">7</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="device_model">
+                        <property name="visible">1</property>
+                        <property name="xalign">0.0</property>
+                        <property name="selectable">1</property>
+                        <property name="ellipsize">end</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">7</property>
+                      </packing>
+                    </child>
+
+                    <!-- Firmware version -->
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">Firmware Version</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">8</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="firmware_version">
+                        <property name="visible">1</property>
+                        <property name="selectable">1</property>
+                        <property name="xalign">0.0</property>
+                        <property name="ellipsize">end</property>
+                        <property name="wrap">1</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">8</property>
+                      </packing>
+                    </child>
+
+                    <!-- IMEI/ICCID -->
+                    <child>
+                      <object class="GtkLabel" id="identifier_label">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">IMEI</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">9</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="device_identifier">
+                        <property name="visible">1</property>
+                        <property name="selectable">1</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">9</property>
+                      </packing>
+                    </child>
+
+                  </object>
+                </child>
+              </object>
+            </child>
+
+          </object> <!-- ./HdyClamp -->
+        </child>
+      </object>
+    </child> <!-- ./internal-child -->
+
+  </template>
+</interface>
diff --git a/panels/wwan/cc-wwan-device-page.c b/panels/wwan/cc-wwan-device-page.c
new file mode 100644
index 000000000..0a04d3379
--- /dev/null
+++ b/panels/wwan/cc-wwan-device-page.c
@@ -0,0 +1,634 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device-page.c
+ *
+ * Copyright 2019 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-page"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+#define GCR_API_SUBJECT_TO_CHANGE
+#include <gcr/gcr.h>
+
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "cc-wwan-data.h"
+#include "cc-wwan-mode-dialog.h"
+#include "cc-wwan-network-dialog.h"
+#include "cc-wwan-details-dialog.h"
+#include "cc-wwan-sim-lock-dialog.h"
+#include "cc-wwan-apn-dialog.h"
+#include "cc-wwan-device-page.h"
+#include "cc-wwan-resources.h"
+
+#include "shell/cc-application.h"
+#include "shell/cc-debug.h"
+#include "shell/cc-object-storage.h"
+
+/**
+ * @short_description: Device settings page
+ * @include: "cc-wwan-device-page.h"
+ *
+ * The Device page allows users to configure device
+ * settings.  Please note that there is no one-to-one
+ * maping for a device settings page and a physical
+ * device.  Say, if a device have two SIM card slots,
+ * there should be two device pages, one for each SIM.
+ */
+
+struct _CcWwanDevicePage
+{
+  GtkBox         parent_instance;
+
+  GtkListBox    *advanced_settings_list;
+  CcListRow     *apn_settings_row;
+  CcListRow     *data_enable_row;
+  CcListRow     *data_roaming_row;
+  GtkListBox    *data_settings_list;
+  CcListRow     *details_row;
+  GtkStack      *main_stack;
+  CcListRow     *network_mode_row;
+  CcListRow     *network_name_row;
+  GtkListBox    *network_settings_list;
+  CcListRow     *sim_lock_row;
+  GtkButton     *unlock_button;
+
+  GtkLabel      *notification_label;
+
+  CcWwanDevice  *device;
+  CcWwanData    *wwan_data;
+  GDBusProxy    *wwan_proxy;
+
+  CcWwanApnDialog     *apn_dialog;
+  CcWwanDetailsDialog *details_dialog;
+  CcWwanModeDialog    *network_mode_dialog;
+  CcWwanNetworkDialog *network_dialog;
+  CcWwanSimLockDialog *sim_lock_dialog;
+
+  gint                 sim_index;
+  /* Set if a change is triggered in a signal’s callback,
+   * to avoid re-triggering of callback.  This is used
+   * instead of blocking handlers where the signal may be
+   * emitted async and the block/unblock may not work right
+   */
+  gboolean is_self_change;
+  gboolean is_retry;
+};
+
+G_DEFINE_TYPE (CcWwanDevicePage, cc_wwan_device_page, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+wwan_data_show_apn_dialog (CcWwanDevicePage *self)
+{
+  GtkWindow *top_level;
+
+  top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
+
+  if (!self->apn_dialog)
+    self->apn_dialog = cc_wwan_apn_dialog_new (top_level, self->device);
+
+  gtk_widget_show (GTK_WIDGET (self->apn_dialog));
+}
+
+static GcrPrompt *
+cc_wwan_device_page_new_prompt (CcWwanDevicePage *self,
+                                MMModemLock       lock)
+{
+  GcrPrompt *prompt;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *description = NULL;
+  g_autofree gchar *warning = NULL;
+  const gchar *message = NULL;
+  guint num;
+
+  prompt = GCR_PROMPT (gcr_system_prompt_open (-1, NULL, &error));
+
+  if (error)
+    {
+      g_warning ("Error opening Prompt: %s", error->message);
+      return NULL;
+    }
+
+  gcr_prompt_set_title (prompt, _("Unlock SIM card"));
+  gcr_prompt_set_continue_label (prompt, _("Unlock"));
+  gcr_prompt_set_cancel_label (prompt, _("Cancel"));
+
+  if (lock == MM_MODEM_LOCK_SIM_PIN)
+    {
+      description = g_strdup_printf (_("Please provide PIN code for SIM %d"), self->sim_index);
+      message = _("Enter PIN to unlock your SIM card");
+    }
+  else if (lock == MM_MODEM_LOCK_SIM_PUK)
+    {
+      description = g_strdup_printf (_("Please provide PUK code for SIM %d"), self->sim_index);
+      message = _("Enter PUK to unlock your SIM card");
+    }
+  else
+    {
+      g_warn_if_reached ();
+      g_object_unref (prompt);
+
+      return NULL;
+    }
+
+  gcr_prompt_set_description (prompt, description);
+  gcr_prompt_set_message (prompt, message);
+
+  num = cc_wwan_device_get_unlock_retries (self->device, lock);
+
+  if (num != MM_UNLOCK_RETRIES_UNKNOWN)
+    {
+      if (self->is_retry)
+        warning = g_strdup_printf (ngettext ("Wrong password entered. You have %1$u try left",
+                                             "Wrong password entered. You have %1$u tries left", num), num);
+      else
+        warning = g_strdup_printf (ngettext ("You have %u try left",
+                                             "You have %u tries left", num), num);
+    }
+  else if (self->is_retry)
+    {
+      warning = g_strdup (_("Wrong password entered."));
+    }
+
+  gcr_prompt_set_warning (prompt, warning);
+
+  return prompt;
+}
+
+static void
+wwan_update_unlock_button (CcWwanDevicePage *self)
+{
+  gtk_button_set_label (self->unlock_button, _("Unlock"));
+  gtk_widget_set_sensitive (GTK_WIDGET (self->unlock_button), TRUE);
+}
+
+static void
+cc_wwan_device_page_unlocked_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  CcWwanDevicePage *self = user_data;
+  wwan_update_unlock_button (self);
+}
+
+static void
+wwan_device_unlock_clicked_cb (CcWwanDevicePage *self)
+{
+  g_autoptr(GError) error = NULL;
+  GcrPrompt *prompt;
+  const gchar *password, *warning;
+  const gchar *pin = "";
+  const gchar *puk = "";
+  MMModemLock lock;
+
+  lock = cc_wwan_device_get_lock (self->device);
+  password = "";
+
+  if (lock != MM_MODEM_LOCK_SIM_PIN &&
+      lock != MM_MODEM_LOCK_SIM_PUK)
+    g_return_if_reached ();
+
+  if (lock == MM_MODEM_LOCK_SIM_PUK)
+    {
+      prompt = cc_wwan_device_page_new_prompt (self, lock);
+
+      warning = _("PUK code should be an 8 digit number");
+      while (password && !cc_wwan_device_pin_valid (password, lock))
+        {
+          password = gcr_prompt_password (prompt, NULL, &error);
+          gcr_prompt_set_warning (prompt, warning);
+        }
+
+      puk = g_strdup (password);
+      password = "";
+      gcr_prompt_close (prompt);
+      g_object_unref (prompt);
+
+      if (error)
+        g_warning ("Error: %s", error->message);
+
+      /* Error or User cancelled PUK */
+      if (!puk)
+        return;
+    }
+
+  prompt = cc_wwan_device_page_new_prompt (self, MM_MODEM_LOCK_SIM_PIN);
+  if (lock == MM_MODEM_LOCK_SIM_PUK)
+    {
+      gcr_prompt_set_password_new (prompt, TRUE);
+      gcr_prompt_set_message (prompt, _("Enter New PIN"));
+      gcr_prompt_set_warning (prompt, "");
+    }
+
+  warning = _("PIN code should be a 4-8 digit number");
+  while (password && !cc_wwan_device_pin_valid (password, MM_MODEM_LOCK_SIM_PIN))
+    {
+      password = gcr_prompt_password (prompt, NULL, &error);
+      gcr_prompt_set_warning (prompt, warning);
+    }
+
+  pin = g_strdup (password);
+  gcr_prompt_close (prompt);
+  g_object_unref (prompt);
+
+  if (error)
+    g_warning ("Error: %s", error->message);
+
+  /* Error or User cancelled PIN */
+  if (!pin)
+    return;
+
+  gtk_button_set_label (self->unlock_button, _("Unlocking..."));
+  gtk_widget_set_sensitive (GTK_WIDGET (self->unlock_button), FALSE);
+
+  if (lock == MM_MODEM_LOCK_SIM_PIN)
+    cc_wwan_device_send_pin (self->device, pin,
+                             NULL, /* cancellable */
+                             cc_wwan_device_page_unlocked_cb,
+                             self);
+  else if (lock == MM_MODEM_LOCK_SIM_PUK)
+    {
+      cc_wwan_device_send_puk (self->device, puk, pin,
+                               NULL, /* Cancellable */
+                               cc_wwan_device_page_unlocked_cb,
+                               self);
+    }
+  else
+    {
+      g_warn_if_reached ();
+    }
+}
+
+static void
+wwan_data_settings_changed_cb (CcWwanDevicePage *self,
+                               GParamSpec       *pspec,
+                               CcListRow        *data_row)
+{
+  gboolean active;
+
+  if (self->is_self_change)
+    {
+      self->is_self_change = FALSE;
+      return;
+    }
+
+  if (cc_wwan_data_get_default_apn (self->wwan_data) == NULL)
+    wwan_data_show_apn_dialog (self);
+
+  /* The user dismissed the dialog for selecting default APN */
+  if (cc_wwan_data_get_default_apn (self->wwan_data) == NULL)
+    {
+      self->is_self_change = TRUE;
+      gtk_widget_activate (GTK_WIDGET (data_row));
+
+      return;
+    }
+
+  active = cc_list_row_get_active (data_row);
+
+  if (data_row == self->data_enable_row)
+    cc_wwan_data_set_enabled (self->wwan_data, active);
+  else
+    cc_wwan_data_set_roaming_enabled (self->wwan_data, active);
+
+  cc_wwan_data_save_settings (self->wwan_data, NULL, NULL, NULL);
+}
+
+static void
+wwan_network_settings_activated_cb (CcWwanDevicePage *self,
+                                    CcListRow        *row)
+{
+  GtkWidget *dialog;
+  GtkWindow *top_level;
+
+  top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
+
+  if (row == self->network_mode_row)
+    {
+      if (!self->network_mode_dialog)
+        self->network_mode_dialog = cc_wwan_mode_dialog_new (top_level, self->device);
+
+      dialog = GTK_WIDGET (self->network_mode_dialog);
+    }
+  else if (row == self->network_name_row)
+    {
+      if (!self->network_dialog)
+        self->network_dialog = cc_wwan_network_dialog_new (top_level, self->device);
+
+      dialog = GTK_WIDGET (self->network_dialog);
+    }
+  else
+    {
+      return;
+    }
+
+  gtk_widget_show (dialog);
+}
+
+static void
+wwan_advanced_settings_activated_cb (CcWwanDevicePage *self,
+                                     CcListRow        *row)
+{
+  GtkWindow *top_level;
+
+  top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
+
+  if (row == self->sim_lock_row)
+    {
+      if (!self->sim_lock_dialog)
+        self->sim_lock_dialog = cc_wwan_sim_lock_dialog_new (top_level, self->device);
+      gtk_widget_show (GTK_WIDGET (self->sim_lock_dialog));
+    }
+  else if (row == self->details_row)
+    {
+      if (!self->details_dialog)
+        self->details_dialog = cc_wwan_details_dialog_new (top_level, self->device);
+      gtk_widget_show (GTK_WIDGET (self->details_dialog));
+    }
+  else if (row == self->apn_settings_row)
+    {
+      wwan_data_show_apn_dialog (self);
+    }
+  else
+    {
+      g_return_if_reached ();
+    }
+}
+
+static void
+cc_wwan_device_page_update_data (CcWwanDevicePage *self)
+{
+  gboolean has_data;
+
+  if (self->wwan_data == cc_wwan_device_get_data (self->device))
+    return;
+
+  self->wwan_data = cc_wwan_device_get_data (self->device);
+  has_data = self->wwan_data != NULL;
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->data_settings_list), has_data);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->apn_settings_row), has_data);
+
+  if (!has_data)
+    return;
+
+  g_signal_handlers_block_by_func (self->data_roaming_row,
+                                   wwan_data_settings_changed_cb, self);
+  g_signal_handlers_block_by_func (self->data_enable_row,
+                                   wwan_data_settings_changed_cb, self);
+
+  g_object_set (self->data_roaming_row, "active",
+                cc_wwan_data_get_roaming_enabled (self->wwan_data), NULL);
+
+  g_object_set (self->data_enable_row, "active",
+                cc_wwan_data_get_enabled (self->wwan_data), NULL);
+
+  g_signal_handlers_unblock_by_func (self->data_roaming_row,
+                                     wwan_data_settings_changed_cb, self);
+  g_signal_handlers_unblock_by_func (self->data_enable_row,
+                                     wwan_data_settings_changed_cb, self);
+}
+
+static void
+cc_wwan_device_page_update (CcWwanDevicePage *self)
+{
+  GtkStack *main_stack;
+  MMModemLock lock;
+
+  main_stack = self->main_stack;
+  if (!cc_wwan_device_has_sim (self->device))
+    gtk_stack_set_visible_child_name (main_stack, "no-sim-view");
+  else if ((lock = cc_wwan_device_get_lock (self->device)) == MM_MODEM_LOCK_SIM_PIN ||
+           lock == MM_MODEM_LOCK_SIM_PUK)
+    gtk_stack_set_visible_child_name (main_stack, "sim-lock-view");
+  else
+    gtk_stack_set_visible_child_name (main_stack, "settings-view");
+}
+
+static void
+cc_wwan_locks_changed_cb (CcWwanDevicePage *self)
+{
+  const gchar *label;
+
+  if (cc_wwan_device_get_sim_lock (self->device))
+    label = _("Enabled");
+  else
+    label = _("Disabled");
+
+  cc_list_row_set_secondary_label (self->sim_lock_row, label);
+  cc_wwan_device_page_update (self);
+}
+
+static void
+cc_wwan_device_page_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  CcWwanDevicePage *self = (CcWwanDevicePage *)object;
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      self->device = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_device_page_constructed (GObject *object)
+{
+  CcWwanDevicePage *self = (CcWwanDevicePage *)object;
+
+  G_OBJECT_CLASS (cc_wwan_device_page_parent_class)->constructed (object);
+
+  cc_wwan_device_page_update_data (self);
+
+  g_object_bind_property (self->device, "operator-name",
+                          self->network_name_row, "secondary-label",
+                          G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+  g_object_bind_property (self->device, "network-mode",
+                          self->network_mode_row, "secondary-label",
+                          G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+  g_signal_connect_object (self->device, "notify::enabled-locks",
+                           (GCallback)cc_wwan_locks_changed_cb,
+                           self, G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->device, "notify::has-data",
+                           (GCallback)cc_wwan_device_page_update_data,
+                           self, G_CONNECT_SWAPPED);
+
+  cc_wwan_device_page_update (self);
+  cc_wwan_locks_changed_cb (self);
+}
+
+static void
+cc_wwan_device_page_dispose (GObject *object)
+{
+  CcWwanDevicePage *self = (CcWwanDevicePage *)object;
+
+  g_clear_pointer ((GtkWidget **)&self->apn_dialog, gtk_widget_destroy);
+  g_clear_pointer ((GtkWidget **)&self->details_dialog, gtk_widget_destroy);
+  g_clear_pointer ((GtkWidget **)&self->network_mode_dialog, gtk_widget_destroy);
+  g_clear_pointer ((GtkWidget **)&self->network_dialog, gtk_widget_destroy);
+  g_clear_pointer ((GtkWidget **)&self->sim_lock_dialog, gtk_widget_destroy);
+
+  g_clear_object (&self->wwan_proxy);
+  g_clear_object (&self->device);
+
+  G_OBJECT_CLASS (cc_wwan_device_page_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_device_page_class_init (CcWwanDevicePageClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = cc_wwan_device_page_set_property;
+  object_class->constructed  = cc_wwan_device_page_constructed;
+  object_class->dispose = cc_wwan_device_page_dispose;
+
+  g_type_ensure (CC_TYPE_WWAN_DEVICE);
+
+  properties[PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "The WWAN Device",
+                         CC_TYPE_WWAN_DEVICE,
+                         G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/control-center/wwan/cc-wwan-device-page.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, advanced_settings_list);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, apn_settings_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_enable_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_roaming_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, data_settings_list);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, details_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, main_stack);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_mode_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_name_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, network_settings_list);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, sim_lock_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanDevicePage, unlock_button);
+
+  gtk_widget_class_bind_template_callback (widget_class, wwan_device_unlock_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, wwan_data_settings_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, wwan_network_settings_activated_cb);
+  gtk_widget_class_bind_template_callback (widget_class, wwan_advanced_settings_activated_cb);
+}
+
+static void
+cc_wwan_device_page_init (CcWwanDevicePage *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_list_box_set_header_func (self->data_settings_list,
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (self->network_settings_list,
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (self->advanced_settings_list,
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+}
+
+static void
+cc_wwan_error_changed_cb (CcWwanDevicePage *self)
+{
+  const gchar *message;
+
+  message = cc_wwan_device_get_simple_error (self->device);
+
+  if (!message)
+    return;
+
+  /*
+   * The label is first set to empty, which will result in
+   * the revealer to be closed.  Then the real label is
+   * set.  This will animate the revealer which can bring
+   * the user's attention.
+   */
+  gtk_label_set_label (self->notification_label, "");
+  gtk_label_set_label (self->notification_label, message);
+}
+
+CcWwanDevicePage *
+cc_wwan_device_page_new (CcWwanDevice *device,
+                         GtkWidget    *notification_label)
+{
+  CcWwanDevicePage *self;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+  self = g_object_new (CC_TYPE_WWAN_DEVICE_PAGE,
+                       "device", device,
+                       NULL);
+
+  self->notification_label = GTK_LABEL (notification_label);
+
+  g_signal_connect_object (self->device, "notify::error",
+                           G_CALLBACK (cc_wwan_error_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  return self;
+}
+
+CcWwanDevice *
+cc_wwan_device_page_get_device (CcWwanDevicePage *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE_PAGE (self), NULL);
+
+  return self->device;
+}
+
+void
+cc_wwan_device_page_set_sim_index (CcWwanDevicePage *self,
+                                   gint              sim_index)
+{
+  g_return_if_fail (CC_IS_WWAN_DEVICE_PAGE (self));
+  g_return_if_fail (sim_index >= 1);
+
+  self->sim_index = sim_index;
+}
diff --git a/panels/wwan/cc-wwan-device-page.h b/panels/wwan/cc-wwan-device-page.h
new file mode 100644
index 000000000..923346a89
--- /dev/null
+++ b/panels/wwan/cc-wwan-device-page.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device-page.h
+ *
+ * Copyright 2019 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 <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_DEVICE_PAGE (cc_wwan_device_page_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDevicePage, cc_wwan_device_page, CC, WWAN_DEVICE_PAGE, GtkBox)
+
+CcWwanDevicePage *cc_wwan_device_page_new           (CcWwanDevice     *device,
+                                                     GtkWidget        *notification_label);
+CcWwanDevice     *cc_wwan_device_page_get_device    (CcWwanDevicePage *self);
+void              cc_wwan_device_page_set_sim_index (CcWwanDevicePage *self,
+                                                     gint              sim_index);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-device-page.ui b/panels/wwan/cc-wwan-device-page.ui
new file mode 100644
index 000000000..f77bd707d
--- /dev/null
+++ b/panels/wwan/cc-wwan-device-page.ui
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcWwanDevicePage" parent="GtkBox">
+    <property name="visible">1</property>
+    <child>
+      <object class="GtkStack" id="main_stack">
+        <property name="visible">1</property>
+        <property name="homogeneous">0</property>
+
+        <!-- SIM not inserted view -->
+        <child>
+          <object class="GtkBox">
+            <property name="visible">1</property>
+            <property name="expand">1</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="orientation">vertical</property>
+            <property name="margin-bottom">64</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">1</property>
+                <property name="icon-name">auth-sim-missing</property>
+                <property name="pixel-size">192</property>
+                <property name="margin-bottom">18</property>
+                <style>
+                  <class name="dim-label" />
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">1</property>
+                <property name="wrap">1</property>
+                <property name="label" translatable="yes">No SIM</property>
+                <attributes>
+                  <attribute name="weight" value="bold" />
+                  <attribute name="scale" value="1.2" />
+                </attributes>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">1</property>
+                <property name="wrap">1</property>
+                <property name="label" translatable="yes">Insert a SIM card to use this modem</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">no-sim-view</property>
+          </packing>
+        </child>
+
+        <!-- SIM locked view -->
+        <child>
+          <object class="GtkBox">
+            <property name="visible">1</property>
+            <property name="expand">1</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="orientation">vertical</property>
+            <property name="margin-bottom">64</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">1</property>
+                <property name="icon-name">auth-sim-locked</property>
+                <property name="pixel-size">192</property>
+                <property name="margin-bottom">18</property>
+                <style>
+                 <class name="dim-label" />
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">1</property>
+                <property name="wrap">1</property>
+                <property name="label" translatable="yes">SIM Locked</property>
+                <property name="margin-bottom">32</property>
+                <attributes>
+                  <attribute name="weight" value="bold" />
+                  <attribute name="scale" value="1.2" />
+                </attributes>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="unlock_button">
+                <property name="visible">1</property>
+                <property name="use-underline">1</property>
+                <property name="label" translatable="yes">_Unlock</property>
+                <signal name="clicked" handler="wwan_device_unlock_clicked_cb" swapped="yes" />
+                <style>
+                  <class name="suggested-action" />
+                </style>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">sim-lock-view</property>
+          </packing>
+        </child> <!--  -->
+
+        <!-- Network Settings -->
+        <child>
+          <object class="GtkBox">
+            <property name="visible">1</property>
+            <property name="margin-top">18</property>
+            <property name="orientation">vertical</property>
+
+            <!-- Network Settings Title -->
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">1</property>
+                <property name="label" translatable="yes">Network</property>
+                <property name="xalign">0.0</property>
+                <property name="margin-bottom">12</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+            </child>
+
+            <!-- Internet settings -->
+            <child>
+              <object class="GtkListBox" id="data_settings_list">
+                <property name="visible">1</property>
+                <property name="margin-bottom">32</property>
+                <property name="selection-mode">none</property>
+                <style>
+                 <class name="frame" />
+                </style>
+
+                <!-- Enable/Disable Data -->
+                <child>
+                  <object class="CcListRow" id="data_enable_row">
+                    <property name="visible">1</property>
+                    <property name="use-underline">1</property>
+                    <property name="show-switch">1</property>
+                    <property name="title" translatable="yes">_Mobile Data</property>
+                    <property name="subtitle" translatable="yes">Access data using mobile network</property>
+                    <signal name="notify::active" handler="wwan_data_settings_changed_cb" swapped="yes" />
+                  </object>
+                </child>
+
+                <!-- Data Roaming -->
+                <child>
+                  <object class="CcListRow" id="data_roaming_row">
+                    <property name="visible">1</property>
+                    <property name="use-underline">1</property>
+                    <property name="show-switch">1</property>
+                    <property name="title" translatable="yes">_Data Roaming</property>
+                    <property name="subtitle" translatable="yes">Use mobile data when roaming</property>
+                    <signal name="notify::active" handler="wwan_data_settings_changed_cb" swapped="yes" />
+                  </object>
+                </child>
+
+              </object>
+            </child>
+
+            <!-- Network Settings -->
+            <child>
+              <object class="GtkListBox" id="network_settings_list" >
+                <property name="visible">1</property>
+                <property name="margin-bottom">32</property>
+                <property name="selection-mode">none</property>
+                <style>
+                  <class name="frame" />
+                </style>
+                <signal name="row-activated" handler="wwan_network_settings_activated_cb" swapped="yes" />
+
+                <!-- Network Mode -->
+                <child>
+                  <object class="CcListRow" id="network_mode_row">
+                    <property name="visible">1</property>
+                    <property name="use-underline">1</property>
+                    <property name="icon-name">go-next-symbolic</property>
+                    <property name="title" translatable="yes">_Network Mode</property>
+                  </object>
+                </child>
+
+                <!-- Network -->
+                <child>
+                  <object class="CcListRow" id="network_name_row">
+                    <property name="visible">1</property>
+                    <property name="use-underline">1</property>
+                    <property name="icon-name">go-next-symbolic</property>
+                    <property name="title" translatable="yes">N_etwork</property>
+                  </object>
+                </child>
+
+              </object>
+            </child>
+
+
+            <!-- Advanced Settings Title -->
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">1</property>
+                <property name="label" translatable="yes">Advanced</property>
+                <property name="xalign">0.0</property>
+                <property name="margin-bottom">12</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+            </child>
+
+            <!-- Advanced settings -->
+            <child>
+              <object class="GtkListBox" id="advanced_settings_list" >
+                <property name="visible">1</property>
+                <property name="margin-bottom">32</property>
+                <property name="selection-mode">none</property>
+                <style>
+                  <class name="frame" />
+                </style>
+                <signal name="row-activated" handler="wwan_advanced_settings_activated_cb" swapped="yes" />
+
+                <!-- Accesss Point Settings -->
+                <child>
+                  <object class="CcListRow" id="apn_settings_row">
+                    <property name="visible">1</property>
+                    <property name="use-underline">1</property>
+                    <property name="icon-name">go-next-symbolic</property>
+                    <property name="title" translatable="yes">_Access Point Names</property>
+                  </object>
+                </child>
+
+                <!-- SIM Lock -->
+                <child>
+                  <object class="CcListRow" id="sim_lock_row">
+                    <property name="visible">1</property>
+                    <property name="use-underline">1</property>
+                    <property name="icon-name">go-next-symbolic</property>
+                    <property name="title" translatable="yes">_SIM Lock</property>
+                    <property name="subtitle" translatable="yes">Lock SIM with PIN</property>
+                  </object>
+                </child>
+
+                <!-- Modem Details -->
+                <child>
+                  <object class="CcListRow" id="details_row">
+                    <property name="visible">1</property>
+                    <property name="use-underline">1</property>
+                    <property name="icon-name">go-next-symbolic</property>
+                    <property name="title" translatable="yes">M_odem Details</property>
+                  </object>
+                </child>
+
+              </object>
+            </child>
+
+          </object>
+          <packing>
+            <property name="name">settings-view</property>
+          </packing>
+        </child>
+
+      </object> <!-- ./GtkStack main_stack -->
+    </child>
+  </template>
+  <object class="GtkSizeGroup">
+    <property name="mode">both</property>
+    <widgets>
+      <widget name="apn_settings_row"/>
+      <widget name="sim_lock_row"/>
+      <widget name="details_row"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/panels/wwan/cc-wwan-device.c b/panels/wwan/cc-wwan-device.c
new file mode 100644
index 000000000..31baff95c
--- /dev/null
+++ b/panels/wwan/cc-wwan-device.c
@@ -0,0 +1,1355 @@
+/* -*- 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
+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);
+      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 (wwan_device_emit_data_changed),
+                               self, G_CONNECT_SWAPPED);
+      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 (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_get_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);
+  if (permission)
+    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_own_numbers (CcWwanDevice *self)
+{
+  const char *const *own_numbers;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  own_numbers = mm_modem_get_own_numbers (self->modem);
+
+  if (!own_numbers)
+    return NULL;
+
+  return g_strjoinv ("\n", (char **)own_numbers);
+}
+
+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/panels/wwan/cc-wwan-device.h b/panels/wwan/cc-wwan-device.h
new file mode 100644
index 000000000..e484bcf30
--- /dev/null
+++ b/panels/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_own_numbers    (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/panels/wwan/cc-wwan-errors-private.h b/panels/wwan/cc-wwan-errors-private.h
new file mode 100644
index 000000000..761b82f35
--- /dev/null
+++ b/panels/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/panels/wwan/cc-wwan-mode-dialog.c b/panels/wwan/cc-wwan-mode-dialog.c
new file mode 100644
index 000000000..e5917a41c
--- /dev/null
+++ b/panels/wwan/cc-wwan-mode-dialog.c
@@ -0,0 +1,327 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-mode-dialog.c
+ *
+ * Copyright 2019 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-network-mode-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "list-box-helper.h"
+#include "cc-wwan-mode-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: WWAN network type selection dialog
+ */
+
+#define CC_TYPE_WWAN_MODE_ROW (cc_wwan_mode_row_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanModeRow, cc_wwan_mode_row, CC, WWAN_MODE_ROW, GtkListBoxRow)
+
+struct _CcWwanModeDialog
+{
+  GtkDialog      parent_instance;
+
+  CcWwanDevice  *device;
+  GtkListBox    *network_mode_list;
+  CcWwanModeRow *selected_row;
+
+  MMModemMode preferred;
+  MMModemMode allowed;
+  MMModemMode new_allowed;
+  MMModemMode new_preferred;
+};
+
+G_DEFINE_TYPE (CcWwanModeDialog, cc_wwan_mode_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+struct _CcWwanModeRow
+{
+  GtkListBoxRow  parent_instance;
+  GtkImage      *ok_emblem;
+  MMModemMode    allowed;
+  MMModemMode    preferred;
+};
+
+G_DEFINE_TYPE (CcWwanModeRow, cc_wwan_mode_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_wwan_mode_row_class_init (CcWwanModeRowClass *klass)
+{
+}
+
+static void
+cc_wwan_mode_row_init (CcWwanModeRow *row)
+{
+}
+
+static void
+cc_wwan_mode_changed_cb (CcWwanModeDialog *self,
+                         CcWwanModeRow    *row)
+{
+  g_assert (CC_IS_WWAN_MODE_DIALOG (self));
+  g_assert (CC_IS_WWAN_MODE_ROW (row));
+
+  if (row == self->selected_row)
+    return;
+
+  gtk_widget_show (GTK_WIDGET (row->ok_emblem));
+
+  if (self->selected_row)
+    gtk_widget_hide (GTK_WIDGET (self->selected_row->ok_emblem));
+
+  self->selected_row = row;
+}
+
+static void
+cc_wwan_mode_dialog_ok_clicked_cb (CcWwanModeDialog *self)
+{
+  g_assert (CC_IS_WWAN_MODE_DIALOG (self));
+
+  if (self->selected_row)
+    {
+      cc_wwan_device_set_current_mode (self->device,
+                                       self->selected_row->allowed,
+                                       self->selected_row->preferred,
+                                       NULL, NULL, NULL);
+    }
+  else
+    {
+      g_return_if_reached ();
+    }
+
+  gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static GtkWidget *
+cc_wwan_mode_dialog_row_new (CcWwanModeDialog *self,
+                             MMModemMode       allowed,
+                             MMModemMode       preferred)
+{
+  CcWwanModeRow *row;
+  GtkWidget *box, *label, *image;
+  g_autofree gchar *mode = NULL;
+
+  g_assert (CC_WWAN_MODE_DIALOG (self));
+
+  row = g_object_new (CC_TYPE_WWAN_MODE_ROW, NULL);
+  row->allowed = allowed;
+  row->preferred = preferred;
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+  gtk_widget_show (box);
+  g_object_set (box, "margin", 18, NULL);
+  gtk_container_add (GTK_CONTAINER (row), box);
+
+  mode = cc_wwan_device_get_string_from_mode (self->device, allowed, preferred);
+  label = gtk_label_new (mode);
+  gtk_widget_show (label);
+  gtk_widget_set_hexpand (label, TRUE);
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  gtk_container_add (GTK_CONTAINER (box), label);
+
+  /* image should be hidden by default */
+  image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON);
+  gtk_container_add (GTK_CONTAINER (box), image);
+  row->ok_emblem = GTK_IMAGE (image);
+
+  return GTK_WIDGET (row);
+}
+
+static void
+cc_wwan_mode_dialog_update (CcWwanModeDialog *self)
+{
+  MMModemMode allowed;
+  MMModemMode modes[][2] = {
+    {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G},
+    {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, 0},
+    {MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G},
+    {MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, 0},
+    {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G},
+    {MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, 0},
+    {MM_MODEM_MODE_4G, 0},
+    {MM_MODEM_MODE_3G, 0},
+    {MM_MODEM_MODE_2G, 0},
+  };
+  size_t i;
+
+  g_assert (CC_IS_WWAN_MODE_DIALOG (self));
+
+  if (!cc_wwan_device_get_supported_modes (self->device, &allowed, NULL))
+    {
+      g_warning ("No modes supported by modem");
+      return;
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (modes); i++)
+    {
+      GtkWidget *row;
+
+      if ((modes[i][0] & allowed) != modes[i][0])
+        continue;
+
+      if (modes[i][1] && !(modes[i][1] & allowed))
+        continue;
+
+      row = cc_wwan_mode_dialog_row_new (self, modes[i][0], modes[i][1]);
+      gtk_widget_show (row);
+      gtk_container_add (GTK_CONTAINER (self->network_mode_list), row);
+    }
+}
+
+static void
+cc_wwan_mode_dialog_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      self->device = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_mode_dialog_constructed (GObject *object)
+{
+  CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object);
+
+  G_OBJECT_CLASS (cc_wwan_mode_dialog_parent_class)->constructed (object);
+
+  if(!cc_wwan_device_get_current_mode (self->device, &self->allowed, &self->preferred))
+    g_warning ("Can't get allowed and preferred wwan modes");
+
+  cc_wwan_mode_dialog_update (self);
+}
+
+static void
+cc_wwan_mode_dialog_dispose (GObject *object)
+{
+  CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (object);
+
+  g_clear_object (&self->device);
+
+  G_OBJECT_CLASS (cc_wwan_mode_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_mode_dialog_update_mode (CcWwanModeRow    *row,
+                                 CcWwanModeDialog *self)
+{
+  if (self->allowed == row->allowed && self->preferred == row->preferred)
+    {
+      self->selected_row = row;
+      gtk_widget_show (GTK_WIDGET (row->ok_emblem));
+    }
+  else
+    gtk_widget_hide (GTK_WIDGET (row->ok_emblem));
+}
+
+static void
+cc_wwan_mode_dialog_show (GtkWidget *widget)
+{
+  CcWwanModeDialog *self = CC_WWAN_MODE_DIALOG (widget);
+
+  if(!cc_wwan_device_get_current_mode (self->device, &self->allowed, &self->preferred))
+    {
+      g_warning ("Can't get allowed and preferred wwan modes");
+      goto end;
+    }
+
+  gtk_container_foreach (GTK_CONTAINER (self->network_mode_list),
+                         (GtkCallback)cc_wwan_mode_dialog_update_mode,
+                         self);
+ end:
+  GTK_WIDGET_CLASS (cc_wwan_mode_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wwan_mode_dialog_class_init (CcWwanModeDialogClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = cc_wwan_mode_dialog_set_property;
+  object_class->constructed  = cc_wwan_mode_dialog_constructed;
+  object_class->dispose = cc_wwan_mode_dialog_dispose;
+
+  widget_class->show = cc_wwan_mode_dialog_show;
+
+  properties[PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "The WWAN Device",
+                         CC_TYPE_WWAN_DEVICE,
+                         G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/control-center/wwan/cc-wwan-mode-dialog.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcWwanModeDialog, network_mode_list);
+
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_mode_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_mode_dialog_ok_clicked_cb);
+}
+
+static void
+cc_wwan_mode_dialog_init (CcWwanModeDialog *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_list_box_set_header_func (self->network_mode_list,
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+}
+
+CcWwanModeDialog *
+cc_wwan_mode_dialog_new (GtkWindow    *parent_window,
+                         CcWwanDevice *device)
+{
+  g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+  return g_object_new (CC_TYPE_WWAN_MODE_DIALOG,
+                       "transient-for", parent_window,
+                       "use-header-bar", 1,
+                       "device", device,
+                       NULL);
+}
diff --git a/panels/wwan/cc-wwan-mode-dialog.h b/panels/wwan/cc-wwan-mode-dialog.h
new file mode 100644
index 000000000..2399f0b7b
--- /dev/null
+++ b/panels/wwan/cc-wwan-mode-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-mode-dialog.h
+ *
+ * Copyright 2019 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 <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_MODE_DIALOG (cc_wwan_mode_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanModeDialog, cc_wwan_mode_dialog, CC, WWAN_MODE_DIALOG, GtkDialog)
+
+CcWwanModeDialog *cc_wwan_mode_dialog_new (GtkWindow    *parent_window,
+                                           CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-mode-dialog.ui b/panels/wwan/cc-wwan-mode-dialog.ui
new file mode 100644
index 000000000..e0a924a39
--- /dev/null
+++ b/panels/wwan/cc-wwan-mode-dialog.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcWwanModeDialog" parent="GtkDialog">
+    <property name="title" translatable="yes">Network Mode</property>
+    <property name="default-height">480</property>
+    <property name="default-width">360</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">1</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
+        <property name="margin-top">18</property>
+        <property name="margin-bottom">18</property>
+        <child>
+          <object class="GtkFrame">
+            <property name="visible">1</property>
+            <child>
+              <object class="GtkListBox" id="network_mode_list">
+                <property name="visible">1</property>
+                <property name="selection-mode">none</property>
+                <signal name="row-activated" handler="cc_wwan_mode_changed_cb" swapped="yes" />
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child type="action">
+      <object class="GtkButton" id="button_cancel">
+        <property name="visible">1</property>
+        <property name="use-underline">1</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="button_ok">
+        <property name="visible">1</property>
+        <property name="can-default">1</property>
+        <property name="use-underline">1</property>
+        <property name="label" translatable="yes">_Set</property>
+        <signal name="clicked" handler="cc_wwan_mode_dialog_ok_clicked_cb" swapped="yes"/>
+        <style>
+          <class name="suggested-action "/>
+        </style>
+      </object>
+    </child>
+
+    <action-widgets>
+      <action-widget response="cancel">button_cancel</action-widget>
+      <action-widget response="apply" default="true">button_ok</action-widget>
+    </action-widgets>
+  </template>
+</interface>
diff --git a/panels/wwan/cc-wwan-network-dialog.c b/panels/wwan/cc-wwan-network-dialog.c
new file mode 100644
index 000000000..1c8883b88
--- /dev/null
+++ b/panels/wwan/cc-wwan-network-dialog.c
@@ -0,0 +1,443 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-network-dialog.c
+ *
+ * Copyright 2019 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-network-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "cc-wwan-errors-private.h"
+#include "cc-wwan-network-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: WWAN network operator selection dialog
+ */
+
+#define CC_TYPE_WWAN_NETWORK_ROW (cc_wwan_network_row_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanNetworkRow, cc_wwan_network_row, CC, WWAN_NETWORK_ROW, GtkListBoxRow)
+
+struct _CcWwanNetworkDialog
+{
+  GtkDialog     parent_instance;
+
+  CcListRow    *automatic_row;
+  GtkButton    *button_apply;
+  GtkSpinner   *loading_spinner;
+  GtkBox       *network_search_title;
+  GtkLabel     *notification_label;
+  GtkRevealer  *notification_revealer;
+  GtkListBox   *operator_list_box;
+  GtkButton    *refresh_button;
+
+  CcWwanDevice *device;
+  GList        *operator_list;
+
+  CcWwanNetworkRow *selected_row;
+
+  GCancellable *search_cancellable;
+
+  guint         revealer_timeout_id;
+  gboolean      no_update_network;
+};
+
+G_DEFINE_TYPE (CcWwanNetworkDialog, cc_wwan_network_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+struct _CcWwanNetworkRow
+{
+  GtkListBoxRow  parent_instance;
+  GtkImage      *ok_emblem;
+  gchar         *operator_code;
+};
+
+G_DEFINE_TYPE (CcWwanNetworkRow, cc_wwan_network_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_wwan_network_row_finalize (GObject *object)
+{
+  CcWwanNetworkRow *row = (CcWwanNetworkRow *)object;
+
+  g_free (row->operator_code);
+
+  G_OBJECT_CLASS (cc_wwan_network_row_parent_class)->finalize (object);
+}
+
+static void
+cc_wwan_network_row_class_init (CcWwanNetworkRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = cc_wwan_network_row_finalize;
+}
+
+static void
+cc_wwan_network_row_init (CcWwanNetworkRow *row)
+{
+}
+
+static void
+cc_wwan_on_notification_closed (CcWwanNetworkDialog *self,
+                                GtkWidget           *button)
+{
+  g_assert (CC_IS_WWAN_NETWORK_DIALOG (self));
+
+  gtk_revealer_set_reveal_child (GTK_REVEALER (self->notification_revealer), FALSE);
+
+  if (self->revealer_timeout_id != 0)
+    g_source_remove (self->revealer_timeout_id);
+
+  self->revealer_timeout_id = 0;
+}
+
+static gboolean
+cc_wwan_on_notification_timeout (gpointer user_data)
+{
+  cc_wwan_on_notification_closed (user_data, NULL);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+cc_wwan_network_changed_cb (CcWwanNetworkDialog *self,
+                            CcWwanNetworkRow    *row)
+{
+  if (row == self->selected_row)
+    return;
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->button_apply), TRUE);
+  gtk_widget_show (GTK_WIDGET (row->ok_emblem));
+
+  if (self->selected_row)
+    gtk_widget_hide (GTK_WIDGET (self->selected_row->ok_emblem));
+
+  self->selected_row = row;
+}
+
+/*
+ * cc_wwan_network_dialog_row_new:
+ * @self: a #CcWwanNetworkDialog
+ * @operator_name: (transfer full): The long operator name
+ * @operator_id: (transfer full): operator id
+ */
+static CcWwanNetworkRow *
+cc_wwan_network_dialog_row_new (CcWwanNetworkDialog *self,
+                                const gchar         *operator_name,
+                                const gchar         *operator_code)
+{
+  CcWwanNetworkRow *row;
+  GtkWidget *box, *label, *image;
+
+  row = g_object_new (CC_TYPE_WWAN_NETWORK_ROW, NULL);
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+  gtk_widget_show (box);
+  g_object_set (box, "margin", 18, NULL);
+  gtk_container_add (GTK_CONTAINER (row), box);
+
+  label = gtk_label_new (operator_name);
+  gtk_widget_show (label);
+  gtk_widget_set_hexpand (label, TRUE);
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  gtk_container_add (GTK_CONTAINER (box), label);
+
+  image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON);
+  row->ok_emblem = GTK_IMAGE (image);
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (row->ok_emblem));
+
+  row->operator_code = g_strdup (operator_code);
+
+  return row;
+}
+
+static void
+cc_wwan_network_dialog_update_current_network (CcWwanNetworkDialog *self)
+{
+  CcWwanNetworkRow *row;
+  const gchar *operator_name;
+
+  operator_name = cc_wwan_device_get_operator_name (self->device);
+
+  if (!operator_name || operator_name[0] == '\0')
+    return;
+
+  gtk_container_foreach (GTK_CONTAINER (self->operator_list_box),
+                         (GtkCallback)gtk_widget_destroy, NULL);
+
+  row = cc_wwan_network_dialog_row_new (self, operator_name, "");
+  self->selected_row = row;
+  gtk_container_add (GTK_CONTAINER (self->operator_list_box), GTK_WIDGET (row));
+  gtk_widget_show_all (GTK_WIDGET (self->operator_list_box));
+}
+
+static void
+cc_wwan_network_dialog_update (CcWwanNetworkDialog *self)
+{
+  CcWwanNetworkRow *row;
+  GList *item;
+  const gchar *operator_code, *operator_name;
+
+  gtk_container_foreach (GTK_CONTAINER (self->operator_list_box),
+                         (GtkCallback)gtk_widget_destroy, NULL);
+
+  for (item = self->operator_list; item; item = item->next)
+    {
+      operator_code = mm_modem_3gpp_network_get_operator_code (item->data);
+      operator_name = mm_modem_3gpp_network_get_operator_long (item->data);
+
+      row = cc_wwan_network_dialog_row_new (self, operator_name, operator_code);
+      gtk_widget_show (GTK_WIDGET (row));
+      gtk_container_add (GTK_CONTAINER (self->operator_list_box), GTK_WIDGET (row));
+    }
+}
+
+static void
+cc_wwan_network_scan_complete_cb (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  g_autoptr(CcWwanNetworkDialog) self = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (self->operator_list)
+    g_list_free_full (self->operator_list, (GDestroyNotify)mm_modem_3gpp_network_free);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->refresh_button), TRUE);
+  gtk_spinner_stop (self->loading_spinner);
+  self->operator_list = cc_wwan_device_scan_networks_finish (self->device, result, &error);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), !error);
+
+  if (!error)
+    {
+      cc_wwan_network_dialog_update (self);
+      gtk_widget_show (GTK_WIDGET (self->operator_list_box));
+    }
+  else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      self->no_update_network = TRUE;
+      gtk_widget_activate (GTK_WIDGET (self->automatic_row));
+      gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), FALSE);
+
+      gtk_label_set_label (self->notification_label,
+                           cc_wwan_error_get_message (error));
+      gtk_revealer_set_reveal_child (self->notification_revealer, TRUE);
+      self->revealer_timeout_id = g_timeout_add_seconds (5, cc_wwan_on_notification_timeout, self);
+
+      gtk_widget_show (GTK_WIDGET (self->operator_list_box));
+      g_warning ("Error: scanning networks failed: %s", error->message);
+    }
+}
+
+static void
+cc_wwan_network_dialog_refresh_networks (CcWwanNetworkDialog *self)
+{
+  gtk_widget_set_sensitive (GTK_WIDGET (self->refresh_button), FALSE);
+  gtk_spinner_start (self->loading_spinner);
+  cc_wwan_device_scan_networks (self->device, self->search_cancellable,
+                                (GAsyncReadyCallback)cc_wwan_network_scan_complete_cb,
+                                g_object_ref (self));
+}
+
+static void
+cc_wwan_network_dialog_apply_clicked_cb (CcWwanNetworkDialog *self)
+{
+  gboolean is_auto;
+
+  g_assert (CC_IS_WWAN_NETWORK_DIALOG (self));
+
+  is_auto = cc_list_row_get_active (self->automatic_row);
+
+  if (is_auto)
+    cc_wwan_device_register_network (self->device, "", NULL, NULL, NULL);
+  else if (self->selected_row)
+    cc_wwan_device_register_network (self->device, self->selected_row->operator_code, NULL, NULL, self);
+  else
+    g_warn_if_reached ();
+
+  gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static void
+cc_wwan_auto_network_changed_cb (CcWwanNetworkDialog *self,
+                                 GParamSpec          *pspec,
+                                 CcListRow           *auto_network_row)
+{
+  gboolean is_auto;
+
+  g_assert (CC_IS_WWAN_NETWORK_DIALOG (self));
+  g_assert (CC_IS_LIST_ROW (auto_network_row));
+
+  is_auto = cc_list_row_get_active (auto_network_row);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->button_apply), is_auto);
+
+  if (self->no_update_network)
+    {
+      self->no_update_network = FALSE;
+      return;
+    }
+
+  self->selected_row = NULL;
+  gtk_widget_set_visible (GTK_WIDGET (self->network_search_title), !is_auto);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->operator_list_box), !is_auto);
+  gtk_widget_hide (GTK_WIDGET (self->operator_list_box));
+
+  if (is_auto)
+    {
+      g_cancellable_cancel (self->search_cancellable);
+      g_cancellable_reset (self->search_cancellable);
+    }
+  else
+    {
+      cc_wwan_network_dialog_refresh_networks (self);
+    }
+}
+
+static void
+cc_wwan_network_dialog_show (GtkWidget *widget)
+{
+  CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)widget;
+  gboolean is_auto;
+
+  is_auto = cc_wwan_device_is_auto_network (self->device);
+
+  g_object_set (self->automatic_row, "active", is_auto, NULL);
+
+  cc_wwan_network_dialog_update_current_network (self);
+
+  GTK_WIDGET_CLASS (cc_wwan_network_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wwan_network_dialog_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)object;
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      self->device = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_network_dialog_dispose (GObject *object)
+{
+  CcWwanNetworkDialog *self = (CcWwanNetworkDialog *)object;
+
+  if (self->revealer_timeout_id != 0)
+    g_source_remove (self->revealer_timeout_id);
+
+  self->revealer_timeout_id = 0;
+
+  g_cancellable_cancel (self->search_cancellable);
+
+  g_clear_object (&self->search_cancellable);
+  g_clear_object (&self->device);
+
+  G_OBJECT_CLASS (cc_wwan_network_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_network_dialog_class_init (CcWwanNetworkDialogClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = cc_wwan_network_dialog_set_property;
+  object_class->dispose = cc_wwan_network_dialog_dispose;
+
+  widget_class->show = cc_wwan_network_dialog_show;
+
+  properties[PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "The WWAN Device",
+                         CC_TYPE_WWAN_DEVICE,
+                         G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/control-center/wwan/cc-wwan-network-dialog.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, automatic_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, button_apply);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, loading_spinner);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, network_search_title);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, notification_label);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, notification_revealer);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, operator_list_box);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanNetworkDialog, refresh_button);
+
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_on_notification_closed);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_auto_network_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_dialog_refresh_networks);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_network_dialog_apply_clicked_cb);
+}
+
+static void
+cc_wwan_network_dialog_init (CcWwanNetworkDialog *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->search_cancellable = g_cancellable_new ();
+
+  gtk_list_box_set_header_func (self->operator_list_box,
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+}
+
+CcWwanNetworkDialog *
+cc_wwan_network_dialog_new (GtkWindow    *parent_window,
+                            CcWwanDevice *device)
+{
+  g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+  return g_object_new (CC_TYPE_WWAN_NETWORK_DIALOG,
+                       "transient-for", parent_window,
+                       "use-header-bar", 1,
+                       "device", device,
+                       NULL);
+}
diff --git a/panels/wwan/cc-wwan-network-dialog.h b/panels/wwan/cc-wwan-network-dialog.h
new file mode 100644
index 000000000..1818a0876
--- /dev/null
+++ b/panels/wwan/cc-wwan-network-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-network-dialog.h
+ *
+ * Copyright 2019 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 <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_NETWORK_DIALOG (cc_wwan_network_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanNetworkDialog, cc_wwan_network_dialog, CC, WWAN_NETWORK_DIALOG, GtkDialog)
+
+CcWwanNetworkDialog *cc_wwan_network_dialog_new (GtkWindow    *parent_window,
+                                                 CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-network-dialog.ui b/panels/wwan/cc-wwan-network-dialog.ui
new file mode 100644
index 000000000..03223b333
--- /dev/null
+++ b/panels/wwan/cc-wwan-network-dialog.ui
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcWwanNetworkDialog" parent="GtkDialog">
+    <property name="title" translatable="yes">Network</property>
+    <property name="default-height">480</property>
+    <property name="default-width">360</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">1</property>
+        <property name="border-width">0</property>
+        <property name="width-request">340</property>
+        <property name="height-request">360</property>
+        <child>
+          <object class="GtkOverlay">
+            <property name="visible">1</property>
+            <child type="overlay">
+              <object class="GtkRevealer" id="notification_revealer">
+                <property name="visible">1</property>
+                <property name="halign">center</property>
+                <property name="valign">start</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">1</property>
+                    <property name="spacing">12</property>
+                    <style>
+                      <class name="frame" />
+                      <class name="app-notification" />
+                    </style>
+                    <child>
+                      <object class="GtkLabel" id="notification_label">
+                        <property name="visible">1</property>
+                        <property name="wrap">1</property>
+                        <property name="wrap-mode">word-char</property>
+                        <property name="use-markup">1</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton">
+                        <property name="visible">1</property>
+                        <property name="relief">none</property>
+                        <signal name="clicked" handler="cc_wwan_on_notification_closed" swapped="yes" />
+                        <child internal-child="accessible">
+                          <object class="AtkObject">
+                            <property name="accessible-name" translatable="yes">Close</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">1</property>
+                            <property name="icon-name">window-close-symbolic</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">1</property>
+                <property name="margin-start">12</property>
+                <property name="margin-end">12</property>
+                <property name="margin-top">18</property>
+                <property name="margin-bottom">18</property>
+                <property name="orientation">vertical</property>
+
+                <!-- Automatic Network Selection Switch -->
+                <child>
+                  <object class="GtkListBox">
+                    <property name="visible">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="margin-bottom">18</property>
+                    <style>
+                      <class name="frame" />
+                    </style>
+                    <child>
+                      <object class="CcListRow" id="automatic_row">
+                        <property name="visible">1</property>
+                        <property name="show-switch">1</property>
+                        <property name="use-underline">1</property>
+                        <property name="title" translatable="yes">_Automatic</property>
+                        <signal name="notify::active" handler="cc_wwan_auto_network_changed_cb" 
swapped="yes" />
+                      </object>
+                    </child>
+                  </object>
+                </child>
+
+                <!-- Network Selection List Title and Spinner -->
+                <child>
+                  <object class="GtkBox" id="network_search_title" >
+                    <property name="visible">1</property>
+                    <property name="margin-bottom">9</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">1</property>
+                        <property name="label" translatable="yes">Choose Network</property>
+                        <property name="xalign">0.0</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSpinner" id="loading_spinner">
+                        <property name="visible">1</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="refresh_button">
+                        <property name="visible">1</property>
+                        <signal name="clicked" handler="cc_wwan_network_dialog_refresh_networks" 
swapped="yes" />
+                        <child internal-child="accessible">
+                          <object class="AtkObject">
+                            <property name="accessible-name" translatable="yes">Refresh Network 
Providers</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">1</property>
+                            <property name="icon-name">view-refresh-symbolic</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="pack-type">end</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+
+                <!-- Network Selection List -->
+                <child>
+                  <object class="GtkScrolledWindow">
+                    <property name="visible">1</property>
+                    <property name="hscrollbar-policy">never</property>
+                    <property name="propagate-natural-height">1</property>
+                    <child>
+                      <object class="GtkListBox" id="operator_list_box">
+                        <property name="visible">0</property>
+                        <property name="sensitive">0</property>
+                        <property name="selection-mode">none</property>
+                        <signal name="row-activated" handler="cc_wwan_network_changed_cb" swapped="yes" />
+                        <style>
+                         <class name="frame" />
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child> <!-- ./internal-child -->
+
+    <child type="action">
+      <object class="GtkButton" id="button_cancel">
+        <property name="visible">1</property>
+        <property name="use-underline">1</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="button_apply">
+        <property name="visible">1</property>
+        <property name="can-default">1</property>
+        <property name="use-underline">1</property>
+        <property name="label" translatable="yes">_Set</property>
+        <signal name="clicked" handler="cc_wwan_network_dialog_apply_clicked_cb" swapped="yes"/>
+        <style>
+          <class name="suggested-action "/>
+        </style>
+      </object>
+    </child>
+
+    <action-widgets>
+      <action-widget response="cancel">button_cancel</action-widget>
+      <action-widget response="apply" default="true">button_apply</action-widget>
+    </action-widgets>
+  </template>
+</interface>
diff --git a/panels/wwan/cc-wwan-panel.c b/panels/wwan/cc-wwan-panel.c
new file mode 100644
index 000000000..963c46900
--- /dev/null
+++ b/panels/wwan/cc-wwan-panel.c
@@ -0,0 +1,929 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-panel.c
+ *
+ * Copyright 2019 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-panel"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "cc-wwan-device.h"
+#include "cc-wwan-data.h"
+#include "cc-wwan-device-page.h"
+#include "cc-wwan-panel.h"
+#include "cc-wwan-resources.h"
+
+#include "shell/cc-application.h"
+#include "shell/cc-debug.h"
+#include "shell/cc-object-storage.h"
+
+typedef enum {
+  OPERATION_NULL,
+  OPERATION_SHOW_DEVICE,
+} CmdlineOperation;
+
+struct _CcWwanPanel
+{
+  CcPanel parent_instance;
+
+  GtkListBox       *data_select_listbox;
+  GtkPopover       *data_select_popover;
+  GtkLabel         *data_sim_label;
+  GtkListBox       *data_sim_select_listbox;
+  GtkStack         *devices_stack;
+  GtkStackSwitcher *devices_switcher;
+  GtkSwitch        *enable_switch;
+  GtkStack         *main_stack;
+  GtkRevealer      *multi_device_revealer;
+  GtkLabel         *notification_label;
+  GtkRevealer      *notification_revealer;
+
+  GDBusProxy   *rfkill_proxy;
+  MMManager    *mm_manager;
+  NMClient     *nm_client;
+
+  /* The default device that will be used for data */
+  CcWwanDevice *data_device;
+  GListStore   *devices;
+  GListStore   *data_devices;
+  GCancellable *cancellable;
+
+  CmdlineOperation  arg_operation;
+  char             *arg_device;
+
+  guint revealer_timeout_id;
+};
+
+enum {
+  PROP_0,
+  PROP_PARAMETERS
+};
+
+G_DEFINE_TYPE (CcWwanPanel, cc_wwan_panel, CC_TYPE_PANEL)
+
+
+#define CC_TYPE_DATA_DEVICE_ROW (cc_data_device_row_get_type())
+G_DECLARE_FINAL_TYPE (CcDataDeviceRow, cc_data_device_row, CC, DATA_DEVICE_ROW, GtkListBoxRow)
+
+struct _CcDataDeviceRow
+{
+  GtkListBoxRow  parent_instance;
+
+  GtkImage      *ok_emblem;
+  CcWwanDevice  *device;
+};
+
+G_DEFINE_TYPE (CcDataDeviceRow, cc_data_device_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_data_device_row_class_init (CcDataDeviceRowClass *klass)
+{
+}
+
+static void
+cc_data_device_row_init (CcDataDeviceRow *row)
+{
+}
+
+static CmdlineOperation
+cmdline_operation_from_string (const gchar *str)
+{
+  if (g_strcmp0 (str, "show-device") == 0)
+    return OPERATION_SHOW_DEVICE;
+
+  g_warning ("Invalid additional argument %s", str);
+  return OPERATION_NULL;
+}
+
+static void
+reset_command_line_args (CcWwanPanel *self)
+{
+  self->arg_operation = OPERATION_NULL;
+  g_clear_pointer (&self->arg_device, g_free);
+}
+
+static gboolean
+verify_argv (CcWwanPanel  *self,
+             const char  **args)
+{
+       switch (self->arg_operation)
+    {
+    case OPERATION_SHOW_DEVICE:
+      if (self->arg_device == NULL)
+        {
+          g_warning ("Operation %s requires an object path", args[0]);
+          return FALSE;
+        }
+    default:
+      return TRUE;
+    }
+}
+
+static void
+handle_argv (CcWwanPanel *self)
+{
+  if (self->arg_operation == OPERATION_SHOW_DEVICE &&
+      self->arg_operation)
+    {
+      g_autoptr(GList) pages = NULL;
+
+      pages = gtk_container_get_children (GTK_CONTAINER (self->devices_stack));
+
+      for (GList *page = pages; page; page = page->next)
+        {
+          CcWwanDevice *device;
+
+          device = cc_wwan_device_page_get_device (page->data);
+
+          if (g_strcmp0 (cc_wwan_device_get_path (device), self->arg_device) == 0)
+            {
+              gtk_stack_set_visible_child (GTK_STACK (self->devices_stack), page->data);
+              g_debug ("Opening device %s", self->arg_device);
+              reset_command_line_args (self);
+              return;
+            }
+        }
+    }
+}
+
+static gboolean
+wwan_panel_device_is_supported (GDBusObject *object)
+{
+  MMObject *mm_object;
+  MMModem *modem;
+  MMModemCapability capability;
+
+  g_assert (G_IS_DBUS_OBJECT (object));
+
+  mm_object = MM_OBJECT (object);
+  modem = mm_object_get_modem (mm_object);
+  capability = mm_modem_get_current_capabilities (modem);
+
+  /* We Support only GSM/3G/LTE devices */
+  if (capability & (MM_MODEM_CAPABILITY_GSM_UMTS |
+                    MM_MODEM_CAPABILITY_LTE |
+                    MM_MODEM_CAPABILITY_LTE_ADVANCED))
+    return TRUE;
+
+  return FALSE;
+}
+
+static gint
+wwan_model_get_item_index (GListModel *model,
+                           gpointer    item)
+{
+  guint i, n_items;
+
+  g_assert (G_IS_LIST_MODEL (model));
+  g_assert (G_IS_OBJECT (item));
+
+  n_items = g_list_model_get_n_items (model);
+
+  for (i = 0; i < n_items; i++)
+    {
+      g_autoptr(GObject) object = NULL;
+
+      object = g_list_model_get_item (model, i);
+
+      if (object == item)
+        return i;
+    }
+
+  return -1;
+}
+
+static CcWwanDevice *
+wwan_model_get_item_from_mm_object (GListModel *model,
+                                    MMObject   *mm_object)
+{
+  const gchar *modem_path, *device_path;
+  guint i, n_items;
+
+  n_items = g_list_model_get_n_items (model);
+  modem_path = mm_object_get_path (mm_object);
+
+  for (i = 0; i < n_items; i++)
+    {
+      g_autoptr(CcWwanDevice) device = NULL;
+
+      device = g_list_model_get_item (model, i);
+      device_path = cc_wwan_device_get_path (device);
+
+      if (g_str_equal (modem_path, device_path))
+        return g_steal_pointer (&device);
+    }
+
+  return NULL;
+}
+
+static CcDataDeviceRow *
+cc_data_device_row_new (CcWwanDevice *device,
+                        CcWwanPanel  *self)
+{
+  CcDataDeviceRow *row;
+  GtkWidget *box, *label, *image;
+  g_autofree gchar *operator = NULL;
+  gint index;
+
+  row = g_object_new (CC_TYPE_DATA_DEVICE_ROW, NULL);
+  row->device = device;
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+  gtk_widget_show (box);
+  g_object_set (box, "margin", 12, NULL);
+  gtk_container_add (GTK_CONTAINER (row), box);
+
+  index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device);
+  operator = g_strdup_printf ("SIM %d", index + 1);
+  label = gtk_label_new (operator);
+  gtk_widget_show (label);
+  gtk_container_add (GTK_CONTAINER (box), label);
+
+  image = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON);
+  row->ok_emblem = GTK_IMAGE (image);
+  gtk_container_add (GTK_CONTAINER (box), image);
+
+  return row;
+}
+
+static void
+wwan_notification_close_clicked_cb (CcWwanPanel *self)
+{
+  gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
+
+  if (self->revealer_timeout_id != 0)
+    g_source_remove (self->revealer_timeout_id);
+
+  self->revealer_timeout_id = 0;
+}
+
+static void
+wwan_data_selector_clicked_cb (CcWwanPanel *self)
+{
+  if (gtk_widget_is_visible (GTK_WIDGET (self->data_select_popover)))
+    gtk_popover_popdown (self->data_select_popover);
+  else
+    gtk_popover_popup (self->data_select_popover);
+}
+
+static void
+cc_wwan_panel_update_data_selection (CcDataDeviceRow *row,
+                                     CcWwanPanel     *self)
+{
+  if (self->data_device == row->device)
+    {
+      g_autofree gchar *str = NULL;
+      gint i;
+
+      i = wwan_model_get_item_index (G_LIST_MODEL (self->devices), row->device);
+      g_assert (i >= 0);
+
+      /* Human index starts from 1 */
+      str = g_strdup_printf ("SIM %d", i + 1);
+      gtk_label_set_label (self->data_sim_label, str);
+
+      gtk_widget_show (GTK_WIDGET (row->ok_emblem));
+    }
+  else
+    {
+      gtk_widget_hide (GTK_WIDGET (row->ok_emblem));
+    }
+}
+
+static void
+cc_wwan_data_item_activate_cb (CcWwanPanel     *self,
+                               CcDataDeviceRow *row)
+{
+  CcWwanData *data;
+
+  gtk_popover_popdown (self->data_select_popover);
+
+  if (row->device == self->data_device)
+    return;
+
+  /* Set lower priority for previously selected APN */
+  data = cc_wwan_device_get_data (self->data_device);
+  cc_wwan_data_set_priority (data, CC_WWAN_APN_PRIORITY_LOW);
+  cc_wwan_data_save_settings (data, NULL, NULL, NULL);
+
+  /* Set high priority for currently selected APN */
+  data = cc_wwan_device_get_data (row->device);
+  cc_wwan_data_set_priority (data, CC_WWAN_APN_PRIORITY_HIGH);
+  cc_wwan_data_save_settings (data, NULL, NULL, NULL);
+
+  self->data_device = row->device;
+  gtk_container_foreach (GTK_CONTAINER (self->data_select_listbox),
+                         (GtkCallback) cc_wwan_panel_update_data_selection, self);
+}
+
+static void
+wwan_on_airplane_off_clicked_cb (CcWwanPanel *self)
+{
+  g_debug ("Airplane Mode Off clicked, disabling airplane mode");
+  g_dbus_proxy_call (self->rfkill_proxy,
+                     "org.freedesktop.DBus.Properties.Set",
+                     g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill',"
+                                           "'AirplaneMode', %v)",
+                                           g_variant_new_boolean (FALSE)),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1,
+                     self->cancellable,
+                     NULL,
+                     NULL);
+}
+
+static gboolean
+cc_wwan_panel_get_cached_dbus_property (GDBusProxy  *proxy,
+                                        const gchar *property)
+{
+  g_autoptr(GVariant) result = NULL;
+
+  g_assert (G_IS_DBUS_PROXY (proxy));
+  g_assert (property && *property);
+
+  result = g_dbus_proxy_get_cached_property (proxy, property);
+  g_assert (!result || g_variant_is_of_type (result, G_VARIANT_TYPE_BOOLEAN));
+
+  return result ? g_variant_get_boolean (result) : FALSE;
+}
+
+static void
+cc_wwan_panel_update_view (CcWwanPanel *self)
+{
+  gboolean has_airplane, is_airplane = FALSE, enabled = FALSE;
+
+  has_airplane = cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "HasAirplaneMode");
+  has_airplane &= cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "ShouldShowAirplaneMode");
+
+  if (has_airplane)
+    {
+      is_airplane = cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "AirplaneMode");
+      is_airplane |= cc_wwan_panel_get_cached_dbus_property (self->rfkill_proxy, "HardwareAirplaneMode");
+    }
+
+  if (self->nm_client)
+    enabled = nm_client_wwan_get_enabled (self->nm_client);
+
+  if (has_airplane && is_airplane)
+    gtk_stack_set_visible_child_name (self->main_stack, "airplane-mode");
+  else if (enabled && g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 0)
+    gtk_stack_set_visible_child_name (self->main_stack, "device-settings");
+  else
+    gtk_stack_set_visible_child_name (self->main_stack, "no-wwan-devices");
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->enable_switch), !is_airplane);
+
+  if (enabled)
+    gtk_revealer_set_reveal_child (self->multi_device_revealer,
+                                   g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 1);
+}
+
+static void
+cc_wwan_panel_on_notification_closed (CcWwanPanel *self,
+                                      GtkWidget   *button)
+{
+  gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
+
+  if (self->revealer_timeout_id != 0)
+    g_source_remove (self->revealer_timeout_id);
+
+  self->revealer_timeout_id = 0;
+}
+
+static gboolean
+cc_wwan_panel_on_notification_timeout (gpointer user_data)
+{
+  cc_wwan_panel_on_notification_closed (user_data, NULL);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+cc_wwan_panel_notification_changed_cb (CcWwanPanel *self)
+{
+  const gchar *label;
+
+  label = gtk_label_get_label (self->notification_label);
+
+  if (label && *label)
+    {
+      gtk_revealer_set_reveal_child (self->notification_revealer, TRUE);
+      self->revealer_timeout_id = g_timeout_add_seconds (5, cc_wwan_panel_on_notification_timeout, self);
+    }
+  else
+    {
+      cc_wwan_panel_on_notification_closed (self, NULL);
+    }
+}
+
+static void
+cc_wwan_panel_add_device (CcWwanPanel  *self,
+                          CcWwanDevice *device)
+{
+  CcWwanDevicePage *device_page;
+  g_autofree gchar *operator_name = NULL;
+  g_autofree gchar *stack_name = NULL;
+  guint n_items;
+
+  g_list_store_append (self->devices, device);
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->devices));
+  operator_name = g_strdup_printf (_("SIM %d"), n_items);
+  stack_name = g_strdup_printf ("sim-%d", n_items);
+
+  device_page = cc_wwan_device_page_new (device, GTK_WIDGET (self->notification_label));
+  cc_wwan_device_page_set_sim_index (device_page, n_items);
+  gtk_stack_add_titled (self->devices_stack,
+                        GTK_WIDGET (device_page), stack_name, operator_name);
+}
+
+static void
+cc_wwan_panel_update_page_title (CcWwanDevicePage *device_page,
+                                 CcWwanPanel      *self)
+{
+  g_autofree gchar *title = NULL;
+  g_autofree gchar *name = NULL;
+  CcWwanDevice *device;
+  GtkWidget *parent;
+  gint index;
+
+  device = cc_wwan_device_page_get_device (device_page);
+
+  parent = gtk_widget_get_parent (GTK_WIDGET (device_page));
+  index  = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device);
+
+  if (index == -1)
+    g_return_if_reached ();
+
+  /* index starts with 0, but we need human readable index to be 1+ */
+  cc_wwan_device_page_set_sim_index (device_page, index + 1);
+  title = g_strdup_printf (_("SIM %d"), index + 1);
+  name = g_strdup_printf ("sim-%d", index + 1);
+  gtk_container_child_set (GTK_CONTAINER (parent),
+                           GTK_WIDGET (device_page),
+                           "title", title,
+                           "name", name,
+                           NULL);
+}
+
+static void
+cc_wwan_panel_remove_mm_object (CcWwanPanel *self,
+                                MMObject    *mm_object)
+{
+  g_autoptr(CcWwanDevice) device = NULL;
+  GtkWidget *device_page;
+  g_autofree gchar *stack_name = NULL;
+  guint n_items;
+  gint index;
+
+  device = wwan_model_get_item_from_mm_object (G_LIST_MODEL (self->devices), mm_object);
+
+  if (!device)
+    return;
+
+  index = wwan_model_get_item_index (G_LIST_MODEL (self->data_devices), device);
+  if (index != -1)
+    g_list_store_remove (self->data_devices, index);
+
+  index = wwan_model_get_item_index (G_LIST_MODEL (self->devices), device);
+  if (index == -1)
+    return;
+
+  g_list_store_remove (self->devices, index);
+  stack_name = g_strdup_printf ("sim-%d", index + 1);
+  device_page = gtk_stack_get_child_by_name (self->devices_stack, stack_name);
+  gtk_container_remove (GTK_CONTAINER (self->devices_stack), device_page);
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->data_devices));
+  g_list_model_items_changed (G_LIST_MODEL (self->data_devices), 0, n_items, n_items);
+  gtk_container_foreach (GTK_CONTAINER (self->devices_stack),
+                         (GtkCallback)cc_wwan_panel_update_page_title,
+                         self);
+}
+
+static void
+cc_wwan_panel_update_data_connections (CcWwanPanel *self)
+{
+  CcWwanData *device_data, *active_data = NULL;
+  guint n_items;
+  gint i;
+
+  /*
+   * We can’t predict the order in which the data of device is enabled.
+   * But we have to keep data store in the same order as device store.
+   * So let’s remove every data device and re-add.
+   */
+  g_list_store_remove_all (self->data_devices);
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->devices));
+
+  for (i = 0; i < n_items; i++)
+    {
+      g_autoptr(CcWwanDevice) device = NULL;
+
+      device = g_list_model_get_item (G_LIST_MODEL (self->devices), i);
+      device_data = cc_wwan_device_get_data (device);
+
+      if (!device_data)
+        continue;
+
+      if ((!active_data ||
+           cc_wwan_data_get_priority (device_data) > cc_wwan_data_get_priority (active_data)) &&
+          cc_wwan_data_get_enabled (device_data))
+        {
+          active_data = device_data;
+          self->data_device = device;
+        }
+
+      if (cc_wwan_data_get_enabled (device_data))
+        g_list_store_append (self->data_devices, device);
+    }
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->data_sim_select_listbox),
+                            g_list_model_get_n_items (G_LIST_MODEL (self->data_devices)) > 1);
+  if (active_data)
+    gtk_container_foreach (GTK_CONTAINER (self->data_select_listbox),
+                           (GtkCallback)cc_wwan_panel_update_data_selection, self);
+  else
+    gtk_label_set_label (self->data_sim_label, "");
+}
+
+static void
+cc_wwan_panel_update_devices (CcWwanPanel *self)
+{
+  GList *devices, *iter;
+
+  devices = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->mm_manager));
+
+  for (iter = devices; iter; iter = iter->next)
+    {
+      MMObject *mm_object = iter->data;
+      CcWwanDevice *device;
+
+      if(!wwan_panel_device_is_supported (iter->data))
+        continue;
+
+      device = cc_wwan_device_new (mm_object, G_OBJECT (self->nm_client));
+      cc_wwan_panel_add_device (self, device);
+      g_signal_connect_object (device, "notify::has-data",
+                               G_CALLBACK (cc_wwan_panel_update_data_connections),
+                               self, G_CONNECT_SWAPPED);
+
+      if (cc_wwan_device_get_data (device))
+        g_list_store_append (self->data_devices, device);
+    }
+
+  cc_wwan_panel_update_data_connections (self);
+  handle_argv (self);
+}
+
+static void
+wwan_panel_device_added_cb (CcWwanPanel *self,
+                            GDBusObject *object)
+{
+  CcWwanDevice *device;
+
+  if(!wwan_panel_device_is_supported (object))
+    return;
+
+  device = cc_wwan_device_new (MM_OBJECT (object), G_OBJECT (self->nm_client));
+  cc_wwan_panel_add_device (self, device);
+  g_signal_connect_object (device, "notify::has-data",
+                           G_CALLBACK (cc_wwan_panel_update_data_connections),
+                           self, G_CONNECT_SWAPPED);
+  cc_wwan_panel_update_view (self);
+  handle_argv (self);
+}
+
+static void
+wwan_panel_device_removed_cb (CcWwanPanel *self,
+                              GDBusObject *object)
+{
+  if (!wwan_panel_device_is_supported (object))
+    return;
+
+  cc_wwan_panel_remove_mm_object (self, MM_OBJECT (object));
+
+  gtk_revealer_set_reveal_child (self->multi_device_revealer,
+                                 g_list_model_get_n_items (G_LIST_MODEL (self->devices)) > 1);
+}
+
+static GPtrArray *
+variant_av_to_string_array (GVariant *array)
+{
+  GVariant *v;
+  GPtrArray *strv;
+  GVariantIter iter;
+  gsize count;
+
+  count = g_variant_iter_init (&iter, array);
+  strv = g_ptr_array_sized_new (count + 1);
+
+  while (g_variant_iter_next (&iter, "v", &v))
+    {
+      g_ptr_array_add (strv, (gpointer)g_variant_get_string (v, NULL));
+      g_variant_unref (v);
+    }
+  g_ptr_array_add (strv, NULL); /* NULL-terminate the strv data array */
+
+  return strv;
+}
+
+static void
+cc_wwan_panel_set_property (GObject      *object,
+                            guint         property_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  CcWwanPanel *self = CC_WWAN_PANEL (object);
+
+  switch (property_id)
+    {
+    case PROP_PARAMETERS:
+      {
+        GVariant *parameters;
+
+        reset_command_line_args (self);
+
+        parameters = g_value_get_variant (value);
+        if (parameters)
+          {
+            g_autoptr(GPtrArray) array = NULL;
+            const gchar **args;
+
+            array = variant_av_to_string_array (parameters);
+            args = (const gchar **) array->pdata;
+
+            g_debug ("Invoked with operation %s", args[0]);
+
+            if (args[0])
+              self->arg_operation = cmdline_operation_from_string (args[0]);
+            if (args[0] && args[1])
+              self->arg_device = g_strdup (args[1]);
+
+            if (!verify_argv (self, (const char **) args))
+              {
+                reset_command_line_args (self);
+                return;
+              }
+            g_debug ("Calling handle_argv() after setting property");
+            handle_argv (self);
+          }
+        break;
+      }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+cc_wwan_panel_constructed (GObject *object)
+{
+  CcWwanPanel *self = (CcWwanPanel *)object;
+
+  G_OBJECT_CLASS (cc_wwan_panel_parent_class)->constructed (object);
+
+       cc_shell_embed_widget_in_header (cc_panel_get_shell (CC_PANEL (self)),
+                                   GTK_WIDGET (self->enable_switch), GTK_POS_RIGHT);
+
+  if (self->nm_client)
+    {
+      g_object_bind_property (self->nm_client, "wwan-enabled",
+                              self->enable_switch, "active",
+                              G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    }
+}
+
+static void
+cc_wwan_panel_dispose (GObject *object)
+{
+  CcWwanPanel *self = (CcWwanPanel *)object;
+
+  if (self->revealer_timeout_id != 0)
+    g_source_remove (self->revealer_timeout_id);
+
+  self->revealer_timeout_id = 0;
+
+  g_cancellable_cancel (self->cancellable);
+
+  g_clear_object (&self->devices);
+  g_clear_object (&self->data_devices);
+  g_clear_object (&self->mm_manager);
+  g_clear_object (&self->nm_client);
+  g_clear_object (&self->cancellable);
+  g_clear_object (&self->rfkill_proxy);
+  g_clear_pointer (&self->arg_device, g_free);
+
+  G_OBJECT_CLASS (cc_wwan_panel_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_panel_class_init (CcWwanPanelClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = cc_wwan_panel_set_property;
+  object_class->constructed = cc_wwan_panel_constructed;
+  object_class->dispose = cc_wwan_panel_dispose;
+
+  g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/control-center/wwan/cc-wwan-panel.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_select_listbox);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_select_popover);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_sim_label);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, data_sim_select_listbox);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, devices_stack);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, devices_switcher);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, enable_switch);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, main_stack);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, multi_device_revealer);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, notification_label);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanPanel, notification_revealer);
+
+  gtk_widget_class_bind_template_callback (widget_class, wwan_on_airplane_off_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, wwan_notification_close_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, wwan_data_selector_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_data_item_activate_cb);
+}
+
+static void
+cc_wwan_panel_init (CcWwanPanel *self)
+{
+  g_autoptr(GError) error = NULL;
+
+  g_resources_register (cc_wwan_get_resource ());
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->cancellable = g_cancellable_new ();
+  self->devices = g_list_store_new (CC_TYPE_WWAN_DEVICE);
+  self->data_devices = g_list_store_new (CC_TYPE_WWAN_DEVICE);
+  gtk_list_box_bind_model (GTK_LIST_BOX (self->data_select_listbox),
+                           G_LIST_MODEL (self->data_devices),
+                           (GtkListBoxCreateWidgetFunc) cc_data_device_row_new,
+                           self, NULL);
+
+  g_signal_connect_object (self->notification_label, "notify::label",
+                           G_CALLBACK (cc_wwan_panel_notification_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  if (cc_object_storage_has_object (CC_OBJECT_NMCLIENT))
+    {
+      self->nm_client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT);
+      g_signal_connect_object (self->nm_client,
+                               "notify::wwan-enabled",
+                               G_CALLBACK (cc_wwan_panel_update_view),
+                               self, G_CONNECT_SWAPPED);
+
+    }
+  else
+    {
+      g_warn_if_reached ();
+    }
+
+  if (cc_object_storage_has_object ("CcObjectStorage::mm-manager"))
+    {
+      self->mm_manager = cc_object_storage_get_object ("CcObjectStorage::mm-manager");
+
+      g_signal_connect_object (self->mm_manager, "object-added",
+                               G_CALLBACK (wwan_panel_device_added_cb),
+                               self, G_CONNECT_SWAPPED);
+      g_signal_connect_object (self->mm_manager, "object-removed",
+                               G_CALLBACK (wwan_panel_device_removed_cb),
+                               self, G_CONNECT_SWAPPED);
+
+      cc_wwan_panel_update_devices (self);
+    }
+  else
+    {
+      g_warn_if_reached ();
+    }
+
+  /* Acquire Airplane Mode proxy */
+  self->rfkill_proxy = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SESSION,
+                                                                 G_DBUS_PROXY_FLAGS_NONE,
+                                                                 "org.gnome.SettingsDaemon.Rfkill",
+                                                                 "/org/gnome/SettingsDaemon/Rfkill",
+                                                                 "org.gnome.SettingsDaemon.Rfkill",
+                                                                 self->cancellable,
+                                                                 &error);
+
+  if (error)
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_printerr ("Error creating rfkill proxy: %s\n", error->message);
+    }
+  else
+    {
+      g_signal_connect_object (self->rfkill_proxy,
+                               "g-properties-changed",
+                               G_CALLBACK (cc_wwan_panel_update_view),
+                               self, G_CONNECT_SWAPPED);
+
+      cc_wwan_panel_update_view (self);
+    }
+}
+
+static void
+wwan_update_panel_visibility (MMManager *mm_manager)
+{
+  CcApplication *application;
+  GList *devices;
+  gboolean has_wwan;
+
+  g_assert (MM_IS_MANAGER (mm_manager));
+
+  CC_TRACE_MSG ("Updating WWAN panel visibility");
+
+  has_wwan = FALSE;
+  devices = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (mm_manager));
+
+  for (GList *item = devices; item != NULL; item = item->next)
+    {
+      if(wwan_panel_device_is_supported (item->data))
+        {
+          has_wwan = TRUE;
+          break;
+        }
+    }
+
+  /* Set the new visibility */
+  application = CC_APPLICATION (g_application_get_default ());
+  cc_shell_model_set_panel_visibility (cc_application_get_model (application),
+                                       "wwan",
+                                       has_wwan ? CC_PANEL_VISIBLE : CC_PANEL_VISIBLE_IN_SEARCH);
+
+  g_debug ("WWAN panel visible: %s", has_wwan ? "yes" : "no");
+
+  g_list_free_full (devices, (GDestroyNotify)g_object_unref);
+}
+
+void
+cc_wwan_panel_static_init_func (void)
+{
+  g_autoptr(GDBusConnection) system_bus = NULL;
+  g_autoptr(MMManager) mm_manager = NULL;
+  g_autoptr(GError) error = NULL;
+
+  /*
+   * There could be other modems that are only handled by rfkill,
+   * and not available via ModemManager.  But as this panel
+   * makes use of ModemManager APIs, we only care devices
+   * supported by ModemManager.
+   */
+  system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+  if (system_bus == NULL)
+    g_warning ("Error connecting to system D-Bus: %s", error->message);
+  else
+    mm_manager = mm_manager_new_sync (system_bus,
+                                      G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+                                      NULL, &error);
+
+  if (mm_manager == NULL)
+    {
+      CcApplication *application;
+
+      g_warning ("Error connecting to ModemManager: %s", error->message);
+
+      application = CC_APPLICATION (g_application_get_default ());
+      cc_shell_model_set_panel_visibility (cc_application_get_model (application),
+                                           "wwan", FALSE);
+      return;
+    }
+  else
+    {
+      cc_object_storage_add_object ("CcObjectStorage::mm-manager", mm_manager);
+    }
+
+  g_debug ("Monitoring ModemManager for WWAN devices");
+
+  g_signal_connect (mm_manager, "object-added", G_CALLBACK (wwan_update_panel_visibility), NULL);
+  g_signal_connect (mm_manager, "object-removed", G_CALLBACK (wwan_update_panel_visibility), NULL);
+
+  wwan_update_panel_visibility (mm_manager);
+}
diff --git a/panels/wwan/cc-wwan-panel.h b/panels/wwan/cc-wwan-panel.h
new file mode 100644
index 000000000..57d2dae26
--- /dev/null
+++ b/panels/wwan/cc-wwan-panel.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-panel.h
+ *
+ * Copyright 2019 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 <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_PANEL (cc_wwan_panel_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanPanel, cc_wwan_panel, CC, WWAN_PANEL, CcPanel)
+
+void                 cc_wwan_panel_static_init_func              (void);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-panel.ui b/panels/wwan/cc-wwan-panel.ui
new file mode 100644
index 000000000..5258c42c3
--- /dev/null
+++ b/panels/wwan/cc-wwan-panel.ui
@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcWwanPanel" parent="CcPanel">
+    <property name="visible">1</property>
+
+    <child>
+      <object class="GtkOverlay">
+        <property name="visible">1</property>
+
+        <!-- Notification Revealer -->
+        <child type="overlay">
+          <object class="GtkRevealer" id="notification_revealer">
+            <property name="visible">1</property>
+            <property name="halign">center</property>
+            <property name="valign">start</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">1</property>
+                <property name="spacing">12</property>
+                <style>
+                  <class name="frame" />
+                  <class name="app-notification" />
+                </style>
+                <child>
+                  <object class="GtkLabel" id="notification_label">
+                    <property name="visible">1</property>
+                    <property name="wrap">1</property>
+                    <property name="wrap-mode">word</property>
+                    <property name="use-markup">1</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="visible">1</property>
+                    <property name="relief">none</property>
+                    <signal name="clicked" handler="wwan_notification_close_clicked_cb" swapped="yes" />
+                    <child internal-child="accessible">
+                      <object class="AtkObject">
+                        <property name="accessible-name" translatable="yes">Close</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">1</property>
+                        <property name="icon-name">window-close-symbolic</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="visible">1</property>
+            <property name="hscrollbar-policy">never</property>
+            <property name="min-content-height">500</property>
+            <child>
+              <object class="HdyClamp">
+                <property name="visible">1</property>
+                <property name="margin-top">0</property>
+                <property name="margin-bottom">32</property>
+                <property name="margin-start">18</property>
+                <property name="margin-end">18</property>
+
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">1</property>
+                    <property name="hexpand">1</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkStack" id="main_stack">
+                        <property name="visible">1</property>
+                        <property name="homogeneous">0</property>
+                        <property name="transition-type">crossfade</property>
+
+                        <!-- "No WWAN Adapter" page -->
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">1</property>
+                            <property name="expand">1</property>
+                            <property name="valign">center</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="visible">1</property>
+                                <property name="icon-name">network-cellular-offline-symbolic</property>
+                                <property name="pixel-size">192</property>
+                                <property name="margin-bottom">18</property>
+                                <style>
+                                 <class name="dim-label" />
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="wrap">1</property>
+                                <property name="label" translatable="yes">No WWAN Adapter Found</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold" />
+                                  <attribute name="scale" value="1.2" />
+                                </attributes>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="wrap">1</property>
+                                <property name="label" translatable="yes">Make sure you have a Wireless 
Wan/Cellular device</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="name">no-wwan-devices</property>
+                          </packing>
+                        </child>
+
+                        <!-- "Airplane Mode" page -->
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">1</property>
+                            <property name="expand">1</property>
+                            <property name="orientation">vertical</property>
+                            <property name="valign">center</property>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="visible">1</property>
+                                <property name="icon-name">airplane-mode-symbolic</property>
+                                <property name="pixel-size">192</property>
+                                <property name="margin-bottom">18</property>
+                                <style>
+                                 <class name="dim-label" />
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="wrap">1</property>
+                                <property name="label" translatable="yes">Airplane Mode On</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold" />
+                                  <attribute name="scale" value="1.2" />
+                                </attributes>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="wrap">1</property>
+                                <property name="label" translatable="yes">Wireless Wan is disabled when 
airplane mode is on</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkButton">
+                                <property name="visible">1</property>
+                                <property name="halign">center</property>
+                                <property name="use-underline">1</property>
+                                <property name="margin-top">24</property>
+                                <property name="label" translatable="yes">_Turn off Airplane Mode</property>
+                                <signal name="clicked" handler="wwan_on_airplane_off_clicked_cb" 
swapped="yes" />
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="name">airplane-mode</property>
+                          </packing>
+                        </child>
+
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">1</property>
+                            <property name="orientation">vertical</property>
+
+                            <child>
+                              <object class="GtkRevealer" id="multi_device_revealer">
+                                <property name="visible">1</property>
+                                <property name="margin-top">18</property>
+                                <child>
+                                  <object class="GtkBox">
+                                    <property name="visible">1</property>
+                                    <property name="orientation">vertical</property>
+
+                                    <!-- Data SIM selector -->
+                                    <child>
+                                      <object class="GtkListBox" id="data_sim_select_listbox">
+                                        <property name="visible">1</property>
+                                        <property name="selection-mode">none</property>
+                                        <property name="margin-bottom">32</property>
+                                        <signal name="row-activated" handler="wwan_data_selector_clicked_cb" 
swapped="yes" />
+                                        <style>
+                                         <class name="frame" />
+                                        </style>
+                                        <child>
+                                          <object class="GtkGrid">
+                                            <property name="visible">1</property>
+                                            <property name="border-width">9</property>
+                                            <property name="margin-start">9</property>
+                                            <property name="margin-end">9</property>
+                                            <property name="column-spacing">12</property>
+
+                                            <!-- Title -->
+                                            <child>
+                                              <object class="GtkLabel">
+                                                <property name="visible">1</property>
+                                                <property name="hexpand">1</property>
+                                                <property name="label" translatable="yes">Data 
Connection</property>
+                                                <property name="xalign">0.0</property>
+                                              </object>
+                                              <packing>
+                                                <property name="left-attach">0</property>
+                                                <property name="top-attach">0</property>
+                                              </packing>
+                                            </child>
+
+                                            <!-- SubTitle -->
+                                            <child>
+                                              <object class="GtkLabel">
+                                                <property name="visible">1</property>
+                                                <property name="hexpand">1</property>
+                                                <property name="label" translatable="yes">SIM card used for 
internet</property>
+                                                <property name="xalign">0.0</property>
+                                                <attributes>
+                                                  <attribute name="scale" value="0.88" />
+                                                </attributes>
+                                                <style>
+                                                  <class name="dim-label" />
+                                                </style>
+                                              </object>
+                                              <packing>
+                                                <property name="left-attach">0</property>
+                                                <property name="top-attach">1</property>
+                                              </packing>
+                                            </child>
+
+                                            <!-- Network Name -->
+                                            <child>
+                                              <object class="GtkLabel" id="data_sim_label">
+                                                <property name="visible">1</property>
+                                                <property name="valign">center</property>
+                                              </object>
+                                              <packing>
+                                                <property name="left-attach">1</property>
+                                                <property name="top-attach">0</property>
+                                                <property name="height">2</property>
+                                              </packing>
+                                            </child>
+
+                                            <!-- Popover Arrow -->
+                                            <child>
+                                              <object class="GtkImage" id="popover_arrow">
+                                                <property name="visible">1</property>
+                                                <property name="valign">center</property>
+                                                <property name="icon-name">pan-down-symbolic</property>
+                                              </object>
+                                              <packing>
+                                                <property name="left-attach">2</property>
+                                                <property name="top-attach">0</property>
+                                                <property name="height">2</property>
+                                              </packing>
+                                            </child>
+
+                                          </object>
+                                        </child>
+                                      </object>
+                                    </child>
+
+                                    <!-- Device (SIM) Name -->
+                                    <child>
+                                      <object class="GtkStackSwitcher" id="devices_switcher">
+                                        <property name="stack">devices_stack</property>
+                                        <property name="visible">1</property>
+                                        <property name="hexpand">1</property>
+                                        <property name="halign">center</property>
+                                      </object>
+                                    </child>
+
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+
+                            <!-- Device (SIM) settings page -->
+                            <child>
+                              <object class="GtkStack" id="devices_stack">
+                                <property name="visible">1</property>
+                                <property name="homogeneous">0</property>
+                              </object>
+                            </child>
+
+                          </object>
+                          <packing>
+                            <property name="name">device-settings</property>
+                          </packing>
+                        </child>
+
+                      </object> <!-- ./GtkStack main_stack -->
+                    </child>
+                  </object>
+                </child>
+
+              </object> <!-- ./HdyClamp -->
+            </child>
+          </object> <!-- ./GtkScrolledWindow -->
+        </child>
+
+      </object>
+    </child>
+
+  </template>
+
+  <object class="GtkPopover" id="data_select_popover">
+    <property name="position">bottom</property>
+    <property name="relative-to">popover_arrow</property>
+    <child>
+      <object class="GtkListBox" id="data_select_listbox">
+        <property name="visible">1</property>
+        <property name="selection-mode">none</property>
+        <signal name="row-activated" handler="cc_wwan_data_item_activate_cb" swapped="yes" />
+      </object>
+    </child>
+  </object>
+
+  <!-- Cellular panel on/off switch -->
+  <object class="GtkSwitch" id="enable_switch">
+    <property name="visible">1</property>
+    <child internal-child="accessible">
+      <object class="AtkObject">
+        <property name="accessible-name" translatable="yes">Enable Mobile Network</property>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.c b/panels/wwan/cc-wwan-sim-lock-dialog.c
new file mode 100644
index 000000000..14adbf415
--- /dev/null
+++ b/panels/wwan/cc-wwan-sim-lock-dialog.c
@@ -0,0 +1,310 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-network-dialog.c
+ *
+ * Copyright 2019 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-sim-lock-dialog"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <libmm-glib.h>
+
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "cc-wwan-sim-lock-dialog.h"
+#include "cc-wwan-resources.h"
+
+/**
+ * @short_description: Dialog to manage SIM Locks like PIN
+ */
+
+#define PIN_MINIMUM_LENGTH 4
+#define PIN_MAXIMUM_LENGTH 8
+
+struct _CcWwanSimLockDialog
+{
+  GtkDialog     parent_instance;
+
+  CcWwanDevice *device;
+
+  GtkButton    *apply_button;
+  GtkStack     *button_stack;
+  GtkGrid      *lock_change_grid;
+  CcListRow    *lock_row;
+  GtkEntry     *new_pin_entry;
+  GtkButton    *next_button;
+  GtkEntry     *pin_confirm_entry;
+  GtkEntry     *pin_entry;
+  GtkStack     *pin_settings_stack;
+};
+
+G_DEFINE_TYPE (CcWwanSimLockDialog, cc_wwan_sim_lock_dialog, GTK_TYPE_DIALOG)
+
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+cc_wwan_sim_lock_changed_cb (CcWwanSimLockDialog *self)
+{
+  gboolean row_enabled, lock_enabled;
+
+  lock_enabled = cc_wwan_device_get_sim_lock (self->device);
+  row_enabled = cc_list_row_get_active (self->lock_row);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), lock_enabled != row_enabled);
+  gtk_widget_set_visible (GTK_WIDGET (self->lock_change_grid), row_enabled && lock_enabled);
+}
+
+static void
+cc_wwan_pin_next_clicked_cb (CcWwanSimLockDialog *self)
+{
+  gtk_stack_set_visible_child_name (self->pin_settings_stack, "pin-entry");
+  gtk_entry_set_text (self->pin_entry, "");
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE);
+  gtk_stack_set_visible_child (self->button_stack,
+                               GTK_WIDGET (self->apply_button));
+}
+
+static void
+cc_wwan_pin_apply_clicked_cb (CcWwanSimLockDialog *self)
+{
+  const gchar *pin, *new_pin;
+  gboolean row_enabled, lock_enabled;
+
+  gtk_widget_hide (GTK_WIDGET (self));
+
+  lock_enabled = cc_wwan_device_get_sim_lock (self->device);
+  row_enabled = cc_list_row_get_active (self->lock_row);
+  pin = gtk_entry_get_text (self->pin_entry);
+  new_pin = gtk_entry_get_text (self->new_pin_entry);
+
+  if (lock_enabled != row_enabled)
+    {
+      if (row_enabled)
+        cc_wwan_device_enable_pin (self->device, pin, NULL, NULL, NULL);
+      else
+        cc_wwan_device_disable_pin (self->device, pin, NULL, NULL, NULL);
+
+      return;
+    }
+
+  cc_wwan_device_change_pin (self->device, pin, new_pin, NULL, NULL, NULL);
+}
+
+static void
+cc_wwan_pin_entry_text_inserted_cb (CcWwanSimLockDialog *self,
+                                    gchar               *new_text,
+                                    gint                 new_text_length,
+                                    gpointer             position,
+                                    GtkEditable         *editable)
+{
+  size_t digit_end;
+  size_t len;
+
+  if (!new_text || !*new_text)
+    return;
+
+  if (new_text_length == 1 && g_ascii_isdigit (*new_text))
+    return;
+
+  if (new_text_length == -1)
+    len = strlen (new_text);
+  else
+    len = new_text_length;
+
+  if (len == 1 && g_ascii_isdigit (*new_text))
+    return;
+
+  digit_end = strspn (new_text, "1234567890");
+
+  /* The maximum length possible for PIN is 8 */
+  if (len <= 8 &&  digit_end == len)
+    return;
+
+  g_signal_stop_emission_by_name (editable, "insert-text");
+  gtk_widget_error_bell (GTK_WIDGET (editable));
+}
+
+static void
+cc_wwan_pin_entry_changed_cb (CcWwanSimLockDialog *self)
+{
+  const gchar *new_pin, *confirm_pin;
+
+  new_pin = gtk_entry_get_text (self->new_pin_entry);
+  confirm_pin = gtk_entry_get_text (self->pin_confirm_entry);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE);
+
+  /* A PIN should have a minimum length of 4 */
+  if (!new_pin || !confirm_pin || strlen (new_pin) < 4)
+    return;
+
+  if (g_str_equal (new_pin, confirm_pin))
+    gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), TRUE);
+}
+
+
+static void
+cc_wwan_pin_entered_cb (CcWwanSimLockDialog *self)
+{
+  const gchar *pin;
+  gsize len;
+  gboolean enable_apply;
+
+  pin = gtk_entry_get_text (self->pin_entry);
+
+  if (!pin || !*pin)
+    {
+      gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE);
+      return;
+    }
+
+  len = strlen (pin);
+  enable_apply = len >= PIN_MINIMUM_LENGTH && len <= PIN_MAXIMUM_LENGTH;
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), enable_apply);
+}
+
+static void
+cc_wwan_sim_lock_dialog_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)object;
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      self->device = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_sim_lock_dialog_show (GtkWidget *widget)
+{
+  CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)widget;
+  gboolean lock_enabled;
+
+  gtk_entry_set_text (self->pin_entry, "");
+  gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE);
+
+  lock_enabled = cc_wwan_device_get_sim_lock (self->device);
+  g_object_set (self->lock_row, "active", lock_enabled, NULL);
+  gtk_widget_set_visible (GTK_WIDGET (self->lock_change_grid), lock_enabled);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), FALSE);
+  gtk_stack_set_visible_child (self->button_stack,
+                               GTK_WIDGET (self->next_button));
+  gtk_button_set_label (self->apply_button, _("_Set"));
+
+  gtk_stack_set_visible_child_name (self->pin_settings_stack, "pin-settings");
+
+  gtk_entry_set_text (self->pin_entry, "");
+  gtk_entry_set_text (self->new_pin_entry, "");
+  gtk_entry_set_text (self->pin_confirm_entry, "");
+
+  GTK_WIDGET_CLASS (cc_wwan_sim_lock_dialog_parent_class)->show (widget);
+}
+
+static void
+cc_wwan_sim_lock_dialog_dispose (GObject *object)
+{
+  CcWwanSimLockDialog *self = (CcWwanSimLockDialog *)object;
+
+  g_clear_object (&self->device);
+
+  G_OBJECT_CLASS (cc_wwan_sim_lock_dialog_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_sim_lock_dialog_class_init (CcWwanSimLockDialogClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = cc_wwan_sim_lock_dialog_set_property;
+  object_class->dispose = cc_wwan_sim_lock_dialog_dispose;
+
+  widget_class->show = cc_wwan_sim_lock_dialog_show;
+
+  properties[PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "The WWAN Device",
+                         CC_TYPE_WWAN_DEVICE,
+                         G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/control-center/wwan/cc-wwan-sim-lock-dialog.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, apply_button);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, button_stack);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, lock_change_grid);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, lock_row);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, new_pin_entry);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, next_button);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_confirm_entry);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_entry);
+  gtk_widget_class_bind_template_child (widget_class, CcWwanSimLockDialog, pin_settings_stack);
+
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_sim_lock_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_next_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_apply_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entry_text_inserted_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entry_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, cc_wwan_pin_entered_cb);
+}
+
+static void
+cc_wwan_sim_lock_dialog_init (CcWwanSimLockDialog *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcWwanSimLockDialog *
+cc_wwan_sim_lock_dialog_new (GtkWindow    *parent_window,
+                             CcWwanDevice *device)
+{
+  g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), NULL);
+
+  return g_object_new (CC_TYPE_WWAN_SIM_LOCK_DIALOG,
+                       "transient-for", parent_window,
+                       "use-header-bar", 1,
+                       "device", device,
+                       NULL);
+}
diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.h b/panels/wwan/cc-wwan-sim-lock-dialog.h
new file mode 100644
index 000000000..b6d1d5a9e
--- /dev/null
+++ b/panels/wwan/cc-wwan-sim-lock-dialog.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-sim-lock-dialog.h
+ *
+ * Copyright 2019 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 <handy.h>
+#include <shell/cc-panel.h>
+
+#include "cc-wwan-device.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WWAN_SIM_LOCK_DIALOG (cc_wwan_sim_lock_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanSimLockDialog, cc_wwan_sim_lock_dialog, CC, WWAN_SIM_LOCK_DIALOG, GtkDialog)
+
+CcWwanSimLockDialog *cc_wwan_sim_lock_dialog_new (GtkWindow    *parent_window,
+                                                  CcWwanDevice *device);
+
+G_END_DECLS
diff --git a/panels/wwan/cc-wwan-sim-lock-dialog.ui b/panels/wwan/cc-wwan-sim-lock-dialog.ui
new file mode 100644
index 000000000..48a946be4
--- /dev/null
+++ b/panels/wwan/cc-wwan-sim-lock-dialog.ui
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcWwanSimLockDialog" parent="GtkDialog">
+    <property name="default-height">480</property>
+    <property name="default-width">360</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="visible">1</property>
+        <property name="title" translatable="yes">SIM Lock</property>
+        <child>
+          <object class="GtkStack" id="button_stack">
+            <property name="visible">1</property>
+
+            <!-- Next Buttoon -->
+            <child>
+              <object class="GtkButton" id="next_button">
+                <property name="visible">1</property>
+                <property name="sensitive">0</property>
+                <property name="use-underline">1</property>
+                <property name="label" translatable="yes">_Next</property>
+                <signal name="clicked" handler="cc_wwan_pin_next_clicked_cb" swapped="yes" />
+                <style>
+                 <class name="suggested-action" />
+                </style>
+              </object>
+              <packing>
+                <property name="name">next</property>
+              </packing>
+            </child>
+
+            <!-- Apply button -->
+            <child>
+              <object class="GtkButton" id="apply_button">
+                <property name="visible">1</property>
+                <property name="use-underline">1</property>
+                <signal name="clicked" handler="cc_wwan_pin_apply_clicked_cb" swapped="yes" />
+                <style>
+                 <class name="suggested-action" />
+                </style>
+              </object>
+              <packing>
+                <property name="name">apply</property>
+              </packing>
+            </child>
+
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">1</property>
+        <property name="border-width">0</property>
+        <property name="width-request">340</property>
+        <property name="height-request">360</property>
+
+        <child>
+          <object class="HdyClamp">
+            <property name="visible">1</property>
+            <property name="margin-top">32</property>
+            <property name="margin-bottom">32</property>
+            <property name="margin-start">18</property>
+            <property name="margin-end">18</property>
+            <child>
+              <object class="GtkOverlay">
+                <property name="visible">1</property>
+                <child type="overlay">
+                  <object class="GtkRevealer" id="notification_revealer">
+                    <property name="visible">1</property>
+                    <property name="halign">center</property>
+                    <property name="valign">start</property>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">1</property>
+                        <property name="spacing">12</property>
+                        <style>
+                         <class name="frame" />
+                         <class name="app-notification" />
+                        </style>
+                        <child>
+                          <object class="GtkLabel" id="notification_label">
+                            <property name="visible">1</property>
+                            <property name="wrap">1</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="use-markup">1</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton">
+                            <property name="visible">1</property>
+                            <property name="relief">none</property>
+                            <!-- <signal name="clicked" handler="cc_wwan_on_notification_closed" 
object="CcWwanSimLockDialog" swapped="yes" /> -->
+                            <child internal-child="accessible">
+                              <object class="AtkObject">
+                                <property name="accessible-name" translatable="yes">Close</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="visible">1</property>
+                                <property name="icon-name">window-close-symbolic</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+
+                <child>
+                  <object class="GtkStack" id="pin_settings_stack">
+                    <property name="visible">1</property>
+                    <property name="transition-type">slide-left</property>
+
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">1</property>
+                        <property name="orientation">vertical</property>
+
+                        <!-- SIM Lock Switch -->
+                        <child>
+                          <object class="GtkListBox">
+                            <property name="visible">1</property>
+                            <property name="selection-mode">none</property>
+                            <property name="margin-bottom">18</property>
+                            <style>
+                              <class name="frame" />
+                            </style>
+                            <child>
+                              <object class="CcListRow" id="lock_row">
+                                <property name="visible">1</property>
+                                <property name="show-switch">1</property>
+                                <property name="use-underline">1</property>
+                                <property name="title" translatable="yes">_Lock SIM with PIN</property>
+                                <signal name="notify::active" handler="cc_wwan_sim_lock_changed_cb" 
swapped="yes" />
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+
+                        <child>
+                          <object class="GtkGrid" id="lock_change_grid">
+                            <property name="visible">0</property>
+                            <property name="row-spacing">18</property>
+                            <property name="column-spacing">12</property>
+
+                            <!-- SIM Lock Change Title -->
+                            <child>
+                              <object class="GtkLabel" id="lock_change_title">
+                                <property name="visible">1</property>
+                                <property name="label" translatable="yes">Change PIN</property>
+                                <property name="margin-bottom">9</property>
+                                <property name="xalign">0.0</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">0</property>
+                                <property name="width">2</property>
+                              </packing>
+                            </child>
+
+                            <!-- PIN Entry -->
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="label">New PIN</property>
+                                <property name="halign">end</property>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkEntry" id="new_pin_entry">
+                                <property name="visible">1</property>
+                                <property name="visibility">0</property>
+                                <property name="input-purpose">password</property>
+                                <property name="input-hints">no-emoji</property>
+                                <property name="max-length">8</property>
+                                <property name="max-width-chars">32</property>
+                                <signal name="notify::text" handler="cc_wwan_pin_entry_changed_cb" 
swapped="yes" />
+                                <signal name="insert-text" handler="cc_wwan_pin_entry_text_inserted_cb" 
swapped="yes" />
+                              </object>
+                              <packing>
+                                <property name="left-attach">1</property>
+                                <property name="top-attach">1</property>
+                              </packing>
+                            </child>
+
+                            <!-- Confirm PIN Entry -->
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="label">Confirm</property>
+                                <property name="halign">end</property>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">2</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkEntry" id="pin_confirm_entry">
+                                <property name="visible">1</property>
+                                <property name="visibility">0</property>
+                                <property name="input-purpose">password</property>
+                                <property name="input-hints">no-emoji</property>
+                                <property name="max-length">8</property>
+                                <property name="max-width-chars">32</property>
+                                <signal name="notify::text" handler="cc_wwan_pin_entry_changed_cb" 
swapped="yes" />
+                                <signal name="insert-text" handler="cc_wwan_pin_entry_changed_cb" 
swapped="yes" />
+                              </object>
+                              <packing>
+                                <property name="left-attach">1</property>
+                                <property name="top-attach">2</property>
+                              </packing>
+                            </child>
+
+                          </object>
+                        </child>
+
+                      </object>
+                      <packing>
+                        <property name="name">pin-settings</property>
+                      </packing>
+                    </child>
+
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">1</property>
+                        <property name="orientation">vertical</property>
+                        <property name="expand">1</property>
+                        <property name="valign">center</property>
+                        <property name="halign">center</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">1</property>
+                            <property name="pixel-size">128</property>
+                            <property name="icon-name">dialog-password-symbolic</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="">
+                            <property name="visible">1</property>
+                            <property name="label" translatable="yes">Enter current PIN to change SIM lock 
settings</property>
+                            <property name="margin-bottom">24</property>
+                            <property name="halign">center</property>
+                            <style>
+                              <class name="dim-label" />
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="pin_entry">
+                            <property name="visible">1</property>
+                            <property name="visibility">0</property>
+                            <property name="input-purpose">password</property>
+                            <property name="input-hints">no-emoji</property>
+                            <property name="max-length">8</property>
+                            <property name="max-width-chars">32</property>
+                            <signal name="notify::text" handler="cc_wwan_pin_entered_cb" swapped="yes" />
+                            <signal name="insert-text" handler="cc_wwan_pin_entry_text_inserted_cb" 
swapped="yes" />
+                            <!-- We have custom widgets and no actions, so "activates-default" won't work -->
+                            <signal name="activate" handler="cc_wwan_pin_apply_clicked_cb" swapped="yes" />
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="name">pin-entry</property>
+                      </packing>
+                    </child>
+
+                  </object> <!-- ./GtkStack pin_settings_stack -->
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child> <!-- ./internal-child -->
+
+    <child type="action">
+      <object class="GtkButton" id="cancel_button">
+        <property name="visible">1</property>
+        <property name="use-underline">1</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <signal name="clicked" handler="gtk_widget_hide" swapped="yes"/>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="cancel">cancel_button</action-widget>
+    </action-widgets>
+
+  </template>
+</interface>
diff --git a/panels/wwan/gnome-wwan-panel.desktop.in.in b/panels/wwan/gnome-wwan-panel.desktop.in.in
new file mode 100644
index 000000000..351a8edde
--- /dev/null
+++ b/panels/wwan/gnome-wwan-panel.desktop.in.in
@@ -0,0 +1,16 @@
+[Desktop Entry]
+Name=Mobile Network
+Comment=Configure Telephony and mobile data connections
+Exec=gnome-control-center wwan
+# FIXME
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=network-cellular-signal-excellent
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;X-GNOME-NetworkSettings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings;
+OnlyShowIn=GNOME;Unity;
+StartupNotify=true
+# Translators: Search terms to find the WWAN panel. Do NOT translate or localize the semicolons! The list 
MUST also end with a semicolon!
+Keywords=cellular;wwan;telephony;sim;mobile;
diff --git a/panels/wwan/meson.build b/panels/wwan/meson.build
new file mode 100644
index 000000000..8c1b02f26
--- /dev/null
+++ b/panels/wwan/meson.build
@@ -0,0 +1,61 @@
+gcr_dep = [dependency('gcr-3')]
+
+deps = common_deps + network_manager_deps + gcr_dep + [polkit_gobject_dep]
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+          input : desktop + '.in.in',
+         output : desktop + '.in',
+  configuration : desktop_conf
+)
+
+i18n.merge_file(
+       desktop,
+         type : 'desktop',
+        input : desktop_in,
+       output : desktop,
+       po_dir : po_dir,
+      install : true,
+  install_dir : control_center_desktopdir
+)
+
+sources = files(
+  'cc-wwan-panel.c',
+  'cc-wwan-device.c',
+  'cc-wwan-data.c',
+  'cc-wwan-device-page.c',
+  'cc-wwan-mode-dialog.c',
+  'cc-wwan-network-dialog.c',
+  'cc-wwan-details-dialog.c',
+  'cc-wwan-sim-lock-dialog.c',
+  'cc-wwan-apn-dialog.c',
+)
+
+resource_data = files(
+  'cc-wwan-panel.ui',
+  'cc-wwan-device-page.ui',
+  'cc-wwan-mode-dialog.ui',
+  'cc-wwan-network-dialog.ui',
+  'cc-wwan-details-dialog.ui',
+  'cc-wwan-sim-lock-dialog.ui',
+  'cc-wwan-apn-dialog.ui',
+)
+
+sources += gnome.compile_resources(
+  'cc-' + cappletname + '-resources',
+  cappletname + '.gresource.xml',
+        c_name : 'cc_' + cappletname,
+  dependencies : resource_data,
+        export : true
+)
+
+cflags += '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir)
+
+panels_libs += static_library(
+           cappletname,
+              sources : sources,
+  include_directories : [ top_inc, common_inc ],
+         dependencies : deps,
+               c_args : cflags
+)
diff --git a/panels/wwan/wwan.gresource.xml b/panels/wwan/wwan.gresource.xml
new file mode 100644
index 000000000..f128a164a
--- /dev/null
+++ b/panels/wwan/wwan.gresource.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/control-center/wwan">
+    <file preprocess="xml-stripblanks">cc-wwan-panel.ui</file>
+    <file preprocess="xml-stripblanks">cc-wwan-device-page.ui</file>
+    <file preprocess="xml-stripblanks">cc-wwan-mode-dialog.ui</file>
+    <file preprocess="xml-stripblanks">cc-wwan-network-dialog.ui</file>
+    <file preprocess="xml-stripblanks">cc-wwan-details-dialog.ui</file>
+    <file preprocess="xml-stripblanks">cc-wwan-sim-lock-dialog.ui</file>
+    <file preprocess="xml-stripblanks">cc-wwan-apn-dialog.ui</file>
+  </gresource>
+</gresources>
diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c
index fe536dbd9..c5532ba5b 100644
--- a/shell/cc-panel-list.c
+++ b/shell/cc-panel-list.c
@@ -381,6 +381,7 @@ static const gchar * const panel_order[] = {
   /* Main page */
   "wifi",
   "network",
+  "wwan",
   "mobile-broadband",
   "bluetooth",
   "background",
diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c
index fcbf398ca..67f3f1b46 100644
--- a/shell/cc-panel-loader.c
+++ b/shell/cc-panel-loader.c
@@ -65,6 +65,9 @@ extern GType cc_user_panel_get_type (void);
 #ifdef BUILD_WACOM
 extern GType cc_wacom_panel_get_type (void);
 #endif /* BUILD_WACOM */
+#ifdef BUILD_WWAN
+extern GType cc_wwan_panel_get_type (void);
+#endif /* BUILD_WWAN */
 extern GType cc_location_panel_get_type (void);
 extern GType cc_camera_panel_get_type (void);
 extern GType cc_microphone_panel_get_type (void);
@@ -80,6 +83,9 @@ extern void cc_wifi_panel_static_init_func (void);
 #ifdef BUILD_WACOM
 extern void cc_wacom_panel_static_init_func (void);
 #endif /* BUILD_WACOM */
+#ifdef BUILD_WWAN
+extern void cc_wwan_panel_static_init_func (void);
+#endif /* BUILD_WWAN */
 
 #define PANEL_TYPE(name, get_type, init_func) { name, get_type, init_func }
 
@@ -131,6 +137,9 @@ static CcPanelLoaderVtable default_panels[] =
 #ifdef BUILD_WACOM
   PANEL_TYPE("wacom",            cc_wacom_panel_get_type,                cc_wacom_panel_static_init_func),
 #endif
+#ifdef BUILD_WWAN
+  PANEL_TYPE("wwan",             cc_wwan_panel_get_type,                 cc_wwan_panel_static_init_func),
+#endif
 };
 
 /* Override for the panel vtable. When NULL, the default_panels will


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