diff --git a/JamiInstaller/Product.wxs b/JamiInstaller/Product.wxs
index fe6a02bd955103e9c0fd1eec357b06d88a985104..5c2987ec84316f510192d652646438f63a01825d 100644
--- a/JamiInstaller/Product.wxs
+++ b/JamiInstaller/Product.wxs
@@ -4,7 +4,7 @@
   <Product Id="*" Name="$(var.Name)" Language="1033" Version="$(fun.AutoVersion(1.0))" Manufacturer="$(var.Manufacturer)" UpgradeCode="7c45b52b-0390-4fe8-947a-3f13e82dd346">
     <Package InstallerVersion="301" Compressed="yes" InstallScope="perMachine" />
 
-    <MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
+    <MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes"/>
     <MediaTemplate EmbedCab="yes" CompressionLevel="high" />
 
     <!--Icon File should be in release folder(not wix project), otherwise cannot be read-->
@@ -12,6 +12,9 @@
     <Property Id="ARPPRODUCTICON" Value="icon.ico" />
     <Property Id="ARPNOMODIFY" Value="1" />
 
+    <!-- It seems that QtWebEngineProcess.exe versioning requires us to force reinstall. -->
+    <Property Id="REINSTALLMODE" Value="dm" />
+
     <Feature Id="ProductFeature" Title="Main" Level="1" Absent="disallow">
       <ComponentGroupRef Id="StandardComponents" Primary="yes" />
       <ComponentGroupRef Id="HeatGenerated" />
@@ -38,7 +41,13 @@
     <WixVariable Id="WixUIDialogBmp" Value="main-banner.bmp" />
     <WixVariable Id="WixUISupportPerUser" Value="0" />
 
-    <CustomAction Id="removeOldJamiFiles" Directory="APPLICATIONFOLDER" ExeCommand="cmd /c &quot;del vc_redist.x64.exe; del uninstall.exe; del WinSparkle.dll;&quot;" Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" />
+    <CustomAction Id="removeOldJamiFiles"
+                  Directory="APPLICATIONFOLDER"
+                  ExeCommand="cmd /c &quot;del vc_redist.x64.exe; del uninstall.exe; del WinSparkle.dll;&quot;"
+                  Execute="deferred"
+                  Return="ignore"
+                  HideTarget="no"
+                  Impersonate="no"/>
 
     <Property Id="QtExecCmdLine" Value='"[WindowsFolder]\System32\taskkill.exe" /F /IM QtWebEngineProcess.exe /IM Jami.exe'/>
     <CustomAction Id="JamiProcesses.TaskKill"
diff --git a/jami-qt.pro b/jami-qt.pro
index d3933c6e7bed8c4b2f275949937ed8a7522dace1..762114c51274b5109797b0a3077cb91f95828ba3 100644
--- a/jami-qt.pro
+++ b/jami-qt.pro
@@ -111,7 +111,9 @@ unix {
 
 # Input
 HEADERS += \
+        src/networkmanager.h \
         src/smartlistmodel.h \
+        src/updatemanager.h \
         src/utils.h \
         src/bannedlistmodel.h \
         src/version.h \
@@ -159,7 +161,9 @@ HEADERS += \
 SOURCES += \
         src/bannedlistmodel.cpp \
         src/accountlistmodel.cpp \
+        src/networkmanager.cpp \
         src/runguard.cpp \
+        src/updatemanager.cpp \
         src/webchathelpers.cpp \
         src/main.cpp \
         src/smartlistmodel.cpp \
diff --git a/src/MainApplicationWindow.qml b/src/MainApplicationWindow.qml
index 8d0da0d6f74fe4ec10ef3729e150fc1890d99808..632e0355a3340147621a5683b58606a55cf6f8d9 100644
--- a/src/MainApplicationWindow.qml
+++ b/src/MainApplicationWindow.qml
@@ -15,6 +15,8 @@ import "commoncomponents"
 ApplicationWindow {
     id: root
 
+    property ApplicationWindow appWindow: root
+
     AccountMigrationDialog{
         id: accountMigrationDialog
 
@@ -25,11 +27,11 @@ ApplicationWindow {
         }
     }
 
-    function close() {
+    function close(force = false) {
         // If we're in the onboarding wizard or 'MinimizeOnClose'
         // is set, then we can quit
-        if (!SettingsAdapter.getAppValue(Settings.MinimizeOnClose) ||
-            !UtilsAdapter.getAccountListSize()) {
+        if (force || !SettingsAdapter.getAppValue(Settings.MinimizeOnClose) ||
+                !UtilsAdapter.getAccountListSize()) {
             Qt.quit()
         } else {
             // hide to the systray
diff --git a/src/commoncomponents/SimpleMessageDialog.qml b/src/commoncomponents/SimpleMessageDialog.qml
index 01a0c72b09c0773f385c9348a762fb5599e34969..5ee049ffb95eac7fd35a815c0880b1ae44e5f68f 100644
--- a/src/commoncomponents/SimpleMessageDialog.qml
+++ b/src/commoncomponents/SimpleMessageDialog.qml
@@ -35,27 +35,29 @@ BaseDialog {
     property var buttonTitles: []
     property var buttonCallBacks: []
     property var buttonStyles: []
-    property alias description: descriptionText.text
+    property alias infoText: infoText.text
+    property alias innerContentData: innerContent.data
 
-    function openWithParameters(title, info) {
+    function openWithParameters(title, info = "") {
         root.title = title
-        descriptionText.text = info
+        if (info !== "")
+            root.infoText = info
         open()
     }
 
     contentItem: Rectangle {
-        id: simpleMessageDialogContentRect
+        id: container
 
         implicitWidth: Math.max(JamiTheme.preferredDialogWidth,
                                 buttonTitles.length * (JamiTheme.preferredFieldWidth / 2
                                 + JamiTheme.preferredMarginSize))
-        implicitHeight: JamiTheme.preferredDialogHeight / 2
+        implicitHeight: JamiTheme.preferredDialogHeight / 2 - JamiTheme.preferredMarginSize
 
         ColumnLayout {
             anchors.fill: parent
 
             Label {
-                id: descriptionText
+                id: infoText
 
                 Layout.alignment: Qt.AlignCenter
                 Layout.preferredWidth: JamiTheme.preferredDialogWidth - JamiTheme.preferredMarginSize
@@ -67,6 +69,13 @@ BaseDialog {
                 verticalAlignment: Text.AlignVCenter
             }
 
+            Item {
+                id: innerContent
+                Layout.topMargin: JamiTheme.preferredMarginSize / 2
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+            }
+
             RowLayout {
                 spacing: JamiTheme.preferredMarginSize
 
diff --git a/src/connectivitymonitor.cpp b/src/connectivitymonitor.cpp
index 04c048acd58e6fb3728faef2cd2e2b673f0df9c8..014faa4fc3dcd967c942c6523f4a28eef4493664 100644
--- a/src/connectivitymonitor.cpp
+++ b/src/connectivitymonitor.cpp
@@ -18,9 +18,8 @@
 
 #include "connectivitymonitor.h"
 
-#include <QDebug>
-
 #ifdef Q_OS_WIN
+#include <QDebug>
 #include <atlbase.h>
 #include <netlistmgr.h>
 
@@ -159,4 +158,4 @@ ConnectivityMonitor::~ConnectivityMonitor()
     destroy();
     CoUninitialize();
 }
-#endif // Q_OS_WIN
\ No newline at end of file
+#endif // Q_OS_WIN
diff --git a/src/connectivitymonitor.h b/src/connectivitymonitor.h
index 916b4666c00da6350aad16d8b48de111e2deef5a..f746669e8750d59aa9f755911aa7053c3ead88a9 100644
--- a/src/connectivitymonitor.h
+++ b/src/connectivitymonitor.h
@@ -21,10 +21,9 @@
 #include <QObject>
 
 #ifdef Q_OS_WIN
-class ConnectivityMonitor : public QObject
+class ConnectivityMonitor final : public QObject
 {
     Q_OBJECT
-
 public:
     explicit ConnectivityMonitor(QObject* parent = 0);
     ~ConnectivityMonitor();
@@ -43,4 +42,22 @@ private:
     class NetworkEventHandler* netEventHandler_;
     unsigned long cookie_;
 };
-#endif // Q_OS_WIN
\ No newline at end of file
+
+#else
+// Dummy implementation for non-Windows platforms.
+// TODO: platform implementations should be in the daemon.
+
+// clang-format off
+class ConnectivityMonitor final : public QObject
+{
+    Q_OBJECT
+public:
+    explicit ConnectivityMonitor(QObject* parent = 0) : QObject(parent) {};
+    ~ConnectivityMonitor() = default;
+
+    bool isOnline() { return false; };
+signals:
+    void connectivityChanged();
+};
+// clang-format on
+#endif // Q_OS_WIN
diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml
index e3a2df84470c0281807d94c5ee1ed5a3e9cd8309..19a0d1d155a6f6bfa6fa23a9d721fad555b95b3f 100644
--- a/src/constant/JamiStrings.qml
+++ b/src/constant/JamiStrings.qml
@@ -268,9 +268,22 @@ Item {
     property string tipChooseDownloadFolder: qsTr("Choose download directory")
     property string recordCall: qsTr("Record call")
 
-    // UpdateSettings
+    // Updates
     property string betaInstall: qsTr("Install beta version")
+    property string checkForUpdates: qsTr("Check for updates now")
     property string enableAutoUpdates: qsTr("Enable/Disable automatic updates")
+    property string tipAutoUpdate: qsTr("toggle automatic updates")
+    property string updatesTitle: qsTr("Updates")
+    property string updateDialogTitle: qsTr("Update")
+    property string updateFound: qsTr("A new version of Jami was found\n Would you like to update now?")
+    property string updateNotFound: qsTr("No new version of Jami was found")
+    property string updateCheckError: qsTr("An error occured when checking for a new version")
+    property string updateDownloadNetworkError: qsTr("Installer download failed due to a network error")
+    property string updateDownloadCanceled: qsTr("Installer download canceled")
+    property string updateDownloading: "Downloading"
+    property string confirmBeta: qsTr("This will uninstall your current Release version and you can always download the latest Release version on our website")
+    property string networkDisconnected: qsTr("Network disconnected")
+    property string genericError: qsTr("Something went wrong")
 
     // Recording Settings
     property string tipRecordFolder: qsTr("Select a record directory")
@@ -365,4 +378,8 @@ Item {
 
     // Update settings
     property string update: qsTr("Automatically check for updates")
+
+    // Generic dialog options
+    property string optionOk: qsTr("Ok")
+    property string optionCancel: qsTr("Cancel")
 }
diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml
index b4b7bf2907185b8d1c2ff726dae12dfe7d150765..b9a3707f2f107a5128aa237512226d63801c1f86 100644
--- a/src/constant/JamiTheme.qml
+++ b/src/constant/JamiTheme.qml
@@ -32,7 +32,7 @@ Item {
     property string notificationRed: "#ff3b30"
     property string unPresenceOrange: "orange"
     property string backgroundColor: lightGrey_
-    property string backgroundDarkColor: lightGreyTab_
+    property string backgroundDarkColor: rgb256(220, 220, 220)
 
     property string screenSelectionBorderGreen: "green"
 
@@ -97,28 +97,21 @@ Item {
     property int preferredDialogWidth: 400
     property int preferredDialogHeight: 300
 
+    // Misc.
+    property color white: "white"
+    property color darkGrey: rgb256(63, 63, 63)
+
     // Jami theme colors
     function rgb256(r, g, b) {
-        return Qt.rgba(r / 256, g / 256, b / 256, 1)
+        return Qt.rgba(r / 255, g / 255, b / 255, 1.0)
     }
 
-    property color blue_: "#109ede"
     property color wizardBlueButtons: "#28b1ed"
     property color blueLogo_: rgb256(0, 7, 71)
-    property color lightBlue_: "#c1ebf0"
     property color lightGrey_: rgb256(242, 242, 242)
-    property color lightGreyTab_: rgb256(220, 220, 220)
-    property color imGrey_: "#dedee0"
-    property color imBlue_: "#cfebf5"
-    property color lightBlack_: rgb256(63, 63, 63)
     property color grey_: rgb256(160, 160, 160)
     property color red_: rgb256(251, 72, 71)
-    property color lightRed_: rgb256(252, 91, 90)
-    property color darkRed_: rgb256(219, 55, 54)
-    property color notificationRed_: rgb256(255, 59, 48)
     property color urgentOrange_: rgb256(255, 165, 0)
     property color green_: rgb256(127, 255, 0)
     property color presenceGreen_: rgb256(76, 217, 100)
-    property color smartlistSelection_: rgb256(240, 240, 240)
-    property color smartlistHighlight_: rgb256(245, 245, 245)
 }
diff --git a/src/lrcinstance.h b/src/lrcinstance.h
index 145cb6bf5d4aa383e8a3a3a644cd25d8f772d294..ebebd20da893a49e52666e74d44f431146331430 100644
--- a/src/lrcinstance.h
+++ b/src/lrcinstance.h
@@ -25,6 +25,7 @@
 #endif
 
 #include "accountlistmodel.h"
+#include "updatemanager.h"
 #include "rendermanager.h"
 #include "appsettingsmanager.h"
 #include "utils.h"
@@ -55,6 +56,8 @@
 
 #include <memory>
 
+class ConnectivityMonitor;
+
 using namespace lrc::api;
 
 using migrateCallback = std::function<void()>;
@@ -65,15 +68,21 @@ class LRCInstance : public QObject
     Q_OBJECT
 
 public:
-    static LRCInstance& instance(migrateCallback willMigrate = {}, migrateCallback didMigrate = {})
+    static LRCInstance& instance(migrateCallback willMigrate = {},
+                                 migrateCallback didMigrate = {},
+                                 const QString& updateUrl = {},
+                                 ConnectivityMonitor* connectivityMonitor = {})
     {
-        static LRCInstance instance_(willMigrate, didMigrate);
+        static LRCInstance instance_(willMigrate, didMigrate, updateUrl, connectivityMonitor);
         return instance_;
     }
 
-    static void init(migrateCallback willMigrate = {}, migrateCallback didMigrate = {})
+    static void init(migrateCallback willMigrate = {},
+                     migrateCallback didMigrate = {},
+                     const QString& updateUrl = {},
+                     ConnectivityMonitor* connectivityMonitor = {})
     {
-        instance(willMigrate, didMigrate);
+        instance(willMigrate, didMigrate, updateUrl, connectivityMonitor);
     }
 
     static Lrc& getAPI()
@@ -86,6 +95,11 @@ public:
         return instance().renderer_.get();
     }
 
+    static UpdateManager* getUpdateManager()
+    {
+        return instance().updateManager_.get();
+    }
+
     static void connectivityChanged()
     {
         instance().lrc_->connectivityChanged();
@@ -443,16 +457,22 @@ signals:
     void restoreAppRequested();
     void notificationClicked(bool forceToTop = false);
     void updateSmartList();
+    void quitEngineRequested();
 
 private:
-    LRCInstance(migrateCallback willMigrateCb = {}, migrateCallback didMigrateCb = {})
+    LRCInstance(migrateCallback willMigrateCb = {},
+                migrateCallback didMigrateCb = {},
+                const QString& updateUrl = {},
+                ConnectivityMonitor* connectivityMonitor = {})
     {
         lrc_ = std::make_unique<Lrc>(willMigrateCb, didMigrateCb);
         renderer_ = std::make_unique<RenderManager>(lrc_->getAVModel());
+        updateManager_ = std::make_unique<UpdateManager>(updateUrl, connectivityMonitor);
     };
 
     std::unique_ptr<Lrc> lrc_;
     std::unique_ptr<RenderManager> renderer_;
+    std::unique_ptr<UpdateManager> updateManager_;
     AccountListModel accountListModel_;
     QString selectedAccountId_;
     QString selectedConvUid_;
diff --git a/src/main.cpp b/src/main.cpp
index 12a7fbd4b90988f49de1f5e530a959f012878f41..20ca27dde922613ba6ce88dd62bb7612491ebb10 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -20,6 +20,7 @@
 
 #include "mainapplication.h"
 #include "runguard.h"
+#include "version.h"
 
 #include <QCryptographicHash>
 #include <QtWebEngine>
@@ -60,14 +61,12 @@ main(int argc, char* argv[])
     QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
     QApplication::setQuitOnLastWindowClosed(false);
     QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
+    QCoreApplication::setApplicationVersion(QString(VERSION_STRING));
     QApplication::setHighDpiScaleFactorRoundingPolicy(
         Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
     QtWebEngine::initialize();
 
-    // Allow QtWebEngine to load local resources.
-    char ARG_DISABLE_WEB_SECURITY[] = "--disable-web-security";
-    auto newArgv = parseInputArgument(argc, argv, ARG_DISABLE_WEB_SECURITY);
-    MainApplication app(argc, newArgv);
+    MainApplication app(argc, argv);
 
     /*
      * Runguard to make sure that only one instance runs at a time.
diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp
index e27965c1a99318e44d8d3c6b75b96e85ebbaffec..bdb6676fe560dc4ae2e869c91863177375c0c0d1 100644
--- a/src/mainapplication.cpp
+++ b/src/mainapplication.cpp
@@ -22,14 +22,18 @@
 #include "mainapplication.h"
 
 #include "appsettingsmanager.h"
-#include "globalinstances.h"
+#include "connectivitymonitor.h"
 #include "globalsystemtray.h"
 #include "qmlregister.h"
 #include "qrimageprovider.h"
 #include "pixbufmanipulator.h"
 #include "tintedbuttonimageprovider.h"
 
+#include "globalinstances.h"
+
 #include <QAction>
+#include <QCommandLineParser>
+#include <QCoreApplication>
 #include <QFontDatabase>
 #include <QMenu>
 #include <QQmlContext>
@@ -45,6 +49,15 @@
 #include <gnutls/gnutls.h>
 #endif
 
+namespace opts {
+// Keys used to store command-line options.
+constexpr static const char STARTMINIMIZED[] = "STARTMINIMIZED";
+constexpr static const char DEBUG[] = "DEBUG";
+constexpr static const char DEBUGCONSOLE[] = "DEBUGCONSOLE";
+constexpr static const char DEBUGFILE[] = "DEBUGFILE";
+constexpr static const char UPDATEURL[] = "UPDATEURL";
+} // namespace opts
+
 static void
 consoleDebug()
 {
@@ -106,6 +119,7 @@ fileDebug(QFile* debugFile)
 MainApplication::MainApplication(int& argc, char** argv)
     : QApplication(argc, argv)
     , engine_(new QQmlApplicationEngine())
+    , connectivityMonitor_(new ConnectivityMonitor(this))
 {
     QObject::connect(this, &QApplication::aboutToQuit, [this] { cleanup(); });
 }
@@ -120,10 +134,10 @@ MainApplication::init()
         setenv("QT_QPA_PLATFORMTHEME", "gtk3", true);
 #endif
 
-    for (auto string : QCoreApplication::arguments()) {
-        if (string == "-d" || string == "--debug") {
-            consoleDebug();
-        }
+    auto results = parseArguments();
+
+    if (results[opts::DEBUG].toBool()) {
+        consoleDebug();
     }
 
     Utils::removeOldVersions();
@@ -135,31 +149,33 @@ MainApplication::init()
 #endif
 
     GlobalInstances::setPixmapManipulator(std::make_unique<PixbufManipulator>());
-    initLrc();
-
-    initConnectivityMonitor();
+    initLrc(results[opts::UPDATEURL].toString(), connectivityMonitor_);
 
-#ifdef Q_OS_WINDOWS
-    QObject::connect(&LRCInstance::instance(), &LRCInstance::notificationClicked, [] {
-        for (QWindow* appWindow : qApp->allWindows()) {
-            if (appWindow->objectName().compare("mainViewWindow"))
-                continue;
-            // clang-format off
-            ::SetWindowPos((HWND) appWindow->winId(),
-                           HWND_TOPMOST, 0, 0, 0, 0,
-                           SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
-            ::SetWindowPos((HWND) appWindow->winId(),
-                           HWND_NOTOPMOST, 0, 0, 0, 0,
-                           SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
-            // clang-format on
-            return;
-        }
+#ifdef Q_OS_WIN
+    connect(connectivityMonitor_, &ConnectivityMonitor::connectivityChanged, [] {
+        LRCInstance::connectivityChanged();
     });
-#endif
+#endif // Q_OS_WIN
 
-    bool startMinimized {false};
-    parseArguments(startMinimized);
+    QObject::connect(
+        &LRCInstance::instance(),
+        &LRCInstance::quitEngineRequested,
+        this,
+        [this] { engine_->quit(); },
+        Qt::DirectConnection);
+
+    if (results[opts::DEBUGFILE].toBool()) {
+        debugFile_.reset(new QFile(getDebugFilePath()));
+        debugFile_->open(QIODevice::WriteOnly | QIODevice::Truncate);
+        debugFile_->close();
+        fileDebug(debugFile_.get());
+    }
 
+    if (results[opts::DEBUGCONSOLE].toBool()) {
+        vsConsoleDebug();
+    }
+
+    connectForceWindowToTop();
     initSettings();
     initSystray();
     initQmlEngine();
@@ -205,7 +221,7 @@ MainApplication::loadTranslations()
 }
 
 void
-MainApplication::initLrc()
+MainApplication::initLrc(const QString& downloadUrl, ConnectivityMonitor* cm)
 {
     /*
      * Init mainwindow and finish splash when mainwindow shows up.
@@ -226,51 +242,59 @@ MainApplication::initLrc()
                 std::this_thread::sleep_for(std::chrono::milliseconds(10));
             }
             isMigrating = false;
-        });
+        },
+        downloadUrl,
+        cm);
     LRCInstance::subscribeToDebugReceived();
     LRCInstance::getAPI().holdConferences = false;
 }
 
-void
-MainApplication::initConnectivityMonitor()
+const QVariantMap
+MainApplication::parseArguments()
 {
-#ifdef Q_OS_WIN
-    connectivityMonitor_.reset(new ConnectivityMonitor(this));
-    connect(connectivityMonitor_.get(), &ConnectivityMonitor::connectivityChanged, [] {
-        LRCInstance::connectivityChanged();
-    });
-#endif // Q_OS_WIN
-}
+    QVariantMap results;
+    QCommandLineParser parser;
+    parser.addHelpOption();
+    parser.addVersionOption();
+
+    QCommandLineOption minimizedOption(QStringList() << "m"
+                                                     << "minimized",
+                                       "Start minimized.");
+    parser.addOption(minimizedOption);
+
+    QCommandLineOption debugOption(QStringList() << "d"
+                                                 << "debug",
+                                   "Debug out.");
+    parser.addOption(debugOption);
 
-void
-MainApplication::parseArguments(bool& startMinimized)
-{
-    QString uri = "";
-
-    for (auto string : QCoreApplication::arguments()) {
-        if (string.startsWith("jami:")) {
-            uri = string;
-        } else {
-            if (string == "-m" || string == "--minimized") {
-                startMinimized = true;
-            }
 #ifdef Q_OS_WINDOWS
-            debugFile_.reset(new QFile(getDebugFilePath()));
-            auto dbgFile = string == "-f" || string == "--file";
-            auto dbgConsole = string == "-c" || string == "--vsconsole";
-            if (dbgFile || dbgConsole) {
-                if (dbgFile) {
-                    debugFile_->open(QIODevice::WriteOnly | QIODevice::Truncate);
-                    debugFile_->close();
-                    fileDebug(debugFile_.get());
-                }
-                if (dbgConsole) {
-                    vsConsoleDebug();
-                }
-            }
+    QCommandLineOption debugConsoleOption(QStringList() << "c"
+                                                        << "console",
+                                          "Debug out to IDE console.");
+    parser.addOption(debugConsoleOption);
+
+    QCommandLineOption debugFileOption(QStringList() << "f"
+                                                     << "file",
+                                       "Debug to file.");
+    parser.addOption(debugFileOption);
+
+    QCommandLineOption updateUrlOption(QStringList() << "u"
+                                                     << "url",
+                                       "Reference <url> for client versioning.",
+                                       "url");
+    parser.addOption(updateUrlOption);
 #endif
-        }
-    }
+
+    parser.process(*this);
+
+    results[opts::STARTMINIMIZED] = parser.isSet(minimizedOption);
+    results[opts::DEBUG] = parser.isSet(debugOption);
+#ifdef Q_OS_WINDOWS
+    results[opts::DEBUGCONSOLE] = parser.isSet(debugConsoleOption);
+    results[opts::DEBUGFILE] = parser.isSet(debugFileOption);
+    results[opts::UPDATEURL] = parser.value(updateUrlOption);
+#endif
+    return results;
 }
 
 void
@@ -333,3 +357,25 @@ MainApplication::cleanup()
 #endif
     QApplication::exit(0);
 }
+
+void
+MainApplication::connectForceWindowToTop()
+{
+#ifdef Q_OS_WINDOWS
+    QObject::connect(&LRCInstance::instance(), &LRCInstance::notificationClicked, [] {
+        for (QWindow* appWindow : qApp->allWindows()) {
+            if (appWindow->objectName().compare("mainViewWindow"))
+                continue;
+            // clang-format off
+            ::SetWindowPos((HWND) appWindow->winId(),
+                           HWND_TOPMOST, 0, 0, 0, 0,
+                           SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+            ::SetWindowPos((HWND) appWindow->winId(),
+                           HWND_NOTOPMOST, 0, 0, 0, 0,
+                           SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+            // clang-format on
+            return;
+        }
+    });
+#endif
+}
diff --git a/src/mainapplication.h b/src/mainapplication.h
index fd72c947bd650c882a4ac12029a26ad19705ddf1..5c3585e9658ad4e6270d1638804a4302294f1b3e 100644
--- a/src/mainapplication.h
+++ b/src/mainapplication.h
@@ -1,4 +1,4 @@
-/*
+/*!
  * Copyright (C) 2020 by Savoir-faire Linux
  * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
  * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
@@ -20,8 +20,6 @@
 
 #pragma once
 
-#include "connectivitymonitor.h"
-
 #include <QFile>
 #include <QApplication>
 #include <QQmlApplicationEngine>
@@ -29,6 +27,8 @@
 
 #include <memory>
 
+class ConnectivityMonitor;
+
 class MainApplication : public QApplication
 {
     Q_OBJECT
@@ -41,19 +41,17 @@ public:
 
 private:
     void loadTranslations();
-    void initLrc();
-    void initConnectivityMonitor();
-    void parseArguments(bool& startMinimized);
+    void initLrc(const QString& downloadUrl, ConnectivityMonitor* cm);
+    const QVariantMap parseArguments();
     void setApplicationFont();
     void initQmlEngine();
     void initSettings();
     void initSystray();
     void cleanup();
+    void connectForceWindowToTop();
 
 private:
-#ifdef Q_OS_WIN
-    QScopedPointer<ConnectivityMonitor> connectivityMonitor_;
-#endif // Q_OS_WIN
     QScopedPointer<QFile> debugFile_;
     QQmlApplicationEngine* engine_;
+    ConnectivityMonitor* connectivityMonitor_;
 };
diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9f4a3f7337495d116829096ef062fa1f0652928b
--- /dev/null
+++ b/src/networkmanager.cpp
@@ -0,0 +1,166 @@
+/*!
+ * Copyright (C) 2019-2020 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * 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/>.
+ */
+
+#include "networkmanager.h"
+
+#include "connectivitymonitor.h"
+#include "utils.h"
+
+#include <QMetaEnum>
+#include <QtNetwork>
+
+NetWorkManager::NetWorkManager(ConnectivityMonitor* cm, QObject* parent)
+    : QObject(parent)
+    , manager_(new QNetworkAccessManager(this))
+    , reply_(nullptr)
+    , connectivityMonitor_(cm)
+{
+    emit statusChanged(GetStatus::IDLE);
+    connect(connectivityMonitor_, &ConnectivityMonitor::connectivityChanged, [this] {
+        auto connected = connectivityMonitor_->isOnline();
+        if (connected && !lastConnectionState_) {
+            manager_->deleteLater();
+            manager_ = new QNetworkAccessManager(this);
+            qWarning() << "connectivity changed, reset QNetworkAccessManager";
+        }
+        lastConnectionState_ = connected;
+    });
+}
+
+void
+NetWorkManager::get(const QUrl& url, const DoneCallBack& doneCb, const QString& path)
+{
+    if (!connectivityMonitor_->isOnline()) {
+        emit errorOccured(GetError::DISCONNECTED);
+        return;
+    }
+
+    if (reply_ && reply_->isRunning()) {
+        qWarning() << Q_FUNC_INFO << "currently downloading";
+        return;
+    } else if (url.isEmpty()) {
+        qWarning() << Q_FUNC_INFO << "missing url";
+        return;
+    }
+
+    if (!path.isEmpty()) {
+        QFileInfo fileInfo(url.path());
+        QString fileName = fileInfo.fileName();
+
+        file_.reset(new QFile(path + "/" + fileName));
+        if (!file_->open(QIODevice::WriteOnly)) {
+            emit errorOccured(GetError::ACCESS_DENIED);
+            file_.reset(nullptr);
+            return;
+        }
+    }
+
+    QNetworkRequest request(url);
+    reply_ = manager_->get(request);
+
+    emit statusChanged(GetStatus::STARTED);
+
+    connect(reply_,
+            QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred),
+            [this, doneCb, path](QNetworkReply::NetworkError error) {
+                reply_->disconnect();
+                reset(true);
+                qWarning() << Q_FUNC_INFO << "NetworkError: "
+                           << QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(error);
+                emit errorOccured(GetError::NETWORK_ERROR);
+            });
+
+    connect(reply_, &QNetworkReply::finished, [this, doneCb, path]() {
+        reply_->disconnect();
+        QString response = {};
+        if (path.isEmpty())
+            response = QString(reply_->readAll());
+        reset(!path.isEmpty());
+        emit statusChanged(GetStatus::FINISHED);
+        if (doneCb)
+            doneCb(response);
+    });
+
+    connect(reply_,
+            &QNetworkReply::downloadProgress,
+            this,
+            &NetWorkManager::downloadProgressChanged);
+
+    connect(reply_, &QNetworkReply::readyRead, this, &NetWorkManager::onHttpReadyRead);
+
+#if QT_CONFIG(ssl)
+    connect(reply_,
+            SIGNAL(sslErrors(const QList<QSslError>&)),
+            this,
+            SLOT(onSslErrors(QList<QSslError>)),
+            Qt::UniqueConnection);
+#endif
+}
+
+void
+NetWorkManager::reset(bool flush)
+{
+    reply_->deleteLater();
+    reply_ = nullptr;
+    if (file_ && flush) {
+        file_->flush();
+        file_->close();
+        file_.reset(nullptr);
+    }
+}
+
+void
+NetWorkManager::onSslErrors(const QList<QSslError>& sslErrors)
+{
+#if QT_CONFIG(ssl)
+    QString errorsString;
+    for (const QSslError& error : sslErrors) {
+        if (errorsString.length() > 0) {
+            errorsString += "\n";
+        }
+        errorsString += error.errorString();
+    }
+    emit errorOccured(GetError::SSL_ERROR, errorsString);
+    return;
+#else
+    Q_UNUSED(sslErrors);
+#endif
+}
+
+void
+NetWorkManager::onHttpReadyRead()
+{
+    /*
+     * This slot gets called every time the QNetworkReply has new data.
+     * We read all of its new data and write it into the file.
+     * That way we use less RAM than when reading it at the finished()
+     * signal of the QNetworkReply
+     */
+    if (file_)
+        file_->write(reply_->readAll());
+}
+
+void
+NetWorkManager::cancelRequest()
+{
+    if (reply_) {
+        reply_->abort();
+        emit errorOccured(GetError::CANCELED);
+    }
+}
diff --git a/src/networkmanager.h b/src/networkmanager.h
new file mode 100644
index 0000000000000000000000000000000000000000..b5ddab5f18626a0a1d0d4e2528fe6c9957aaa01c
--- /dev/null
+++ b/src/networkmanager.h
@@ -0,0 +1,76 @@
+/*!
+ * Copyright (C) 2019-2020 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QFile>
+#include <QSslError>
+#include <QNetworkReply>
+
+class QNetworkAccessManager;
+class ConnectivityMonitor;
+
+class NetWorkManager : public QObject
+{
+    Q_OBJECT
+public:
+    explicit NetWorkManager(ConnectivityMonitor* cm, QObject* parent = nullptr);
+    virtual ~NetWorkManager() = default;
+
+    enum GetStatus { IDLE, STARTED, FINISHED };
+
+    enum GetError { DISCONNECTED, NETWORK_ERROR, ACCESS_DENIED, SSL_ERROR, CANCELED };
+    Q_ENUM(GetError)
+
+    using DoneCallBack = std::function<void(const QString&)>;
+
+    /*!
+     * using qt get request to store the reply in file
+     * @param url - network address
+     * @param doneCb - done callback
+     * @param path - optional file saving path, if empty
+     * a string will be passed as the second paramter of doneCb
+     */
+    void get(const QUrl& url, const DoneCallBack& doneCb = {}, const QString& path = {});
+
+    /*!
+     * manually abort the current request
+     */
+    Q_INVOKABLE void cancelRequest();
+
+signals:
+    void statusChanged(GetStatus error);
+    void downloadProgressChanged(qint64 bytesRead, qint64 totalBytes);
+    void errorOccured(GetError error, const QString& msg = {});
+
+private slots:
+    void onSslErrors(const QList<QSslError>& sslErrors);
+    void onHttpReadyRead();
+
+private:
+    void reset(bool flush = true);
+
+    QNetworkAccessManager* manager_;
+    QNetworkReply* reply_;
+    QScopedPointer<QFile> file_;
+    ConnectivityMonitor* connectivityMonitor_;
+    bool lastConnectionState_;
+};
+Q_DECLARE_METATYPE(NetWorkManager*)
diff --git a/src/qmlregister.cpp b/src/qmlregister.cpp
index 29ef8019b2778db9f472aadaa0383702f40edf77..5130a11f49d016989f7fd8a5b70fddeb489dfd59 100644
--- a/src/qmlregister.cpp
+++ b/src/qmlregister.cpp
@@ -35,6 +35,7 @@
 #include "mediahandleritemlistmodel.h"
 #include "messagesadapter.h"
 #include "namedirectory.h"
+#include "updatemanager.h"
 #include "preferenceitemlistmodel.h"
 #include "pluginitemlistmodel.h"
 #include "pluginlistpreferencemodel.h"
@@ -137,6 +138,7 @@ registerTypes()
     QML_REGISTERSINGLETONTYPE_CUSTOM("net.jami.Models", AVModel, 1, 0, &LRCInstance::avModel())
     QML_REGISTERSINGLETONTYPE_CUSTOM("net.jami.Models", PluginModel, 1, 0, &LRCInstance::pluginModel())
     QML_REGISTERSINGLETONTYPE_CUSTOM("net.jami.Models", RenderManager, 1, 0, LRCInstance::renderer())
+    QML_REGISTERSINGLETONTYPE_CUSTOM("net.jami.Models", UpdateManager, 1, 0, LRCInstance::getUpdateManager())
 
     /*
      * Qml singleton components
@@ -179,5 +181,6 @@ registerTypes()
      * Enums
      */
     QML_REGISTERUNCREATABLE("net.jami.Enums", Settings, 1, 0);
+    QML_REGISTERUNCREATABLE("net.jami.Enums", NetWorkManager, 1, 0);
 }
 // clang-format on
diff --git a/src/settingsview/components/GeneralSettingsPage.qml b/src/settingsview/components/GeneralSettingsPage.qml
index 1dc91db2ac73026e594f680997e0c4c90dc8802d..be925d8266acfc0903f1818023b4cee2c2849d81 100644
--- a/src/settingsview/components/GeneralSettingsPage.qml
+++ b/src/settingsview/components/GeneralSettingsPage.qml
@@ -87,6 +87,7 @@ Rectangle {
                 UpdateSettings {
                     Layout.fillWidth: true
                     Layout.leftMargin: JamiTheme.preferredMarginSize
+                    Layout.rightMargin: JamiTheme.preferredMarginSize
                     Layout.bottomMargin: JamiTheme.preferredMarginSize
                     visible: Qt.platform.os == "windows"? true : false
                 }
diff --git a/src/settingsview/components/LinkedDevices.qml b/src/settingsview/components/LinkedDevices.qml
index 759843d23861ec0a4adeb23ebe5b1faa1dd38a1d..85434d235b9e5bd5d1d49cda8092eb3590a0161e 100644
--- a/src/settingsview/components/LinkedDevices.qml
+++ b/src/settingsview/components/LinkedDevices.qml
@@ -94,7 +94,7 @@ ColumnLayout {
         property string idOfDev: ""
 
         title: qsTr("Remove Device")
-        description: qsTr("Are you sure you wish to remove this device?")
+        infoText: qsTr("Are you sure you wish to remove this device?")
 
         buttonTitles: [qsTr("Ok"), qsTr("Cancel")]
         buttonStyles: [SimpleMessageDialog.ButtonStyle.TintedBlue,
diff --git a/src/settingsview/components/RecordingSettings.qml b/src/settingsview/components/RecordingSettings.qml
index 879c7496f6e031b79da811c68abb8d6b82e41212..897163d49076738ea2d3fa78c2aea9173085d9b6 100644
--- a/src/settingsview/components/RecordingSettings.qml
+++ b/src/settingsview/components/RecordingSettings.qml
@@ -72,6 +72,7 @@ ColumnLayout {
 
     ToggleSwitch {
         id: alwaysRecordingCheckBox
+
         Layout.fillWidth: true
         Layout.leftMargin: JamiTheme.preferredMarginSize
 
@@ -85,6 +86,7 @@ ColumnLayout {
 
     ToggleSwitch {
         id: recordPreviewCheckBox
+
         Layout.fillWidth: true
         Layout.leftMargin: JamiTheme.preferredMarginSize
 
@@ -185,4 +187,4 @@ ColumnLayout {
             onClicked: recordPathDialog.open()
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/settingsview/components/UpdateSettings.qml b/src/settingsview/components/UpdateSettings.qml
index 21e6b8efb7ca222a56d63808149664b02a12acd2..849e8e6daeab279d03423c028131854e3c6ec505 100644
--- a/src/settingsview/components/UpdateSettings.qml
+++ b/src/settingsview/components/UpdateSettings.qml
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2020 by Savoir-faire Linux
  * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
  *
  * 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
@@ -17,30 +18,23 @@
  */
 
 import QtQuick 2.15
-import QtQuick.Window 2.14
 import QtQuick.Controls 2.15
+import QtQuick.Controls.Styles 1.4
 import QtQuick.Controls.Universal 2.12
 import QtQuick.Layouts 1.3
-import QtGraphicalEffects 1.14
-import QtQuick.Controls.Styles 1.4
-import net.jami.Models 1.0
 import net.jami.Adapters 1.0
-import Qt.labs.platform 1.1
 import net.jami.Enums 1.0
+import net.jami.Models 1.0
 import "../../commoncomponents"
 
 ColumnLayout {
     id: root
 
-    //TODO: complete check for update and check for Beta slot functions
-    function checkForUpdateSlot() {}
-    function installBetaSlot() {}
-
     Label {
         Layout.fillWidth: true
         Layout.preferredHeight: JamiTheme.preferredFieldHeight
 
-        text: qsTr("Updates")
+        text: JamiStrings.updatesTitle
         font.pointSize: JamiTheme.headerFontSize
         font.kerning: true
 
@@ -51,48 +45,205 @@ ColumnLayout {
     ToggleSwitch {
         id: autoUpdateCheckBox
 
+        Layout.fillWidth: true
+        Layout.leftMargin: JamiTheme.preferredMarginSize
+
         checked: SettingsAdapter.getAppValue(Settings.Key.AutoUpdate)
-        labelText: JamiStrings.update
-        fontPointSize: JamiTheme.settingsFontSize
 
+        labelText: JamiStrings.update
         tooltipText: JamiStrings.enableAutoUpdates
+        fontPointSize: JamiTheme.settingsFontSize
 
-        onSwitchToggled: SettingsAdapter.setAppValue(Settings.Key.AutoUpdate, checked)
+        onSwitchToggled: {
+            SettingsAdapter.setAppValue(Settings.Key.AutoUpdate, checked)
+            UpdateManager.setAutoUpdateCheck(checked)
+        }
     }
 
-    HoverableRadiusButton {
+    MaterialButton {
         id: checkUpdateButton
 
         Layout.alignment: Qt.AlignHCenter
         Layout.preferredWidth: JamiTheme.preferredFieldWidth
         Layout.preferredHeight: JamiTheme.preferredFieldHeight
 
-        radius: height / 2
+        color: enabled? JamiTheme.buttonTintedBlack : JamiTheme.buttonTintedGrey
+        hoveredColor: JamiTheme.buttonTintedBlackHovered
+        pressedColor: JamiTheme.buttonTintedBlackPressed
+        outlined: true
 
-        toolTipText: qsTr("Check for updates now")
-        text: qsTr("Updates")
-        fontPointSize: JamiTheme.buttonFontSize
+        toolTipText: JamiStrings.checkForUpdates
+        text: JamiStrings.checkForUpdates
 
-        onClicked: {
-            checkForUpdateSlot()
-        }
+        onClicked: UpdateManager.checkForUpdates()
     }
 
-    HoverableRadiusButton {
+    MaterialButton {
         id: installBetaButton
 
         Layout.alignment: Qt.AlignHCenter
         Layout.preferredWidth: JamiTheme.preferredFieldWidth
         Layout.preferredHeight: JamiTheme.preferredFieldHeight
 
-        radius: height / 2
+        color: enabled? JamiTheme.buttonTintedBlack : JamiTheme.buttonTintedGrey
+        hoveredColor: JamiTheme.buttonTintedBlackHovered
+        pressedColor: JamiTheme.buttonTintedBlackPressed
+        outlined: true
 
-        toolTipText: qsTr("Install the latest beta version")
+        toolTipText: JamiStrings.betaInstall
         text: JamiStrings.betaInstall
-        fontPointSize: JamiTheme.buttonFontSize
 
         onClicked: {
-            installBetaSlot()
+            confirmInstallDialog.beta = true
+            confirmInstallDialog.openWithParameters(JamiStrings.updateDialogTitle,
+                                                    JamiStrings.confirmBeta)
+        }
+    }
+
+    Component.onCompleted: {
+        // Quiet check for updates on start if set to.
+        if (SettingsAdapter.getAppValue(Settings.AutoUpdate)) {
+            UpdateManager.checkForUpdates(true)
+            UpdateManager.setAutoUpdateCheck(true)
+        }
+    }
+
+    Connections {
+        target: UpdateManager
+
+        function errorToString(error) {
+            switch(error){
+            case NetWorkManager.ACCESS_DENIED:
+                return JamiStrings.genericError
+            case NetWorkManager.DISCONNECTED:
+                return JamiStrings.networkDisconnected
+            case NetWorkManager.NETWORK_ERROR:
+            case NetWorkManager.SSL_ERROR:
+                return JamiStrings.updateDownloadNetworkError
+            case NetWorkManager.CANCELED:
+                return JamiStrings.updateDownloadCanceled
+            default: return {}
+            }
+        }
+
+        function onUpdateCheckReplyReceived(ok, found) {
+            if (!ok) {
+                issueDialog.openWithParameters(JamiStrings.updateDialogTitle,
+                                               JamiStrings.updateCheckError)
+                return
+            }
+            if (!found) {
+                issueDialog.openWithParameters(JamiStrings.updateDialogTitle,
+                                               JamiStrings.updateNotFound)
+            } else {
+                confirmInstallDialog.openWithParameters(JamiStrings.updateDialogTitle,
+                                                        JamiStrings.updateFound)
+            }
+        }
+
+        function onUpdateCheckErrorOccurred(error) {
+            issueDialog.openWithParameters(JamiStrings.updateDialogTitle,
+                                           errorToString(error))
+        }
+
+        function onUpdateDownloadStarted() {
+            downloadDialog.setDownloadProgress(0, 0)
+            downloadDialog.openWithParameters(JamiStrings.updateDialogTitle)
+        }
+
+        function onUpdateDownloadProgressChanged(bytesRead, totalBytes) {
+            downloadDialog.setDownloadProgress(bytesRead, totalBytes)
+        }
+
+        function onUpdateDownloadErrorOccurred(error) {
+            downloadDialog.close()
+            issueDialog.openWithParameters(JamiStrings.updateDialogTitle,
+                                           errorToString(error))
         }
+
+        function onUpdateDownloadFinished() { downloadDialog.close() }
+    }
+
+    SimpleMessageDialog {
+        id: confirmInstallDialog
+
+        property bool beta: false
+
+        buttonTitles: [JamiStrings.optionOk, JamiStrings.optionCancel]
+        buttonStyles: [
+            SimpleMessageDialog.ButtonStyle.TintedBlue,
+            SimpleMessageDialog.ButtonStyle.TintedBlue
+        ]
+        buttonCallBacks: [function() {UpdateManager.applyUpdates(beta)}]
+    }
+
+    SimpleMessageDialog {
+        id: issueDialog
+
+        buttonTitles: [JamiStrings.optionOk]
+        buttonStyles: [SimpleMessageDialog.ButtonStyle.TintedBlue]
+        buttonCallBacks: []
+    }
+
+    SimpleMessageDialog {
+        id: downloadDialog
+
+        property int bytesRead: 0
+        property int totalBytes: 0
+        property string hSizeRead:  UtilsAdapter.humanFileSize(bytesRead)
+        property string hTotalBytes: UtilsAdapter.humanFileSize(totalBytes)
+        property alias progressBarValue: progressBar.value
+
+        function setDownloadProgress(bytesRead, totalBytes) {
+            downloadDialog.bytesRead = bytesRead
+            downloadDialog.totalBytes = totalBytes
+        }
+
+        infoText: JamiStrings.updateDownloading +
+                  " (%1 / %2)".arg(hSizeRead).arg(hTotalBytes)
+
+        innerContentData: ProgressBar {
+            id: progressBar
+
+            value: downloadDialog.bytesRead /
+                   downloadDialog.totalBytes
+
+            anchors.left: parent.left
+            anchors.leftMargin: JamiTheme.preferredMarginSize
+            anchors.right: parent.right
+            anchors.rightMargin: JamiTheme.preferredMarginSize
+
+            background: Rectangle {
+                implicitWidth: parent.width
+                implicitHeight: 24
+                color: JamiTheme.darkGrey
+            }
+
+            contentItem: Item {
+                implicitWidth: parent.width
+                implicitHeight: 22
+
+                Rectangle {
+                    width: progressBar.visualPosition * parent.width
+                    height: parent.height
+                    color: JamiTheme.selectionBlue
+                }
+                Label {
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    anchors.verticalCenter: parent.verticalCenter
+
+                    color: JamiTheme.white
+                    font.bold: true
+                    font.pointSize: JamiTheme.textFontSize + 1
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                    text: Math.ceil(progressBar.value * 100).toString() + "%"
+                }
+            }
+        }
+
+        buttonTitles: [JamiStrings.optionCancel]
+        buttonStyles: [SimpleMessageDialog.ButtonStyle.TintedBlue]
+        buttonCallBacks: [function() {UpdateManager.cancelUpdate()}]
     }
-}
\ No newline at end of file
+}
diff --git a/src/updatemanager.cpp b/src/updatemanager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a72fa30d6f0492d2e9ce7e3665fca2c4708e557
--- /dev/null
+++ b/src/updatemanager.cpp
@@ -0,0 +1,159 @@
+/*!
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * 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/>.
+ */
+
+#include "updatemanager.h"
+
+#include "appsettingsmanager.h"
+#include "lrcinstance.h"
+#include "utils.h"
+#include "version.h"
+
+#include <QProcess>
+#include <QTimer>
+
+#ifdef BETA
+static constexpr bool isBeta = true;
+#else
+static constexpr bool isBeta = false;
+#endif
+
+static constexpr int updatePeriod = 1000 * 60 * 60 * 24; // one day in millis
+
+UpdateManager::UpdateManager(const QString& url, ConnectivityMonitor* cm, QObject* parent)
+    : NetWorkManager(cm, parent)
+    , baseUrl_(url.isEmpty() ? "https://dl.jami.net/windows" : url.toLatin1())
+    , tempPath_(Utils::WinGetEnv("TEMP"))
+    , updateTimer_(new QTimer(this))
+{
+    connect(updateTimer_, &QTimer::timeout, [this] {
+        // Quiet period update check.
+        checkForUpdates(true);
+    });
+}
+
+void
+UpdateManager::setAutoUpdateCheck(bool state)
+{
+    // Quiet check for updates periodically, if set to.
+    if (!state) {
+        updateTimer_->stop();
+        return;
+    }
+    updateTimer_->start(updatePeriod);
+}
+
+void
+UpdateManager::checkForUpdates(bool quiet)
+{
+    disconnect();
+
+    // Fail without UI if this is a programmatic check.
+    if (!quiet)
+        connect(this, &NetWorkManager::errorOccured, this, &UpdateManager::updateCheckErrorOccurred);
+
+    cleanUpdateFiles();
+    QUrl versionUrl {isBeta ? QUrl::fromEncoded(baseUrl_ + "/beta/version")
+                            : QUrl::fromEncoded(baseUrl_ + "/version")};
+
+    get(versionUrl, [this, quiet](const QString& latestVersionString) {
+        if (latestVersionString.isEmpty()) {
+            qWarning() << "Error checking version";
+            if (!quiet)
+                emit updateCheckReplyReceived(false);
+            return;
+        }
+        auto currentVersion = QString(VERSION_STRING).toULongLong();
+        auto latestVersion = latestVersionString.toULongLong();
+        qDebug() << "latest: " << latestVersion << " current: " << currentVersion;
+        if (latestVersion > currentVersion) {
+            qDebug() << "New version found";
+            emit updateCheckReplyReceived(true, true);
+        } else {
+            qDebug() << "No new version found";
+            if (!quiet)
+                emit updateCheckReplyReceived(true, false);
+        }
+    });
+}
+
+void
+UpdateManager::applyUpdates(bool beta)
+{
+    disconnect();
+    connect(this, &NetWorkManager::errorOccured, this, &UpdateManager::updateDownloadErrorOccurred);
+    connect(this, &NetWorkManager::statusChanged, [this](GetStatus status) {
+        switch (status) {
+        case GetStatus::STARTED:
+            connect(this,
+                    &NetWorkManager::downloadProgressChanged,
+                    this,
+                    &UpdateManager::updateDownloadProgressChanged);
+            emit updateDownloadStarted();
+            break;
+        case GetStatus::FINISHED:
+            emit updateDownloadFinished();
+            break;
+        default:
+            break;
+        }
+    });
+
+    QUrl downloadUrl {(beta || isBeta) ? QUrl::fromEncoded(baseUrl_ + "/beta/jami.beta.x64.msi")
+                                       : QUrl::fromEncoded(baseUrl_ + "/jami.release.x64.msi")};
+
+    get(
+        downloadUrl,
+        [this, downloadUrl](const QString&) {
+            LRCInstance::reset();
+            emit LRCInstance::instance().quitEngineRequested();
+            auto args = QString(" /passive /norestart WIXNONUILAUNCH=1");
+            QProcess process;
+            process.start("powershell ",
+                          QStringList() << tempPath_ + "\\" + downloadUrl.fileName() << "/L*V"
+                                        << tempPath_ + "\\jami_x64_install.log" + args);
+            process.waitForFinished();
+        },
+        tempPath_);
+}
+
+void
+UpdateManager::cancelUpdate()
+{
+    cancelRequest();
+}
+
+void
+UpdateManager::cleanUpdateFiles()
+{
+    /*
+     * Delete all logs and msi in the %TEMP% directory before launching.
+     */
+    QString dir = QString(Utils::WinGetEnv("TEMP"));
+    QDir log_dir(dir, {"jami*.log"});
+    for (const QString& filename : log_dir.entryList()) {
+        log_dir.remove(filename);
+    }
+    QDir msi_dir(dir, {"jami*.msi"});
+    for (const QString& filename : msi_dir.entryList()) {
+        msi_dir.remove(filename);
+    }
+    QDir version_dir(dir, {"version"});
+    for (const QString& filename : version_dir.entryList()) {
+        version_dir.remove(filename);
+    }
+}
diff --git a/src/updatemanager.h b/src/updatemanager.h
new file mode 100644
index 0000000000000000000000000000000000000000..ba9fa6475bfc834888c4759c8c8331402edd1ac3
--- /dev/null
+++ b/src/updatemanager.h
@@ -0,0 +1,54 @@
+/*!
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include "networkmanager.h"
+
+class ConnectivityMonitor;
+class QTimer;
+
+class UpdateManager final : public NetWorkManager
+{
+    Q_OBJECT
+public:
+    explicit UpdateManager(const QString& url, ConnectivityMonitor* cm, QObject* parent = nullptr);
+    ~UpdateManager() = default;
+
+    Q_INVOKABLE void checkForUpdates(bool quiet = false);
+    Q_INVOKABLE void applyUpdates(bool beta = false);
+    Q_INVOKABLE void cancelUpdate();
+    Q_INVOKABLE void setAutoUpdateCheck(bool state);
+
+signals:
+    void updateCheckReplyReceived(bool ok, bool found = false);
+    void updateCheckErrorOccurred(GetError error);
+    void updateDownloadStarted();
+    void updateDownloadProgressChanged(qint64 bytesRead, qint64 totalBytes);
+    void updateDownloadErrorOccurred(GetError error);
+    void updateDownloadFinished();
+    void appCloseRequested();
+
+private:
+    QByteArray baseUrl_;
+    QString tempPath_;
+    QTimer* updateTimer_;
+
+    void cleanUpdateFiles();
+};
+Q_DECLARE_METATYPE(UpdateManager*)
diff --git a/src/utils.cpp b/src/utils.cpp
index 805d803f148dcf48470016f9a934f718a1e30c91..858f44bedfd96be712b7fee31a748a8ce2c7ac5e 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -403,47 +403,6 @@ Utils::getProjectCredits()
     return credits;
 }
 
-void
-Utils::cleanUpdateFiles()
-{
-    /*
-     * Delete all logs and msi in the %TEMP% directory before launching.
-     */
-    QString dir = QString(Utils::WinGetEnv("TEMP"));
-    QDir log_dir(dir, {"jami*.log"});
-    for (const QString& filename : log_dir.entryList()) {
-        log_dir.remove(filename);
-    }
-    QDir msi_dir(dir, {"jami*.msi"});
-    for (const QString& filename : msi_dir.entryList()) {
-        msi_dir.remove(filename);
-    }
-    QDir version_dir(dir, {"version"});
-    for (const QString& filename : version_dir.entryList()) {
-        version_dir.remove(filename);
-    }
-}
-
-void
-Utils::checkForUpdates(bool withUI, QWidget* parent)
-{
-    Q_UNUSED(withUI)
-    Q_UNUSED(parent)
-    /*
-     * TODO: check update logic.
-     */
-}
-
-void
-Utils::applyUpdates(bool updateToBeta, QWidget* parent)
-{
-    Q_UNUSED(updateToBeta)
-    Q_UNUSED(parent)
-    /*
-     * TODO: update logic.
-     */
-}
-
 inline QString
 removeEndlines(const QString& str)
 {
diff --git a/src/utils.h b/src/utils.h
index 3bb7a08a27504516ff8ad5f256d27f41f8ce141e..ccecb8752835848394ed16eba370154c301572ca 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -77,18 +77,6 @@ QString getChangeLog();
 QString getProjectCredits();
 void removeOldVersions();
 
-/*
- * Updates
- */
-#ifdef BETA
-static constexpr bool isBeta = true;
-#else
-static constexpr bool isBeta = false;
-#endif
-void cleanUpdateFiles();
-void checkForUpdates(bool withUI, QWidget* parent = nullptr);
-void applyUpdates(bool updateToBeta, QWidget* parent = nullptr);
-
 /*
  * LRC helpers
  */
diff --git a/src/utilsadapter.cpp b/src/utilsadapter.cpp
index dea883ed11fc57b3768108439e1deba28243525d..81eaaf80549ff3cf06d1b64eb1d3010bec1449a0 100644
--- a/src/utilsadapter.cpp
+++ b/src/utilsadapter.cpp
@@ -234,11 +234,11 @@ UtilsAdapter::hasVideoCall()
 }
 
 bool
-UtilsAdapter::hasCall(const QString &accountId)
+UtilsAdapter::hasCall(const QString& accountId)
 {
     auto activeCalls = LRCInstance::getActiveCalls();
-    for (const auto &callId : activeCalls) {
-        auto &accountInfo = LRCInstance::accountModel().getAccountInfo(accountId);
+    for (const auto& callId : activeCalls) {
+        auto& accountInfo = LRCInstance::accountModel().getAccountInfo(accountId);
         if (accountInfo.callModel->hasCall(callId)) {
             return true;
         }
@@ -247,11 +247,11 @@ UtilsAdapter::hasCall(const QString &accountId)
 }
 
 const QString
-UtilsAdapter::getCallConvForAccount(const QString &accountId)
+UtilsAdapter::getCallConvForAccount(const QString& accountId)
 {
     // TODO: Currently returning first call, establish priority according to state?
-    for (const auto &callId : LRCInstance::getActiveCalls()) {
-        auto &accountInfo = LRCInstance::accountModel().getAccountInfo(accountId);
+    for (const auto& callId : LRCInstance::getActiveCalls()) {
+        auto& accountInfo = LRCInstance::accountModel().getAccountInfo(accountId);
         if (accountInfo.callModel->hasCall(callId)) {
             return LRCInstance::getConversationFromCallId(callId, accountId).uid;
         }
@@ -278,10 +278,9 @@ UtilsAdapter::getCallId(const QString& accountId, const QString& convUid)
 }
 
 int
-UtilsAdapter::getCallStatus(const QString &callId)
+UtilsAdapter::getCallStatus(const QString& callId)
 {
-    const auto callStatus = LRCInstance::getCallInfo(
-                callId, LRCInstance::getCurrAccId());
+    const auto callStatus = LRCInstance::getCallInfo(callId, LRCInstance::getCurrAccId());
     return static_cast<int>(callStatus->status);
 }
 
@@ -400,3 +399,9 @@ UtilsAdapter::isImage(const QString& fileExt)
 {
     return Utils::isImage(fileExt);
 }
+
+QString
+UtilsAdapter::humanFileSize(qint64 fileSize)
+{
+    return Utils::humanFileSize(fileSize);
+}
diff --git a/src/utilsadapter.h b/src/utilsadapter.h
index 3306e5ccff6df7b072520c7d4de0c977471c060f..1d955312a2ae6f56c04802c1152fd13fcdbac1fe 100644
--- a/src/utilsadapter.h
+++ b/src/utilsadapter.h
@@ -64,10 +64,10 @@ public:
     Q_INVOKABLE void startPreviewing(bool force);
     Q_INVOKABLE void stopPreviewing();
     Q_INVOKABLE bool hasVideoCall();
-    Q_INVOKABLE bool hasCall(const QString &accountId);
-    Q_INVOKABLE const QString getCallConvForAccount(const QString &accountId);
+    Q_INVOKABLE bool hasCall(const QString& accountId);
+    Q_INVOKABLE const QString getCallConvForAccount(const QString& accountId);
     Q_INVOKABLE const QString getCallId(const QString& accountId, const QString& convUid);
-    Q_INVOKABLE int getCallStatus(const QString &callId);
+    Q_INVOKABLE int getCallStatus(const QString& callId);
     Q_INVOKABLE const QString getCallStatusStr(int statusInt);
     Q_INVOKABLE QString getStringUTF8(QString string);
     Q_INVOKABLE bool validateRegNameForm(const QString& regName);
@@ -83,6 +83,7 @@ public:
     Q_INVOKABLE QString fileName(const QString& path);
     Q_INVOKABLE QString getExt(const QString& path);
     Q_INVOKABLE bool isImage(const QString& fileExt);
+    Q_INVOKABLE QString humanFileSize(qint64 fileSize);
 
 private:
     QClipboard* clipboard_;