summary refs log tree commit diff
path: root/src/devices/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bluetooth')
-rw-r--r--src/devices/bluetooth/meson.build36
-rw-r--r--src/devices/bluetooth/nm-bluez-common.h25
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c1320
-rw-r--r--src/devices/bluetooth/nm-bluez-device.h84
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c2881
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.h39
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c456
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.h57
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c355
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.h40
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c928
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.h59
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c596
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.h40
-rw-r--r--src/devices/bluetooth/nm-bt-error.c18
-rw-r--r--src/devices/bluetooth/nm-bt-error.h18
-rw-r--r--src/devices/bluetooth/nm-device-bt.c898
-rw-r--r--src/devices/bluetooth/nm-device-bt.h52
-rw-r--r--src/devices/bluetooth/tests/nm-bt-test.c220
19 files changed, 4197 insertions, 3925 deletions
diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build
index b2f67ceb..f0507c23 100644
--- a/src/devices/bluetooth/meson.build
+++ b/src/devices/bluetooth/meson.build
@@ -1,16 +1,12 @@
 sources = files(
-  'nm-bluez-device.c',
   'nm-bluez-manager.c',
-  'nm-bluez4-adapter.c',
-  'nm-bluez4-manager.c',
-  'nm-bluez5-manager.c',
   'nm-bt-error.c',
   'nm-device-bt.c',
 )
 
 deps = [
+  daemon_nm_default_dep,
   libnm_wwan_dep,
-  nm_dep,
 ]
 
 if enable_bluez5_dun
@@ -19,9 +15,21 @@ if enable_bluez5_dun
   deps += bluez5_dep
 endif
 
+libnm_device_plugin_bluetooth_static = static_library(
+  'nm-device-plugin-bluetooth-static',
+  sources: sources,
+  dependencies: deps,
+  c_args: daemon_c_flags,
+)
+
+libnm_device_plugin_bluetooth_static_dep = declare_dependency(
+  link_whole: libnm_device_plugin_bluetooth_static,
+)
+
+deps += libnm_device_plugin_bluetooth_static_dep
+
 libnm_device_plugin_bluetooth = shared_module(
   'nm-device-plugin-bluetooth',
-  sources: sources,
   dependencies: deps,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
@@ -38,9 +46,13 @@ test(
   args: [libnm_device_plugin_bluetooth.full_path(), linker_script_devices],
 )
 
-# FIXME: check_so_symbols replacement
-'''
-check-local-devices-bluetooth: src/devices/bluetooth/libnm-device-plugin-bluetooth.la
-  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so "$(srcdir)/linker-script-devices.ver"
-  $(call check_so_symbols,$(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so)
-'''
+if enable_tests
+  test_unit = 'nm-bt-test'
+
+  executable(
+    test_unit,
+    'tests/' + test_unit + '.c',
+    dependencies: [ libnetwork_manager_test_dep, deps ],
+    c_args: test_c_flags,
+  )
+endif
diff --git a/src/devices/bluetooth/nm-bluez-common.h b/src/devices/bluetooth/nm-bluez-common.h
index 956375bb..ade4434b 100644
--- a/src/devices/bluetooth/nm-bluez-common.h
+++ b/src/devices/bluetooth/nm-bluez-common.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
@@ -26,19 +12,12 @@
 #define NM_BLUEZ_SERVICE           "org.bluez"
 
 #define NM_BLUEZ_MANAGER_PATH      "/"
-#define NM_OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager"
 
 #define NM_BLUEZ5_ADAPTER_INTERFACE "org.bluez.Adapter1"
 #define NM_BLUEZ5_DEVICE_INTERFACE  "org.bluez.Device1"
 #define NM_BLUEZ5_NETWORK_INTERFACE "org.bluez.Network1"
 #define NM_BLUEZ5_NETWORK_SERVER_INTERFACE "org.bluez.NetworkServer1"
 
-#define NM_BLUEZ4_MANAGER_INTERFACE "org.bluez.Manager"
-#define NM_BLUEZ4_ADAPTER_INTERFACE "org.bluez.Adapter"
-#define NM_BLUEZ4_DEVICE_INTERFACE  "org.bluez.Device"
-#define NM_BLUEZ4_SERIAL_INTERFACE  "org.bluez.Serial"
-#define NM_BLUEZ4_NETWORK_INTERFACE "org.bluez.Network"
-
 #define NM_BLUEZ_MANAGER_BDADDR_ADDED "bdaddr-added"
 #define NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED "network-server-added"
 
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
deleted file mode 100644
index fb0a72c4..00000000
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ /dev/null
@@ -1,1320 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2009 - 2012 Red Hat, Inc.
- * Copyright (C) 2013 Intel Corporation.
- */
-
-#include "nm-default.h"
-
-#include "nm-bluez-device.h"
-
-#include "nm-core-internal.h"
-#include "nm-bt-error.h"
-#include "nm-bluez-common.h"
-#include "settings/nm-settings.h"
-#include "settings/nm-settings-connection.h"
-#include "NetworkManagerUtils.h"
-
-#if WITH_BLUEZ5_DUN
-#include "nm-bluez5-dun.h"
-#endif
-
-/*****************************************************************************/
-
-#define VARIANT_IS_OF_TYPE_BOOLEAN(v)      ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_BOOLEAN) ))
-#define VARIANT_IS_OF_TYPE_STRING(v)       ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING) ))
-#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v)  ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_OBJECT_PATH) ))
-#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING_ARRAY) ))
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE (NMBluezDevice,
-	PROP_PATH,
-	PROP_ADDRESS,
-	PROP_NAME,
-	PROP_CAPABILITIES,
-	PROP_USABLE,
-	PROP_CONNECTED,
-);
-
-enum {
-	INITIALIZED,
-	REMOVED,
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	char *path;
-	GDBusConnection *dbus_connection;
-
-	GDBusProxy *proxy;
-
-	GDBusProxy *adapter5;
-	gboolean adapter_powered;
-
-	int bluez_version;
-
-	gboolean initialized;
-	gboolean usable;
-	NMBluetoothCapabilities connection_bt_type;
-
-	guint check_emit_usable_id;
-
-	char *adapter_address;
-	char *address;
-	char *name;
-	guint32 capabilities;
-	gboolean connected;
-	gboolean paired;
-
-	char *b4_iface;
-#if WITH_BLUEZ5_DUN
-	NMBluez5DunContext *b5_dun_context;
-#endif
-
-	NMSettings *settings;
-	GSList *connections;
-
-	NMSettingsConnection *pan_connection;
-	gboolean pan_connection_no_autocreate;
-} NMBluezDevicePrivate;
-
-struct _NMBluezDevice {
-	GObject parent;
-	NMBluezDevicePrivate _priv;
-};
-
-struct _NMBluezDeviceClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMBluezDevice, nm_bluez_device, G_TYPE_OBJECT)
-
-#define NM_BLUEZ_DEVICE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluezDevice, NM_IS_BLUEZ_DEVICE)
-
-/*****************************************************************************/
-
-static void cp_connection_added (NMSettings *settings,
-                                 NMSettingsConnection *sett_conn,
-                                 NMBluezDevice *self);
-static gboolean connection_compatible (NMBluezDevice *self, NMSettingsConnection *sett_conn);
-
-/*****************************************************************************/
-
-const char *
-nm_bluez_device_get_path (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->path;
-}
-
-const char *
-nm_bluez_device_get_address (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->address;
-}
-
-gboolean
-nm_bluez_device_get_initialized (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->initialized;
-}
-
-gboolean
-nm_bluez_device_get_usable (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->usable;
-}
-
-const char *
-nm_bluez_device_get_name (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->name;
-}
-
-guint32
-nm_bluez_device_get_capabilities (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), 0);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->capabilities;
-}
-
-gboolean
-nm_bluez_device_get_connected (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv;
-
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE);
-
-	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	return priv->connected;
-}
-
-static void
-pan_connection_check_create (NMBluezDevice *self)
-{
-	gs_unref_object NMConnection *connection = NULL;
-	NMSettingsConnection *added;
-	NMSetting *setting;
-	gs_free char *id = NULL;
-	char uuid[37];
-	GError *error = NULL;
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	g_return_if_fail (priv->capabilities & NM_BT_CAPABILITY_NAP);
-	g_return_if_fail (priv->connections == NULL);
-	g_return_if_fail (priv->name);
-
-	if (priv->pan_connection || priv->pan_connection_no_autocreate) {
-		/* already have a connection or we don't want to create one, nothing to do. */
-		return;
-	}
-
-	/* Only try once to create a connection. If it does not succeed, we do not try again. Also,
-	 * if the connection gets deleted later, do not create another one for this device. */
-	priv->pan_connection_no_autocreate = TRUE;
-
-	/* create a new connection */
-
-	connection = nm_simple_connection_new ();
-
-	/* Setting: Connection */
-	nm_utils_uuid_generate_buf (uuid);
-	id = g_strdup_printf (_("%s Network"), priv->name);
-	setting = nm_setting_connection_new ();
-	g_object_set (setting,
-	              NM_SETTING_CONNECTION_ID, id,
-	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
-	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_BLUETOOTH_SETTING_NAME,
-	              NULL);
-	nm_connection_add_setting (connection, setting);
-
-	/* Setting: Bluetooth */
-	setting = nm_setting_bluetooth_new ();
-	g_object_set (G_OBJECT (setting),
-	              NM_SETTING_BLUETOOTH_BDADDR, priv->address,
-	              NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU,
-	              NULL);
-	nm_connection_add_setting (connection, setting);
-
-	if (!nm_connection_normalize (connection, NULL, NULL, &error)) {
-		nm_log_err (LOGD_BT, "bluez[%s] couldn't generate a connection for NAP device: %s",
-		            priv->path, error->message);
-		g_error_free (error);
-		g_return_if_reached ();
-	}
-
-	/* Adding a new connection raises a signal which eventually calls check_emit_usable (again)
-	 * which then already finds the suitable connection in priv->connections. This is confusing,
-	 * so block the signal. check_emit_usable will succeed after this function call returns. */
-	g_signal_handlers_block_by_func (priv->settings, cp_connection_added, self);
-	nm_settings_add_connection (priv->settings,
-	                            connection,
-	                            NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
-	                            NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
-	                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
-	                            &added,
-	                            &error);
-	g_signal_handlers_unblock_by_func (priv->settings, cp_connection_added, self);
-
-	if (added) {
-		nm_assert (!g_slist_find (priv->connections, added));
-		nm_assert (connection_compatible (self, added));
-		priv->connections = g_slist_prepend (priv->connections, g_object_ref (added));
-		priv->pan_connection = added;
-		nm_log_dbg (LOGD_BT, "bluez[%s] added new Bluetooth connection for NAP device: '%s' (%s)", priv->path, id, uuid);
-	} else {
-		nm_log_warn (LOGD_BT, "bluez[%s] couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s",
-		             priv->path, id, uuid, error->message);
-		g_clear_error (&error);
-	}
-}
-
-static gboolean
-check_emit_usable (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	gboolean new_usable;
-
-	/* only expect the supported capabilities set. */
-	nm_assert ((priv->capabilities & ~(NM_BT_CAPABILITY_NAP | NM_BT_CAPABILITY_DUN)) == NM_BT_CAPABILITY_NONE );
-
-	new_usable = (   priv->initialized && priv->capabilities
-	              && priv->name && priv->paired
-	              && (   (priv->bluez_version == 4)
-	                  || (priv->bluez_version == 5 && priv->adapter5 && priv->adapter_powered))
-	              && priv->dbus_connection && priv->address && priv->adapter_address);
-
-	if (!new_usable)
-		goto END;
-
-	if (priv->connections)
-		goto END;
-
-	if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) {
-		/* non NAP devices are only usable, if they already have a connection. */
-		new_usable = FALSE;
-		goto END;
-	}
-
-	pan_connection_check_create (self);
-	new_usable = !!priv->pan_connection;
-
-END:
-	if (new_usable != priv->usable) {
-		priv->usable = new_usable;
-		_notify (self, PROP_USABLE);
-	}
-
-	return G_SOURCE_REMOVE;
-}
-
-static void
-check_emit_usable_schedule (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	if (priv->check_emit_usable_id == 0)
-		priv->check_emit_usable_id = g_idle_add ((GSourceFunc) check_emit_usable, self);
-}
-
-/*****************************************************************************/
-
-static gboolean
-connection_compatible (NMBluezDevice *self, NMSettingsConnection *sett_conn)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
-	NMSettingBluetooth *s_bt;
-	const char *bt_type;
-	const char *bdaddr;
-
-	if (!nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME))
-		return FALSE;
-
-	s_bt = nm_connection_get_setting_bluetooth (connection);
-	if (!s_bt)
-		return FALSE;
-
-	if (!priv->address)
-		return FALSE;
-
-	bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
-	if (!bdaddr)
-		return FALSE;
-	if (!nm_utils_hwaddr_matches (bdaddr, -1, priv->address, -1))
-		return FALSE;
-
-	bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
-
-	if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_NAP))
-		return FALSE;
-
-	if (   g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN)
-	    && !(priv->capabilities & NM_BT_CAPABILITY_DUN))
-		return FALSE;
-
-	if (   g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU)
-	    && !(priv->capabilities & NM_BT_CAPABILITY_NAP))
-		return FALSE;
-
-	return TRUE;
-}
-
-static gboolean
-_internal_track_connection (NMBluezDevice *self,
-                            NMSettingsConnection *sett_conn,
-                            gboolean tracked)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	gboolean was_tracked;
-
-	was_tracked = !!g_slist_find (priv->connections, sett_conn);
-	if (was_tracked == !!tracked)
-		return FALSE;
-
-	if (tracked)
-		priv->connections = g_slist_prepend (priv->connections, g_object_ref (sett_conn));
-	else {
-		priv->connections = g_slist_remove (priv->connections, sett_conn);
-		if (priv->pan_connection == sett_conn)
-			priv->pan_connection = NULL;
-		g_object_unref (sett_conn);
-	}
-
-	return TRUE;
-}
-
-static void
-cp_connection_added (NMSettings *settings,
-                     NMSettingsConnection *sett_conn,
-                     NMBluezDevice *self)
-{
-	if (connection_compatible (self, sett_conn)) {
-		if (_internal_track_connection (self, sett_conn, TRUE))
-			check_emit_usable (self);
-	}
-}
-
-static void
-cp_connection_removed (NMSettings *settings,
-                       NMSettingsConnection *sett_conn,
-                       NMBluezDevice *self)
-{
-	if (_internal_track_connection (self, sett_conn, FALSE))
-		check_emit_usable (self);
-}
-
-static void
-cp_connection_updated (NMSettings *settings,
-                       NMSettingsConnection *sett_conn,
-                       guint update_reason_u,
-                       NMBluezDevice *self)
-{
-	if (_internal_track_connection (self, sett_conn,
-	                                connection_compatible (self, sett_conn)))
-		check_emit_usable_schedule (self);
-}
-
-static void
-load_connections (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	NMSettingsConnection *const*connections;
-	guint i;
-	gboolean changed = FALSE;
-
-	connections = nm_settings_get_connections (priv->settings, NULL);
-	for (i = 0; connections[i]; i++) {
-		if (connection_compatible (self, connections[i]))
-			changed |= _internal_track_connection (self, connections[i], TRUE);
-	}
-	if (changed)
-		check_emit_usable (self);
-}
-
-/*****************************************************************************/
-
-static void
-bluez_disconnect_cb (GDBusConnection *dbus_connection,
-                     GAsyncResult *res,
-                     gpointer user_data)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) user_data);
-	GError *error = NULL;
-	GVariant *variant;
-
-	variant = g_dbus_connection_call_finish (dbus_connection, res, &error);
-	if (!variant) {
-		if (!strstr (error->message, "org.bluez.Error.NotConnected"))
-			nm_log_warn (LOGD_BT, "bluez[%s]: failed to disconnect: %s", priv->path, error->message);
-		g_error_free (error);
-	} else
-		g_variant_unref (variant);
-
-	g_object_unref (NM_BLUEZ_DEVICE (user_data));
-}
-
-void
-nm_bluez_device_disconnect (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GVariant *args = NULL;
-	const char *dbus_iface = NULL;
-
-	g_return_if_fail (priv->dbus_connection);
-
-	/* FIXME: if we are in the process of connecting and cancel the
-	 * connection attempt, we must complete the pending connect request.
-	 * However, we must also ensure that we don't leave a connected device. */
-	if (priv->connection_bt_type == NM_BT_CAPABILITY_DUN) {
-		if (priv->bluez_version == 4) {
-			/* Can't pass a NULL interface name through dbus to bluez, so just
-			 * ignore the disconnect if the interface isn't known.
-			 */
-			if (!priv->b4_iface)
-				goto out;
-			args = g_variant_new ("(s)", priv->b4_iface),
-			dbus_iface = NM_BLUEZ4_SERIAL_INTERFACE;
-		} else if (priv->bluez_version == 5) {
-#if WITH_BLUEZ5_DUN
-			nm_bluez5_dun_cleanup (priv->b5_dun_context);
-#endif
-			priv->connected = FALSE;
-			goto out;
-		}
-	} else if (priv->connection_bt_type == NM_BT_CAPABILITY_NAP) {
-		if (priv->bluez_version == 4)
-			dbus_iface = NM_BLUEZ4_NETWORK_INTERFACE;
-		else if (priv->bluez_version == 5)
-			dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE;
-		else
-			g_assert_not_reached ();
-	} else
-		g_assert_not_reached ();
-
-	g_dbus_connection_call (priv->dbus_connection,
-	                        NM_BLUEZ_SERVICE,
-	                        priv->path,
-	                        dbus_iface,
-	                        "Disconnect",
-	                        args ?: g_variant_new("()"),
-	                        NULL,
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        10000,
-	                        NULL,
-	                        (GAsyncReadyCallback) bluez_disconnect_cb,
-	                        g_object_ref (self));
-
-out:
-	g_clear_pointer (&priv->b4_iface, g_free);
-	priv->connection_bt_type = NM_BT_CAPABILITY_NONE;
-}
-
-static void
-_connect_complete (NMBluezDevice *self,
-                   const char *device,
-                   NMBluezDeviceConnectCallback callback,
-                   gpointer callback_user_data,
-                   GError *error)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	nm_assert ((device || error) && !(device && error));
-
-	if (   device
-	    && priv->bluez_version == 5) {
-		priv->connected = TRUE;
-		_notify (self, PROP_CONNECTED);
-	}
-
-	if (callback)
-		callback (self, device, error, callback_user_data);
-}
-
-static void
-_connect_cb (GObject *source_object,
-                  GAsyncResult *res,
-                  gpointer user_data)
-{
-	gs_unref_object NMBluezDevice *self = NULL;
-	NMBluezDevicePrivate *priv;
-	NMBluezDeviceConnectCallback callback;
-	gpointer callback_user_data;
-	gs_free_error GError *error = NULL;
-	char *device = NULL;
-	gs_unref_variant GVariant *variant = NULL;
-
-	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
-
-	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	variant = _nm_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, G_VARIANT_TYPE ("(s)"), &error);
-	if (variant) {
-		g_variant_get (variant, "(s)", &device);
-		priv->b4_iface = device;
-	}
-
-	_connect_complete (self, device, callback, callback_user_data, error);
-}
-
-#if WITH_BLUEZ5_DUN
-static void
-_connect_cb_bluez5_dun (NMBluez5DunContext *context,
-                        const char *device,
-                        GError *error,
-                        gpointer user_data)
-{
-	gs_unref_object NMBluezDevice *self = NULL;
-	gs_unref_object GCancellable *cancellable = NULL;
-	NMBluezDeviceConnectCallback callback;
-	gpointer callback_user_data;
-	gs_free_error GError *cancelled_error = NULL;
-
-	nm_utils_user_data_unpack (user_data, &self, &cancellable, &callback, &callback_user_data);
-
-	/* FIXME(shutdown): the async operation nm_bluez5_dun_connect() should be cancellable.
-	 * Fake it here. */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error))
-		error = cancelled_error;
-
-	_connect_complete (self, device, callback, callback_user_data, error);
-}
-#else /* WITH_BLUEZ5_DUN */
-static void
-_connect_cb_bluez5_dun_idle_no_b5 (gpointer user_data,
-                                   GCancellable *cancellable)
-{
-	gs_unref_object NMBluezDevice *self = NULL;
-	NMBluezDeviceConnectCallback callback;
-	gpointer callback_user_data;
-	gs_free_error GError *error = NULL;
-
-	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
-
-	if (!g_cancellable_set_error_if_cancelled (cancellable, &error)) {
-		g_set_error (&error,
-		             NM_BT_ERROR,
-		             NM_BT_ERROR_DUN_CONNECT_FAILED,
-		             "NetworkManager built without support for Bluez 5");
-	}
-	callback (self, NULL, error, callback_user_data);
-}
-#endif /* WITH_BLUEZ5_DUN */
-
-void
-nm_bluez_device_connect_async (NMBluezDevice *self,
-                               NMBluetoothCapabilities connection_bt_type,
-                               GCancellable *cancellable,
-                               NMBluezDeviceConnectCallback callback,
-                               gpointer callback_user_data)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	const char *dbus_iface = NULL;
-	const char *connect_type = NULL;
-
-	g_return_if_fail (priv->capabilities & connection_bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP));
-
-	priv->connection_bt_type = connection_bt_type;
-
-	if (connection_bt_type == NM_BT_CAPABILITY_NAP) {
-		connect_type = BLUETOOTH_CONNECT_NAP;
-		if (priv->bluez_version == 4)
-			dbus_iface = NM_BLUEZ4_NETWORK_INTERFACE;
-		else if (priv->bluez_version == 5)
-			dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE;
-	} else if (connection_bt_type == NM_BT_CAPABILITY_DUN) {
-		connect_type = BLUETOOTH_CONNECT_DUN;
-		if (priv->bluez_version == 4)
-			dbus_iface = NM_BLUEZ4_SERIAL_INTERFACE;
-		else if (priv->bluez_version == 5) {
-#if WITH_BLUEZ5_DUN
-			if (priv->b5_dun_context == NULL)
-				priv->b5_dun_context = nm_bluez5_dun_new (priv->adapter_address, priv->address);
-			nm_bluez5_dun_connect (priv->b5_dun_context,
-			                       _connect_cb_bluez5_dun,
-			                       nm_utils_user_data_pack (g_object_ref (self),
-			                                                nm_g_object_ref (cancellable),
-			                                                callback,
-			                                                callback_user_data));
-#else
-			if (callback) {
-				nm_utils_invoke_on_idle (_connect_cb_bluez5_dun_idle_no_b5,
-				                         nm_utils_user_data_pack (g_object_ref (self),
-				                                                  callback,
-				                                                  callback_user_data),
-				                         cancellable);
-			}
-#endif
-			return;
-		}
-	} else
-		g_return_if_reached ();
-
-	/* FIXME: we need to remember that a connect is in progress.
-	 * So, if the request gets cancelled, that we disconnect the
-	 * connection that was established in the meantime. */
-	g_dbus_connection_call (priv->dbus_connection,
-	                        NM_BLUEZ_SERVICE,
-	                        priv->path,
-	                        dbus_iface,
-	                        "Connect",
-	                        g_variant_new ("(s)", connect_type),
-	                        NULL,
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        20000,
-	                        cancellable,
-	                        _connect_cb,
-	                        nm_utils_user_data_pack (g_object_ref (self),
-	                                                 callback,
-	                                                 callback_user_data));
-}
-
-/*****************************************************************************/
-
-static void
-set_adapter_address (NMBluezDevice *self, const char *address)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	g_return_if_fail (address);
-
-	if (priv->adapter_address)
-		g_free (priv->adapter_address);
-	priv->adapter_address = g_strdup (address);
-}
-
-static guint32
-convert_uuids_to_capabilities (const char **strings)
-{
-	const char **iter;
-	guint32 capabilities = 0;
-
-	for (iter = strings; iter && *iter; iter++) {
-		char **parts;
-
-		parts = g_strsplit (*iter, "-", -1);
-		if (parts && parts[0]) {
-			switch (g_ascii_strtoull (parts[0], NULL, 16)) {
-			case 0x1103:
-				capabilities |= NM_BT_CAPABILITY_DUN;
-				break;
-			case 0x1116:
-				capabilities |= NM_BT_CAPABILITY_NAP;
-				break;
-			default:
-				break;
-			}
-		}
-		g_strfreev (parts);
-	}
-
-	return capabilities;
-}
-
-static void
-_set_property_capabilities (NMBluezDevice *self, const char **uuids)
-{
-	guint32 uint_val;
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	uint_val = convert_uuids_to_capabilities (uuids);
-	if (priv->capabilities != uint_val) {
-		if (priv->capabilities) {
-			/* changing (relevant) capabilities is not supported and ignored -- except setting initially */
-			nm_log_warn (LOGD_BT, "bluez[%s] ignore change of capabilities for Bluetooth device from %u to %u",
-			             priv->path, priv->capabilities, uint_val);
-			return;
-		}
-		nm_log_dbg (LOGD_BT, "bluez[%s] set capabilities for Bluetooth device: %s%s%s", priv->path,
-		            uint_val & NM_BT_CAPABILITY_NAP ? "NAP" : "",
-		            ((uint_val & NM_BT_CAPABILITY_DUN) && (uint_val &NM_BT_CAPABILITY_NAP)) ? " | " : "",
-		            uint_val & NM_BT_CAPABILITY_DUN ? "DUN" : "");
-		priv->capabilities = uint_val;
-		_notify (self, PROP_CAPABILITIES);
-	}
-}
-
-/**
- * priv->address can only be set one to a certain (non NULL) value. Every later attempt
- * to reset it to another value will be ignored and a warning will be logged.
- **/
-static void
-_set_property_address (NMBluezDevice *self, const char *addr)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	if (g_strcmp0 (priv->address, addr) == 0)
-		return;
-
-	if (!addr) {
-		nm_log_warn (LOGD_BT, "bluez[%s] cannot reset address from '%s' to NULL", priv->path, priv->address);
-		return;
-	}
-
-	if (priv->address != NULL) {
-		nm_log_warn (LOGD_BT, "bluez[%s] cannot reset address from '%s' to '%s'", priv->path, priv->address, addr);
-		return;
-	}
-
-	if (!nm_utils_hwaddr_valid (addr, ETH_ALEN)) {
-		nm_log_warn (LOGD_BT, "bluez[%s] cannot set address to '%s' (invalid value)", priv->path, addr);
-		return;
-	}
-
-	priv->address = g_strdup (addr);
-	_notify (self, PROP_ADDRESS);
-}
-
-static void
-_take_variant_property_address (NMBluezDevice *self, GVariant *v)
-{
-	_set_property_address (self, VARIANT_IS_OF_TYPE_STRING (v) ? g_variant_get_string (v, NULL) : NULL);
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-_take_variant_property_name (NMBluezDevice *self, GVariant *v)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	const char *str;
-
-	if (VARIANT_IS_OF_TYPE_STRING (v)) {
-		str = g_variant_get_string (v, NULL);
-		if (g_strcmp0 (priv->name, str)) {
-			g_free (priv->name);
-			priv->name = g_strdup (str);
-			_notify (self, PROP_NAME);
-		}
-	}
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-_take_variant_property_uuids (NMBluezDevice *self, GVariant *v)
-{
-	if (VARIANT_IS_OF_TYPE_STRING_ARRAY (v)) {
-		const char **uuids = g_variant_get_strv (v, NULL);
-
-		_set_property_capabilities (self, uuids);
-		g_free (uuids);
-	}
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-_take_variant_property_connected (NMBluezDevice *self, GVariant *v)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	if (VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
-		gboolean connected = g_variant_get_boolean (v);
-
-		if (priv->connected != connected) {
-			priv->connected = connected;
-			_notify (self, PROP_CONNECTED);
-		}
-	}
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-_take_variant_property_paired (NMBluezDevice *self, GVariant *v)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	if (VARIANT_IS_OF_TYPE_BOOLEAN (v))
-		priv->paired = g_variant_get_boolean (v);
-
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-adapter5_on_properties_changed (GDBusProxy *proxy,
-                                GVariant *changed_properties,
-                                GStrv invalidated_properties,
-                                gpointer user_data)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data);
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GVariantIter i;
-	const char *property;
-	GVariant *v;
-
-	g_variant_iter_init (&i, changed_properties);
-	while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
-		if (!strcmp (property, "Powered") && VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
-			gboolean powered = g_variant_get_boolean (v);
-			if (priv->adapter_powered != powered)
-				priv->adapter_powered = powered;
-		}
-		g_variant_unref (v);
-	}
-
-	check_emit_usable (self);
-}
-
-static void
-adapter5_on_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-	GVariant *v;
-
-	priv->adapter5 = g_dbus_proxy_new_for_bus_finish (res, &error);
-	if (!priv->adapter5) {
-		nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire adapter proxy: %s.", priv->path, error->message);
-		g_clear_error (&error);
-		g_signal_emit (self, signals[INITIALIZED], 0, FALSE);
-	} else {
-		g_signal_connect (priv->adapter5, "g-properties-changed",
-		                  G_CALLBACK (adapter5_on_properties_changed), self);
-
-		/* Check adapter's powered state */
-		v = g_dbus_proxy_get_cached_property (priv->adapter5, "Powered");
-		priv->adapter_powered = VARIANT_IS_OF_TYPE_BOOLEAN (v) ? g_variant_get_boolean (v) : FALSE;
-		if (v)
-			g_variant_unref (v);
-
-		v = g_dbus_proxy_get_cached_property (priv->adapter5, "Address");
-		if (VARIANT_IS_OF_TYPE_STRING (v))
-			set_adapter_address (self, g_variant_get_string (v, NULL));
-
-		priv->initialized = TRUE;
-		g_signal_emit (self, signals[INITIALIZED], 0, TRUE);
-
-		check_emit_usable (self);
-	}
-
-	g_object_unref (self);
-}
-
-static void
-_take_one_variant_property (NMBluezDevice *self, const char *property, GVariant *v)
-{
-	if (v) {
-		if (!g_strcmp0 (property, "Address"))
-			_take_variant_property_address (self, v);
-		else if (!g_strcmp0 (property, "Connected"))
-			_take_variant_property_connected (self, v);
-		else if (!g_strcmp0 (property, "Paired"))
-			_take_variant_property_paired (self, v);
-		else if (!g_strcmp0 (property, "Name"))
-			_take_variant_property_name (self, v);
-		else if (!g_strcmp0 (property, "UUIDs"))
-			_take_variant_property_uuids (self, v);
-		else
-			g_variant_unref (v);
-	}
-}
-
-static void
-_set_properties (NMBluezDevice *self, GVariant *properties)
-{
-	GVariantIter i;
-	const char *property;
-	GVariant *v;
-
-	g_object_freeze_notify (G_OBJECT (self));
-	g_variant_iter_init (&i, properties);
-	while (g_variant_iter_next (&i, "{&sv}", &property, &v))
-		_take_one_variant_property (self, property, v);
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
-properties_changed (GDBusProxy *proxy,
-                    GVariant *changed_properties,
-                    GStrv invalidated_properties,
-                    gpointer user_data)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data);
-
-	_set_properties (self, changed_properties);
-	check_emit_usable (self);
-}
-
-static void
-bluez4_property_changed (GDBusProxy *proxy,
-                         const char *property,
-                         GVariant   *v,
-                         gpointer    user_data)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data);
-
-	_take_one_variant_property (self, property, v);
-	check_emit_usable (self);
-}
-
-static void
-get_properties_cb_4 (GObject *source_object, GAsyncResult *res, gpointer user_data)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data);
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *err = NULL;
-	GVariant *v_properties, *v_dict;
-
-	v_properties = _nm_dbus_proxy_call_finish (priv->proxy, res,
-	                                           G_VARIANT_TYPE ("(a{sv})"),
-	                                           &err);
-	if (!v_properties) {
-		g_dbus_error_strip_remote_error (err);
-		nm_log_warn (LOGD_BT, "bluez[%s] error getting device properties: %s",
-		             priv->path, err->message);
-		g_error_free (err);
-		g_signal_emit (self, signals[INITIALIZED], 0, FALSE);
-		goto END;
-	}
-
-	v_dict = g_variant_get_child_value (v_properties, 0);
-	_set_properties (self, v_dict);
-	g_variant_unref (v_dict);
-	g_variant_unref (v_properties);
-
-	/* Check if any connections match this device */
-	load_connections (self);
-
-	priv->initialized = TRUE;
-	g_signal_emit (self, signals[INITIALIZED], 0, TRUE);
-
-	check_emit_usable (self);
-
-END:
-	g_object_unref (self);
-}
-
-static void
-query_properties (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GVariant *v;
-
-	switch (priv->bluez_version) {
-	case 4:
-		g_dbus_proxy_call (priv->proxy, "GetProperties", NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, 3000,
-		                   NULL, get_properties_cb_4, g_object_ref (self));
-		break;
-	case 5:
-		g_object_freeze_notify (G_OBJECT (self));
-		_take_variant_property_address   (self, g_dbus_proxy_get_cached_property (priv->proxy, "Address"));
-		_take_variant_property_connected (self, g_dbus_proxy_get_cached_property (priv->proxy, "Connected"));
-		_take_variant_property_paired    (self, g_dbus_proxy_get_cached_property (priv->proxy, "Paired"));
-		_take_variant_property_name      (self, g_dbus_proxy_get_cached_property (priv->proxy, "Name"));
-		_take_variant_property_uuids     (self, g_dbus_proxy_get_cached_property (priv->proxy, "UUIDs"));
-		g_object_thaw_notify (G_OBJECT (self));
-
-		v = g_dbus_proxy_get_cached_property (priv->proxy, "Adapter");
-		if (VARIANT_IS_OF_TYPE_OBJECT_PATH (v)) {
-			g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-			                          G_DBUS_PROXY_FLAGS_NONE,
-			                          NULL,
-			                          NM_BLUEZ_SERVICE,
-			                          g_variant_get_string (v, NULL),
-			                          NM_BLUEZ5_ADAPTER_INTERFACE,
-			                          NULL,
-			                          (GAsyncReadyCallback) adapter5_on_acquired,
-			                          g_object_ref (self));
-			g_variant_unref (v);
-		} else {
-			/* If the Adapter property is unset at this point, we won't try to acquire the adapter later on
-			 * and the device stays unusable. This should not happen, but if it does, log a debug message. */
-			nm_log_dbg (LOGD_BT, "bluez[%s] device has no adapter property and cannot be used.", priv->path);
-		}
-
-		/* Check if any connections match this device */
-		load_connections (self);
-
-		break;
-	}
-}
-
-static void
-on_proxy_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
-
-	if (!priv->proxy) {
-		nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire device proxy: %s.", priv->path, error->message);
-		g_clear_error (&error);
-		g_signal_emit (self, signals[INITIALIZED], 0, FALSE);
-	} else {
-		g_signal_connect (priv->proxy, "g-properties-changed",
-		                  G_CALLBACK (properties_changed), self);
-		if (priv->bluez_version == 4) {
-			/* Watch for custom Bluez4 PropertyChanged signals */
-			_nm_dbus_signal_connect (priv->proxy, "PropertyChanged", G_VARIANT_TYPE ("(sv)"),
-			                         G_CALLBACK (bluez4_property_changed), self);
-		}
-
-		query_properties (self);
-	}
-	g_object_unref (self);
-}
-
-static void
-on_bus_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	priv->dbus_connection = g_bus_get_finish (res, &error);
-
-	if (!priv->dbus_connection) {
-		nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire bus connection: %s.", priv->path, error->message);
-		g_clear_error (&error);
-		g_signal_emit (self, signals[INITIALIZED], 0, FALSE);
-	} else
-		check_emit_usable (self);
-
-	g_object_unref (self);
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) object);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		g_value_set_string (value, priv->path);
-		break;
-	case PROP_ADDRESS:
-		g_value_set_string (value, priv->address);
-		break;
-	case PROP_NAME:
-		g_value_set_string (value, priv->name);
-		break;
-	case PROP_CAPABILITIES:
-		g_value_set_uint (value, priv->capabilities);
-		break;
-	case PROP_USABLE:
-		g_value_set_boolean (value, priv->usable);
-		break;
-	case PROP_CONNECTED:
-		g_value_set_boolean (value, priv->connected);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) object);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		/* construct-only */
-		priv->path = g_value_dup_string (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_bluez_device_init (NMBluezDevice *self)
-{
-}
-
-NMBluezDevice *
-nm_bluez_device_new (const char *path,
-                     const char *adapter_address,
-                     NMSettings *settings,
-                     int bluez_version)
-{
-	NMBluezDevice *self;
-	NMBluezDevicePrivate *priv;
-	const char *interface_name = NULL;
-
-	g_return_val_if_fail (path != NULL, NULL);
-	g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
-	g_return_val_if_fail (bluez_version == 4 || bluez_version == 5, NULL);
-
-	self = (NMBluezDevice *) g_object_new (NM_TYPE_BLUEZ_DEVICE,
-	                                       NM_BLUEZ_DEVICE_PATH, path,
-	                                       NULL);
-	if (!self)
-		return NULL;
-
-	nm_log_dbg (LOGD_BT, "bluez[%s] create NMBluezDevice", path);
-
-	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	priv->bluez_version = bluez_version;
-	priv->settings = g_object_ref (settings);
-	g_return_val_if_fail (bluez_version == 5 || (bluez_version == 4 && adapter_address), NULL);
-	if (adapter_address)
-		set_adapter_address (self, adapter_address);
-
-	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED,   G_CALLBACK (cp_connection_added),   self);
-	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, G_CALLBACK (cp_connection_removed), self);
-	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (cp_connection_updated), self);
-
-	g_bus_get (G_BUS_TYPE_SYSTEM,
-	           NULL,
-	           (GAsyncReadyCallback) on_bus_acquired,
-	           g_object_ref (self));
-
-	switch (priv->bluez_version) {
-	case 4:
-		interface_name = NM_BLUEZ4_DEVICE_INTERFACE;
-		break;
-	case 5:
-		interface_name = NM_BLUEZ5_DEVICE_INTERFACE;
-		break;
-	}
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_NONE,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          priv->path,
-	                          interface_name,
-	                          NULL,
-	                          (GAsyncReadyCallback) on_proxy_acquired,
-	                          g_object_ref (self));
-	return self;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (object);
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	NMSettingsConnection *to_delete = NULL;
-
-	nm_clear_g_source (&priv->check_emit_usable_id);
-
-	if (priv->pan_connection) {
-		/* Check whether we want to remove the created connection. If so, we take a reference
-		 * and delete it at the end of dispose(). */
-		if (NM_FLAGS_HAS (nm_settings_connection_get_flags (priv->pan_connection),
-		                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
-			to_delete = g_object_ref (priv->pan_connection);
-
-		priv->pan_connection = NULL;
-	}
-
-#if WITH_BLUEZ5_DUN
-	if (priv->b5_dun_context) {
-		nm_bluez5_dun_free (priv->b5_dun_context);
-		priv->b5_dun_context = NULL;
-	}
-#endif
-
-	if (priv->settings) {
-		g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_added, self);
-		g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_removed, self);
-		g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_updated, self);
-	}
-
-	g_slist_free_full (priv->connections, g_object_unref);
-	priv->connections = NULL;
-
-	if (priv->adapter5) {
-		g_signal_handlers_disconnect_by_func (priv->adapter5, adapter5_on_properties_changed, self);
-		g_clear_object (&priv->adapter5);
-	}
-
-	g_clear_object (&priv->dbus_connection);
-
-	G_OBJECT_CLASS (nm_bluez_device_parent_class)->dispose (object);
-
-	if (to_delete) {
-		nm_log_dbg (LOGD_BT, "bluez[%s] removing Bluetooth connection for NAP device: '%s' (%s)", priv->path,
-		            nm_settings_connection_get_id (to_delete), nm_settings_connection_get_uuid (to_delete));
-		nm_settings_connection_delete (to_delete, FALSE);
-		g_object_unref (to_delete);
-	}
-
-	g_clear_object (&priv->settings);
-}
-
-static void
-finalize (GObject *object)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) object);
-
-	nm_log_dbg (LOGD_BT, "bluez[%s]: finalize NMBluezDevice", priv->path);
-
-	g_free (priv->path);
-	g_free (priv->adapter_address);
-	g_free (priv->address);
-	g_free (priv->name);
-	g_free (priv->b4_iface);
-
-	if (priv->proxy)
-		g_signal_handlers_disconnect_by_data (priv->proxy, object);
-	g_clear_object (&priv->proxy);
-
-	G_OBJECT_CLASS (nm_bluez_device_parent_class)->finalize (object);
-}
-
-static void
-nm_bluez_device_class_init (NMBluezDeviceClass *config_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-
-	object_class->get_property = get_property;
-	object_class->set_property = set_property;
-	object_class->dispose = dispose;
-	object_class->finalize = finalize;
-
-	obj_properties[PROP_PATH] =
-	     g_param_spec_string (NM_BLUEZ_DEVICE_PATH, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_ADDRESS] =
-	     g_param_spec_string (NM_BLUEZ_DEVICE_ADDRESS, "", "",
-	                          NULL,
-	                          G_PARAM_READABLE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_NAME] =
-	     g_param_spec_string (NM_BLUEZ_DEVICE_NAME, "", "",
-	                          NULL,
-	                          G_PARAM_READABLE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_CAPABILITIES] =
-	     g_param_spec_uint (NM_BLUEZ_DEVICE_CAPABILITIES, "", "",
-	                        0, G_MAXUINT, 0,
-	                        G_PARAM_READABLE |
-	                        G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_USABLE] =
-	     g_param_spec_boolean (NM_BLUEZ_DEVICE_USABLE, "", "",
-	                           FALSE,
-	                           G_PARAM_READABLE |
-	                           G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_CONNECTED] =
-	     g_param_spec_boolean (NM_BLUEZ_DEVICE_CONNECTED, "", "",
-	                           FALSE,
-	                           G_PARAM_READABLE |
-	                           G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	signals[INITIALIZED] = g_signal_new (NM_BLUEZ_DEVICE_INITIALIZED,
-	                                     G_OBJECT_CLASS_TYPE (object_class),
-	                                     G_SIGNAL_RUN_LAST,
-	                                     0,
-	                                     NULL, NULL, NULL,
-	                                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
-
-	signals[REMOVED] =     g_signal_new (NM_BLUEZ_DEVICE_REMOVED,
-	                                     G_OBJECT_CLASS_TYPE (object_class),
-	                                     G_SIGNAL_RUN_LAST,
-	                                     0,
-	                                     NULL, NULL, NULL,
-	                                     G_TYPE_NONE, 0);
-}
-
diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h
deleted file mode 100644
index e8dd2ced..00000000
--- a/src/devices/bluetooth/nm-bluez-device.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2009 - 2014 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_BLUEZ_DEVICE_H__
-#define __NETWORKMANAGER_BLUEZ_DEVICE_H__
-
-#include "nm-connection.h"
-
-#define NM_TYPE_BLUEZ_DEVICE            (nm_bluez_device_get_type ())
-#define NM_BLUEZ_DEVICE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_DEVICE, NMBluezDevice))
-#define NM_BLUEZ_DEVICE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ_DEVICE, NMBluezDeviceClass))
-#define NM_IS_BLUEZ_DEVICE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_DEVICE))
-#define NM_IS_BLUEZ_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ_DEVICE))
-#define NM_BLUEZ_DEVICE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ_DEVICE, NMBluezDeviceClass))
-
-/* Properties */
-#define NM_BLUEZ_DEVICE_PATH         "path"
-#define NM_BLUEZ_DEVICE_ADDRESS      "address"
-#define NM_BLUEZ_DEVICE_NAME         "name"
-#define NM_BLUEZ_DEVICE_CAPABILITIES "capabilities"
-#define NM_BLUEZ_DEVICE_USABLE       "usable"
-#define NM_BLUEZ_DEVICE_CONNECTED    "connected"
-
-/* Signals */
-#define NM_BLUEZ_DEVICE_INITIALIZED  "initialized"
-#define NM_BLUEZ_DEVICE_REMOVED      "removed"
-
-typedef struct _NMBluezDevice NMBluezDevice;
-typedef struct _NMBluezDeviceClass NMBluezDeviceClass;
-
-GType nm_bluez_device_get_type (void);
-
-NMBluezDevice *nm_bluez_device_new (const char *path,
-                                    const char *adapter_address,
-                                    NMSettings *settings,
-                                    int bluez_version);
-
-const char *nm_bluez_device_get_path (NMBluezDevice *self);
-
-gboolean nm_bluez_device_get_initialized (NMBluezDevice *self);
-
-gboolean nm_bluez_device_get_usable (NMBluezDevice *self);
-
-const char *nm_bluez_device_get_address (NMBluezDevice *self);
-
-const char *nm_bluez_device_get_name (NMBluezDevice *self);
-
-guint32 nm_bluez_device_get_capabilities (NMBluezDevice *self);
-
-gboolean nm_bluez_device_get_connected (NMBluezDevice *self);
-
-typedef void (*NMBluezDeviceConnectCallback) (NMBluezDevice *self,
-                                              const char *device,
-                                              GError *error,
-                                              gpointer user_data);
-
-void
-nm_bluez_device_connect_async (NMBluezDevice *self,
-                               NMBluetoothCapabilities connection_bt_type,
-                               GCancellable *cancellable,
-                               NMBluezDeviceConnectCallback callback,
-                               gpointer callback_user_data);
-
-void
-nm_bluez_device_disconnect (NMBluezDevice *self);
-
-#endif /* __NETWORKMANAGER_BLUEZ_DEVICE_H__ */
-
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 7577ab8b..ef087d83 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -1,73 +1,185 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2013 - 2014 Red Hat, Inc.
  */
 
 #include "nm-default.h"
 
+#include "nm-bluez-manager.h"
+
 #include <signal.h>
 #include <stdlib.h>
 #include <gmodule.h>
 
+#include "nm-glib-aux/nm-dbus-aux.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-dbus-manager.h"
 #include "devices/nm-device-factory.h"
 #include "devices/nm-device-bridge.h"
 #include "nm-setting-bluetooth.h"
 #include "settings/nm-settings.h"
-#include "nm-bluez4-manager.h"
-#include "nm-bluez5-manager.h"
-#include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
 #include "nm-device-bt.h"
+#include "nm-manager.h"
+#include "nm-bluez5-dun.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "nm-std-aux/nm-dbus-compat.h"
 
 /*****************************************************************************/
 
-#define NM_TYPE_BLUEZ_MANAGER            (nm_bluez_manager_get_type ())
-#define NM_BLUEZ_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManager))
-#define NM_BLUEZ_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass))
-#define NM_IS_BLUEZ_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_MANAGER))
-#define NM_IS_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_BLUEZ_MANAGER))
-#define NM_BLUEZ_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass))
+#if WITH_BLUEZ5_DUN
+#define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_DUN
+#else
+#define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_NONE
+#endif
+#define _NM_BT_CAPABILITY_SUPPORTED (NM_BT_CAPABILITY_NAP | _NM_BT_CAPABILITY_SUPPORTED_DUN)
+
+typedef struct {
+	const char *bdaddr;
+	CList lst_head;
+	NMBluetoothCapabilities bt_type:8;
+	char bdaddr_data[];
+} ConnDataHead;
+
+typedef struct {
+	NMSettingsConnection *sett_conn;
+	ConnDataHead *cdata_hd;
+	CList lst;
+} ConnDataElem;
 
 typedef struct {
-	int bluez_version;
+	GCancellable *ext_cancellable;
+	GCancellable *int_cancellable;
+	NMBtVTableRegisterCallback callback;
+	gpointer callback_user_data;
+	gulong ext_cancelled_id;
+} NetworkServerRegisterReqData;
+
+typedef struct {
+	GCancellable *ext_cancellable;
+	GCancellable *int_cancellable;
+	NMBluezManagerConnectCb callback;
+	gpointer callback_user_data;
+	char *device_name;
+	gulong ext_cancelled_id;
+	guint timeout_id;
+	guint timeout_wait_connect_id;
+} DeviceConnectReqData;
+
+typedef struct {
+	const char *object_path;
+
+	NMBluezManager *self;
 
+	/* Fields name with "d_" prefix are purely cached values from BlueZ's
+	 * ObjectManager D-Bus interface. There is no logic whatsoever about
+	 * them.
+	 */
+
+	CList process_change_lst;
+
+	struct {
+		char *address;
+	} d_adapter;
+
+	struct {
+		char *address;
+		char *name;
+		char *adapter;
+	} d_device;
+
+	struct {
+		char *interface;
+	} d_network;
+
+	struct {
+		CList lst;
+		char *adapter_address;
+		NMDevice *device_br;
+		NetworkServerRegisterReqData *r_req_data;
+	} x_network_server;
+
+	struct {
+		NMSettingsConnection *panu_connection;
+		NMDeviceBt *device_bt;
+		DeviceConnectReqData *c_req_data;
+		NMBluez5DunContext *connect_dun_context;
+		gulong device_bt_signal_id;
+	} x_device;
+
+	/* indicate whether the D-Bus object has the particular D-Bus interface. */
+	bool d_has_adapter_iface:1;
+	bool d_has_device_iface:1;
+	bool d_has_network_iface:1;
+	bool d_has_network_server_iface:1;
+
+	/* cached D-Bus properties for Device1 ("d_device*"). */
+	NMBluetoothCapabilities d_device_capabilities:6;
+	bool d_device_connected:1;
+	bool d_device_paired:1;
+
+	/* cached D-Bus properties for Network1 ("d_network*"). */
+	bool d_network_connected:1;
+
+	/* cached D-Bus properties for Adapter1 ("d_adapter*"). */
+	bool d_adapter_powered:1;
+
+	/* properties related to device ("x_device*"). */
+	NMBluetoothCapabilities x_device_connect_bt_type:6;
+	bool x_device_is_usable:1;
+	bool x_device_is_connected:1;
+
+	bool x_device_panu_connection_allow_create:1;
+
+	/* flag to remember last time when we checked wether the object
+	 * was  a suitable adapter that is usable to a device. */
+	bool was_usable_adapter_for_device_before:1;
+
+	char _object_path_intern[];
+} BzDBusObj;
+
+typedef struct {
+	NMManager *manager;
 	NMSettings *settings;
-	NMBluez4Manager *manager4;
-	NMBluez5Manager *manager5;
 
-	guint watch_name_id;
+	GDBusConnection *dbus_connection;
+
+	NMBtVTableNetworkServer vtable_network_server;
+
+	GCancellable *name_owner_get_cancellable;
+	GCancellable *get_managed_objects_cancellable;
+
+	GHashTable *bzobjs;
+
+	char *name_owner;
+
+	GHashTable *conn_data_heads;
+	GHashTable *conn_data_elems;
+
+	CList network_server_lst_head;
+
+	CList process_change_lst_head;
+
+	guint name_owner_changed_id;
 
-	GDBusProxy *introspect_proxy;
-	GCancellable *async_cancellable;
+	guint managed_objects_changed_id;
+
+	guint properties_changed_id;
+
+	guint process_change_idle_id;
+
+	bool settings_registered:1;
 } NMBluezManagerPrivate;
 
-typedef struct {
+struct _NMBluezManager {
 	NMDeviceFactory parent;
 	NMBluezManagerPrivate _priv;
-} NMBluezManager;
+};
 
-typedef struct {
+struct _NMBluezManagerClass {
 	NMDeviceFactoryClass parent;
-} NMBluezManagerClass;
-
-static GType nm_bluez_manager_get_type (void);
+};
 
 G_DEFINE_TYPE (NMBluezManager, nm_bluez_manager, NM_TYPE_DEVICE_FACTORY);
 
@@ -93,313 +205,2583 @@ nm_device_factory_create (GError **error)
 
 /*****************************************************************************/
 
-static void check_bluez_and_try_setup (NMBluezManager *self);
+static NMBluetoothCapabilities
+convert_uuids_to_capabilities (const char *const*strv)
+{
+	NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE;
+
+	if (strv) {
+		for (; strv[0]; strv++) {
+			gs_free char *s_part1 = NULL;
+			const char *str = strv[0];
+			const char *s;
+
+			s = strchr (str, '-');
+			if (!s)
+				continue;
+
+			s_part1 = g_strndup (str, s - str);
+			switch (g_ascii_strtoull (s_part1, NULL, 16)) {
+			case 0x1103:
+				capabilities |= NM_BT_CAPABILITY_DUN;
+				break;
+			case 0x1116:
+				capabilities |= NM_BT_CAPABILITY_NAP;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	return capabilities;
+}
+
+/*****************************************************************************/
+
+static void _cleanup_for_name_owner (NMBluezManager *self);
+static void _connect_disconnect (NMBluezManager *self,
+                                 BzDBusObj *bzobj,
+                                 const char *reason);
+static gboolean _bzobjs_network_server_is_usable (const BzDBusObj *bzobj,
+                                                  gboolean require_powered);
+static gboolean _bzobjs_is_dead (const BzDBusObj *bzobj);
+static gboolean _bzobjs_device_is_usable (const BzDBusObj *bzobj,
+                                          BzDBusObj **out_adapter_bzobj,
+                                          gboolean *out_create_panu_connection);
+static gboolean _bzobjs_adapter_is_usable_for_device (const BzDBusObj *bzobj);
+static ConnDataHead *_conn_track_find_head (NMBluezManager *self,
+                                            NMBluetoothCapabilities bt_type,
+                                            const char *bdaddr);
+static void _process_change_idle_schedule (NMBluezManager *self,
+                                           BzDBusObj *bzobj);
+static void _network_server_unregister_bridge (NMBluezManager *self,
+                                               BzDBusObj *bzobj,
+                                               const char *reason);
+static gboolean _connect_timeout_wait_connected_cb (gpointer user_data);
 
 /*****************************************************************************/
 
-struct AsyncData {
+static void
+_dbus_call_complete_cb_nop (GObject *source_object,
+                            GAsyncResult *res,
+                            gpointer user_data)
+{
+	/* we don't do anything at all. The only reason to register this
+	 * callback is so that GDBusConnection keeps the cancellable alive
+	 * long enough until the call completes.
+	 *
+	 * Note that this cancellable in turn is registered via
+	 * nm_shutdown_wait_obj_register_*(), to block shutdown until
+	 * we are done. */
+}
+
+/*****************************************************************************/
+
+static void
+_network_server_register_req_data_complete (NetworkServerRegisterReqData *r_req_data,
+                                            GError *error)
+{
+	nm_clear_g_signal_handler (r_req_data->ext_cancellable, &r_req_data->ext_cancelled_id);
+
+	nm_clear_g_cancellable (&r_req_data->int_cancellable);
+
+	if (r_req_data->callback) {
+		gs_free_error GError *error_cancelled = NULL;
+
+		if (g_cancellable_set_error_if_cancelled (r_req_data->ext_cancellable, &error_cancelled))
+			error = error_cancelled;
+
+		r_req_data->callback (error, r_req_data->callback_user_data);
+	}
+
+	g_object_unref (r_req_data->ext_cancellable);
+	nm_g_slice_free (r_req_data);
+}
+
+static void
+_device_connect_req_data_complete (DeviceConnectReqData *c_req_data,
+                                   NMBluezManager *self,
+                                   const char *device_name,
+                                   GError *error)
+{
+	nm_assert ((!!device_name) != (!!error));
+
+	nm_clear_g_signal_handler (c_req_data->ext_cancellable, &c_req_data->ext_cancelled_id);
+
+	nm_clear_g_cancellable (&c_req_data->int_cancellable);
+	nm_clear_g_source (&c_req_data->timeout_id);
+	nm_clear_g_source (&c_req_data->timeout_wait_connect_id);
+
+	if (c_req_data->callback) {
+		gs_free_error GError *error_cancelled = NULL;
+
+		if (g_cancellable_set_error_if_cancelled (c_req_data->ext_cancellable, &error_cancelled)) {
+			error = error_cancelled;
+			device_name = NULL;
+		}
+
+		c_req_data->callback (self, TRUE, device_name, error, c_req_data->callback_user_data);
+	}
+
+	g_object_unref (c_req_data->ext_cancellable);
+	nm_clear_g_free (&c_req_data->device_name);
+	nm_g_slice_free (c_req_data);
+}
+
+/*****************************************************************************/
+
+static BzDBusObj *
+_bz_dbus_obj_new (NMBluezManager *self,
+                  const char *object_path)
+{
+	BzDBusObj *bzobj;
+	gsize l;
+
+	nm_assert (NM_IS_BLUEZ_MANAGER (self));
+
+	l = strlen (object_path) + 1;
+
+	bzobj = g_malloc (sizeof (BzDBusObj) + l);
+	*bzobj = (BzDBusObj) {
+		.object_path                           = bzobj->_object_path_intern,
+		.self                                  = self,
+		.x_network_server.lst                  = C_LIST_INIT (bzobj->x_network_server.lst),
+		.process_change_lst                    = C_LIST_INIT (bzobj->process_change_lst),
+		.x_device_panu_connection_allow_create = TRUE,
+	};
+	memcpy (bzobj->_object_path_intern, object_path, l);
+
+	return bzobj;
+}
+
+static void
+_bz_dbus_obj_free (BzDBusObj *bzobj)
+{
+	nm_assert (bzobj);
+	nm_assert (NM_IS_BLUEZ_MANAGER (bzobj->self));
+	nm_assert (!bzobj->x_network_server.device_br);
+	nm_assert (!bzobj->x_network_server.r_req_data);
+	nm_assert (!bzobj->x_device.c_req_data);
+
+	c_list_unlink_stale (&bzobj->process_change_lst);
+	c_list_unlink_stale (&bzobj->x_network_server.lst);
+	g_free (bzobj->x_network_server.adapter_address);
+	g_free (bzobj->d_adapter.address);
+	g_free (bzobj->d_network.interface);
+	g_free (bzobj->d_device.address);
+	g_free (bzobj->d_device.name);
+	g_free (bzobj->d_device.adapter);
+	g_free (bzobj);
+}
+
+/*****************************************************************************/
+
+static const char *
+_bzobj_to_string (const BzDBusObj *bzobj, char *buf, gsize len)
+{
+	char *buf0 = buf;
+	const char *prefix = "";
+	gboolean device_is_usable;
+	gboolean create_panu_connection = FALSE;
+	gboolean network_server_is_usable;
+	char sbuf_cap[100];
+
+	if (len > 0)
+		buf[0] = '\0';
+
+	if (bzobj->d_has_adapter_iface) {
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append_str (&buf, &len, "Adapter1 {");
+		if (bzobj->d_adapter.address) {
+			nm_utils_strbuf_append (&buf, &len, " d.address: \"%s\"", bzobj->d_adapter.address);
+			if (bzobj->d_adapter_powered)
+				nm_utils_strbuf_append_str (&buf, &len, ",");
+		}
+		if (bzobj->d_adapter_powered)
+			nm_utils_strbuf_append (&buf, &len, " d.powered: 1");
+		nm_utils_strbuf_append_str (&buf, &len, " }");
+	}
+
+	if (bzobj->d_has_device_iface) {
+		const char *prefix1 = "";
+
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append_str (&buf, &len, "Device1 {");
+		if (bzobj->d_device.address) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.address: \"%s\"", prefix1, bzobj->d_device.address);
+			prefix1 = ",";
+		}
+		if (bzobj->d_device.name) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.name: \"%s\"", prefix1, bzobj->d_device.name);
+			prefix1 = ",";
+		}
+		if (bzobj->d_device.adapter) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.adapter: \"%s\"", prefix1, bzobj->d_device.adapter);
+			prefix1 = ",";
+		}
+		if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.capabilities: \"%s\"",
+			                        prefix1,
+			                        nm_bluetooth_capability_to_string (bzobj->d_device_capabilities, sbuf_cap, sizeof (sbuf_cap)));
+			prefix1 = ",";
+		}
+		if (bzobj->d_device_connected) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.connected: 1", prefix1);
+			prefix1 = ",";
+		}
+		if (bzobj->d_device_paired) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.paired: 1", prefix1);
+			prefix1 = ",";
+		}
+		nm_utils_strbuf_append_str (&buf, &len, " }");
+	}
+
+	network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE);
+
+	if (   bzobj->d_has_network_server_iface
+	    || network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst))
+	    || !c_list_is_empty (&bzobj->x_network_server.lst)
+	    || !nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address)
+	    || bzobj->x_network_server.device_br
+	    || bzobj->x_network_server.r_req_data) {
+
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+
+		nm_utils_strbuf_append (&buf, &len, "NetworkServer1 { ");
+
+		if (!bzobj->d_has_network_server_iface)
+			nm_utils_strbuf_append (&buf, &len, " has-d-iface: 0, ");
+
+		if (network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst)))
+			nm_utils_strbuf_append (&buf, &len, "usable: %d, used: %d", !!network_server_is_usable, !network_server_is_usable);
+		else if (network_server_is_usable)
+			nm_utils_strbuf_append (&buf, &len, "used: 1");
+		else
+			nm_utils_strbuf_append (&buf, &len, "usable: 0");
+
+		if (!nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address)) {
+			if (bzobj->x_network_server.adapter_address)
+				nm_utils_strbuf_append (&buf, &len, ", adapter-address: \"%s\"", bzobj->x_network_server.adapter_address);
+			else
+				nm_utils_strbuf_append (&buf, &len, ", adapter-address: <NULL>");
+		}
+
+		if (bzobj->x_network_server.device_br)
+			nm_utils_strbuf_append (&buf, &len, ", bridge-device: 1");
+
+		if (bzobj->x_network_server.r_req_data)
+			nm_utils_strbuf_append (&buf, &len, ", register-in-progress: 1");
+
+		nm_utils_strbuf_append_str (&buf, &len, " }");
+	}
+
+	device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, &create_panu_connection);
+
+	if (   bzobj->d_has_network_iface
+	    || bzobj->d_network.interface
+	    || bzobj->d_network_connected
+	    || create_panu_connection
+	    || bzobj->x_device.panu_connection
+	    || device_is_usable != bzobj->x_device_is_usable
+	    || bzobj->x_device.device_bt
+	    || bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE
+	    || bzobj->x_device.connect_dun_context
+	    || bzobj->x_device.c_req_data
+	    || bzobj->x_device_is_connected != bzobj->d_network_connected) {
+
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append_str (&buf, &len, "Network1 {");
+		if (bzobj->d_network.interface)
+			nm_utils_strbuf_append (&buf, &len, " d.interface: \"%s\", ", bzobj->d_network.interface);
+		if (bzobj->d_network_connected)
+			nm_utils_strbuf_append (&buf, &len, " d.connected: %d, ", !!bzobj->d_network_connected);
+		if (!bzobj->d_has_network_iface)
+			nm_utils_strbuf_append (&buf, &len, " has-d-iface: 0, ");
+		if (device_is_usable != bzobj->x_device_is_usable)
+			nm_utils_strbuf_append (&buf, &len, " usable: %d, used: %d", !!device_is_usable, !device_is_usable);
+		else if (device_is_usable)
+			nm_utils_strbuf_append (&buf, &len, " used: 1");
+		else
+			nm_utils_strbuf_append (&buf, &len, " usable: 0");
+
+		if (create_panu_connection)
+			nm_utils_strbuf_append (&buf, &len, ", create-panu-connection: 1");
+
+		if (bzobj->x_device.panu_connection)
+			nm_utils_strbuf_append (&buf, &len, ", has-panu-connection: 1");
+
+		if (bzobj->x_device.device_bt)
+			nm_utils_strbuf_append (&buf, &len, ", has-device: 1");
+
+		if (   bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE
+		    || bzobj->x_device.connect_dun_context) {
+			nm_utils_strbuf_append (&buf, &len, ", connect: %s%s",
+			                        nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)),
+			                        bzobj->x_device.connect_dun_context ? ",with-dun-context" : "");
+		}
+
+		if (bzobj->x_device.c_req_data)
+			nm_utils_strbuf_append (&buf, &len, ", connecting: 1");
+
+		if (bzobj->x_device_is_connected != bzobj->d_network_connected)
+			nm_utils_strbuf_append (&buf, &len, ", connected: %d", !!bzobj->x_device_is_connected);
+
+		nm_utils_strbuf_append_str (&buf, &len, " }");
+	}
+
+	if (_bzobjs_is_dead (bzobj)) {
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append_str (&buf, &len, "dead: 1");
+	}
+
+	if (!c_list_is_empty (&bzobj->process_change_lst)) {
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append (&buf, &len, "change-pending-on-idle: 1");
+	}
+
+	if (_bzobjs_adapter_is_usable_for_device (bzobj) != bzobj->was_usable_adapter_for_device_before) {
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append (&buf, &len, "change-usable-adapter-for-device: 1");
+	}
+
+	return buf0;
+}
+
+#define _LOG_bzobj(bzobj, context) \
+	G_STMT_START { \
+		const BzDBusObj *const _bzobj = (bzobj); \
+		char _buf[500]; \
+		\
+		_LOGT ("change %-21s %s : { %s }", \
+		       (context), \
+		       _bzobj->object_path, \
+		       _bzobj_to_string (_bzobj, _buf, sizeof (_buf))); \
+	} G_STMT_END
+
+static gboolean
+_bzobjs_is_dead (const BzDBusObj *bzobj)
+{
+	return    !bzobj->d_has_adapter_iface
+	       && !bzobj->d_has_device_iface
+	       && !bzobj->d_has_network_iface
+	       && !bzobj->d_has_network_server_iface
+	       && c_list_is_empty (&bzobj->process_change_lst);
+}
+
+static BzDBusObj *
+_bzobjs_get (NMBluezManager *self, const char *object_path)
+{
+	return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->bzobjs, &object_path);
+}
+
+static BzDBusObj *
+_bzobjs_add (NMBluezManager *self,
+             const char *object_path)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj;
+
+	bzobj = _bz_dbus_obj_new (self, object_path);
+	if (!g_hash_table_add (priv->bzobjs, bzobj))
+		nm_assert_not_reached ();
+	return bzobj;
+}
+
+static void
+_bzobjs_del (BzDBusObj *bzobj)
+{
+	nm_assert (bzobj);
+	nm_assert (bzobj == _bzobjs_get (bzobj->self, bzobj->object_path));
+
+	if (!g_hash_table_remove (NM_BLUEZ_MANAGER_GET_PRIVATE (bzobj->self)->bzobjs, bzobj))
+		nm_assert_not_reached ();
+}
+
+static void
+_bzobjs_del_if_dead (BzDBusObj *bzobj)
+{
+	if (_bzobjs_is_dead (bzobj))
+		_bzobjs_del (bzobj);
+}
+
+static BzDBusObj *
+_bzobjs_init (NMBluezManager *self, BzDBusObj **inout, const char *object_path)
+{
+	nm_assert (NM_IS_BLUEZ_MANAGER (self));
+	nm_assert (object_path);
+	nm_assert (inout);
+
+	if (!*inout) {
+		*inout = _bzobjs_get (self, object_path);
+		if (!*inout)
+			*inout = _bzobjs_add (self, object_path);
+	}
+
+	nm_assert (nm_streq ((*inout)->object_path, object_path));
+	nm_assert (*inout == _bzobjs_get (self, object_path));
+	return *inout;
+}
+
+static gboolean
+_bzobjs_adapter_is_usable_for_device (const BzDBusObj *bzobj)
+{
+	return    bzobj->d_has_adapter_iface
+	       && bzobj->d_adapter.address
+	       && bzobj->d_adapter_powered;
+}
+
+static gboolean
+_bzobjs_device_is_usable (const BzDBusObj *bzobj,
+                          BzDBusObj **out_adapter_bzobj,
+                          gboolean *out_create_panu_connection)
+{
 	NMBluezManager *self;
-	GCancellable *async_cancellable;
-};
+	NMBluezManagerPrivate *priv;
+	gboolean usable_dun = FALSE;
+	gboolean usable_nap = FALSE;
+	BzDBusObj *bzobj_adapter;
+	gboolean create_panu_connection = FALSE;
+
+	if (   !bzobj->d_has_device_iface
+	    || !NM_FLAGS_ANY ((NMBluetoothCapabilities) bzobj->d_device_capabilities, _NM_BT_CAPABILITY_SUPPORTED)
+	    || !bzobj->d_device.name
+	    || !bzobj->d_device.address
+	    || !bzobj->d_device_paired
+	    || !bzobj->d_device.adapter)
+		goto out_unusable;
+
+	self = bzobj->self;
+
+	priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	if (!priv->settings_registered)
+		goto out_unusable;
+
+	bzobj_adapter = _bzobjs_get (self, bzobj->d_device.adapter);
+	if (   !bzobj_adapter
+	    || !_bzobjs_adapter_is_usable_for_device (bzobj_adapter))
+		goto out_unusable;
+
+#if WITH_BLUEZ5_DUN
+	if (NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_DUN)) {
+		if (_conn_track_find_head (self, NM_BT_CAPABILITY_DUN, bzobj->d_device.address))
+			usable_dun = TRUE;
+	}
+#endif
+
+	if (NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP)) {
+		if (!bzobj->d_has_network_iface)
+			usable_nap = FALSE;
+		else if (_conn_track_find_head (self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address))
+			usable_nap = TRUE;
+		else if (bzobj->x_device_panu_connection_allow_create) {
+			/* We didn't yet try to create a connection. Presume we are going to create
+			 * it when the time comes... */
+			usable_nap = TRUE;
+			create_panu_connection = TRUE;
+		}
+	}
+
+	if (   !usable_dun
+	    && !usable_nap) {
+		if (   bzobj->x_device.device_bt
+		    && nm_device_get_state (NM_DEVICE (bzobj->x_device.device_bt)) > NM_DEVICE_STATE_DISCONNECTED) {
+			/* The device is still activated... the absence of a profile does not
+			 * render it unusable (yet). But since there is no more profile, the
+			 * device is probably about to disconnect. */
+		} else
+			goto out_unusable;
+	}
+
+	NM_SET_OUT (out_create_panu_connection, create_panu_connection);
+	NM_SET_OUT (out_adapter_bzobj, bzobj_adapter);
+	return TRUE;
 
-static struct AsyncData *
-async_data_pack (NMBluezManager *self)
+out_unusable:
+	NM_SET_OUT (out_create_panu_connection, FALSE);
+	NM_SET_OUT (out_adapter_bzobj, NULL);
+	return FALSE;
+}
+
+static gboolean
+_bzobjs_device_is_connected (const BzDBusObj *bzobj)
 {
-	struct AsyncData *data = g_new (struct AsyncData, 1);
+	nm_assert (_bzobjs_device_is_usable (bzobj, NULL, NULL));
+
+	if (   !bzobj->d_has_device_iface
+	    || !bzobj->d_device_connected)
+		return FALSE;
+
+	if (   bzobj->d_has_network_iface
+	    && bzobj->d_network_connected)
+		return TRUE;
+	if (bzobj->x_device.connect_dun_context) {
+		/* As long as we have a dun-context, we consider it connected.
+		 *
+		 * We require NMDeviceBt to try to connect to the modem, and if that fails,
+		 * it will disconnect. */
+		return TRUE;
+	}
+	return FALSE;
+}
 
-	data->self = self;
-	data->async_cancellable = g_object_ref (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->async_cancellable);
-	return data;
+static gboolean
+_bzobjs_network_server_is_usable (const BzDBusObj *bzobj,
+                                  gboolean require_powered)
+{
+	return    bzobj->d_has_network_server_iface
+	       && bzobj->d_has_adapter_iface
+	       && bzobj->d_adapter.address
+	       && (   !require_powered
+	           || bzobj->d_adapter_powered);
 }
 
-static NMBluezManager *
-async_data_unpack (struct AsyncData *async_data)
+/*****************************************************************************/
+
+static ConnDataHead *
+_conn_data_head_new (NMBluetoothCapabilities bt_type,
+                     const char *bdaddr)
 {
-	NMBluezManager *self = g_cancellable_is_cancelled (async_data->async_cancellable)
-	                       ? NULL : async_data->self;
+	ConnDataHead *cdata_hd;
+	gsize l;
 
-	g_object_unref (async_data->async_cancellable);
-	g_free (async_data);
-	return self;
+	nm_assert (NM_IN_SET (bt_type, NM_BT_CAPABILITY_DUN,
+	                               NM_BT_CAPABILITY_NAP));
+	nm_assert (bdaddr);
+
+	l = strlen (bdaddr) + 1;
+	cdata_hd = g_malloc (sizeof (ConnDataHead) + l);
+	*cdata_hd = (ConnDataHead) {
+		.bdaddr   = cdata_hd->bdaddr_data,
+		.lst_head = C_LIST_INIT (cdata_hd->lst_head),
+		.bt_type  = bt_type,
+	};
+	memcpy (cdata_hd->bdaddr_data, bdaddr, l);
+
+	nm_assert (cdata_hd->bt_type == bt_type);
+
+	return cdata_hd;
+}
+
+static guint
+_conn_data_head_hash (gconstpointer ptr)
+{
+	const ConnDataHead *cdata_hd = ptr;
+	NMHashState h;
+
+	nm_hash_init (&h, 520317467u);
+	nm_hash_update_val (&h, (NMBluetoothCapabilities) cdata_hd->bt_type);
+	nm_hash_update_str (&h, cdata_hd->bdaddr);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_conn_data_head_equal (gconstpointer a, gconstpointer b)
+{
+	const ConnDataHead *cdata_hd_a = a;
+	const ConnDataHead *cdata_hd_b = b;
+
+	return     cdata_hd_a->bt_type == cdata_hd_b->bt_type
+	        && nm_streq (cdata_hd_a->bdaddr, cdata_hd_b->bdaddr);
+}
+
+static ConnDataHead *
+_conn_track_find_head (NMBluezManager *self,
+                       NMBluetoothCapabilities bt_type,
+                       const char *bdaddr)
+{
+	ConnDataHead cdata_hd = {
+		.bt_type = bt_type,
+		.bdaddr  = bdaddr,
+	};
+
+	return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->conn_data_heads, &cdata_hd);
+}
+
+static ConnDataElem *
+_conn_track_find_elem (NMBluezManager *self,
+                       NMSettingsConnection *sett_conn)
+{
+	G_STATIC_ASSERT (G_STRUCT_OFFSET (ConnDataElem, sett_conn) == 0);
+
+	return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->conn_data_elems, &sett_conn);
+}
+
+static gboolean
+_conn_track_is_relevant_connection (NMConnection *connection,
+                                    NMBluetoothCapabilities *out_bt_type,
+                                    const char **out_bdaddr)
+{
+	NMSettingBluetooth *s_bt;
+	NMBluetoothCapabilities bt_type;
+	const char *bdaddr;
+	const char *b_type;
+
+	s_bt = nm_connection_get_setting_bluetooth (connection);
+	if (!s_bt)
+		return FALSE;
+
+	if (!nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME))
+		return FALSE;
+
+	bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
+	if (!bdaddr)
+		return FALSE;
+
+	b_type = nm_setting_bluetooth_get_connection_type (s_bt);
+
+	if (nm_streq (b_type, NM_SETTING_BLUETOOTH_TYPE_DUN))
+		bt_type = NM_BT_CAPABILITY_DUN;
+	else if (nm_streq (b_type, NM_SETTING_BLUETOOTH_TYPE_PANU))
+		bt_type = NM_BT_CAPABILITY_NAP;
+	else
+		return FALSE;
+
+	NM_SET_OUT (out_bt_type, bt_type);
+	NM_SET_OUT (out_bdaddr, bdaddr);
+	return TRUE;
+}
+
+static gboolean
+_conn_track_is_relevant_sett_conn (NMSettingsConnection *sett_conn,
+                                   NMBluetoothCapabilities *out_bt_type,
+                                   const char **out_bdaddr)
+{
+	NMConnection *connection;
+
+	connection = nm_settings_connection_get_connection (sett_conn);
+	if (!connection)
+		return FALSE;
+
+	return _conn_track_is_relevant_connection (connection, out_bt_type, out_bdaddr);
+}
+
+static gboolean
+_conn_track_is_relevant_for_sett_conn (NMSettingsConnection *sett_conn,
+                                       NMBluetoothCapabilities bt_type,
+                                       const char *bdaddr)
+{
+	NMBluetoothCapabilities x_bt_type;
+	const char *x_bdaddr;
+
+	return    bdaddr
+	       && _conn_track_is_relevant_sett_conn (sett_conn, &x_bt_type, &x_bdaddr)
+	       && x_bt_type == bt_type
+	       && nm_streq (x_bdaddr, bdaddr);
+}
+
+static void
+_conn_track_schedule_notify (NMBluezManager *self,
+                             NMBluetoothCapabilities bt_type,
+                             const char *bdaddr)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	GHashTableIter iter;
+	BzDBusObj *bzobj;
+
+	g_hash_table_iter_init (&iter, priv->bzobjs);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) {
+		gboolean device_is_usable;
+
+		device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL);
+		if (bzobj->x_device_is_usable != device_is_usable)
+			_process_change_idle_schedule (self, bzobj);
+	}
 }
 
-/**
- * Cancel any current attempt to detect the version and cleanup
- * the related fields.
- **/
 static void
-cleanup_checking (NMBluezManager *self, gboolean do_unwatch_name)
+_conn_track_update (NMBluezManager *self,
+                    NMSettingsConnection *sett_conn,
+                    gboolean track,
+                    gboolean *out_changed,
+                    gboolean *out_changed_usable,
+                    ConnDataElem **out_conn_data_elem)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	ConnDataHead *cdata_hd;
+	ConnDataElem *cdata_el;
+	ConnDataElem *cdata_el_remove = NULL;
+	NMBluetoothCapabilities bt_type;
+	const char *bdaddr;
+	gboolean changed = FALSE;
+	gboolean changed_usable = FALSE;
+	char sbuf_cap[100];
+
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
+
+	cdata_el = _conn_track_find_elem (self, sett_conn);
+
+	if (track)
+		track = _conn_track_is_relevant_sett_conn (sett_conn, &bt_type, &bdaddr);
 
-	nm_clear_g_cancellable (&priv->async_cancellable);
+	if (!track) {
+		cdata_el_remove = g_steal_pointer (&cdata_el);
+		goto out_remove;
+	}
+
+	if (cdata_el) {
+		cdata_hd = cdata_el->cdata_hd;
+		if (   cdata_hd->bt_type != bt_type
+	        || !nm_streq (cdata_hd->bdaddr, bdaddr))
+			cdata_el_remove = g_steal_pointer (&cdata_el);
+	}
+
+	if (!cdata_el) {
+		_LOGT ("connecton: track for %s, %s: %s (%s)",
+		       nm_bluetooth_capability_to_string (bt_type, sbuf_cap, sizeof (sbuf_cap)),
+		       bdaddr,
+		       nm_settings_connection_get_uuid (sett_conn),
+		       nm_settings_connection_get_id (sett_conn));
+		changed = TRUE;
+		cdata_hd = _conn_track_find_head (self, bt_type, bdaddr);
+		if (!cdata_hd) {
+			changed_usable = TRUE;
+			cdata_hd = _conn_data_head_new (bt_type, bdaddr);
+			if (!g_hash_table_add (priv->conn_data_heads, cdata_hd))
+				nm_assert_not_reached ();
+			_conn_track_schedule_notify (self, bt_type, bdaddr);
+		}
+		cdata_el = g_slice_new (ConnDataElem);
+		cdata_el->sett_conn = sett_conn;
+		cdata_el->cdata_hd = cdata_hd;
+		c_list_link_tail (&cdata_hd->lst_head, &cdata_el->lst);
+		if (!g_hash_table_add (priv->conn_data_elems, cdata_el))
+			nm_assert_not_reached ();
+	}
 
-	g_clear_object (&priv->introspect_proxy);
+out_remove:
+	if (cdata_el_remove) {
+		GHashTableIter iter;
+		BzDBusObj *bzobj;
+
+		_LOGT ("connecton: untrack for %s, %s: %s (%s)",
+		       nm_bluetooth_capability_to_string (cdata_el_remove->cdata_hd->bt_type, sbuf_cap, sizeof (sbuf_cap)),
+		       cdata_el_remove->cdata_hd->bdaddr,
+		       nm_settings_connection_get_uuid (sett_conn),
+		       nm_settings_connection_get_id (sett_conn));
+
+		g_hash_table_iter_init (&iter, priv->bzobjs);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) {
+			if (bzobj->x_device.panu_connection == sett_conn)
+				bzobj->x_device.panu_connection = NULL;
+		}
 
-	if (do_unwatch_name && priv->watch_name_id) {
-		g_bus_unwatch_name (priv->watch_name_id);
-		priv->watch_name_id = 0;
+		changed = TRUE;
+		cdata_hd = cdata_el_remove->cdata_hd;
+		c_list_unlink_stale (&cdata_el_remove->lst);
+		if (!g_hash_table_remove (priv->conn_data_elems, cdata_el_remove))
+			nm_assert_not_reached ();
+		if (c_list_is_empty (&cdata_hd->lst_head)) {
+			changed_usable = TRUE;
+			_conn_track_schedule_notify (self, cdata_hd->bt_type, cdata_hd->bdaddr);
+			if (!g_hash_table_remove (priv->conn_data_heads, cdata_hd))
+				nm_assert_not_reached ();
+		}
 	}
+
+	NM_SET_OUT (out_changed, changed);
+	NM_SET_OUT (out_changed_usable, changed_usable);
+	NM_SET_OUT (out_conn_data_elem, cdata_el);
+}
+
+/*****************************************************************************/
+
+static void
+cp_connection_added (NMSettings *settings,
+                     NMSettingsConnection *sett_conn,
+                     NMBluezManager *self)
+{
+	_conn_track_update (self, sett_conn, TRUE, NULL, NULL, NULL);
 }
 
 static void
-manager_bdaddr_added_cb (GObject *manager,
-                         NMBluezDevice *bt_device,
-                         const char *bdaddr,
-                         const char *name,
-                         const char *object_path,
-                         guint32 capabilities,
-                         gpointer user_data)
+cp_connection_updated (NMSettings *settings,
+                       NMSettingsConnection *sett_conn,
+                       guint update_reason_u,
+                       NMBluezManager *self)
 {
-	NMBluezManager *self = NM_BLUEZ_MANAGER (user_data);
-	NMDevice *device;
-	gboolean has_dun = (capabilities & NM_BT_CAPABILITY_DUN);
-	gboolean has_nap = (capabilities & NM_BT_CAPABILITY_NAP);
+	_conn_track_update (self, sett_conn, TRUE, NULL, NULL, NULL);
+}
 
-	g_return_if_fail (bdaddr != NULL);
-	g_return_if_fail (name != NULL);
-	g_return_if_fail (object_path != NULL);
-	g_return_if_fail (capabilities != NM_BT_CAPABILITY_NONE);
-	g_return_if_fail (NM_IS_BLUEZ_DEVICE (bt_device));
+static void
+cp_connection_removed (NMSettings *settings,
+                       NMSettingsConnection *sett_conn,
+                       NMBluezManager *self)
+{
+	_conn_track_update (self, sett_conn, FALSE, NULL, NULL, NULL);
+}
+
+/*****************************************************************************/
 
-	device = nm_device_bt_new (bt_device, object_path, bdaddr, name, capabilities);
-	if (!device)
+static NMBluezManager *
+_network_server_get_bluez_manager (const NMBtVTableNetworkServer *vtable_network_server)
+{
+	NMBluezManager *self;
+
+	self = (NMBluezManager *) (((char *) vtable_network_server) - G_STRUCT_OFFSET (NMBluezManager, _priv.vtable_network_server));
+
+	g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (self), NULL);
+
+	return self;
+}
+
+static BzDBusObj *
+_network_server_find_has_device (NMBluezManagerPrivate *priv,
+                                 NMDevice *device)
+{
+	BzDBusObj *bzobj;
+
+	c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) {
+		if (bzobj->x_network_server.device_br == device)
+			return bzobj;
+	}
+	return NULL;
+}
+
+static BzDBusObj *
+_network_server_find_available (NMBluezManagerPrivate *priv,
+                                const char *addr,
+                                NMDevice *device_accept_busy)
+{
+	BzDBusObj *bzobj;
+
+	c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) {
+		if (bzobj->x_network_server.device_br) {
+			if (bzobj->x_network_server.device_br != device_accept_busy)
+				continue;
+		}
+		if (   addr
+		    && !nm_streq (addr, bzobj->d_adapter.address))
+			continue;
+		nm_assert (!bzobj->x_network_server.r_req_data);
+		return bzobj;
+	}
+	return NULL;
+}
+
+static gboolean
+_network_server_vt_is_available (const NMBtVTableNetworkServer *vtable,
+                                 const char *addr,
+                                 NMDevice *device_accept_busy)
+{
+	NMBluezManager *self = _network_server_get_bluez_manager (vtable);
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	return !!_network_server_find_available (priv, addr, device_accept_busy);
+}
+
+static void
+_network_server_register_cb (GObject *source_object,
+                             GAsyncResult *res,
+                             gpointer user_data)
+{
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	BzDBusObj *bzobj;
+
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+	if (   !ret
+	    && nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
-	_LOGI ("BT device %s (%s) added (%s%s%s)",
-	       name,
-	       bdaddr,
-	       has_dun ? "DUN" : "",
-	       has_dun && has_nap ? " " : "",
-	       has_nap ? "NAP" : "");
-	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device);
-	g_object_unref (device);
+	bzobj = user_data;
+
+	if (!ret) {
+		_LOGT ("NAP: [%s]: registering failed: %s", bzobj->object_path, error->message);
+	} else
+		_LOGT ("NAP: [%s]: registration successful", bzobj->object_path);
+
+	g_clear_object (&bzobj->x_network_server.r_req_data->int_cancellable);
+	_network_server_register_req_data_complete (g_steal_pointer (&bzobj->x_network_server.r_req_data), error);
+}
+
+static void
+_network_server_register_cancelled_cb (GCancellable *cancellable,
+                                       BzDBusObj *bzobj)
+{
+	_network_server_unregister_bridge (bzobj->self, bzobj, "registration cancelled");
+}
+
+static gboolean
+_network_server_vt_register_bridge (const NMBtVTableNetworkServer *vtable,
+                                    const char *addr,
+                                    NMDevice *device,
+                                    GCancellable *cancellable,
+                                    NMBtVTableRegisterCallback callback,
+                                    gpointer callback_user_data,
+                                    GError **error)
+{
+	NMBluezManager *self = _network_server_get_bluez_manager (vtable);
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	NetworkServerRegisterReqData *r_req_data;
+	BzDBusObj *bzobj;
+	const char *ifname;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+	g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE);
+
+	nm_assert (!g_cancellable_is_cancelled (cancellable));
+	nm_assert (!_network_server_find_has_device (priv, device));
+
+	ifname = nm_device_get_iface (device);
+	g_return_val_if_fail (ifname, FALSE);
+
+	g_return_val_if_fail (ifname, FALSE);
+
+	bzobj = _network_server_find_available (priv, addr, NULL);
+	if (!bzobj) {
+		/* The device checked that a network server is available, before
+		 * starting the activation, but for some reason it no longer is.
+		 * Indicate that the activation should not proceed. */
+		if (addr) {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "adapter %s is not available for %s",
+			                    addr, ifname);
+		} else {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "no adapter available for %s",
+			                    ifname);
+		}
+		return FALSE;
+	}
+
+	_LOGD ("NAP: [%s]: registering \"%s\" on adapter %s",
+	       bzobj->object_path,
+	       ifname,
+	       bzobj->d_adapter.address);
+
+	r_req_data = g_slice_new (NetworkServerRegisterReqData);
+	*r_req_data = (NetworkServerRegisterReqData) {
+		.int_cancellable     = g_cancellable_new (),
+		.ext_cancellable     = g_object_ref (cancellable),
+		.callback            = callback,
+		.callback_user_data  = callback_user_data,
+		.ext_cancelled_id    = g_signal_connect (cancellable,
+		                                         "cancelled",
+		                                         G_CALLBACK (_network_server_register_cancelled_cb),
+		                                         bzobj),
+	};
+
+	bzobj->x_network_server.device_br = g_object_ref (device);
+	bzobj->x_network_server.r_req_data = r_req_data;
+
+	g_dbus_connection_call (priv->dbus_connection,
+	                        priv->name_owner,
+	                        bzobj->object_path,
+	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
+	                        "Register",
+	                        g_variant_new ("(ss)",
+	                                       BLUETOOTH_CONNECT_NAP,
+	                                       ifname),
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        -1,
+	                        bzobj->x_network_server.r_req_data->int_cancellable,
+	                        _network_server_register_cb,
+	                        bzobj);
+	return TRUE;
+}
+
+static void
+_network_server_unregister_bridge_complete_on_idle_cb (gpointer user_data,
+                                                       GCancellable *cancellable)
+{
+	gs_free_error GError *error = NULL;
+	gs_free char *reason  = NULL;
+	NetworkServerRegisterReqData *r_req_data;
+
+	nm_utils_user_data_unpack (user_data, &r_req_data, &reason);
+
+	nm_utils_error_set (&error, NM_UTILS_ERROR_UNKNOWN,
+	                    "registration was aborted due to %s",
+	                    reason);
+	_network_server_register_req_data_complete (r_req_data, error);
 }
 
 static void
-manager_network_server_added_cb (GObject *manager,
-                                 gpointer user_data)
+_network_server_unregister_bridge (NMBluezManager *self,
+                                   BzDBusObj *bzobj,
+                                   const char *reason)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	_nm_unused gs_unref_object NMDevice *device = NULL;
+	NetworkServerRegisterReqData *r_req_data;
+
+	nm_assert (NM_IS_DEVICE (bzobj->x_network_server.device_br));
+
+	_LOGD ("NAP: [%s]: unregistering \"%s\" (%s)",
+	       bzobj->object_path,
+	       nm_device_get_iface (bzobj->x_network_server.device_br),
+	       reason);
+
+	device = g_steal_pointer (&bzobj->x_network_server.device_br);
+
+	r_req_data = g_steal_pointer (&bzobj->x_network_server.r_req_data);
+
+	if (priv->name_owner) {
+		gs_unref_object GCancellable *cancellable = NULL;
+
+		cancellable = g_cancellable_new ();
+
+		nm_shutdown_wait_obj_register_cancellable_full (cancellable,
+		                                                g_strdup_printf ("bt-unregister-nap[%s]", bzobj->object_path),
+		                                                TRUE);
+
+		g_dbus_connection_call (priv->dbus_connection,
+		                        priv->name_owner,
+		                        bzobj->object_path,
+		                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
+		                        "Unregister",
+		                        g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP),
+		                        NULL,
+		                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+		                        -1,
+		                        cancellable,
+		                        _dbus_call_complete_cb_nop,
+		                        NULL);
+	}
+
+	if (r_req_data) {
+		nm_clear_g_cancellable (&r_req_data->int_cancellable);
+		nm_utils_invoke_on_idle (_network_server_unregister_bridge_complete_on_idle_cb,
+		                         nm_utils_user_data_pack (r_req_data, g_strdup (reason)),
+		                         r_req_data->ext_cancellable);
+	}
+
+	_nm_device_bridge_notify_unregister_bt_nap (device, reason);
+}
+
+static gboolean
+_network_server_vt_unregister_bridge (const NMBtVTableNetworkServer *vtable,
+                                      NMDevice *device)
 {
-	nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (user_data), NULL);
+	NMBluezManager *self = _network_server_get_bluez_manager (vtable);
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+
+	bzobj = _network_server_find_has_device (priv, device);
+	if (bzobj)
+		_network_server_unregister_bridge (self, bzobj, "disconnecting");
+
+	return TRUE;
 }
 
 static void
-setup_version_number (NMBluezManager *self, int bluez_version)
+_network_server_process_change (BzDBusObj *bzobj,
+                                gboolean *out_emit_device_availability_changed)
 {
+	NMBluezManager *self = bzobj->self;
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gboolean network_server_is_usable;
+	gboolean emit_device_availability_changed = FALSE;
+
+	network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE);
 
-	g_return_if_fail (!priv->bluez_version);
+	if (!network_server_is_usable) {
 
-	_LOGI ("use BlueZ version %d", bluez_version);
+		if (!c_list_is_empty (&bzobj->x_network_server.lst)) {
+			emit_device_availability_changed = TRUE;
+			c_list_unlink (&bzobj->x_network_server.lst);
+		}
 
-	priv->bluez_version = bluez_version;
+		nm_clear_g_free (&bzobj->x_network_server.adapter_address);
+
+		if (bzobj->x_network_server.device_br) {
+			_network_server_unregister_bridge (self,
+			                                   bzobj,
+			                                     _bzobjs_network_server_is_usable (bzobj, FALSE)
+			                                   ? "adapter disabled"
+			                                   : "adapter disappeared");
+		}
+
+	} else {
+
+		if (!nm_streq0 (bzobj->x_network_server.adapter_address, bzobj->d_adapter.address)) {
+			emit_device_availability_changed = TRUE;
+			g_free (bzobj->x_network_server.adapter_address);
+			bzobj->x_network_server.adapter_address = g_strdup (bzobj->d_adapter.address);
+		}
 
-	/* Just detected the version. Cleanup the ongoing checking/detection. */
-	cleanup_checking (self, TRUE);
+		if (c_list_is_empty (&bzobj->x_network_server.lst)) {
+			emit_device_availability_changed = TRUE;
+			c_list_link_tail (&priv->network_server_lst_head, &bzobj->x_network_server.lst);
+		}
+
+	}
+
+	if (emit_device_availability_changed)
+		NM_SET_OUT (out_emit_device_availability_changed, TRUE);
 }
 
+/*****************************************************************************/
+
 static void
-setup_bluez4 (NMBluezManager *self)
+_conn_create_panu_connection (NMBluezManager *self,
+                              BzDBusObj *bzobj)
 {
-	NMBluez4Manager *manager;
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingsConnection *added;
+	NMSetting *setting;
+	gs_free char *id = NULL;
+	char uuid[37];
+	gs_free_error GError *error = NULL;
+
+	nm_utils_uuid_generate_buf (uuid);
+	id = g_strdup_printf (_("%s Network"), bzobj->d_device.name);
+
+	connection = nm_simple_connection_new ();
+
+	setting = nm_setting_connection_new ();
+	g_object_set (setting,
+	              NM_SETTING_CONNECTION_ID, id,
+	              NM_SETTING_CONNECTION_UUID, uuid,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_BLUETOOTH_SETTING_NAME,
+	              NULL);
+	nm_connection_add_setting (connection, setting);
+
+	setting = nm_setting_bluetooth_new ();
+	g_object_set (setting,
+	              NM_SETTING_BLUETOOTH_BDADDR, bzobj->d_device.address,
+	              NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU,
+	              NULL);
+	nm_connection_add_setting (connection, setting);
+
+	if (!nm_connection_normalize (connection, NULL, NULL, &error)) {
+		_LOGE ("connection: couldn't generate a connection for NAP device: %s",
+		       error->message);
+		g_return_if_reached ();
+	}
 
-	g_return_if_fail (!priv->manager4 && !priv->manager5 && !priv->bluez_version);
+	nm_assert (_conn_track_is_relevant_connection (connection, NULL, NULL));
+
+	_LOGT ("connection: create in-memory PANU connection %s (%s) for device \"%s\" (%s)",
+	       uuid,
+	       id,
+	       bzobj->d_device.name,
+	       bzobj->d_device.address);
+
+	nm_settings_add_connection (priv->settings,
+	                            connection,
+	                            NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+	                            NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+	                            &added,
+	                            &error);
+	if (!added) {
+		_LOGW ("connection: couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s",
+		       id, uuid, error->message);
+		return;
+	}
 
-	setup_version_number (self, 4);
-	priv->manager4 = manager = nm_bluez4_manager_new (priv->settings);
+	if (   !_conn_track_is_relevant_for_sett_conn (added, NM_BT_CAPABILITY_NAP, bzobj->d_device.address)
+	    || !_conn_track_find_elem (self, added)
+	    || bzobj->x_device.panu_connection) {
+		_LOGE ("connection: something went wrong creating PANU connection %s (%s) for device '%s'",
+		       uuid, id, bzobj->d_device.address);
+		g_return_if_reached ();
+	}
 
-	g_signal_connect (manager,
-	                  NM_BLUEZ_MANAGER_BDADDR_ADDED,
-	                  G_CALLBACK (manager_bdaddr_added_cb),
-	                  self);
+	bzobj->x_device.panu_connection = added;
+}
+
+/*****************************************************************************/
+
+static void
+_device_state_changed_cb (NMDevice *device,
+                          guint new_state_u,
+                          guint old_state_u,
+                          guint reason_u,
+                          gpointer user_data)
+{
+	BzDBusObj *bzobj = user_data;
 
-	nm_bluez4_manager_query_devices (manager);
+	if (!_bzobjs_device_is_usable (bzobj, NULL, NULL)) {
+		/* the device got unusable? Need to revisit it... */
+		_process_change_idle_schedule (bzobj->self, bzobj);
+	}
 }
 
 static void
-setup_bluez5 (NMBluezManager *self)
+_device_process_change (BzDBusObj *bzobj)
+{
+	NMBluezManager *self = bzobj->self;
+	gs_unref_object NMDeviceBt *device_added = NULL;
+	gs_unref_object NMDeviceBt *device_deleted = NULL;
+	gboolean device_is_usable;
+	gboolean create_panu_connection = FALSE;
+
+	device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, &create_panu_connection);
+
+	if (create_panu_connection) {
+		bzobj->x_device_panu_connection_allow_create = FALSE;
+		_conn_create_panu_connection (self, bzobj);
+		device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL);
+	} else {
+		if (   device_is_usable
+		    && bzobj->x_device_panu_connection_allow_create
+		    && NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP)
+		    && _conn_track_find_head (self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address) ) {
+			/* We have a useable device and also a panu-connection. We block future attemps
+			 * to generate a connection. */
+			bzobj->x_device_panu_connection_allow_create = FALSE;
+		}
+		if (bzobj->x_device.panu_connection) {
+			if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (bzobj->x_device.panu_connection),
+			                   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+				/* the connection that we generated earlier still exists, but it's not longer the same
+				 * as it was when we created it. Forget about it, so that we don't delete the profile later... */
+				bzobj->x_device.panu_connection = NULL;
+			} else {
+				if (   !device_is_usable
+				    || !_conn_track_is_relevant_for_sett_conn (bzobj->x_device.panu_connection,
+				                                               NM_BT_CAPABILITY_NAP,
+				                                               bzobj->d_device.address)) {
+					_LOGT ("connection: delete in-memory PANU connection %s (%s) as device %s",
+					       nm_settings_connection_get_uuid (bzobj->x_device.panu_connection),
+					       nm_settings_connection_get_id (bzobj->x_device.panu_connection),
+					       !device_is_usable ? "is now unusable" : "no longer matches");
+					bzobj->x_device_panu_connection_allow_create = TRUE;
+					nm_settings_connection_delete (g_steal_pointer (&bzobj->x_device.panu_connection), FALSE);
+				}
+			}
+		}
+	}
+
+	bzobj->x_device_is_connected =    device_is_usable
+	                               && _bzobjs_device_is_connected (bzobj);
+
+	bzobj->x_device_is_usable = device_is_usable;
+
+	if (bzobj->x_device.device_bt) {
+		const char *device_to_delete_msg;
+
+		if (!device_is_usable)
+			device_to_delete_msg = "device became unusable";
+		else if (!_nm_device_bt_for_same_device (bzobj->x_device.device_bt,
+		                                         bzobj->object_path,
+		                                         bzobj->d_device.address,
+		                                         NULL,
+		                                         bzobj->d_device_capabilities))
+			device_to_delete_msg = "device is no longer compatible";
+		else
+			device_to_delete_msg = NULL;
+
+		if (device_to_delete_msg) {
+			nm_clear_g_signal_handler (bzobj->x_device.device_bt, &bzobj->x_device.device_bt_signal_id);
+
+			device_deleted = g_steal_pointer (&bzobj->x_device.device_bt);
+
+			_LOGD ("[%s]: drop device because %s",
+			       bzobj->object_path,
+			       device_to_delete_msg);
+
+			_connect_disconnect (self, bzobj, device_to_delete_msg);
+		}
+	}
+
+	if (device_is_usable) {
+		if (!bzobj->x_device.device_bt) {
+			bzobj->x_device.device_bt = nm_device_bt_new (self,
+			                                              bzobj->object_path,
+			                                              bzobj->d_device.address,
+			                                              bzobj->d_device.name,
+			                                              bzobj->d_device_capabilities);
+			device_added = g_object_ref (bzobj->x_device.device_bt);
+			bzobj->x_device.device_bt_signal_id = g_signal_connect (device_added,
+			                                                        NM_DEVICE_STATE_CHANGED,
+			                                                        G_CALLBACK (_device_state_changed_cb),
+			                                                        bzobj);
+		} else
+			_nm_device_bt_notify_set_name (bzobj->x_device.device_bt, bzobj->d_device.name);
+
+		_nm_device_bt_notify_set_connected (bzobj->x_device.device_bt, bzobj->x_device_is_connected);
+	}
+
+	if (   bzobj->x_device.c_req_data
+	    && !bzobj->x_device.c_req_data->int_cancellable
+	    && bzobj->x_device_is_connected) {
+		gs_free char *device_name = g_steal_pointer (&bzobj->x_device.c_req_data->device_name);
+
+		_device_connect_req_data_complete (g_steal_pointer (&bzobj->x_device.c_req_data),
+		                                   self,
+		                                   device_name,
+		                                   NULL);
+	}
+
+	if (device_added)
+		g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device_added);
+
+	if (device_deleted)
+		_nm_device_bt_notify_removed (device_deleted);
+}
+
+/*****************************************************************************/
+
+static void
+_process_change_idle_all (NMBluezManager *self,
+                          gboolean *out_emit_device_availability_changed)
 {
-	NMBluez5Manager *manager;
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj;
+
+	while ((bzobj = c_list_first_entry (&priv->process_change_lst_head, BzDBusObj, process_change_lst))) {
+
+		c_list_unlink (&bzobj->process_change_lst);
+
+		_LOG_bzobj (bzobj, "before-processing");
+
+		_device_process_change (bzobj);
+
+		_network_server_process_change (bzobj, out_emit_device_availability_changed);
+
+		_LOG_bzobj (bzobj, "after-processing");
+
+		_bzobjs_del_if_dead (bzobj);
+	}
+
+	nm_clear_g_source (&priv->process_change_idle_id);
+}
 
-	g_return_if_fail (!priv->manager4 && !priv->manager5 && !priv->bluez_version);
+static gboolean
+_process_change_idle_cb (gpointer user_data)
+{
+	NMBluezManager *self = user_data;
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gboolean emit_device_availability_changed = FALSE;
 
-	setup_version_number (self, 5);
-	priv->manager5 = manager = nm_bluez5_manager_new (priv->settings);
+	_process_change_idle_all (self, &emit_device_availability_changed);
 
-	g_signal_connect (manager,
-	                  NM_BLUEZ_MANAGER_BDADDR_ADDED,
-	                  G_CALLBACK (manager_bdaddr_added_cb),
-	                  self);
-	g_signal_connect (manager,
-	                  NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED,
-	                  G_CALLBACK (manager_network_server_added_cb),
-	                  self);
+	if (emit_device_availability_changed)
+		nm_manager_notify_device_availibility_maybe_changed (priv->manager);
 
-	nm_bluez5_manager_query_devices (manager);
+	return G_SOURCE_CONTINUE;
 }
 
 static void
-watch_name_on_appeared (GDBusConnection *connection,
-                        const char *name,
-                        const char *name_owner,
-                        gpointer user_data)
+_process_change_idle_schedule (NMBluezManager *self,
+                               BzDBusObj *bzobj)
 {
-	check_bluez_and_try_setup (NM_BLUEZ_MANAGER (user_data));
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj->process_change_lst);
+	if (priv->process_change_idle_id == 0)
+		priv->process_change_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 1, _process_change_idle_cb, self, NULL);
 }
 
 static void
-check_bluez_and_try_setup_final_step (NMBluezManager *self, int bluez_version, const char *reason)
+_dbus_process_changes (NMBluezManager *self,
+                       BzDBusObj *bzobj,
+                       const char *log_reason)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gboolean network_server_is_usable;
+	gboolean adapter_is_usable_for_device;
+	gboolean device_is_usable;
+	gboolean changes = FALSE;
+	gboolean recheck_devices_for_adapter = FALSE;
+
+	nm_assert (bzobj);
+
+	_LOG_bzobj (bzobj, log_reason);
+
+	device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL);
+
+	if (bzobj->x_device_is_usable != device_is_usable)
+		changes = TRUE;
+	else if (bzobj->x_device.device_bt) {
+		if (!device_is_usable)
+			changes = TRUE;
+		else {
+			if (   bzobj->x_device_is_connected != _bzobjs_device_is_connected (bzobj)
+			    || !_nm_device_bt_for_same_device (bzobj->x_device.device_bt,
+			                                       bzobj->object_path,
+			                                       bzobj->d_device.address,
+			                                       bzobj->d_device.name,
+			                                       bzobj->d_device_capabilities))
+				changes = TRUE;
+		}
+	}
+
+	adapter_is_usable_for_device = _bzobjs_adapter_is_usable_for_device (bzobj);
+	if (adapter_is_usable_for_device != bzobj->was_usable_adapter_for_device_before) {
+		/* this function does not modify bzobj in any other cases except here.
+		 * Usually changes are processed delayed, in the idle handler.
+		 *
+		 * But the bzobj->was_usable_adapter_for_device_before only exists to know whether
+		 * we need to re-check device availability. It is correct to set the flag
+		 * here, right before we checked. */
+		bzobj->was_usable_adapter_for_device_before = adapter_is_usable_for_device;
+		recheck_devices_for_adapter = TRUE;
+		changes = TRUE;
+	}
+
+	if (!changes) {
+		network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE);
+
+		if (network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst)))
+			changes = TRUE;
+		else if (   bzobj->x_network_server.device_br
+		         && !network_server_is_usable)
+			changes = TRUE;
+		else if (!nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL,
+		                     bzobj->x_network_server.adapter_address))
+			changes = TRUE;
+	}
+
+	if (changes)
+		_process_change_idle_schedule (self, bzobj);
+
+	if (recheck_devices_for_adapter) {
+		GHashTableIter iter;
+		BzDBusObj *bzobj2;
+
+		/* we got a change to the availability of an adapter. We might need to recheck
+		 * all devices that use this adapter... */
+		g_hash_table_iter_init (&iter, priv->bzobjs);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj2, NULL)) {
+			if (bzobj2 == bzobj)
+				continue;
+			if (!nm_streq0 (bzobj2->d_device.adapter, bzobj->object_path))
+				continue;
+			if (c_list_is_empty (&bzobj2->process_change_lst))
+				_dbus_process_changes (self, bzobj2, "adapter-changed");
+			else
+				nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj2->process_change_lst);
+		}
+	}
+
+	_bzobjs_del_if_dead (bzobj);
+}
+
+/*****************************************************************************/
+
+#define ALL_RELEVANT_INTERFACE_NAMES NM_MAKE_STRV (NM_BLUEZ5_ADAPTER_INTERFACE, \
+                                                   NM_BLUEZ5_DEVICE_INTERFACE, \
+                                                   NM_BLUEZ5_NETWORK_INTERFACE, \
+                                                   NM_BLUEZ5_NETWORK_SERVER_INTERFACE)
+
+static gboolean
+_dbus_handle_properties_changed (NMBluezManager *self,
+                                 const char *object_path,
+                                 const char *interface_name,
+                                 GVariant *changed_properties,
+                                 const char *const*invalidated_properties,
+                                 BzDBusObj **inout_bzobj)
+{
+	BzDBusObj *bzobj = NULL;
+	gboolean changed = FALSE;
+	const char *property_name;
+	GVariant *property_value;
+	GVariantIter iter_prop;
+	gsize i;
 
-	g_return_if_fail (!priv->bluez_version);
+	if (!invalidated_properties)
+		invalidated_properties = NM_PTRARRAY_EMPTY (const char *);
 
-	switch (bluez_version) {
-	case 4:
-		setup_bluez4 (self);
-		break;
-	case 5:
-		setup_bluez5 (self);
-		break;
-	default:
-		_LOGD ("detecting BlueZ version failed: %s", reason);
+	nm_assert (g_variant_is_of_type (changed_properties, G_VARIANT_TYPE ("a{sv}")));
 
-		/* cancel current attempts to detect the version. */
-		cleanup_checking (self, FALSE);
-		if (!priv->watch_name_id) {
-			priv->watch_name_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
-			                                        NM_BLUEZ_SERVICE,
-			                                        G_BUS_NAME_WATCHER_FLAGS_NONE,
-			                                        watch_name_on_appeared,
-			                                        NULL,
-			                                        self,
-			                                        NULL);
+	if (inout_bzobj) {
+		bzobj = *inout_bzobj;
+		nm_assert (!bzobj || nm_streq (object_path, bzobj->object_path));
+	}
+
+	if (changed_properties)
+		g_variant_iter_init (&iter_prop, changed_properties);
+
+	if (nm_streq (interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) {
+		_bzobjs_init (self, &bzobj, object_path);
+		if (!bzobj->d_has_adapter_iface) {
+			changed = TRUE;
+			bzobj->d_has_adapter_iface = TRUE;
+		}
+
+		while (   changed_properties
+		       && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) {
+			_nm_unused gs_unref_variant GVariant *property_value_free = property_value;
+
+			if (nm_streq (property_name, "Address")) {
+				gs_free char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING)
+				                  ? nm_utils_hwaddr_canonical (g_variant_get_string (property_value, NULL), ETH_ALEN)
+				                  : NULL;
+
+				if (!nm_streq0 (bzobj->d_adapter.address, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_adapter.address);
+					bzobj->d_adapter.address = g_steal_pointer (&s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Powered")) {
+				bool v =    g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN)
+				         && g_variant_get_boolean (property_value);
+
+				if (bzobj->d_adapter_powered != v) {
+					changed = TRUE;
+					bzobj->d_adapter_powered = v;
+				}
+				continue;
+			}
+		}
+
+		for (i = 0; (property_name = invalidated_properties[i]); i++) {
+			if (nm_streq (property_name, "Address")) {
+				if (bzobj->d_adapter.address) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_adapter.address);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Powered")) {
+				if (bzobj->d_adapter_powered) {
+					changed = TRUE;
+					bzobj->d_adapter_powered = FALSE;
+				}
+				continue;
+			}
+		}
+
+	} else if (nm_streq (interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) {
+		_bzobjs_init (self, &bzobj, object_path);
+		if (!bzobj->d_has_device_iface) {
+			changed = TRUE;
+			bzobj->d_has_device_iface = TRUE;
+		}
+
+		while (   changed_properties
+		       && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) {
+			_nm_unused gs_unref_variant GVariant *property_value_free = property_value;
+
+			if (nm_streq (property_name, "Address")) {
+				gs_free char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING)
+				                  ? nm_utils_hwaddr_canonical (g_variant_get_string (property_value, NULL), ETH_ALEN)
+				                  : NULL;
+
+				if (!nm_streq0 (bzobj->d_device.address, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.address);
+					bzobj->d_device.address = g_steal_pointer (&s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Name")) {
+				const char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING)
+				                ? g_variant_get_string (property_value, NULL)
+				                : NULL;
+
+				if (!nm_streq0 (bzobj->d_device.name, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.name);
+					bzobj->d_device.name = g_strdup (s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Adapter")) {
+				const char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_OBJECT_PATH)
+				                ? g_variant_get_string (property_value, NULL)
+				                : NULL;
+
+				if (!nm_streq0 (bzobj->d_device.adapter, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.adapter);
+					bzobj->d_device.adapter = g_strdup (s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "UUIDs")) {
+				NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE;
+
+				if (g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING_ARRAY)) {
+					gs_free const char **s = g_variant_get_strv (property_value, NULL);
+
+					capabilities = convert_uuids_to_capabilities (s);
+				}
+				if (bzobj->d_device_capabilities != capabilities) {
+					changed = TRUE;
+					bzobj->d_device_capabilities = capabilities;
+					nm_assert (bzobj->d_device_capabilities == capabilities);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Connected")) {
+				bool v =    g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN)
+				         && g_variant_get_boolean (property_value);
+
+				if (bzobj->d_device_connected != v) {
+					changed = TRUE;
+					bzobj->d_device_connected = v;
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Paired")) {
+				bool v =    g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN)
+				         && g_variant_get_boolean (property_value);
+
+				if (bzobj->d_device_paired != v) {
+					changed = TRUE;
+					bzobj->d_device_paired = v;
+				}
+				continue;
+			}
+		}
+
+		for (i = 0; (property_name = invalidated_properties[i]); i++) {
+			if (nm_streq (property_name, "Address")) {
+				if (bzobj->d_device.address) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.address);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Name")) {
+				if (bzobj->d_device.name) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.name);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Adapter")) {
+				if (bzobj->d_device.adapter) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.adapter);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "UUIDs")) {
+				if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) {
+					changed = TRUE;
+					bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE;
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Connected")) {
+				if (bzobj->d_device_connected) {
+					changed = TRUE;
+					bzobj->d_device_connected = FALSE;
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Paired")) {
+				if (bzobj->d_device_paired) {
+					changed = TRUE;
+					bzobj->d_device_paired = FALSE;
+				}
+				continue;
+			}
+		}
+
+	} else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) {
+		_bzobjs_init (self, &bzobj, object_path);
+		if (!bzobj->d_has_network_iface) {
+			changed = TRUE;
+			bzobj->d_has_network_iface = TRUE;
+		}
+
+		while (   changed_properties
+		       && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) {
+			_nm_unused gs_unref_variant GVariant *property_value_free = property_value;
+
+			if (nm_streq (property_name, "Interface")) {
+				const char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING)
+				                ? g_variant_get_string (property_value, NULL)
+				                : NULL;
+
+				if (!nm_streq0 (bzobj->d_network.interface, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_network.interface);
+					bzobj->d_network.interface = g_strdup (s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Connected")) {
+				bool v =    g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN)
+				         && g_variant_get_boolean (property_value);
+
+				if (bzobj->d_network_connected != v) {
+					changed = TRUE;
+					bzobj->d_network_connected = v;
+				}
+				continue;
+			}
+		}
+
+		for (i = 0; (property_name = invalidated_properties[i]); i++) {
+			if (nm_streq (property_name, "Interface")) {
+				if (bzobj->d_network.interface) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_network.interface);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Connected")) {
+				if (bzobj->d_network_connected) {
+					changed = TRUE;
+					bzobj->d_network_connected = FALSE;
+				}
+				continue;
+			}
+		}
+
+	} else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) {
+		_bzobjs_init (self, &bzobj, object_path);
+		if (!bzobj->d_has_network_server_iface) {
+			changed = TRUE;
+			bzobj->d_has_network_server_iface = TRUE;
 		}
-		break;
 	}
+
+	nm_assert (!changed || bzobj);
+
+	if (inout_bzobj)
+		*inout_bzobj = bzobj;
+
+	return changed;
 }
 
 static void
-check_bluez_and_try_setup_do_introspect (GObject *source_object,
-                                         GAsyncResult *res,
-                                         gpointer user_data)
+_dbus_handle_interface_added (NMBluezManager *self,
+                              const char *object_path,
+                              GVariant *ifaces,
+                              gboolean initial_get_managed_objects)
+{
+	BzDBusObj *bzobj = NULL;
+	gboolean changed = FALSE;
+	const char *interface_name;
+	GVariant *changed_properties;
+	GVariantIter iter_ifaces;
+
+	nm_assert (g_variant_is_of_type (ifaces, G_VARIANT_TYPE ("a{sa{sv}}")));
+
+	g_variant_iter_init (&iter_ifaces, ifaces);
+	while (g_variant_iter_next (&iter_ifaces, "{&s@a{sv}}", &interface_name, &changed_properties)) {
+		_nm_unused gs_unref_variant GVariant *changed_properties_free = changed_properties;
+
+		if (_dbus_handle_properties_changed (self, object_path, interface_name, changed_properties, NULL, &bzobj))
+			changed = TRUE;
+	}
+
+	if (changed) {
+		_dbus_process_changes (self,
+		                       bzobj,
+		                         initial_get_managed_objects
+		                       ? "dbus-init"
+		                       : "dbus-iface-added");
+	}
+}
+
+static gboolean
+_dbus_handle_interface_removed (NMBluezManager *self,
+                                const char *object_path,
+                                BzDBusObj **inout_bzobj,
+                                const char *const*removed_interfaces)
 {
-	NMBluezManager *self = async_data_unpack (user_data);
+	gboolean changed = FALSE;
+	BzDBusObj *bzobj;
+	gsize i;
+
+	if (   inout_bzobj
+	    && *inout_bzobj) {
+		bzobj = *inout_bzobj;
+		nm_assert (bzobj == _bzobjs_get (self, object_path));
+	} else {
+		bzobj = _bzobjs_get (self, object_path);
+		if (!bzobj)
+			return FALSE;
+		NM_SET_OUT (inout_bzobj, bzobj);
+	}
+
+	for (i = 0; removed_interfaces[i]; i++) {
+		const char *interface_name = removed_interfaces[i];
+
+		if (nm_streq (interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) {
+			if (bzobj->d_has_adapter_iface) {
+				changed = TRUE;
+				bzobj->d_has_adapter_iface = FALSE;
+			}
+			if (bzobj->d_adapter.address) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_adapter.address);
+			}
+			if (bzobj->d_adapter_powered) {
+				changed = TRUE;
+				bzobj->d_adapter_powered = FALSE;
+			}
+		} else if (nm_streq (interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) {
+			if (bzobj->d_has_device_iface) {
+				changed = TRUE;
+				bzobj->d_has_device_iface = FALSE;
+			}
+			if (bzobj->d_device.address) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_device.address);
+			}
+			if (bzobj->d_device.name) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_device.name);
+			}
+			if (bzobj->d_device.adapter) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_device.adapter);
+			}
+			if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) {
+				changed = TRUE;
+				bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE;
+			}
+			if (bzobj->d_device_connected) {
+				changed = TRUE;
+				bzobj->d_device_connected = FALSE;
+			}
+			if (bzobj->d_device_paired) {
+				changed = TRUE;
+				bzobj->d_device_paired = FALSE;
+			}
+		} else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) {
+			if (bzobj->d_has_network_iface) {
+				changed = TRUE;
+				bzobj->d_has_network_iface = FALSE;
+			}
+			if (bzobj->d_network.interface) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_network.interface);
+			}
+			if (bzobj->d_network_connected) {
+				changed = TRUE;
+				bzobj->d_network_connected = FALSE;
+			}
+		} else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) {
+			if (bzobj->d_has_network_server_iface) {
+				changed = TRUE;
+				bzobj->d_has_network_server_iface = FALSE;
+			}
+		}
+	}
+
+	return changed;
+}
+
+static void
+_dbus_managed_objects_changed_cb (const char *object_path,
+                                  GVariant *added_interfaces_and_properties,
+                                  const char *const*removed_interfaces,
+                                  gpointer user_data)
+{
+	NMBluezManager *self = user_data;
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj = NULL;
+	gboolean changed;
+
+	if (priv->get_managed_objects_cancellable) {
+		/* we still wait for the initial GetManagedObjects(). Ignore the event. */
+		return;
+	}
+
+	if (!added_interfaces_and_properties) {
+		changed = _dbus_handle_interface_removed (self, object_path, &bzobj, removed_interfaces);
+		if (changed)
+			_dbus_process_changes (self, bzobj, "dbus-iface-removed");
+	} else
+		_dbus_handle_interface_added (self, object_path, added_interfaces_and_properties, FALSE);
+}
+
+static void
+_dbus_properties_changed_cb (GDBusConnection *connection,
+                             const char *sender_name,
+                             const char *object_path,
+                             const char *signal_interface_name,
+                             const char *signal_name,
+                             GVariant *parameters,
+                             gpointer user_data)
+{
+	NMBluezManager *self = user_data;
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	const char *interface_name;
+	gs_unref_variant GVariant *changed_properties = NULL;
+	gs_free const char **invalidated_properties = NULL;
+	BzDBusObj *bzobj = NULL;
+
+	if (priv->get_managed_objects_cancellable) {
+		/* we still wait for the initial GetManagedObjects(). Ignore the event. */
+		return;
+	}
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
+		return;
+
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               &interface_name,
+	               &changed_properties,
+	               &invalidated_properties);
+
+	if (_dbus_handle_properties_changed (self, object_path, interface_name, changed_properties, invalidated_properties, &bzobj))
+		_dbus_process_changes (self, bzobj, "dbus-property-changed");
+}
+
+static void
+_dbus_get_managed_objects_cb (GVariant *result,
+                              GError *error,
+                              gpointer user_data)
+{
+	NMBluezManager *self;
 	NMBluezManagerPrivate *priv;
-	GError *error = NULL;
-	GVariant *result;
-	const char *xml_data;
-	int bluez_version = 0;
-	const char *reason = NULL;
+	GVariantIter iter;
+	const char *object_path;
+	GVariant *ifaces;
 
-	if (!self)
+	if (   !result
+	    && nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
+	self = user_data;
 	priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (priv->introspect_proxy);
-	g_return_if_fail (!g_cancellable_is_cancelled (priv->async_cancellable));
-	g_return_if_fail (!priv->bluez_version);
-
-	g_clear_object (&priv->async_cancellable);
+	g_clear_object (&priv->get_managed_objects_cancellable);
 
-	result = _nm_dbus_proxy_call_finish (priv->introspect_proxy, res,
-	                                     G_VARIANT_TYPE ("(s)"), &error);
 	if (!result) {
-		char *reason2;
-
-		g_dbus_error_strip_remote_error (error);
-		reason2 = g_strdup_printf ("introspect failed with %s", error->message);
-		check_bluez_and_try_setup_final_step (self, 0, reason2);
-		g_error_free (error);
-		g_free (reason2);
+		_LOGT ("initial GetManagedObjects() call failed: %s", error->message);
+		_cleanup_for_name_owner (self);
 		return;
 	}
 
-	g_variant_get (result, "(&s)", &xml_data);
+	_LOGT ("initial GetManagedObjects call succeeded");
+
+	g_variant_iter_init (&iter, result);
+	while (g_variant_iter_next (&iter, "{&o@a{sa{sv}}}", &object_path, &ifaces)) {
+		_nm_unused gs_unref_variant GVariant *ifaces_free = ifaces;
+
+		_dbus_handle_interface_added (self, object_path, ifaces, TRUE);
+	}
+}
+
+/*****************************************************************************/
+
+static void
+_cleanup_for_name_owner (NMBluezManager *self)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gboolean emit_device_availability_changed = FALSE;
+	GHashTableIter iter;
+	BzDBusObj *bzobj;
+	gboolean first = TRUE;
+
+	nm_clear_g_cancellable (&priv->get_managed_objects_cancellable);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->managed_objects_changed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->properties_changed_id);
+
+	nm_clear_g_free (&priv->name_owner);
+
+	g_hash_table_iter_init (&iter, priv->bzobjs);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) {
+		if (first) {
+			first = FALSE;
+			_LOGT ("drop all objects form D-Bus cache...");
+		}
+		_dbus_handle_interface_removed (self,
+		                                bzobj->object_path,
+		                                &bzobj,
+		                                ALL_RELEVANT_INTERFACE_NAMES);
+		nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj->process_change_lst);
+	}
+	_process_change_idle_all (self, &emit_device_availability_changed);
+	nm_assert (g_hash_table_size (priv->bzobjs) == 0);
+
+	if (emit_device_availability_changed)
+		nm_manager_notify_device_availibility_maybe_changed (priv->manager);
+}
 
-	/* might not be the best approach to detect the version, but it's good enough in practice. */
-	if (strstr (xml_data, "org.freedesktop.DBus.ObjectManager"))
-		bluez_version = 5;
-	else if (strstr (xml_data, NM_BLUEZ4_MANAGER_INTERFACE))
-		bluez_version = 4;
+static void
+name_owner_changed (NMBluezManager *self,
+                    const char *owner)
+{
+	_nm_unused gs_unref_object NMBluezManager *self_keep_alive = g_object_ref (self);
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	owner = nm_str_not_empty (owner);
+
+	if (!owner)
+		_LOGT ("D-Bus name for bluez has no owner");
 	else
-		reason = "unexpected introspect result";
+		_LOGT ("D-Bus name for bluez has owner %s", owner);
+
+	nm_clear_g_cancellable (&priv->name_owner_get_cancellable);
+
+	if (nm_streq0 (priv->name_owner, owner))
+		return;
+
+	_cleanup_for_name_owner (self);
 
-	g_variant_unref (result);
+	if (!owner)
+		return;
 
-	check_bluez_and_try_setup_final_step (self, bluez_version, reason);
+	priv->name_owner = g_strdup (owner);
+
+	priv->get_managed_objects_cancellable = g_cancellable_new ();
+
+	priv->managed_objects_changed_id = nm_dbus_connection_signal_subscribe_object_manager (priv->dbus_connection,
+	                                                                                       priv->name_owner,
+	                                                                                       NM_BLUEZ_MANAGER_PATH,
+	                                                                                       _dbus_managed_objects_changed_cb,
+	                                                                                       self,
+	                                                                                       NULL);
+
+	priv->properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+	                                                                                      priv->name_owner,
+	                                                                                      NULL,
+	                                                                                      NULL,
+	                                                                                      _dbus_properties_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+
+	nm_dbus_connection_call_get_managed_objects (priv->dbus_connection,
+	                                             priv->name_owner,
+	                                             NM_BLUEZ_MANAGER_PATH,
+	                                             G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                                             20000,
+	                                             priv->get_managed_objects_cancellable,
+	                                             _dbus_get_managed_objects_cb,
+	                                             self);
 }
 
 static void
-check_bluez_and_try_setup_on_new_proxy (GObject *source_object,
-                                        GAsyncResult *res,
-                                        gpointer user_data)
+name_owner_changed_cb (GDBusConnection *connection,
+                       const char *sender_name,
+                       const char *object_path,
+                       const char *interface_name,
+                       const char *signal_name,
+                       GVariant *parameters,
+                       gpointer user_data)
 {
-	NMBluezManager *self = async_data_unpack (user_data);
-	NMBluezManagerPrivate *priv;
-	GError *error = NULL;
+	NMBluezManager *self = user_data;
+	const char *new_owner;
 
-	if (!self)
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
 		return;
 
+	g_variant_get (parameters,
+	               "(&s&s&s)",
+	               NULL,
+	               NULL,
+	               &new_owner);
+
+	name_owner_changed (self, new_owner);
+}
+
+static void
+name_owner_get_cb (const char *name_owner,
+                   GError *error,
+                   gpointer user_data)
+{
+	if (   name_owner
+	    || !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		name_owner_changed (user_data, name_owner);
+}
+
+/*****************************************************************************/
+
+static void
+_cleanup_all (NMBluezManager *self)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	priv->settings_registered = FALSE;
+
+	g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_added, self);
+	g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_updated, self);
+	g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_removed, self);
+
+	g_hash_table_remove_all (priv->conn_data_elems);
+	g_hash_table_remove_all (priv->conn_data_heads);
+
+	_cleanup_for_name_owner (self);
+
+	nm_clear_g_cancellable (&priv->name_owner_get_cancellable);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->name_owner_changed_id);
+}
+
+static void
+start (NMDeviceFactory *factory)
+{
+	NMBluezManager *self;
+	NMBluezManagerPrivate *priv;
+	NMSettingsConnection *const*sett_conns;
+	guint n_sett_conns;
+	guint i;
+
+	g_return_if_fail (NM_IS_BLUEZ_MANAGER (factory));
+
+	self = NM_BLUEZ_MANAGER (factory);
 	priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (!priv->introspect_proxy);
-	g_return_if_fail (!g_cancellable_is_cancelled (priv->async_cancellable));
-	g_return_if_fail (!priv->bluez_version);
+	_cleanup_all (self);
+
+	if (!priv->dbus_connection) {
+		_LOGI ("no D-Bus connection available");
+		return;
+	}
+
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED,   G_CALLBACK (cp_connection_added),   self);
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (cp_connection_updated), self);
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, G_CALLBACK (cp_connection_removed), self);
 
-	priv->introspect_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+	priv->settings_registered = TRUE;
 
-	if (!priv->introspect_proxy) {
-		char *reason = g_strdup_printf ("bluez error creating dbus proxy: %s", error->message);
-		check_bluez_and_try_setup_final_step (self, 0, reason);
-		g_error_free (error);
-		g_free (reason);
+	sett_conns = nm_settings_get_connections (priv->settings, &n_sett_conns);
+	for (i = 0; i < n_sett_conns; i++)
+		_conn_track_update (self, sett_conns[i], TRUE, NULL, NULL, NULL);
+
+	priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection,
+	                                                                                      NM_BLUEZ_SERVICE,
+	                                                                                      name_owner_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+
+	priv->name_owner_get_cancellable = g_cancellable_new ();
+
+	nm_dbus_connection_call_get_name_owner (priv->dbus_connection,
+	                                        NM_BLUEZ_SERVICE,
+	                                        10000,
+	                                        priv->name_owner_get_cancellable,
+	                                        name_owner_get_cb,
+	                                        self);
+}
+
+/*****************************************************************************/
+
+static void
+_connect_returned (NMBluezManager *self,
+                   BzDBusObj *bzobj,
+                   NMBluetoothCapabilities bt_type,
+                   const char *device_name,
+                   NMBluez5DunContext *dun_context,
+                   GError *error)
+{
+	char sbuf_cap[100];
+
+	if (error) {
+		nm_assert (!device_name);
+		nm_assert (!dun_context);
+
+		_LOGI ("%s [%s]: connect failed: %s",
+		       nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)),
+		       bzobj->object_path,
+		       error->message);
+
+		_device_connect_req_data_complete (g_steal_pointer (&bzobj->x_device.c_req_data),
+		                                   self,
+		                                   NULL,
+		                                   error);
+		_connect_disconnect (self, bzobj, "cleanup after connect failure");
 		return;
 	}
 
-	g_dbus_proxy_call (priv->introspect_proxy,
-	                   "Introspect",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NO_AUTO_START,
-	                   3000,
-	                   priv->async_cancellable,
-	                   check_bluez_and_try_setup_do_introspect,
-	                   async_data_pack (self));
+	nm_assert (bzobj->x_device_connect_bt_type == bt_type);
+	nm_assert (device_name);
+	nm_assert ((bt_type == NM_BT_CAPABILITY_DUN) == (!!dun_context));
+	nm_assert (bzobj->x_device.c_req_data);
+
+	g_clear_object (&bzobj->x_device.c_req_data->int_cancellable);
+
+	bzobj->x_device.connect_dun_context = dun_context;
+
+	_LOGD ("%s [%s]: connect successful to device %s",
+	       nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)),
+	       bzobj->object_path,
+	       device_name);
+
+	/* we already have another over-all timer running. But after we connected the device,
+	 * we still need to wait for bluez to acknowledge the connected state (via D-Bus, for NAP).
+	 * For DUN profiles we likely are already fully connected by now.
+	 *
+	 * Anyway, schedule another timeout that is possibly shorter than the overall, original
+	 * timeout. Now this should go down fast. */
+	bzobj->x_device.c_req_data->timeout_wait_connect_id = g_timeout_add (5000,
+	                                                                     _connect_timeout_wait_connected_cb,
+	                                                                     bzobj),
+	bzobj->x_device.c_req_data->device_name = g_strdup (device_name);
+
+	if (   _bzobjs_device_is_usable (bzobj, NULL, NULL)
+	    && _bzobjs_device_is_connected (bzobj)) {
+		/* We are now connected. Schedule the task that completes the state. */
+		_process_change_idle_schedule (self, bzobj);
+	}
+}
+
+#if WITH_BLUEZ5_DUN
+static void
+_connect_dun_notify_tty_hangup_cb (NMBluez5DunContext *context,
+                                   gpointer user_data)
+{
+	BzDBusObj *bzobj = user_data;
+
+	_connect_disconnect (bzobj->self,
+	                     bzobj,
+	                     "DUN connection hung up");
 }
 
 static void
-check_bluez_and_try_setup (NMBluezManager *self)
+_connect_dun_step2_cb (NMBluez5DunContext *context,
+                       const char *rfcomm_dev,
+                       GError *error,
+                       gpointer user_data)
 {
-	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj;
+
+	if (nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	bzobj = user_data;
+
+	if (rfcomm_dev) {
+		/* We want to early notifiy about the rfcomm path. That is because we might still delay
+		 * to signal full activation longer (asynchronously). But the earliest time the callback
+		 * is invoked with the rfcomm path, we just created the device synchronously.
+		 *
+		 * By already notifying the caller about the path early, it avoids a race where ModemManager
+		 * would find the modem before the bluetooth code considers the profile fully activated. */
 
-	g_return_if_fail (!priv->bluez_version);
+		nm_assert (!error);
+		nm_assert (bzobj->x_device.c_req_data);
 
-	/* there should be no ongoing detection. Anyway, cleanup_checking. */
-	cleanup_checking (self, FALSE);
+		if (!g_cancellable_is_cancelled (bzobj->x_device.c_req_data->ext_cancellable))
+			bzobj->x_device.c_req_data->callback (bzobj->self, FALSE, rfcomm_dev, NULL, bzobj->x_device.c_req_data->callback_user_data);
 
-	priv->async_cancellable = g_cancellable_new ();
+		if (!context) {
+			/* No context set. This means, we just got notified about the rfcomm path and need to wait
+			 * longer, for the next callback. */
+			return;
+		}
+	}
 
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          "/",
-	                          DBUS_INTERFACE_INTROSPECTABLE,
-	                          priv->async_cancellable,
-	                          check_bluez_and_try_setup_on_new_proxy,
-	                          async_data_pack (self));
+	_connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, rfcomm_dev, context, error);
 }
 
 static void
-start (NMDeviceFactory *factory)
+_connect_dun_step1_cb (GObject *source_object,
+                       GAsyncResult *res,
+                       gpointer user_data)
+{
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	BzDBusObj *bzobj_adapter;
+	BzDBusObj *bzobj;
+
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+
+	if (   !ret
+	    && nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	bzobj = user_data;
+
+	if (error) {
+		_LOGT ("DUN: [%s]: bluetooth device connect failed: %s", bzobj->object_path, error->message);
+		/* we actually ignore this error. Let's try, maybe we still can connect via DUN. */
+		g_clear_error (&error);
+	} else
+		_LOGT ("DUN: [%s]: bluetooth device connected successfully", bzobj->object_path);
+
+	if (!_bzobjs_device_is_usable (bzobj, &bzobj_adapter, NULL)) {
+		nm_utils_error_set (&error, NM_UTILS_ERROR_UNKNOWN,
+		                    "device %s is not usable for DUN after connect",
+		                    bzobj->object_path);
+		_connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error);
+		return;
+	}
+
+	if (!nm_bluez5_dun_connect (bzobj_adapter->d_adapter.address,
+	                            bzobj->d_device.address,
+	                            bzobj->x_device.c_req_data->int_cancellable,
+	                            _connect_dun_step2_cb,
+	                            bzobj,
+	                            _connect_dun_notify_tty_hangup_cb,
+	                            bzobj,
+	                            &error)) {
+		_connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error);
+		return;
+	}
+}
+#endif
+
+static void
+_connect_nap_cb (GObject *source_object,
+                 GAsyncResult *res,
+                 gpointer user_data)
 {
-	check_bluez_and_try_setup (NM_BLUEZ_MANAGER (factory));
+	gs_unref_variant GVariant *ret = NULL;
+	const char *network_iface_name = NULL;
+	gs_free_error GError *error = NULL;
+	BzDBusObj *bzobj;
+
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+
+	if (   !ret
+	    && nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	if (ret)
+		g_variant_get (ret, "(&s)", &network_iface_name);
+
+	bzobj = user_data;
+
+	_connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_NAP, network_iface_name, NULL, error);
+}
+
+static void
+_connect_cancelled_cb (GCancellable *cancellable,
+                       BzDBusObj *bzobj)
+{
+	_connect_disconnect (bzobj->self, bzobj, "connect cancelled");
 }
 
+static gboolean
+_connect_timeout_wait_connected_cb (gpointer user_data)
+{
+	BzDBusObj *bzobj = user_data;
+
+	bzobj->x_device.c_req_data->timeout_wait_connect_id = 0;
+	_connect_disconnect (bzobj->self, bzobj, "timeout waiting for connected");
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_connect_timeout_cb (gpointer user_data)
+{
+	BzDBusObj *bzobj = user_data;
+
+	bzobj->x_device.c_req_data->timeout_id = 0;
+	_connect_disconnect (bzobj->self, bzobj, "timeout connecting");
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_connect_disconnect (NMBluezManager *self,
+                     BzDBusObj *bzobj,
+                     const char *reason)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	DeviceConnectReqData *c_req_data;
+	char sbuf_cap[100];
+	gboolean bt_type;
+
+	if (bzobj->x_device_connect_bt_type == NM_BT_CAPABILITY_NONE) {
+		nm_assert (!bzobj->x_device.c_req_data);
+		return;
+	}
+
+	bt_type = bzobj->x_device_connect_bt_type;
+	nm_assert (NM_IN_SET (bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP));
+	bzobj->x_device_connect_bt_type = NM_BT_CAPABILITY_NONE;
+
+	c_req_data = g_steal_pointer (&bzobj->x_device.c_req_data);
+
+	_LOGD ("%s [%s]: disconnect due to %s",
+	       nm_bluetooth_capability_to_string (bt_type, sbuf_cap, sizeof (sbuf_cap)),
+	       bzobj->object_path,
+	       reason);
+
+	if (c_req_data)
+		nm_clear_g_cancellable (&c_req_data->int_cancellable);
+
+	if (bt_type == NM_BT_CAPABILITY_DUN) {
+		/* For DUN devices, we also called org.bluez.Device1.Connect() (because in order
+		 * for nm_bluez5_dun_connect() to succeed, we need to be already connected *why??).
+		 *
+		 * But upon disconnect we don't call Disconnect() because we don't know whether somebody
+		 * else also uses the bluetooth device for other purposes. During disconnect we only
+		 * terminate the DUN connection, but don't disconnect entirely. I think that's the
+		 * best we can do. */
+#if WITH_BLUEZ5_DUN
+		nm_clear_pointer (&bzobj->x_device.connect_dun_context, nm_bluez5_dun_disconnect);
+#else
+		nm_assert_not_reached ();
+#endif
+	} else {
+		if (priv->name_owner) {
+			gs_unref_object GCancellable *cancellable = NULL;
+
+			cancellable = g_cancellable_new ();
+
+			nm_shutdown_wait_obj_register_cancellable_full (cancellable,
+			                                                g_strdup_printf ("bt-disconnect-nap[%s]", bzobj->object_path),
+			                                                TRUE);
+
+			g_dbus_connection_call (priv->dbus_connection,
+			                        priv->name_owner,
+			                        bzobj->object_path,
+			                        NM_BLUEZ5_NETWORK_INTERFACE,
+			                        "Disconnect",
+			                        g_variant_new("()"),
+			                        NULL,
+			                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+			                        -1,
+			                        cancellable,
+			                        _dbus_call_complete_cb_nop,
+			                        NULL);
+		}
+	}
+
+	if (c_req_data) {
+		gs_free_error GError *error = NULL;
+
+		nm_utils_error_set (&error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "connect aborted due to %s",
+		                    reason);
+		_device_connect_req_data_complete (c_req_data, self, NULL, error);
+	}
+}
+
+gboolean
+nm_bluez_manager_connect (NMBluezManager *self,
+                          const char *object_path,
+                          NMBluetoothCapabilities connection_bt_type,
+                          int timeout_msec,
+                          GCancellable *cancellable,
+                          NMBluezManagerConnectCb callback,
+                          gpointer callback_user_data,
+                          GError **error)
+{
+	gs_unref_object GCancellable *int_cancellable = NULL;
+	DeviceConnectReqData *c_req_data;
+	NMBluezManagerPrivate *priv;
+	BzDBusObj *bzobj;
+	char sbuf_cap[100];
+
+	g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (self), FALSE);
+	g_return_val_if_fail (NM_IN_SET (connection_bt_type, NM_BT_CAPABILITY_DUN,
+	                                                     NM_BT_CAPABILITY_NAP), FALSE);
+	g_return_val_if_fail (callback, FALSE);
+
+	nm_assert (timeout_msec > 0);
+
+	priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	bzobj = _bzobjs_get (self, object_path);
+
+	if (!bzobj) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "device %s does not exist",
+		                    object_path);
+		return FALSE;
+	}
+
+	if (!_bzobjs_device_is_usable (bzobj, NULL, NULL)) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "device %s is not usable",
+		                    object_path);
+		return FALSE;
+	}
+
+	if (!NM_FLAGS_ALL (bzobj->d_device_capabilities, connection_bt_type)) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "device %s has not the required capabilities",
+		                    object_path);
+		return FALSE;
+	}
+
+#if !WITH_BLUEZ5_DUN
+	if (connection_bt_type == NM_BT_CAPABILITY_DUN) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "DUN is not supported");
+		return FALSE;
+	}
+#endif
+
+	_connect_disconnect (self, bzobj, "new activation");
+
+	_LOGD ("%s [%s]: connecting...",
+	       nm_bluetooth_capability_to_string (connection_bt_type, sbuf_cap, sizeof (sbuf_cap)),
+	       bzobj->object_path);
+
+	int_cancellable = g_cancellable_new();
+
+#if WITH_BLUEZ5_DUN
+	if (connection_bt_type == NM_BT_CAPABILITY_DUN) {
+		g_dbus_connection_call (priv->dbus_connection,
+		                        priv->name_owner,
+		                        bzobj->object_path,
+		                        NM_BLUEZ5_DEVICE_INTERFACE,
+		                        "Connect",
+		                        NULL,
+		                        NULL,
+		                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+		                        timeout_msec,
+		                        int_cancellable,
+		                        _connect_dun_step1_cb,
+		                        bzobj);
+	} else
+#endif
+	{
+		nm_assert (connection_bt_type == NM_BT_CAPABILITY_NAP);
+		g_dbus_connection_call (priv->dbus_connection,
+		                        priv->name_owner,
+		                        bzobj->object_path,
+		                        NM_BLUEZ5_NETWORK_INTERFACE,
+		                        "Connect",
+		                        g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP),
+		                        G_VARIANT_TYPE ("(s)"),
+		                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+		                        timeout_msec,
+		                        int_cancellable,
+		                        _connect_nap_cb,
+		                        bzobj);
+	}
+
+	c_req_data = g_slice_new (DeviceConnectReqData);
+	*c_req_data = (DeviceConnectReqData) {
+		.int_cancellable     = g_steal_pointer (&int_cancellable),
+		.ext_cancellable     = g_object_ref (cancellable),
+		.callback            = callback,
+		.callback_user_data  = callback_user_data,
+		.ext_cancelled_id    = g_signal_connect (cancellable,
+		                                         "cancelled",
+		                                         G_CALLBACK (_connect_cancelled_cb),
+		                                         bzobj),
+		.timeout_id          = g_timeout_add (timeout_msec,
+		                                      _connect_timeout_cb,
+		                                      bzobj),
+	};
+
+	bzobj->x_device_connect_bt_type = connection_bt_type;
+	bzobj->x_device.c_req_data = c_req_data;
+
+	return TRUE;
+}
+
+void
+nm_bluez_manager_disconnect (NMBluezManager *self,
+                             const char *object_path)
+{
+	BzDBusObj *bzobj;
+
+	g_return_if_fail (NM_IS_BLUEZ_MANAGER (self));
+	g_return_if_fail (object_path);
+
+	bzobj = _bzobjs_get (self, object_path);
+	if (!bzobj)
+		return;
+
+	_connect_disconnect (self, bzobj, "disconnected by user");
+}
+
+/*****************************************************************************/
+
 static NMDevice *
 create_device (NMDeviceFactory *factory,
                const char *iface,
@@ -407,8 +2789,8 @@ create_device (NMDeviceFactory *factory,
                NMConnection *connection,
                gboolean *out_ignore)
 {
-	g_warn_if_fail (plink->type == NM_LINK_TYPE_BNEP);
 	*out_ignore = TRUE;
+	g_return_val_if_fail (plink->type == NM_LINK_TYPE_BNEP, NULL);
 	return NULL;
 }
 
@@ -433,7 +2815,25 @@ nm_bluez_manager_init (NMBluezManager *self)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
+	priv->vtable_network_server = (NMBtVTableNetworkServer) {
+		.is_available      = _network_server_vt_is_available,
+		.register_bridge   = _network_server_vt_register_bridge,
+		.unregister_bridge = _network_server_vt_unregister_bridge,
+	};
+
+	c_list_init (&priv->network_server_lst_head);
+	c_list_init (&priv->process_change_lst_head);
+
+	priv->conn_data_heads = g_hash_table_new_full (_conn_data_head_hash, _conn_data_head_equal, g_free, NULL);
+	priv->conn_data_elems = g_hash_table_new_full (nm_pdirect_hash, nm_pdirect_equal, nm_g_slice_free_fcn (ConnDataElem), NULL);
+
+	priv->bzobjs = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _bz_dbus_obj_free, NULL);
+
+	priv->manager = g_object_ref (NM_MANAGER_GET);
 	priv->settings = g_object_ref (NM_SETTINGS_GET);
+	priv->dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET);
+
+	g_atomic_pointer_compare_and_exchange (&nm_bt_vtable_network_server, NULL, &priv->vtable_network_server);
 }
 
 static void
@@ -442,22 +2842,25 @@ dispose (GObject *object)
 	NMBluezManager *self = NM_BLUEZ_MANAGER (object);
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
-	if (priv->manager4) {
-		g_signal_handlers_disconnect_by_func (priv->manager4, manager_bdaddr_added_cb, self);
-		g_clear_object (&priv->manager4);
-	}
-	if (priv->manager5) {
-		g_signal_handlers_disconnect_by_data (priv->manager5, self);
-		g_clear_object (&priv->manager5);
-	}
+	/* FIXME(shutdown): we need a nm_device_factory_stop() hook to first unregister all
+	 *   BzDBusObj instances and do necessary cleanup actions (like disconnecting devices
+	 *   or deleting panu_connection). */
 
-	cleanup_checking (self, TRUE);
+	nm_assert (c_list_is_empty (&priv->network_server_lst_head));
+	nm_assert (c_list_is_empty (&priv->process_change_lst_head));
+	nm_assert (priv->process_change_idle_id == 0);
 
-	priv->bluez_version = 0;
+	g_atomic_pointer_compare_and_exchange (&nm_bt_vtable_network_server, &priv->vtable_network_server, NULL);
+
+	_cleanup_all (self);
 
 	G_OBJECT_CLASS (nm_bluez_manager_parent_class)->dispose (object);
 
 	g_clear_object (&priv->settings);
+	g_clear_object (&priv->manager);
+	g_clear_object (&priv->dbus_connection);
+
+	nm_clear_pointer (&priv->bzobjs, g_hash_table_destroy);
 }
 
 static void
@@ -466,10 +2869,10 @@ nm_bluez_manager_class_init (NMBluezManagerClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS (klass);
 
-	object_class->dispose = dispose;
+	object_class->dispose     = dispose;
 
 	factory_class->get_supported_types = get_supported_types;
-	factory_class->create_device = create_device;
-	factory_class->match_connection = match_connection;
-	factory_class->start = start;
+	factory_class->create_device       = create_device;
+	factory_class->match_connection    = match_connection;
+	factory_class->start               = start;
 }
diff --git a/src/devices/bluetooth/nm-bluez-manager.h b/src/devices/bluetooth/nm-bluez-manager.h
new file mode 100644
index 00000000..85dbaa83
--- /dev/null
+++ b/src/devices/bluetooth/nm-bluez-manager.h
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2009 - 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_BLUEZ_MANAGER_H__
+#define __NM_BLUEZ_MANAGER_H__
+
+#define NM_TYPE_BLUEZ_MANAGER            (nm_bluez_manager_get_type ())
+#define NM_BLUEZ_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManager))
+#define NM_BLUEZ_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass))
+#define NM_IS_BLUEZ_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_MANAGER))
+#define NM_IS_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_BLUEZ_MANAGER))
+#define NM_BLUEZ_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass))
+
+typedef struct _NMBluezManager NMBluezManager;
+typedef struct _NMBluezManagerClass NMBluezManagerClass;
+
+GType nm_bluez_manager_get_type (void);
+
+typedef void (*NMBluezManagerConnectCb) (NMBluezManager *self,
+                                         gboolean is_completed /* or else is early notification with DUN path */,
+                                         const char *device_name,
+                                         GError *error,
+                                         gpointer user_data);
+
+gboolean nm_bluez_manager_connect (NMBluezManager *self,
+                                   const char *object_path,
+                                   NMBluetoothCapabilities connection_bt_type,
+                                   int timeout_msec,
+                                   GCancellable *cancellable,
+                                   NMBluezManagerConnectCb callback,
+                                   gpointer callback_user_data,
+                                   GError **error);
+
+void nm_bluez_manager_disconnect (NMBluezManager *self,
+                                  const char *object_path);
+
+#endif /* __NM_BLUEZ_MANAGER_H__ */
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
deleted file mode 100644
index bd230e90..00000000
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ /dev/null
@@ -1,456 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2009 - 2012 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-bluez4-adapter.h"
-
-#include "nm-dbus-interface.h"
-#include "nm-bluez-device.h"
-#include "nm-bluez-common.h"
-#include "nm-core-internal.h"
-#include "settings/nm-settings.h"
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_PATH,
-	PROP_ADDRESS,
-);
-
-enum {
-	INITIALIZED,
-	DEVICE_ADDED,
-	DEVICE_REMOVED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	char *path;
-	GDBusProxy *proxy;
-	GCancellable *proxy_cancellable;
-	gboolean initialized;
-
-	char *address;
-	GHashTable *devices;
-
-	/* Cached for devices */
-	NMSettings *settings;
-} NMBluez4AdapterPrivate;
-
-struct _NMBluez4Adapter {
-	GObject parent;
-	NMBluez4AdapterPrivate _priv;
-};
-
-struct _NMBluez4AdapterClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMBluez4Adapter, nm_bluez4_adapter, G_TYPE_OBJECT)
-
-#define NM_BLUEZ4_ADAPTER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez4Adapter, NM_IS_BLUEZ4_ADAPTER)
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_BT
-#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez4-adapter", __VA_ARGS__)
-
-/*****************************************************************************/
-
-static void device_do_remove (NMBluez4Adapter *self, NMBluezDevice *device);
-
-/*****************************************************************************/
-
-const char *
-nm_bluez4_adapter_get_path (NMBluez4Adapter *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), NULL);
-
-	return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->path;
-}
-
-const char *
-nm_bluez4_adapter_get_address (NMBluez4Adapter *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), NULL);
-
-	return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->address;
-}
-
-gboolean
-nm_bluez4_adapter_get_initialized (NMBluez4Adapter *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), FALSE);
-
-	return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->initialized;
-}
-
-GSList *
-nm_bluez4_adapter_get_devices (NMBluez4Adapter *self)
-{
-	GSList *devices = NULL;
-	GHashTableIter iter;
-	NMBluezDevice *device;
-
-	g_hash_table_iter_init (&iter, NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->devices);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) {
-		if (nm_bluez_device_get_usable (device))
-			devices = g_slist_append (devices, device);
-	}
-	return devices;
-}
-
-static void
-emit_device_removed (NMBluez4Adapter *self, NMBluezDevice *device)
-{
-	_LOGD ("(%s): bluez device now unusable",
-	       nm_bluez_device_get_path (device));
-	g_signal_emit (self, signals[DEVICE_REMOVED], 0, device);
-}
-
-static void
-device_usable (NMBluezDevice *device, GParamSpec *pspec, gpointer user_data)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-
-	if (nm_bluez_device_get_usable (device)) {
-		_LOGD ("(%s): bluez device now usable (device address is %s)",
-		       nm_bluez_device_get_path (device),
-		       nm_bluez_device_get_address (device));
-		g_signal_emit (self, signals[DEVICE_ADDED], 0, device);
-	} else
-		emit_device_removed (self, device);
-}
-
-static void
-device_initialized (NMBluezDevice *device, gboolean success, gpointer user_data)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-
-	_LOGD ("(%s): bluez device %s",
-	       nm_bluez_device_get_path (device),
-	       success ? "initialized" : "failed to initialize");
-	if (!success)
-		device_do_remove (self, device);
-}
-
-static void
-device_do_remove (NMBluez4Adapter *self, NMBluezDevice *device)
-{
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	if (g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device))) {
-		g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_initialized), self);
-		g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_usable), self);
-
-		if (nm_bluez_device_get_usable (device))
-			emit_device_removed (self, device);
-
-		g_object_unref (device);
-	}
-}
-
-static void
-device_created (GDBusProxy *proxy, const char *path, gpointer user_data)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	device = nm_bluez_device_new (path, priv->address, priv->settings, 4);
-	g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self);
-	g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self);
-	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
-
-	_LOGD ("(%s): new bluez device found", path);
-}
-
-static void
-device_removed (GDBusProxy *proxy, const char *path, gpointer user_data)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	_LOGD ("(%s): bluez device removed", path);
-
-	device = g_hash_table_lookup (priv->devices, path);
-	if (device)
-		device_do_remove (self, device);
-}
-
-static void
-get_properties_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMBluez4Adapter *self;
-	NMBluez4AdapterPrivate *priv;
-	gs_free_error GError *error = NULL;
-	GVariant *ret, *properties;
-	char **devices;
-	int i;
-
-	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(a{sv})"), &error);
-
-	if (   !ret
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_BLUEZ4_ADAPTER (user_data);
-	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	g_clear_object (&priv->proxy_cancellable);
-
-	if (!ret) {
-		g_dbus_error_strip_remote_error (error);
-		_LOGW ("bluez error getting adapter properties: %s", error->message);
-		goto done;
-	}
-
-	properties = g_variant_get_child_value (ret, 0);
-
-	(void) g_variant_lookup (properties, "Address", "s", &priv->address);
-	if (g_variant_lookup (properties, "Devices", "^ao", &devices)) {
-		for (i = 0; devices[i]; i++)
-			device_created (priv->proxy, devices[i], self);
-		g_strfreev (devices);
-	}
-
-	g_variant_unref (properties);
-	g_variant_unref (ret);
-
-	priv->initialized = TRUE;
-
-done:
-	g_signal_emit (self, signals[INITIALIZED], 0, priv->initialized);
-}
-
-static void
-_proxy_new_cb (GObject *source_object,
-               GAsyncResult *result,
-               gpointer user_data)
-{
-	NMBluez4Adapter *self;
-	NMBluez4AdapterPrivate *priv;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *proxy;
-
-	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (   !proxy
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = user_data;
-	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	if (!proxy) {
-		_LOGW ("bluez error creating D-Bus proxy: %s", error->message);
-		g_clear_object (&priv->proxy_cancellable);
-		g_signal_emit (self, signals[INITIALIZED], 0, priv->initialized);
-		return;
-	}
-
-	priv->proxy = proxy;
-
-	_nm_dbus_signal_connect (priv->proxy, "DeviceCreated", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (device_created), self);
-	_nm_dbus_signal_connect (priv->proxy, "DeviceRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (device_removed), self);
-
-	g_dbus_proxy_call (priv->proxy, "GetProperties",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   priv->proxy_cancellable,
-	                   get_properties_cb,
-	                   self);
-}
-
-/*****************************************************************************/
-
-static gboolean
-_find_all (gpointer key, gpointer value, gpointer user_data)
-{
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE ((NMBluez4Adapter *) object);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		g_value_set_string (value, priv->path);
-		break;
-	case PROP_ADDRESS:
-		g_value_set_string (value, priv->address);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE ((NMBluez4Adapter *) object);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		/* construct-only */
-		priv->path = g_value_dup_string (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_bluez4_adapter_init (NMBluez4Adapter *self)
-{
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                       NULL, NULL);
-}
-
-NMBluez4Adapter *
-nm_bluez4_adapter_new (const char *path, NMSettings *settings)
-{
-	NMBluez4Adapter *self;
-	NMBluez4AdapterPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
-
-	self = (NMBluez4Adapter *) g_object_new (NM_TYPE_BLUEZ4_ADAPTER,
-	                                         NM_BLUEZ4_ADAPTER_PATH, path,
-	                                         NULL);
-	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	priv->settings = g_object_ref (settings);
-
-	priv->proxy_cancellable = g_cancellable_new ();
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          priv->path,
-	                          NM_BLUEZ4_ADAPTER_INTERFACE,
-	                          priv->proxy_cancellable,
-	                          _proxy_new_cb,
-	                          self);
-	return self;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (object);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	nm_clear_g_cancellable (&priv->proxy_cancellable);
-
-	while ((device = g_hash_table_find (priv->devices, _find_all, NULL)))
-		device_do_remove (self, device);
-
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_data (priv->proxy, self);
-		g_clear_object (&priv->proxy);
-	}
-
-	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->dispose (object);
-}
-
-static void
-finalize (GObject *object)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (object);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	g_hash_table_destroy (priv->devices);
-	g_free (priv->address);
-	g_free (priv->path);
-
-	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->finalize (object);
-
-	g_object_unref (priv->settings);
-}
-
-static void
-nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-
-	object_class->get_property = get_property;
-	object_class->set_property = set_property;
-	object_class->dispose = dispose;
-	object_class->finalize = finalize;
-
-	obj_properties[PROP_PATH] =
-	     g_param_spec_string (NM_BLUEZ4_ADAPTER_PATH, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_ADDRESS] =
-	     g_param_spec_string (NM_BLUEZ4_ADAPTER_ADDRESS, "", "",
-	                          NULL,
-	                          G_PARAM_READABLE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	signals[INITIALIZED] = g_signal_new (NM_BLUEZ4_ADAPTER_INITIALIZED,
-	                                     G_OBJECT_CLASS_TYPE (object_class),
-	                                     G_SIGNAL_RUN_LAST,
-	                                     0,
-	                                     NULL, NULL,
-	                                     g_cclosure_marshal_VOID__BOOLEAN,
-	                                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
-
-	signals[DEVICE_ADDED] = g_signal_new (NM_BLUEZ4_ADAPTER_DEVICE_ADDED,
-	                                      G_OBJECT_CLASS_TYPE (object_class),
-	                                      G_SIGNAL_RUN_LAST,
-	                                      0,
-	                                      NULL, NULL,
-	                                      g_cclosure_marshal_VOID__OBJECT,
-	                                      G_TYPE_NONE, 1, G_TYPE_OBJECT);
-
-	signals[DEVICE_REMOVED] = g_signal_new (NM_BLUEZ4_ADAPTER_DEVICE_REMOVED,
-	                                        G_OBJECT_CLASS_TYPE (object_class),
-	                                        G_SIGNAL_RUN_LAST,
-	                                        0,
-	                                        NULL, NULL,
-	                                        g_cclosure_marshal_VOID__OBJECT,
-	                                        G_TYPE_NONE, 1, G_TYPE_OBJECT);
-}
-
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.h b/src/devices/bluetooth/nm-bluez4-adapter.h
deleted file mode 100644
index 82bd2de8..00000000
--- a/src/devices/bluetooth/nm-bluez4-adapter.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2009 - 2012 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_BLUEZ4_ADAPTER_H__
-#define __NETWORKMANAGER_BLUEZ4_ADAPTER_H__
-
-#include "nm-bluez-device.h"
-
-#define NM_TYPE_BLUEZ4_ADAPTER            (nm_bluez4_adapter_get_type ())
-#define NM_BLUEZ4_ADAPTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4Adapter))
-#define NM_BLUEZ4_ADAPTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterClass))
-#define NM_IS_BLUEZ4_ADAPTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ4_ADAPTER))
-#define NM_IS_BLUEZ4_ADAPTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ4_ADAPTER))
-#define NM_BLUEZ4_ADAPTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterClass))
-
-/* Properties */
-#define NM_BLUEZ4_ADAPTER_PATH    "path"
-#define NM_BLUEZ4_ADAPTER_ADDRESS "address"
-
-/* Signals */
-#define NM_BLUEZ4_ADAPTER_INITIALIZED      "initialized"
-#define NM_BLUEZ4_ADAPTER_DEVICE_ADDED     "device-added"
-#define NM_BLUEZ4_ADAPTER_DEVICE_REMOVED   "device-removed"
-
-typedef struct _NMBluez4Adapter NMBluez4Adapter;
-typedef struct _NMBluez4AdapterClass NMBluez4AdapterClass;
-
-GType nm_bluez4_adapter_get_type (void);
-
-NMBluez4Adapter *nm_bluez4_adapter_new (const char *path,
-                                        NMSettings *settings);
-
-const char *nm_bluez4_adapter_get_path (NMBluez4Adapter *self);
-
-const char *nm_bluez4_adapter_get_address (NMBluez4Adapter *self);
-
-gboolean nm_bluez4_adapter_get_initialized (NMBluez4Adapter *self);
-
-GSList *nm_bluez4_adapter_get_devices (NMBluez4Adapter *self);
-
-#endif /* __NETWORKMANAGER_BLUEZ4_ADAPTER_H__ */
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
deleted file mode 100644
index 8327776d..00000000
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ /dev/null
@@ -1,355 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2013 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-bluez4-manager.h"
-
-#include <signal.h>
-#include <stdlib.h>
-
-#include "nm-bluez4-adapter.h"
-#include "nm-bluez-common.h"
-#include "nm-core-internal.h"
-#include "settings/nm-settings.h"
-
-/*****************************************************************************/
-
-enum {
-	BDADDR_ADDED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	gulong name_owner_changed_id;
-
-	NMSettings *settings;
-
-	GDBusProxy *proxy;
-	GCancellable *proxy_cancellable;
-
-	NMBluez4Adapter *adapter;
-} NMBluez4ManagerPrivate;
-
-struct _NMBluez4Manager {
-	GObject parent;
-	NMBluez4ManagerPrivate _priv;
-};
-
-struct _NMBluez4ManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMBluez4Manager, nm_bluez4_manager, G_TYPE_OBJECT)
-
-#define NM_BLUEZ4_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez4Manager, NM_IS_BLUEZ4_MANAGER)
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_BT
-#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez4-manager", __VA_ARGS__)
-
-/*****************************************************************************/
-
-static void
-emit_bdaddr_added (NMBluez4Manager *self, NMBluezDevice *device)
-{
-	g_signal_emit (self, signals[BDADDR_ADDED], 0,
-	               device,
-	               nm_bluez_device_get_address (device),
-	               nm_bluez_device_get_name (device),
-	               nm_bluez_device_get_path (device),
-	               nm_bluez_device_get_capabilities (device));
-}
-
-void
-nm_bluez4_manager_query_devices (NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	GSList *devices, *iter;
-
-	if (!priv->adapter)
-		return;
-
-	devices = nm_bluez4_adapter_get_devices (priv->adapter);
-	for (iter = devices; iter; iter = g_slist_next (iter))
-		emit_bdaddr_added (self, NM_BLUEZ_DEVICE (iter->data));
-	g_slist_free (devices);
-}
-
-static void
-device_added (NMBluez4Adapter *adapter, NMBluezDevice *device, gpointer user_data)
-{
-	emit_bdaddr_added (NM_BLUEZ4_MANAGER (user_data), device);
-}
-
-static void
-device_removed (NMBluez4Adapter *adapter, NMBluezDevice *device, gpointer user_data)
-{
-	/* Re-emit the signal on the device for now; flatten this later */
-	g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED);
-}
-
-static void
-adapter_initialized (NMBluez4Adapter *adapter, gboolean success, gpointer user_data)
-{
-	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data);
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	if (success) {
-		GSList *devices, *iter;
-
-		devices = nm_bluez4_adapter_get_devices (adapter);
-		for (iter = devices; iter; iter = g_slist_next (iter))
-			emit_bdaddr_added (self, NM_BLUEZ_DEVICE (iter->data));
-		g_slist_free (devices);
-
-		g_signal_connect (adapter, NM_BLUEZ4_ADAPTER_DEVICE_ADDED,
-		                  G_CALLBACK (device_added), self);
-		g_signal_connect (adapter, NM_BLUEZ4_ADAPTER_DEVICE_REMOVED,
-		                  G_CALLBACK (device_removed), self);
-	} else {
-		g_object_unref (priv->adapter);
-		priv->adapter = NULL;
-	}
-}
-
-static void
-adapter_removed (GDBusProxy *proxy, const char *path, NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	if (priv->adapter && !strcmp (path, nm_bluez4_adapter_get_path (priv->adapter))) {
-		if (nm_bluez4_adapter_get_initialized (priv->adapter)) {
-			GSList *devices, *iter;
-
-			devices = nm_bluez4_adapter_get_devices (priv->adapter);
-			for (iter = devices; iter; iter = g_slist_next (iter))
-				g_signal_emit_by_name (NM_BLUEZ_DEVICE (iter->data), NM_BLUEZ_DEVICE_REMOVED);
-			g_slist_free (devices);
-		}
-
-		g_object_unref (priv->adapter);
-		priv->adapter = NULL;
-	}
-}
-
-static void
-default_adapter_changed (GDBusProxy *proxy, const char *path, NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	const char *cur_path = NULL;
-
-	if (priv->adapter)
-		cur_path = nm_bluez4_adapter_get_path (priv->adapter);
-
-	if (cur_path) {
-		if (!path || strcmp (path, cur_path)) {
-			/* Default adapter changed */
-			adapter_removed (priv->proxy, cur_path, self);
-		} else {
-			/* This adapter is already the default */
-			return;
-		}
-	}
-
-	/* Add the new default adapter */
-	if (path) {
-		priv->adapter = nm_bluez4_adapter_new (path, priv->settings);
-		g_signal_connect (priv->adapter, NM_BLUEZ4_ADAPTER_INITIALIZED,
-		                  G_CALLBACK (adapter_initialized), self);
-	}
-}
-
-static void
-default_adapter_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMBluez4Manager *self;
-	NMBluez4ManagerPrivate *priv;
-	gs_unref_variant GVariant *ret = NULL;
-	gs_free_error GError *error = NULL;
-	const char *default_adapter;
-
-	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(o)"), &error);
-	if (   !ret
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_BLUEZ4_MANAGER (user_data);
-	priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	g_clear_object (&priv->proxy_cancellable);
-
-	if (!ret) {
-		/* Ignore "No such adapter" errors; just means bluetooth isn't active */
-		if (   !_nm_dbus_error_has_name (error, "org.bluez.Error.NoSuchAdapter")
-		    && !_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
-			g_dbus_error_strip_remote_error (error);
-			_LOGW ("bluez error getting default adapter: %s",
-			       error->message);
-		}
-		return;
-	}
-
-	g_variant_get (ret, "(&o)", &default_adapter);
-	default_adapter_changed (priv->proxy, default_adapter, self);
-}
-
-static void
-name_owner_changed (NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	gs_free char *owner = NULL;
-
-	nm_clear_g_cancellable (&priv->proxy_cancellable);
-
-	owner = g_dbus_proxy_get_name_owner (priv->proxy);
-	if (!owner) {
-		/* Throwing away the adapter removes all devices too */
-		g_clear_object (&priv->adapter);
-		return;
-	}
-
-	priv->proxy_cancellable = g_cancellable_new ();
-
-	g_dbus_proxy_call (priv->proxy, "DefaultAdapter",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   priv->proxy_cancellable,
-	                   default_adapter_cb,
-	                   self);
-}
-
-static void
-name_owner_changed_cb (GObject *object,
-                       GParamSpec *pspec,
-                       gpointer user_data)
-{
-	name_owner_changed (user_data);
-}
-
-static void
-_proxy_new_cb (GObject *source_object,
-               GAsyncResult *result,
-               gpointer user_data)
-{
-	NMBluez4Manager *self;
-	NMBluez4ManagerPrivate *priv;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *proxy;
-
-	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (   !proxy
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = user_data;
-	priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	if (!proxy) {
-		_LOGW ("bluez error creating D-Bus proxy: %s", error->message);
-		g_clear_object (&priv->proxy_cancellable);
-		return;
-	}
-
-	priv->proxy = proxy;
-
-	_nm_dbus_signal_connect (priv->proxy, "AdapterRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (adapter_removed), self);
-	_nm_dbus_signal_connect (priv->proxy, "DefaultAdapterChanged", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (default_adapter_changed), self);
-	g_signal_connect (priv->proxy, "notify::g-name-owner",
-	                  G_CALLBACK (name_owner_changed_cb), self);
-
-	name_owner_changed (self);
-}
-
-/*****************************************************************************/
-
-static void
-nm_bluez4_manager_init (NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	priv->proxy_cancellable = g_cancellable_new ();
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          NM_BLUEZ_MANAGER_PATH,
-	                          NM_BLUEZ4_MANAGER_INTERFACE,
-	                          priv->proxy_cancellable,
-	                          _proxy_new_cb,
-	                          self);
-}
-
-NMBluez4Manager *
-nm_bluez4_manager_new (NMSettings *settings)
-{
-	NMBluez4Manager *instance;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
-
-	instance = g_object_new (NM_TYPE_BLUEZ4_MANAGER, NULL);
-	NM_BLUEZ4_MANAGER_GET_PRIVATE (instance)->settings = g_object_ref (settings);
-	return instance;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (object);
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	nm_clear_g_cancellable (&priv->proxy_cancellable);
-
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_data (priv->proxy, self);
-		g_clear_object (&priv->proxy);
-	}
-
-	g_clear_object (&priv->adapter);
-
-	G_OBJECT_CLASS (nm_bluez4_manager_parent_class)->dispose (object);
-
-	g_clear_object (&priv->settings);
-}
-
-static void
-nm_bluez4_manager_class_init (NMBluez4ManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->dispose = dispose;
-
-	signals[BDADDR_ADDED] =
-	    g_signal_new (NM_BLUEZ_MANAGER_BDADDR_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING,
-	                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
-}
-
diff --git a/src/devices/bluetooth/nm-bluez4-manager.h b/src/devices/bluetooth/nm-bluez4-manager.h
deleted file mode 100644
index f46379ff..00000000
--- a/src/devices/bluetooth/nm-bluez4-manager.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2013 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_BLUEZ4_MANAGER_H__
-#define __NETWORKMANAGER_BLUEZ4_MANAGER_H__
-
-#define NM_TYPE_BLUEZ4_MANAGER            (nm_bluez4_manager_get_type ())
-#define NM_BLUEZ4_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ4_MANAGER, NMBluez4Manager))
-#define NM_BLUEZ4_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ4_MANAGER, NMBluez4ManagerClass))
-#define NM_IS_BLUEZ4_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ4_MANAGER))
-#define NM_IS_BLUEZ4_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ4_MANAGER))
-#define NM_BLUEZ4_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ4_MANAGER, NMBluez4ManagerClass))
-
-typedef struct _NMBluez4Manager NMBluez4Manager;
-typedef struct _NMBluez4ManagerClass NMBluez4ManagerClass;
-
-GType nm_bluez4_manager_get_type (void);
-
-NMBluez4Manager *nm_bluez4_manager_new (NMSettings *settings);
-
-void nm_bluez4_manager_query_devices (NMBluez4Manager *manager);
-
-#endif /* __NETWORKMANAGER_BLUEZ4_MANAGER_H__ */
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index b9a1fa0a..af463d1a 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
@@ -32,145 +18,407 @@
 #include "nm-bt-error.h"
 #include "NetworkManagerUtils.h"
 
+#define RFCOMM_FMT "/dev/rfcomm%d"
+
+/*****************************************************************************/
+
+typedef struct {
+	GCancellable *cancellable;
+	NMBluez5DunConnectCb callback;
+	gpointer callback_user_data;
+
+	sdp_session_t *sdp_session;
+
+	GError *rfcomm_sdp_search_error;
+
+	gint64 connect_open_tty_started_at;
+
+	gulong cancelled_id;
+
+	guint source_id;
+
+	guint8 sdp_session_try_count;
+} ConnectData;
+
 struct _NMBluez5DunContext {
+	const char *dst_str;
+
+	ConnectData *cdat;
+
+	NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb;
+	gpointer notify_tty_hangup_user_data;
+
+	char *rfcomm_tty_path;
+
+	int rfcomm_sock_fd;
+	int rfcomm_tty_fd;
+	int rfcomm_tty_no;
+	int rfcomm_channel;
+
+	guint rfcomm_tty_poll_id;
+
 	bdaddr_t src;
 	bdaddr_t dst;
-	char *src_str;
-	char *dst_str;
-	int rfcomm_channel;
-	int rfcomm_fd;
-	int rfcomm_tty_fd;
-	int rfcomm_id;
-	NMBluez5DunFunc callback;
-	gpointer user_data;
-	sdp_session_t *sdp_session;
-	guint sdp_watch_id;
+
+	char src_str[];
 };
 
-static void
-dun_connect (NMBluez5DunContext *context)
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_BT
+#define _NMLOG_PREFIX_NAME "bluez"
+#define _NMLOG(level, context, ...) \
+    G_STMT_START { \
+        if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \
+            const NMBluez5DunContext *const _context = (context); \
+            \
+            _nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \
+                     "%s: DUN[%s] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     _context->src_str \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static void _context_invoke_callback_success (NMBluez5DunContext *context);
+static void _context_invoke_callback_fail_and_free (NMBluez5DunContext *context,
+                                                    GError *error);
+static void _context_free (NMBluez5DunContext *context);
+static int _connect_open_tty (NMBluez5DunContext *context);
+static gboolean _connect_sdp_session_start (NMBluez5DunContext *context,
+                                            GError **error);
+
+/*****************************************************************************/
+
+NM_AUTO_DEFINE_FCN0 (NMBluez5DunContext *, _nm_auto_free_context, _context_free)
+#define nm_auto_free_context nm_auto(_nm_auto_free_context)
+
+/*****************************************************************************/
+
+const char *
+nm_bluez5_dun_context_get_adapter (const NMBluez5DunContext *context)
 {
-	struct sockaddr_rc sa;
-	int devid, try = 30;
-	char tty[100];
-	const int ttylen = sizeof (tty) - 1;
-	GError *error = NULL;
-	int errsv;
+	return context->src_str;
+}
 
-	struct rfcomm_dev_req req = {
-		.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP),
-		.dev_id = -1,
-		.channel = context->rfcomm_channel
-	};
+const char *
+nm_bluez5_dun_context_get_remote (const NMBluez5DunContext *context)
+{
+	return context->dst_str;
+}
 
-	context->rfcomm_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC, BTPROTO_RFCOMM);
-	if (context->rfcomm_fd < 0) {
-		errsv = errno;
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to create RFCOMM socket: (%d) %s",
-		                     errsv, nm_strerror_native (errsv));
-		goto done;
-	}
+const char *
+nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context)
+{
+	return context->rfcomm_tty_path;
+}
 
-	/* Connect to the remote device */
-	sa.rc_family = AF_BLUETOOTH;
-	sa.rc_channel = 0;
-	memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN);
-	if (bind (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof(sa))) {
-		errsv = errno;
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to bind socket: (%d) %s",
-		                     errsv, nm_strerror_native (errsv));
-		goto done;
+/*****************************************************************************/
+
+static gboolean
+_rfcomm_tty_poll_cb (GIOChannel *stream,
+                     GIOCondition condition,
+                     gpointer user_data)
+{
+	NMBluez5DunContext *context = user_data;
+
+	_LOGD (context, "receive %s%s%s signal on rfcomm file descriptor",
+	       NM_FLAGS_HAS (condition, G_IO_ERR)            ? "ERR" : "",
+	       NM_FLAGS_ALL (condition, G_IO_HUP | G_IO_ERR) ? ","   : "",
+	       NM_FLAGS_HAS (condition, G_IO_HUP)            ? "HUP" : "");
+
+	context->rfcomm_tty_poll_id = 0;
+	context->notify_tty_hangup_cb (context,
+	                               context->notify_tty_hangup_user_data);
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_connect_open_tty_retry_cb (gpointer user_data)
+{
+	NMBluez5DunContext *context = user_data;
+	int r;
+
+	r = _connect_open_tty (context);
+	if (r >= 0)
+		return G_SOURCE_REMOVE;
+
+	if (nm_utils_get_monotonic_timestamp_ns () > context->cdat->connect_open_tty_started_at + (30 * 100 * NM_UTILS_NS_PER_MSEC)) {
+		gs_free_error GError *error = NULL;
+
+		context->cdat->source_id = 0;
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "give up waiting to open %s device: %s (%d)",
+		             context->rfcomm_tty_path,
+		             nm_strerror_native (r),
+		             -r);
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
 	}
 
-	sa.rc_channel = context->rfcomm_channel;
-	memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN);
-	if (connect (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof (sa)) ) {
-		errsv = errno;
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to connect to remote device: (%d) %s",
-		                     errsv, nm_strerror_native (errsv));
-		goto done;
+	return G_SOURCE_CONTINUE;
+}
+
+static int
+_connect_open_tty (NMBluez5DunContext *context)
+{
+	nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
+	int fd;
+	int errsv;
+
+	fd = open (context->rfcomm_tty_path, O_RDONLY | O_NOCTTY | O_CLOEXEC);
+	if (fd < 0) {
+		errsv = NM_ERRNO_NATIVE (errno);
+
+		if (context->cdat->source_id == 0) {
+			_LOGD (context, "failed opening tty "RFCOMM_FMT": %s (%d). Start polling...",
+			       context->rfcomm_tty_no,
+			       nm_strerror_native (errsv),
+			       errsv);
+			context->cdat->connect_open_tty_started_at = nm_utils_get_monotonic_timestamp_ns ();
+			context->cdat->source_id = g_timeout_add (100,
+			                                          _connect_open_tty_retry_cb,
+			                                          context);
+		}
+		return -errsv;
 	}
 
-	nm_log_dbg (LOGD_BT, "(%s): connected to %s on channel %d",
-	            context->src_str, context->dst_str, context->rfcomm_channel);
+	context->rfcomm_tty_fd = fd;
+
+	io_channel = g_io_channel_unix_new (context->rfcomm_tty_fd);
+	context->rfcomm_tty_poll_id = g_io_add_watch (io_channel,
+	                                              G_IO_ERR | G_IO_HUP,
+	                                              _rfcomm_tty_poll_cb,
+	                                              context);
+
+	_context_invoke_callback_success (context);
+	return 0;
+}
+
+static void
+_connect_create_rfcomm (NMBluez5DunContext *context)
+{
+	gs_free_error GError *error = NULL;
+	struct rfcomm_dev_req req;
+	int devid;
+	int errsv;
+	int r;
+
+	_LOGD (context, "connected to %s on channel %d",
+	       context->dst_str, context->rfcomm_channel);
 
 	/* Create an RFCOMM kernel device for the DUN channel */
+	memset (&req, 0, sizeof (req));
+	req.dev_id  = -1;
+	req.flags   = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP);
+	req.channel = context->rfcomm_channel;
 	memcpy (&req.src, &context->src, ETH_ALEN);
 	memcpy (&req.dst, &context->dst, ETH_ALEN);
-	devid = ioctl (context->rfcomm_fd, RFCOMMCREATEDEV, &req);
+	devid = ioctl (context->rfcomm_sock_fd, RFCOMMCREATEDEV, &req);
 	if (devid < 0) {
-		errsv = errno;
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to create rfcomm device: (%d) %s",
-		                     errsv, nm_strerror_native (errsv));
-		goto done;
+		errsv = NM_ERRNO_NATIVE (errno);
+		if (errsv == EBADFD) {
+			/* hm. We use a non-blocking socket to connect. Above getsockopt(SOL_SOCKET,SO_ERROR) indicated
+			 * success, but still now we fail with EBADFD. I think that is a bug and we should get the
+			 * failure during connect().
+			 *
+			 * Anyway, craft a less confusing error message than
+			 * "failed to create rfcomm device: File descriptor in bad state (77)". */
+			g_set_error (&error,
+			             NM_BT_ERROR,
+			             NM_BT_ERROR_DUN_CONNECT_FAILED,
+			             "unknown failure to connect to DUN device");
+		} else {
+			g_set_error (&error,
+			             NM_BT_ERROR,
+			             NM_BT_ERROR_DUN_CONNECT_FAILED,
+			             "failed to create rfcomm device: %s (%d)",
+			             nm_strerror_native (errsv), errsv);
+		}
+		_context_invoke_callback_fail_and_free (context, error);
+		return;
 	}
-	context->rfcomm_id = devid;
 
-	snprintf (tty, ttylen, "/dev/rfcomm%d", devid);
-	while ((context->rfcomm_tty_fd = open (tty, O_RDONLY | O_NOCTTY | O_CLOEXEC)) < 0 && try--) {
-		if (try) {
-			g_usleep (100 * 1000);
-			continue;
-		}
+	context->rfcomm_tty_no = devid;
+	context->rfcomm_tty_path = g_strdup_printf (RFCOMM_FMT, devid);
+
+	r = _connect_open_tty (context);
+	if (r < 0) {
+		/* we created the rfcomm device, but cannot yet open it. That means, we are
+		 * not yet fully connected. However, we notify the caller about "what we learned
+		 * so far". Note that this happens synchronously.
+		 *
+		 * The purpose is that once we proceed synchrnously, modem-manager races with
+		 * the detection of the modem. We want to notify the caller first about the
+		 * device name. */
+		context->cdat->callback (NULL,
+		                         context->rfcomm_tty_path,
+		                         NULL,
+		                         context->cdat->callback_user_data);
+	}
+}
 
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to find rfcomm device: %s",
-		                     tty);
-		break;
+static gboolean
+_connect_socket_connect_cb (GIOChannel *stream,
+                            GIOCondition condition,
+                            gpointer user_data)
+{
+	NMBluez5DunContext *context = user_data;
+	gs_free_error GError *error = NULL;
+	int errsv = 0;
+	socklen_t slen = sizeof(errsv);
+	int r;
+
+	context->cdat->source_id = 0;
+
+	r = getsockopt (context->rfcomm_sock_fd, SOL_SOCKET, SO_ERROR, &errsv, &slen);
+
+	if (r < 0) {
+		errsv = errno;
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to complete connecting RFCOMM socket: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
 	}
 
-done:
-	context->callback (context, tty, error, context->user_data);
+	if (errsv != 0) {
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to connect RFCOMM socket: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
+	}
+
+	_connect_create_rfcomm (context);
+	return G_SOURCE_REMOVE;
 }
 
 static void
-sdp_search_cleanup (NMBluez5DunContext *context)
+_connect_socket_connect (NMBluez5DunContext *context)
 {
-	if (context->sdp_session) {
-		sdp_close (context->sdp_session);
-		context->sdp_session = NULL;
+	gs_free_error GError *error = NULL;
+	struct sockaddr_rc sa;
+	int errsv;
+
+	context->rfcomm_sock_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_RFCOMM);
+	if (context->rfcomm_sock_fd < 0) {
+		errsv = errno;
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to create RFCOMM socket: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return;
+	}
+
+	/* Connect to the remote device */
+	memset (&sa, 0, sizeof (sa));
+	sa.rc_family = AF_BLUETOOTH;
+	sa.rc_channel = 0;
+	memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN);
+	if (bind (context->rfcomm_sock_fd,
+	          (struct sockaddr *) &sa,
+	          sizeof(sa)) != 0) {
+		errsv = errno;
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to bind socket: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return;
+	}
+
+	memset (&sa, 0, sizeof (sa));
+	sa.rc_family = AF_BLUETOOTH;
+	sa.rc_channel = context->rfcomm_channel;
+	memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN);
+	if (connect (context->rfcomm_sock_fd,
+	             (struct sockaddr *) &sa,
+	             sizeof (sa)) != 0) {
+		nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
+
+		errsv = errno;
+		if (errsv != EINPROGRESS) {
+			g_set_error (&error,
+			             NM_BT_ERROR,
+			             NM_BT_ERROR_DUN_CONNECT_FAILED,
+			             "failed to connect to remote device: %s (%d)",
+			             nm_strerror_native (errsv), errsv);
+			_context_invoke_callback_fail_and_free (context, error);
+			return;
+		}
+
+		_LOGD (context, "connecting to %s on channel %d...",
+		       context->dst_str,
+		       context->rfcomm_channel);
+
+		io_channel = g_io_channel_unix_new (context->rfcomm_sock_fd);
+		context->cdat->source_id = g_io_add_watch (io_channel,
+		                                           G_IO_OUT,
+		                                           _connect_socket_connect_cb,
+		                                           context);
+		return;
 	}
 
-	nm_clear_g_source (&context->sdp_watch_id);
+	_connect_create_rfcomm (context);
 }
 
 static void
-sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data)
+_connect_sdp_search_cb (uint8_t type,
+                        uint16_t status,
+                        uint8_t *rsp,
+                        size_t size,
+                        void *user_data)
 {
 	NMBluez5DunContext *context = user_data;
-	int scanned, seqlen = 0, bytesleft = size;
+	int scanned;
+	int seqlen = 0;
+	int bytesleft = size;
 	uint8_t dataType;
 	int channel = -1;
 
-	nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search finished with type=%d status=%d",
-	            context->src_str, context->dst_str, status, type);
+	if (   context->cdat->rfcomm_sdp_search_error
+	    || context->rfcomm_channel >= 0)
+		return;
+
+	_LOGD (context, "SDP search finished with type=%d status=%d",
+	       status, type);
 
 	/* SDP response received */
-	if (status || type != SDP_SVC_SEARCH_ATTR_RSP) {
-		GError *error = g_error_new (NM_BT_ERROR,
-		                             NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                             "Did not get a Service Discovery response");
-		context->callback (context, NULL, error, context->user_data);
-		goto done;
+	if (   status
+	    || type != SDP_SVC_SEARCH_ATTR_RSP) {
+		g_set_error (&context->cdat->rfcomm_sdp_search_error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "did not get a Service Discovery response");
+		return;
 	}
 
 	scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen);
 
-	nm_log_dbg (LOGD_BT, "(%s -> %s): SDP sequence type scanned=%d length=%d",
-	            context->src_str, context->dst_str, scanned, seqlen);
+	_LOGD (context, "SDP sequence type scanned=%d length=%d",
+	       scanned, seqlen);
 
 	scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen);
-	if (!scanned || !seqlen) {
+	if (   !scanned
+	    || !seqlen) {
 		/* Short read or unknown sequence type */
-		GError *error = g_error_new (NM_BT_ERROR,
-		                             NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                             "Improper Service Discovery response");
-		context->callback (context, NULL, error, context->user_data);
-		goto done;
+		g_set_error (&context->cdat->rfcomm_sdp_search_error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "improper Service Discovery response");
+		return;
 	}
 
 	rsp += scanned;
@@ -194,90 +442,153 @@ sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t siz
 			channel = sdp_get_proto_port (protos, RFCOMM_UUID);
 			sdp_list_free (protos, NULL);
 
-			nm_log_dbg (LOGD_BT, "(%s -> %s): SDP channel=%d",
-			            context->src_str, context->dst_str, channel);
+			_LOGD (context, "SDP channel=%d",
+			       channel);
 		}
 		sdp_record_free (rec);
 
 		scanned += recsize;
 		rsp += recsize;
 		bytesleft -= recsize;
-	} while ((scanned < (ssize_t) size) && (bytesleft > 0) && (channel < 0));
-
-done:
-	if (channel != -1) {
-		context->rfcomm_channel = channel;
-		dun_connect (context);
+	} while (   scanned < (ssize_t) size
+	         && bytesleft > 0
+	         && channel < 0);
+
+	if (channel == -1) {
+		g_set_error (&context->cdat->rfcomm_sdp_search_error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "did not receive rfcomm-channel");
+		return;
 	}
 
-	sdp_search_cleanup (context);
+	context->rfcomm_channel = channel;
 }
 
 static gboolean
-sdp_search_process_cb (GIOChannel *channel, GIOCondition condition, gpointer user_data)
+_connect_sdp_search_io_cb (GIOChannel *io_channel,
+                           GIOCondition condition,
+                           gpointer user_data)
 {
 	NMBluez5DunContext *context = user_data;
-
-	nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search progressed with condition=%d",
-	            context->src_str, context->dst_str, condition);
+	gs_free_error GError *error = NULL;
+	int errsv;
 
 	if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
-		GError *error = g_error_new (NM_BT_ERROR,
-		                             NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                             "Service Discovery interrupted");
-		context->callback (context, NULL, error, context->user_data);
-		sdp_search_cleanup (context);
-		return FALSE;
+		_LOGD (context, "SDP search returned with invalid IO condition 0x%x",
+		       (guint) condition);
+		error = g_error_new (NM_BT_ERROR,
+		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
+		                     "Service Discovery interrupted");
+		context->cdat->source_id = 0;
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
 	}
 
-	if (sdp_process (context->sdp_session) < 0) {
-		nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search finished",
-		            context->src_str, context->dst_str);
+	if (sdp_process (context->cdat->sdp_session) == 0) {
+		_LOGD (context, "SDP search still not finished");
+		return G_SOURCE_CONTINUE;
+	}
 
-		/* Search finished successfully. */
-		return FALSE;
+	context->cdat->source_id = 0;
+
+	if (   context->rfcomm_channel < 0
+	    && !context->cdat->rfcomm_sdp_search_error) {
+		errsv = sdp_get_error (context->cdat->sdp_session);
+		_LOGD (context, "SDP search failed: %s (%d)",
+		       nm_strerror_native (errsv), errsv);
+		error = g_error_new (NM_BT_ERROR,
+		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
+		                     "Service Discovery failed with %s (%d)",
+		                     nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
 	}
 
-	/* Search progressed successfully. */
-	return TRUE;
+	if (context->cdat->rfcomm_sdp_search_error) {
+		_LOGD (context, "SDP search failed to complete: %s", context->cdat->rfcomm_sdp_search_error->message);
+		_context_invoke_callback_fail_and_free (context, context->cdat->rfcomm_sdp_search_error);
+		return G_SOURCE_REMOVE;
+	}
+
+	nm_clear_pointer (&context->cdat->sdp_session, sdp_close);
+
+	_connect_socket_connect (context);
+
+	return G_SOURCE_REMOVE;
 }
 
 static gboolean
-sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_data)
+_connect_sdp_session_start_on_idle_cb (gpointer user_data)
 {
 	NMBluez5DunContext *context = user_data;
-	sdp_list_t *search, *attrs;
+	gs_free_error GError *error = NULL;
+
+	context->cdat->source_id = 0;
+
+	_LOGD (context, "retry starting sdp-session...");
+
+	if (!_connect_sdp_session_start (context, &error))
+		_context_invoke_callback_fail_and_free (context, error);
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_connect_sdp_io_cb (GIOChannel *io_channel,
+                    GIOCondition condition,
+                    gpointer user_data)
+{
+	NMBluez5DunContext *context = user_data;
+	sdp_list_t *search;
+	sdp_list_t *attrs;
 	uuid_t svclass;
 	uint16_t attr;
-	int fd, fd_err = 0;
-	int err;
+	int fd;
+	int errsv;
+	int fd_err = 0;
+	int r;
 	socklen_t len = sizeof (fd_err);
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
+
+	context->cdat->source_id = 0;
 
-	context->sdp_watch_id = 0;
+	fd = g_io_channel_unix_get_fd (io_channel);
+
+	_LOGD (context, "sdp-session ready to connect with fd=%d", fd);
 
-	fd = g_io_channel_unix_get_fd (channel);
 	if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) {
-		err = errno;
-		nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d",
-		            context->src_str, context->dst_str, err);
-	} else {
-		err = fd_err;
-		nm_log_dbg (LOGD_BT, "(%s -> %s): SO_ERROR error=%d",
-		            context->src_str, context->dst_str, fd_err);
+		errsv = NM_ERRNO_NATIVE (errno);
+		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
+		                     "error for getsockopt on Service Discovery socket: %s (%d)",
+		                     nm_strerror_native (errsv), errsv);
+		goto done;
 	}
 
-	if (err != 0) {
+	if (fd_err != 0) {
+		errsv = nm_errno_native (fd_err);
+
+		if (   NM_IN_SET (errsv, ECONNREFUSED, EHOSTDOWN)
+		    && --context->cdat->sdp_session_try_count > 0) {
+			/* *sigh* */
+			_LOGD (context, "sdp-session failed with %s (%d). Retry in a bit", nm_strerror_native (errsv), errsv);
+			nm_clear_g_source (&context->cdat->source_id);
+			context->cdat->source_id = g_timeout_add (1000,
+			                                          _connect_sdp_session_start_on_idle_cb,
+			                                          context);
+			return G_SOURCE_REMOVE;
+		}
+
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Error on Service Discovery socket: (%d) %s",
-		                     err, nm_strerror_native (err));
+		                     "error on Service Discovery socket: %s (%d)",
+		                     nm_strerror_native (errsv), errsv);
 		goto done;
 	}
 
-	if (sdp_set_notify (context->sdp_session, sdp_search_completed_cb, context) < 0) {
+	if (sdp_set_notify (context->cdat->sdp_session, _connect_sdp_search_cb, context) < 0) {
 		/* Should not be reached, only can fail if we passed bad sdp_session. */
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Could not request Service Discovery notification");
+		                     "could not set Service Discovery notification");
 		goto done;
 	}
 
@@ -286,124 +597,261 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 	attr = SDP_ATTR_PROTO_DESC_LIST;
 	attrs = sdp_list_append (NULL, &attr);
 
-	if (!sdp_service_search_attr_async (context->sdp_session, search, SDP_ATTR_REQ_INDIVIDUAL, attrs)) {
-		/* Set callback responsible for update the internal SDP transaction */
-		context->sdp_watch_id = g_io_add_watch (channel,
-		                                        G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-		                                        sdp_search_process_cb,
-		                                        context);
-	} else {
-		err = sdp_get_error (context->sdp_session);
-		error = g_error_new (NM_BT_ERROR,
-		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Error starting Service Discovery: (%d) %s",
-		                     err, nm_strerror_native (err));
-	}
+	r = sdp_service_search_attr_async (context->cdat->sdp_session,
+	                                   search,
+	                                   SDP_ATTR_REQ_INDIVIDUAL,
+	                                   attrs);
 
 	sdp_list_free (attrs, NULL);
 	sdp_list_free (search, NULL);
 
-done:
-	if (error) {
-		context->callback (context, NULL, error, context->user_data);
-		sdp_search_cleanup (context);
+	if (r < 0) {
+		errsv = nm_errno_native (sdp_get_error (context->cdat->sdp_session));
+		error = g_error_new (NM_BT_ERROR,
+		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
+		                     "error starting Service Discovery: %s (%d)",
+		                     nm_strerror_native (errsv), errsv);
+		goto done;
 	}
 
+	/* Set callback responsible for update the internal SDP transaction */
+	context->cdat->source_id = g_io_add_watch (io_channel,
+	                                           G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+	                                           _connect_sdp_search_io_cb,
+	                                           context);
+
+done:
+	if (error)
+		_context_invoke_callback_fail_and_free (context, error);
 	return G_SOURCE_REMOVE;
 }
 
-NMBluez5DunContext *
-nm_bluez5_dun_new (const char *adapter,
-                   const char *remote)
+/*****************************************************************************/
 
+static void
+_connect_cancelled_cb (GCancellable *cancellable,
+                       NMBluez5DunContext *context)
 {
-	NMBluez5DunContext *context;
-
-	context = g_slice_new0 (NMBluez5DunContext);
-	str2ba (adapter, &context->src);
-	str2ba (remote, &context->dst);
-	context->src_str = g_strdup (adapter);
-	context->dst_str = g_strdup (remote);
-	context->rfcomm_channel = -1;
-	context->rfcomm_id = -1;
-	context->rfcomm_fd = -1;
-	return context;
+	gs_free_error GError *error = NULL;
+
+	if (!g_cancellable_set_error_if_cancelled (cancellable, &error))
+		g_return_if_reached ();
+
+	_context_invoke_callback_fail_and_free (context, error);
 }
 
-void
-nm_bluez5_dun_connect (NMBluez5DunContext *context,
-                       NMBluez5DunFunc callback,
-                       gpointer user_data)
+static gboolean
+_connect_sdp_session_start (NMBluez5DunContext *context,
+                            GError **error)
 {
-	GIOChannel *channel;
+	nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
 
-	context->callback = callback;
-	context->user_data = user_data;
+	nm_assert (context->cdat);
 
-	if (context->rfcomm_channel != -1) {
-		nm_log_dbg (LOGD_BT, "(%s): channel number on device %s cached: %d",
-		            context->src_str, context->dst_str, context->rfcomm_channel);
-		/* FIXME: don't invoke the callback synchronously. */
-		dun_connect (context);
-		return;
+	nm_clear_g_source (&context->cdat->source_id);
+	nm_clear_pointer (&context->cdat->sdp_session, sdp_close);
+
+	context->cdat->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING);
+	if (!context->cdat->sdp_session) {
+		int errsv = nm_errno_native (errno);
+
+		g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to connect to the SDP server: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		return FALSE;
 	}
 
-	nm_log_dbg (LOGD_BT, "(%s): starting channel number discovery for device %s",
-	            context->src_str, context->dst_str);
+	io_channel = g_io_channel_unix_new (sdp_get_socket (context->cdat->sdp_session));
+	context->cdat->source_id = g_io_add_watch (io_channel,
+	                                           G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+	                                           _connect_sdp_io_cb,
+	                                           context);
+	return TRUE;
+}
 
-	context->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING);
-	if (!context->sdp_session) {
-		GError *error;
-		int err = sdp_get_error (context->sdp_session);
+/*****************************************************************************/
+
+gboolean
+nm_bluez5_dun_connect (const char *adapter,
+                       const char *remote,
+                       GCancellable *cancellable,
+                       NMBluez5DunConnectCb callback,
+                       gpointer callback_user_data,
+                       NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb,
+                       gpointer notify_tty_hangup_user_data,
+                       GError **error)
+{
+	nm_auto_free_context NMBluez5DunContext *context = NULL;
+	ConnectData *cdat;
+	gsize src_l;
+	gsize dst_l;
+
+	g_return_val_if_fail (adapter, FALSE);
+	g_return_val_if_fail (remote, FALSE);
+	g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE);
+	g_return_val_if_fail (callback, FALSE);
+	g_return_val_if_fail (notify_tty_hangup_cb, FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	nm_assert (!g_cancellable_is_cancelled (cancellable));
+
+	src_l = strlen (adapter) + 1;
+	dst_l = strlen (remote) + 1;
+
+	cdat = g_slice_new (ConnectData);
+	*cdat = (ConnectData) {
+		.callback              = callback,
+		.callback_user_data    = callback_user_data,
+		.cancellable           = g_object_ref (cancellable),
+		.sdp_session_try_count = 5,
+	};
 
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to connect to the SDP server: (%d) %s",
-		                      err, nm_strerror_native (err));
-		/* FIXME: don't invoke the callback synchronously. */
-		context->callback (context, NULL, error, context->user_data);
-		return;
+	context = g_malloc (sizeof (NMBluez5DunContext) + src_l + dst_l);
+	*context = (NMBluez5DunContext) {
+		.cdat                        = cdat,
+		.notify_tty_hangup_cb        = notify_tty_hangup_cb,
+		.notify_tty_hangup_user_data = notify_tty_hangup_user_data,
+		.rfcomm_tty_no               = -1,
+		.rfcomm_sock_fd              = -1,
+		.rfcomm_tty_fd               = -1,
+		.rfcomm_channel              = -1,
+	};
+	memcpy (&context->src_str[0], adapter, src_l);
+	context->dst_str = &context->src_str[src_l];
+	memcpy ((char *) context->dst_str, remote, dst_l);
+
+	if (str2ba (adapter, &context->src) < 0) {
+		g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "invalid source");
+		return FALSE;
 	}
 
-	/* FIXME(shutdown): make connect cancellable. */
-	channel = g_io_channel_unix_new (sdp_get_socket (context->sdp_session));
-	context->sdp_watch_id = g_io_add_watch (channel,
-	                                        G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-	                                        sdp_connect_watch,
-	                                        context);
-	g_io_channel_unref (channel);
+	if (str2ba (remote, &context->dst) < 0) {
+		g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "invalid remote");
+		return FALSE;
+	}
+
+	context->cdat->cancelled_id = g_signal_connect (context->cdat->cancellable,
+	                                                "cancelled",
+	                                                G_CALLBACK (_connect_cancelled_cb),
+	                                                context);
+
+	if (!_connect_sdp_session_start (context, error))
+		return FALSE;
+
+	_LOGD (context, "starting channel number discovery for device %s",
+	       context->dst_str);
+
+	g_steal_pointer (&context);
+	return TRUE;
 }
 
-/* Only clean up connection-related stuff to allow reconnect */
+/*****************************************************************************/
+
 void
-nm_bluez5_dun_cleanup (NMBluez5DunContext *context)
+nm_bluez5_dun_disconnect (NMBluez5DunContext *context)
 {
-	g_return_if_fail (context != NULL);
+	nm_assert (context);
+	nm_assert (!context->cdat);
 
-	sdp_search_cleanup (context);
+	_LOGD (context, "disconnecting DUN connection");
 
-	if (context->rfcomm_fd >= 0) {
-		if (context->rfcomm_id >= 0) {
-			struct rfcomm_dev_req req = { 0 };
+	_context_free (context);
+}
 
-			req.dev_id = context->rfcomm_id;
-			(void) ioctl (context->rfcomm_fd, RFCOMMRELEASEDEV, &req);
-			context->rfcomm_id = -1;
-		}
-		nm_close (context->rfcomm_fd);
-		context->rfcomm_fd = -1;
-	}
+/*****************************************************************************/
 
-	nm_close (context->rfcomm_tty_fd);
-	context->rfcomm_tty_fd = -1;
+static void
+_context_cleanup_connect_data (NMBluez5DunContext *context)
+{
+	ConnectData *cdat;
+
+	cdat = g_steal_pointer (&context->cdat);
+	if (!cdat)
+		return;
+
+	nm_clear_g_signal_handler (cdat->cancellable, &cdat->cancelled_id);
+
+	nm_clear_g_source (&cdat->source_id);
+
+	nm_clear_pointer (&cdat->sdp_session, sdp_close);
+
+	g_clear_object (&cdat->cancellable);
+
+	g_clear_error (&cdat->rfcomm_sdp_search_error);
+
+	nm_g_slice_free (cdat);
 }
 
-void
-nm_bluez5_dun_free (NMBluez5DunContext *context)
+static void
+_context_invoke_callback (NMBluez5DunContext *context,
+                          GError *error)
+{
+	NMBluez5DunConnectCb callback;
+	gpointer callback_user_data;
+
+	nm_assert (context);
+	nm_assert (context->cdat);
+	nm_assert (context->cdat->callback);
+	nm_assert (error || context->rfcomm_tty_path);
+
+	if (!error)
+		_LOGD (context, "connected via \"%s\"", context->rfcomm_tty_path);
+	else if (nm_utils_error_is_cancelled (error, FALSE))
+		_LOGD (context, "cancelled");
+	else
+		_LOGD (context, "failed to connect: %s", error->message);
+
+	callback = context->cdat->callback;
+	callback_user_data = context->cdat->callback_user_data;
+
+	_context_cleanup_connect_data (context);
+
+	callback (error ? NULL : context,
+	          error ? NULL : context->rfcomm_tty_path,
+	          error,
+	          callback_user_data);
+}
+
+static void
+_context_invoke_callback_success (NMBluez5DunContext *context)
 {
-	g_return_if_fail (context != NULL);
+	nm_assert (context->rfcomm_tty_path);
+	_context_invoke_callback (context, NULL);
+}
+
+static void
+_context_invoke_callback_fail_and_free (NMBluez5DunContext *context,
+                                        GError *error)
+{
+	nm_assert (error);
+	_context_invoke_callback (context, error);
+	_context_free (context);
+}
+
+static void
+_context_free (NMBluez5DunContext *context)
+{
+	nm_assert (context);
+
+	_context_cleanup_connect_data (context);
+
+	nm_clear_g_source (&context->rfcomm_tty_poll_id);
+
+	if (context->rfcomm_sock_fd >= 0) {
+		if (context->rfcomm_tty_no >= 0) {
+			struct rfcomm_dev_req req;
+
+			memset (&req, 0, sizeof (struct rfcomm_dev_req));
+			req.dev_id = context->rfcomm_tty_no;
+			context->rfcomm_tty_no = -1;
+			(void) ioctl (context->rfcomm_sock_fd, RFCOMMRELEASEDEV, &req);
+		}
+		nm_close (nm_steal_fd (&context->rfcomm_sock_fd));
+	}
 
-	nm_bluez5_dun_cleanup (context);
-	g_clear_pointer (&context->src_str, g_free);
-	g_clear_pointer (&context->dst_str, g_free);
-	g_slice_free (NMBluez5DunContext, context);
+	if (context->rfcomm_tty_fd >= 0)
+		nm_close (nm_steal_fd (&context->rfcomm_tty_fd));
+	nm_clear_g_free (&context->rfcomm_tty_path);
+	g_free (context);
 }
diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/devices/bluetooth/nm-bluez5-dun.h
index b605414b..b3f22965 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.h
+++ b/src/devices/bluetooth/nm-bluez5-dun.h
@@ -1,43 +1,38 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#ifndef _NM_BLUEZ5_UTILS_H_
-#define _NM_BLUEZ5_UTILS_H_
+#ifndef __NM_BLUEZ5_DUN_H__
+#define __NM_BLUEZ5_DUN_H__
 
 typedef struct _NMBluez5DunContext NMBluez5DunContext;
 
-typedef void (*NMBluez5DunFunc) (NMBluez5DunContext *context,
-                                 const char *rfcomm_dev,
-                                 GError *error,
-                                 gpointer user_data);
+#if WITH_BLUEZ5_DUN
 
-NMBluez5DunContext *nm_bluez5_dun_new (const char *adapter,
-                                       const char *remote);
+typedef void (*NMBluez5DunConnectCb) (NMBluez5DunContext *context,
+                                      const char *rfcomm_dev,
+                                      GError *error,
+                                      gpointer user_data);
 
-void nm_bluez5_dun_connect (NMBluez5DunContext *context,
-                            NMBluez5DunFunc callback,
-                            gpointer user_data);
+typedef void (*NMBluez5DunNotifyTtyHangupCb) (NMBluez5DunContext *context,
+                                              gpointer user_data);
 
-/* Clean up connection resources */
-void nm_bluez5_dun_cleanup (NMBluez5DunContext *context);
+gboolean nm_bluez5_dun_connect (const char *adapter,
+                                const char *remote,
+                                GCancellable *cancellable,
+                                NMBluez5DunConnectCb callback,
+                                gpointer callback_user_data,
+                                NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb,
+                                gpointer notify_tty_hangup_user_data,
+                                GError **error);
 
-/* Clean up and dispose all resources */
-void nm_bluez5_dun_free (NMBluez5DunContext *context);
+void nm_bluez5_dun_disconnect (NMBluez5DunContext *context);
 
-#endif  /* _NM_BLUEZ5_UTILS_H_ */
+const char *nm_bluez5_dun_context_get_adapter (const NMBluez5DunContext *context);
+const char *nm_bluez5_dun_context_get_remote (const NMBluez5DunContext *context);
+const char *nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context);
+
+#endif /* WITH_BLUEZ5_DUN */
+
+#endif  /* __NM_BLUEZ5_DUN_H__ */
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
deleted file mode 100644
index 9c5e8644..00000000
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ /dev/null
@@ -1,596 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2017 Red Hat, Inc.
- * Copyright (C) 2013 Intel Corporation.
- */
-
-#include "nm-default.h"
-
-#include "nm-bluez5-manager.h"
-
-#include <signal.h>
-#include <stdlib.h>
-
-#include "nm-core-internal.h"
-
-#include "c-list/src/c-list.h"
-#include "nm-bluez-device.h"
-#include "nm-bluez-common.h"
-#include "devices/nm-device-bridge.h"
-#include "settings/nm-settings.h"
-
-/*****************************************************************************/
-
-enum {
-	BDADDR_ADDED,
-	NETWORK_SERVER_ADDED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	NMSettings *settings;
-
-	GDBusProxy *proxy;
-
-	GHashTable *devices;
-
-	CList network_servers;
-} NMBluez5ManagerPrivate;
-
-struct _NMBluez5Manager {
-	GObject parent;
-	NMBtVTableNetworkServer network_server_vtable;
-	NMBluez5ManagerPrivate _priv;
-};
-
-struct _NMBluez5ManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMBluez5Manager, nm_bluez5_manager, G_TYPE_OBJECT)
-
-#define NM_BLUEZ5_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez5Manager, NM_IS_BLUEZ5_MANAGER)
-
-#define NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE(self) (&(self)->network_server_vtable)
-#define NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER(vtable) \
-	NM_BLUEZ5_MANAGER(((char *)(vtable)) - offsetof (struct _NMBluez5Manager, network_server_vtable))
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN LOGD_BT
-#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez5", __VA_ARGS__)
-
-/*****************************************************************************/
-
-static void device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self);
-static void device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self);
-
-/*****************************************************************************/
-
-typedef struct {
-	char *path;
-	char *addr;
-	NMDevice *device;
-	CList lst_ns;
-} NetworkServer;
-
-static NetworkServer *
-_find_network_server (NMBluez5Manager *self, const char *path, NMDevice *device)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NetworkServer *network_server;
-
-	nm_assert (path || NM_IS_DEVICE (device));
-
-	c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) {
-		if (path && !nm_streq (network_server->path, path))
-			continue;
-		if (device && network_server->device != device)
-			continue;
-		return network_server;
-	}
-	return NULL;
-}
-
-static NetworkServer *
-_find_network_server_for_addr (NMBluez5Manager *self, const char *addr)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NetworkServer *network_server;
-
-	c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) {
-		/* The address lookups need a server not assigned to a device
-		 * and tolerate an empty address as a wildcard for "any". */
-		if (   !network_server->device
-		    && (!addr || nm_streq (network_server->addr, addr)))
-			return network_server;
-	}
-	return NULL;
-}
-
-static void
-_network_server_unregister (NMBluez5Manager *self, NetworkServer *network_server)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	if (!network_server->device) {
-		/* Not connected. */
-		return;
-	}
-
-	_LOGI ("NAP: unregistering %s from %s",
-	       nm_device_get_iface (network_server->device),
-	       network_server->addr);
-
-	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
-	                        NM_BLUEZ_SERVICE,
-	                        network_server->path,
-	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
-	                        "Unregister",
-	                        g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP),
-	                        NULL,
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        -1, NULL, NULL, NULL);
-
-	g_clear_object (&network_server->device);
-}
-
-static void
-_network_server_free (NMBluez5Manager *self, NetworkServer *network_server)
-{
-	_network_server_unregister (self, network_server);
-	c_list_unlink_stale (&network_server->lst_ns);
-	g_free (network_server->path);
-	g_free (network_server->addr);
-	g_slice_free (NetworkServer, network_server);
-}
-
-static gboolean
-network_server_is_available (const NMBtVTableNetworkServer *vtable,
-                             const char *addr)
-{
-	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
-
-	return !!_find_network_server_for_addr (self, addr);
-}
-
-static gboolean
-network_server_register_bridge (const NMBtVTableNetworkServer *vtable,
-                                const char *addr,
-                                NMDevice *device)
-{
-	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NetworkServer *network_server = _find_network_server_for_addr (self, addr);
-
-	nm_assert (NM_IS_DEVICE (device));
-	nm_assert (!_find_network_server (self, NULL, device));
-
-	if (!network_server) {
-		/* The device checked that a network server is available, before
-		 * starting the activation, but for some reason it no longer is.
-		 * Indicate that the activation should not proceed. */
-		_LOGI ("NAP: %s is not available for %s", addr, nm_device_get_iface (device));
-		return FALSE;
-	}
-
-	_LOGI ("NAP: registering %s on %s", nm_device_get_iface (device), network_server->addr);
-
-	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
-	                        NM_BLUEZ_SERVICE,
-	                        network_server->path,
-	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
-	                        "Register",
-	                        g_variant_new ("(ss)", BLUETOOTH_CONNECT_NAP, nm_device_get_iface (device)),
-	                        NULL,
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        -1, NULL, NULL, NULL);
-
-	network_server->device = g_object_ref (device);
-
-	return TRUE;
-}
-
-static gboolean
-network_server_unregister_bridge (const NMBtVTableNetworkServer *vtable,
-                                  NMDevice *device)
-{
-	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
-	NetworkServer *network_server = _find_network_server (self, NULL, device);
-
-	if (network_server)
-		_network_server_unregister (self, network_server);
-
-	return TRUE;
-}
-
-static void
-network_server_removed (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
-{
-	NetworkServer *network_server;
-
-	network_server = _find_network_server (self, path, NULL);
-	if (!network_server)
-		return;
-
-	if (network_server->device) {
-		nm_device_queue_state (network_server->device, NM_DEVICE_STATE_DISCONNECTED,
-		                       NM_DEVICE_STATE_REASON_BT_FAILED);
-	}
-	_LOGI ("NAP: removed interface %s", network_server->addr);
-	_network_server_free (self, network_server);
-}
-
-static void
-network_server_added (GDBusProxy *proxy, const char *path, const char *addr, NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NetworkServer *network_server;
-
-	/* If BlueZ messes up and announces a single network server twice,
-	 * make sure we get rid of the older instance first. */
-	network_server_removed (proxy, path, self);
-
-	network_server = g_slice_new0 (NetworkServer);
-	network_server->path = g_strdup (path);
-	network_server->addr = g_strdup (addr);
-	c_list_link_before (&priv->network_servers, &network_server->lst_ns);
-
-	_LOGI ("NAP: added interface %s", addr);
-
-	g_signal_emit (self, signals[NETWORK_SERVER_ADDED], 0);
-}
-
-/*****************************************************************************/
-
-static void
-emit_bdaddr_added (NMBluez5Manager *self, NMBluezDevice *device)
-{
-	g_signal_emit (self, signals[BDADDR_ADDED], 0,
-	               device,
-	               nm_bluez_device_get_address (device),
-	               nm_bluez_device_get_name (device),
-	               nm_bluez_device_get_path (device),
-	               nm_bluez_device_get_capabilities (device));
-}
-
-void
-nm_bluez5_manager_query_devices (NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-	GHashTableIter iter;
-
-	g_hash_table_iter_init (&iter, priv->devices);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) {
-		if (nm_bluez_device_get_usable (device))
-			emit_bdaddr_added (self, device);
-	}
-}
-
-static void
-remove_device (NMBluez5Manager *self, NMBluezDevice *device)
-{
-	g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_initialized), self);
-	g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_usable), self);
-	if (nm_bluez_device_get_usable (device))
-		g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED);
-}
-
-static void
-remove_all_devices (NMBluez5Manager *self)
-{
-	GHashTableIter iter;
-	NMBluezDevice *device;
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	g_hash_table_iter_init (&iter, priv->devices);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) {
-		g_hash_table_iter_steal (&iter);
-		remove_device (self, device);
-		g_object_unref (device);
-	}
-}
-
-static void
-device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self)
-{
-	gboolean usable = nm_bluez_device_get_usable (device);
-
-	_LOGD ("(%s): bluez device now %s",
-	       nm_bluez_device_get_path (device),
-	       usable ? "usable" : "unusable");
-
-	if (usable) {
-		_LOGD ("(%s): bluez device address %s",
-		       nm_bluez_device_get_path (device),
-		       nm_bluez_device_get_address (device));
-		emit_bdaddr_added (self, device);
-	} else
-		g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED);
-}
-
-static void
-device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	_LOGD ("(%s): bluez device %s",
-	       nm_bluez_device_get_path (device),
-	       success ? "initialized" : "failed to initialize");
-	if (!success)
-		g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device));
-}
-
-static void
-device_added (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	device = nm_bluez_device_new (path, NULL, priv->settings, 5);
-	g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self);
-	g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self);
-	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
-
-	_LOGD ("(%s): new bluez device found", path);
-}
-
-static void
-device_removed (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	_LOGD ("(%s): bluez device removed", path);
-
-	device = g_hash_table_lookup (priv->devices, path);
-	if (device) {
-		g_hash_table_steal (priv->devices, nm_bluez_device_get_path (device));
-		remove_device (NM_BLUEZ5_MANAGER (self), device);
-		g_object_unref (device);
-	}
-}
-
-static void
-object_manager_interfaces_added (GDBusProxy      *proxy,
-                                 const char      *path,
-                                 GVariant        *dict,
-                                 NMBluez5Manager *self)
-{
-	if (g_variant_lookup (dict, NM_BLUEZ5_DEVICE_INTERFACE, "a{sv}", NULL))
-		device_added (proxy, path, self);
-	if (g_variant_lookup (dict, NM_BLUEZ5_NETWORK_SERVER_INTERFACE, "a{sv}", NULL)) {
-		gs_unref_variant GVariant *adapter = g_variant_lookup_value (dict, NM_BLUEZ5_ADAPTER_INTERFACE, G_VARIANT_TYPE_DICTIONARY);
-		const char *address;
-
-		if (   adapter
-		    && g_variant_lookup (adapter, "Address", "&s", &address))
-			network_server_added (proxy, path, address, self);
-	}
-}
-
-static void
-object_manager_interfaces_removed (GDBusProxy       *proxy,
-                                   const char       *path,
-                                   const char      **ifaces,
-                                   NMBluez5Manager  *self)
-{
-	if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_DEVICE_INTERFACE))
-		device_removed (proxy, path, self);
-	if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_NETWORK_SERVER_INTERFACE))
-		network_server_removed (proxy, path, self);
-}
-
-static void
-get_managed_objects_cb (GDBusProxy *proxy,
-                        GAsyncResult *res,
-                        NMBluez5Manager *self)
-{
-	gs_unref_variant GVariant *variant0 = NULL;
-	GVariant *variant, *ifaces;
-	GVariantIter i;
-	GError *error = NULL;
-	const char *path;
-
-	variant = _nm_dbus_proxy_call_finish (proxy, res,
-	                                      G_VARIANT_TYPE ("(a{oa{sa{sv}}})"),
-	                                      &error);
-	if (!variant) {
-		if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
-			_LOGW ("Couldn't get managed objects: not running Bluez5?");
-		else {
-			g_dbus_error_strip_remote_error (error);
-			_LOGW ("Couldn't get managed objects: %s", error->message);
-		}
-		g_clear_error (&error);
-		return;
-	}
-	variant0 = g_variant_get_child_value (variant, 0);
-	g_variant_iter_init (&i, variant0);
-	while ((g_variant_iter_next (&i, "{&o*}", &path, &ifaces))) {
-		object_manager_interfaces_added (proxy, path, ifaces, self);
-		g_variant_unref (ifaces);
-	}
-
-	g_variant_unref (variant);
-}
-
-static void name_owner_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data);
-
-static void
-on_proxy_acquired (GObject *object,
-                   GAsyncResult *res,
-                   NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
-
-	if (!priv->proxy) {
-		_LOGW ("Couldn't acquire object manager proxy: %s", error->message);
-		g_clear_error (&error);
-		return;
-	}
-
-	g_signal_connect (priv->proxy, "notify::g-name-owner",
-	                  G_CALLBACK (name_owner_changed_cb), self);
-
-	/* Get already managed devices. */
-	g_dbus_proxy_call (priv->proxy, "GetManagedObjects",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   NULL,
-	                   (GAsyncReadyCallback) get_managed_objects_cb,
-	                   self);
-
-	_nm_dbus_signal_connect (priv->proxy, "InterfacesAdded", G_VARIANT_TYPE ("(oa{sa{sv}})"),
-	                         G_CALLBACK (object_manager_interfaces_added), self);
-	_nm_dbus_signal_connect (priv->proxy, "InterfacesRemoved", G_VARIANT_TYPE ("(oas)"),
-	                         G_CALLBACK (object_manager_interfaces_removed), self);
-}
-
-static void
-bluez_connect (NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	g_return_if_fail (priv->proxy == NULL);
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_NONE,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          NM_BLUEZ_MANAGER_PATH,
-	                          NM_OBJECT_MANAGER_INTERFACE,
-	                          NULL,
-	                          (GAsyncReadyCallback) on_proxy_acquired,
-	                          self);
-}
-
-static void
-name_owner_changed_cb (GObject *object,
-                       GParamSpec *pspec,
-                       gpointer user_data)
-{
-	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (user_data);
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	char *owner;
-
-	if (priv->devices) {
-		owner = g_dbus_proxy_get_name_owner (priv->proxy);
-		if (!owner)
-			remove_all_devices (self);
-		g_free (owner);
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_bluez5_manager_init (NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NMBtVTableNetworkServer *network_server_vtable = NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE (self);
-
-	bluez_connect (self);
-
-	priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                       NULL, g_object_unref);
-
-	c_list_init (&priv->network_servers);
-
-	nm_assert (!nm_bt_vtable_network_server);
-	network_server_vtable->is_available = network_server_is_available;
-	network_server_vtable->register_bridge = network_server_register_bridge;
-	network_server_vtable->unregister_bridge = network_server_unregister_bridge;
-	nm_bt_vtable_network_server = network_server_vtable;
-}
-
-NMBluez5Manager *
-nm_bluez5_manager_new (NMSettings *settings)
-{
-	NMBluez5Manager *instance = NULL;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
-
-	instance = g_object_new (NM_TYPE_BLUEZ5_MANAGER, NULL);
-	NM_BLUEZ5_MANAGER_GET_PRIVATE (instance)->settings = g_object_ref (settings);
-	return instance;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object);
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	CList *iter, *safe;
-
-	c_list_for_each_safe (iter, safe, &priv->network_servers)
-		_network_server_free (self, c_list_entry (iter, NetworkServer, lst_ns));
-
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_func (priv->proxy, G_CALLBACK (name_owner_changed_cb), self);
-		g_clear_object (&priv->proxy);
-	}
-
-	g_hash_table_remove_all (priv->devices);
-
-	G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->dispose (object);
-}
-
-static void
-finalize (GObject *object)
-{
-	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object);
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	g_hash_table_destroy (priv->devices);
-
-	G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->finalize (object);
-
-	g_object_unref (priv->settings);
-}
-
-static void
-nm_bluez5_manager_class_init (NMBluez5ManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->dispose = dispose;
-	object_class->finalize = finalize;
-
-	signals[BDADDR_ADDED] =
-	    g_signal_new (NM_BLUEZ_MANAGER_BDADDR_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING,
-	                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
-
-	signals[NETWORK_SERVER_ADDED] =
-	    g_signal_new (NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 0);
-}
diff --git a/src/devices/bluetooth/nm-bluez5-manager.h b/src/devices/bluetooth/nm-bluez5-manager.h
deleted file mode 100644
index 14ac842e..00000000
--- a/src/devices/bluetooth/nm-bluez5-manager.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2013 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_BLUEZ5_MANAGER_H__
-#define __NETWORKMANAGER_BLUEZ5_MANAGER_H__
-
-#define NM_TYPE_BLUEZ5_MANAGER            (nm_bluez5_manager_get_type ())
-#define NM_BLUEZ5_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ5_MANAGER, NMBluez5Manager))
-#define NM_BLUEZ5_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerClass))
-#define NM_IS_BLUEZ5_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ5_MANAGER))
-#define NM_IS_BLUEZ5_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ5_MANAGER))
-#define NM_BLUEZ5_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerClass))
-
-typedef struct _NMBluez5Manager NMBluez5Manager;
-typedef struct _NMBluez5ManagerClass NMBluez5ManagerClass;
-
-GType nm_bluez5_manager_get_type (void);
-
-NMBluez5Manager *nm_bluez5_manager_new (NMSettings *settings);
-
-void nm_bluez5_manager_query_devices (NMBluez5Manager *manager);
-
-#endif /* __NETWORKMANAGER_BLUEZ5_MANAGER_H__ */
diff --git a/src/devices/bluetooth/nm-bt-error.c b/src/devices/bluetooth/nm-bt-error.c
index bc9e5aa4..8aa4283d 100644
--- a/src/devices/bluetooth/nm-bt-error.c
+++ b/src/devices/bluetooth/nm-bt-error.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
diff --git a/src/devices/bluetooth/nm-bt-error.h b/src/devices/bluetooth/nm-bt-error.h
index ed7ed90d..258fca8d 100644
--- a/src/devices/bluetooth/nm-bt-error.h
+++ b/src/devices/bluetooth/nm-bt-error.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index f8626011..497810c3 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 - 2011 Red Hat, Inc.
  */
 
@@ -23,8 +9,9 @@
 
 #include <stdio.h>
 
+#include "nm-core-internal.h"
 #include "nm-bluez-common.h"
-#include "nm-bluez-device.h"
+#include "nm-bluez-manager.h"
 #include "devices/nm-device-private.h"
 #include "ppp/nm-ppp-manager.h"
 #include "nm-setting-connection.h"
@@ -48,10 +35,12 @@ _LOG_DECLARE_SELF(NMDeviceBt);
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_BT_NAME,
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceBt,
+	PROP_BT_BDADDR,
+	PROP_BT_BZ_MGR,
 	PROP_BT_CAPABILITIES,
-	PROP_BT_DEVICE,
+	PROP_BT_DBUS_PATH,
+	PROP_BT_NAME,
 );
 
 enum {
@@ -64,24 +53,38 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	NMModemManager *modem_manager;
 
-	gboolean mm_running;
+	NMBluezManager *bz_mgr;
 
-	NMBluezDevice *bt_device;
+	char *dbus_path;
 
 	char *bdaddr;
 	char *name;
-	guint32 capabilities;
 
-	gboolean connected;
-	gboolean have_iface;
+	char *connect_rfcomm_iface;
+
+	GSList *connect_modem_candidates;
 
-	char *rfcomm_iface;
 	NMModem *modem;
-	guint timeout_id;
 
-	GCancellable *cancellable;
+	GCancellable *connect_bz_cancellable;
+
+	gulong connect_watch_link_id;
+
+	guint connect_watch_link_idle_id;
+
+	guint connect_wait_modem_id;
+
+	NMBluetoothCapabilities capabilities:6;
+
+	NMBluetoothCapabilities connect_bt_type:6;  /* BT type of the current connection */
+
+	NMDeviceStageState stage1_bt_state:3;
+	NMDeviceStageState stage1_modem_prepare_state:3;
+
+	bool is_connected:1;
+
+	bool mm_running:1;
 
-	guint32 bt_type;  /* BT type of the current connection */
 } NMDeviceBtPrivate;
 
 struct _NMDeviceBt {
@@ -95,42 +98,65 @@ struct _NMDeviceBtClass {
 
 G_DEFINE_TYPE (NMDeviceBt, nm_device_bt, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_BT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceBt, NM_IS_DEVICE_BT)
-
-/*****************************************************************************/
-
-static gboolean modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *out_failure_reason);
+#define NM_DEVICE_BT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceBt, NM_IS_DEVICE_BT, NMDevice)
 
 /*****************************************************************************/
 
-guint32 nm_device_bt_get_capabilities (NMDeviceBt *self)
+NMBluetoothCapabilities nm_device_bt_get_capabilities (NMDeviceBt *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE_BT (self), NM_BT_CAPABILITY_NONE);
 
 	return NM_DEVICE_BT_GET_PRIVATE (self)->capabilities;
 }
 
-static guint32
+static NMBluetoothCapabilities
 get_connection_bt_type (NMConnection *connection)
 {
 	NMSettingBluetooth *s_bt;
 	const char *bt_type;
 
 	s_bt = nm_connection_get_setting_bluetooth (connection);
-	if (!s_bt)
-		return NM_BT_CAPABILITY_NONE;
 
-	bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
-	g_assert (bt_type);
-
-	if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN))
-		return NM_BT_CAPABILITY_DUN;
-	else if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU))
-		return NM_BT_CAPABILITY_NAP;
+	if (s_bt) {
+		bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
+		if (bt_type) {
+			if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN))
+				return NM_BT_CAPABILITY_DUN;
+			else if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU))
+				return NM_BT_CAPABILITY_NAP;
+		}
+	}
 
 	return NM_BT_CAPABILITY_NONE;
 }
 
+static gboolean
+get_connection_bt_type_check (NMDeviceBt *self,
+                              NMConnection *connection,
+                              NMBluetoothCapabilities *out_bt_type,
+                              GError **error)
+{
+	NMBluetoothCapabilities bt_type;
+
+	bt_type = get_connection_bt_type (connection);
+
+	NM_SET_OUT (out_bt_type, bt_type);
+
+	if (bt_type == NM_BT_CAPABILITY_NONE) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "profile is not a PANU/DUN bluetooth type");
+		return FALSE;
+	}
+
+	if (!NM_FLAGS_ALL (NM_DEVICE_BT_GET_PRIVATE (self)->capabilities, bt_type)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "device does not support bluetooth type");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 static NMDeviceCapabilities
 get_generic_capabilities (NMDevice *device)
 {
@@ -142,17 +168,24 @@ can_auto_connect (NMDevice *device,
                   NMSettingsConnection *sett_conn,
                   char **specific_object)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
-	guint32 bt_type;
+	NMDeviceBt *self = NM_DEVICE_BT (device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	NMBluetoothCapabilities bt_type;
 
 	nm_assert (!specific_object || !*specific_object);
 
 	if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->can_auto_connect (device, sett_conn, NULL))
 		return FALSE;
 
+	if (!get_connection_bt_type_check (self,
+	                                   nm_settings_connection_get_connection (sett_conn),
+	                                   &bt_type,
+	                                   NULL))
+		return FALSE;
+
 	/* Can't auto-activate a DUN connection without ModemManager */
-	bt_type = get_connection_bt_type (nm_settings_connection_get_connection (sett_conn));
-	if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE)
+	if (   bt_type == NM_BT_CAPABILITY_DUN
+	    && priv->mm_running == FALSE)
 		return FALSE;
 
 	return TRUE;
@@ -161,20 +194,16 @@ can_auto_connect (NMDevice *device,
 static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBt *self = NM_DEVICE_BT (device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 	NMSettingBluetooth *s_bt;
 	const char *bdaddr;
-	guint32 bt_type;
 
 	if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
-	bt_type = get_connection_bt_type (connection);
-	if (!NM_FLAGS_ALL (priv->capabilities, bt_type)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "device does not support bluetooth type of profile");
+	if (!get_connection_bt_type_check (self, connection, NULL, error))
 		return FALSE;
-	}
 
 	s_bt = nm_connection_get_setting_bluetooth (connection);
 
@@ -200,18 +229,15 @@ check_connection_available (NMDevice *device,
                             const char *specific_object,
                             GError **error)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
-	guint32 bt_type;
+	NMDeviceBt *self = NM_DEVICE_BT (device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	NMBluetoothCapabilities bt_type;
 
-	bt_type = get_connection_bt_type (connection);
-	if (!(bt_type & priv->capabilities)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "device does not support bluetooth type");
+	if (!get_connection_bt_type_check (self, connection, &bt_type, error))
 		return FALSE;
-	}
 
-	/* DUN connections aren't available without ModemManager */
-	if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE) {
+	if (   bt_type == NM_BT_CAPABILITY_DUN
+	    && !priv->mm_running) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 		                            "ModemManager missing for DUN profile");
 		return FALSE;
@@ -227,11 +253,12 @@ complete_connection (NMDevice *device,
                      NMConnection *const*existing_connections,
                      GError **error)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
 	NMSettingBluetooth *s_bt;
 	const char *setting_bdaddr;
 	const char *ctype;
-	gboolean is_dun = FALSE, is_pan = FALSE;
+	gboolean is_dun = FALSE;
+	gboolean is_pan = FALSE;
 	NMSettingGsm *s_gsm;
 	NMSettingCdma *s_cdma;
 	NMSettingSerial *s_serial;
@@ -274,7 +301,10 @@ complete_connection (NMDevice *device,
 		}
 
 		/* PAN can't use any DUN-related settings */
-		if (s_gsm || s_cdma || s_serial || s_ppp) {
+		if (   s_gsm
+		    || s_cdma
+		    || s_serial
+		    || s_ppp) {
 			g_set_error_literal (error,
 			                     NM_CONNECTION_ERROR,
 			                     NM_CONNECTION_ERROR_INVALID_SETTING,
@@ -304,7 +334,8 @@ complete_connection (NMDevice *device,
 		}
 
 		/* Need at least a GSM or a CDMA setting */
-		if (!s_gsm && !s_cdma) {
+		if (   !s_gsm
+		    && !s_cdma) {
 			g_set_error_literal (error,
 			                     NM_CONNECTION_ERROR,
 			                     NM_CONNECTION_ERROR_INVALID_SETTING,
@@ -441,20 +472,19 @@ static void
 modem_auth_result (NMModem *modem, GError *error, gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
+
+	g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
 
 	if (error) {
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else {
-		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-
-		/* Otherwise, on success for GSM/CDMA secrets we need to schedule modem stage1 again */
-		g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
-		if (!modem_stage1 (NM_DEVICE_BT (device), priv->modem, &failure_reason))
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, failure_reason);
+		return;
 	}
+
+	priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT;
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
@@ -463,45 +493,33 @@ modem_prepare_result (NMModem *modem,
                       guint i_reason,
                       gpointer user_data)
 {
-	NMDeviceBt *self = NM_DEVICE_BT (user_data);
-	NMDevice *device = NM_DEVICE (self);
+	NMDeviceBt *self = user_data;
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 	NMDeviceStateReason reason = i_reason;
 	NMDeviceState state;
 
-	state = nm_device_get_state (device);
-	g_return_if_fail (state == NM_DEVICE_STATE_CONFIG || state == NM_DEVICE_STATE_NEED_AUTH);
-
-	if (success) {
-		NMActRequest *req;
-		NMActStageReturn ret;
-		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-
-		req = nm_device_get_act_request (device);
-		g_return_if_fail (req);
-
-		ret = nm_modem_act_stage2_config (modem, req, &failure_reason);
-		switch (ret) {
-		case NM_ACT_STAGE_RETURN_POSTPONE:
-			break;
-		case NM_ACT_STAGE_RETURN_SUCCESS:
-			nm_device_activate_schedule_stage3_ip_config_start (device);
-			break;
-		case NM_ACT_STAGE_RETURN_FAILURE:
-		default:
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, failure_reason);
-			break;
-		}
-	} else {
+	state = nm_device_get_state (NM_DEVICE (self));
+
+	g_return_if_fail (NM_IN_SET (state, NM_DEVICE_STATE_PREPARE,
+	                                    NM_DEVICE_STATE_NEED_AUTH));
+
+	nm_assert (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_PENDING);
+
+	if (!success) {
 		if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT) {
 			/* If the connect failed because the SIM PIN was wrong don't allow
 			 * the device to be auto-activated anymore, which would risk locking
 			 * the SIM if the incorrect PIN continues to be used.
 			 */
-			nm_device_autoconnect_blocked_set (device, NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN);
+			nm_device_autoconnect_blocked_set (NM_DEVICE (self), NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN);
 		}
 
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
+		nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, reason);
+		return;
 	}
+
+	priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
 }
 
 static void
@@ -510,7 +528,7 @@ device_state_changed (NMDevice *device,
                       NMDeviceState old_state,
                       NMDeviceStateReason reason)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
 
 	if (priv->modem)
 		nm_modem_device_state_changed (priv->modem, new_state, old_state);
@@ -519,7 +537,8 @@ device_state_changed (NMDevice *device,
 	 * since the device could be both DUN and NAP capable and thus may not
 	 * change state (which rechecks available connections) when MM comes and goes.
 	 */
-	if (priv->mm_running && (priv->capabilities & NM_BT_CAPABILITY_DUN))
+	if (   priv->mm_running
+	    && NM_FLAGS_HAS (priv->capabilities, NM_BT_CAPABILITY_DUN))
 	    nm_device_recheck_available_connections (device);
 }
 
@@ -541,8 +560,10 @@ modem_ip4_config_result (NMModem *modem,
 		nm_device_ip_method_failed (device,
 		                            AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-	} else
-		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
+		return;
+	}
+
+	nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static void
@@ -561,29 +582,6 @@ ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 	}
 }
 
-static gboolean
-modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *out_failure_reason)
-{
-	NMActRequest *req;
-	NMActStageReturn ret;
-
-	req = nm_device_get_act_request (NM_DEVICE (self));
-	g_return_val_if_fail (req, FALSE);
-
-	ret = nm_modem_act_stage1_prepare (modem, req, out_failure_reason);
-	switch (ret) {
-	case NM_ACT_STAGE_RETURN_POSTPONE:
-	case NM_ACT_STAGE_RETURN_SUCCESS:
-		/* Success, wait for the 'prepare-result' signal */
-		return TRUE;
-	case NM_ACT_STAGE_RETURN_FAILURE:
-	default:
-		break;
-	}
-
-	return FALSE;
-}
-
 /*****************************************************************************/
 
 static void
@@ -593,7 +591,7 @@ modem_cleanup (NMDeviceBt *self)
 
 	if (priv->modem) {
 		g_signal_handlers_disconnect_matched (priv->modem, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
-		g_clear_object (&priv->modem);
+		nm_clear_pointer (&priv->modem, nm_modem_unclaim);
 	}
 }
 
@@ -608,11 +606,13 @@ modem_state_cb (NMModem *modem,
 	NMDevice *device = NM_DEVICE (user_data);
 	NMDeviceState dev_state = nm_device_get_state (device);
 
-	if (new_state <= NM_MODEM_STATE_DISABLING && old_state > NM_MODEM_STATE_DISABLING) {
+	if (   new_state <= NM_MODEM_STATE_DISABLING
+	    && old_state > NM_MODEM_STATE_DISABLING) {
 		/* Will be called whenever something external to NM disables the
 		 * modem directly through ModemManager.
 		 */
-		if (nm_device_is_activating (device) || dev_state == NM_DEVICE_STATE_ACTIVATED) {
+		if (   nm_device_is_activating (device)
+		    || dev_state == NM_DEVICE_STATE_ACTIVATED) {
 			nm_device_state_changed (device,
 			                         NM_DEVICE_STATE_DISCONNECTED,
 			                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
@@ -620,13 +620,15 @@ modem_state_cb (NMModem *modem,
 		}
 	}
 
-	if (new_state < NM_MODEM_STATE_CONNECTING &&
-	    old_state >= NM_MODEM_STATE_CONNECTING &&
-	    dev_state >= NM_DEVICE_STATE_NEED_AUTH &&
-	    dev_state <= NM_DEVICE_STATE_ACTIVATED) {
+	if (   new_state < NM_MODEM_STATE_CONNECTING
+	    && old_state >= NM_MODEM_STATE_CONNECTING
+	    && dev_state >= NM_DEVICE_STATE_NEED_AUTH
+	    && dev_state <= NM_DEVICE_STATE_ACTIVATED) {
 		/* Fail the device if the modem disconnects unexpectedly while the
 		 * device is activating/activated. */
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
 		return;
 	}
 }
@@ -637,66 +639,56 @@ modem_removed_cb (NMModem *modem, gpointer user_data)
 	NMDeviceBt *self = NM_DEVICE_BT (user_data);
 	NMDeviceState state;
 
-	/* Fail the device if the modem was removed while active */
 	state = nm_device_get_state (NM_DEVICE (self));
-	if (   state == NM_DEVICE_STATE_ACTIVATED
-	    || nm_device_is_activating (NM_DEVICE (self))) {
+	if (   nm_device_is_activating (NM_DEVICE (self))
+	    || state == NM_DEVICE_STATE_ACTIVATED) {
 		nm_device_state_changed (NM_DEVICE (self),
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_BT_FAILED);
-	} else
-		modem_cleanup (self);
+		return;
+	}
+
+	modem_cleanup (self);
 }
 
 static gboolean
-component_added (NMDevice *device, GObject *component)
+modem_try_claim (NMDeviceBt *self,
+                 NMModem *modem)
 {
-	NMDeviceBt *self = NM_DEVICE_BT (device);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	NMModem *modem;
+	gs_free char *rfcomm_base_name = NULL;
 	NMDeviceState state;
-	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	if (   !component
-	    || !NM_IS_MODEM (component))
+	if (priv->modem) {
+		if (priv->modem == modem)
+			return TRUE;
 		return FALSE;
+	}
 
-	modem = NM_MODEM (component);
-	if (!priv->rfcomm_iface)
+	if (nm_modem_is_claimed (modem))
 		return FALSE;
 
-	{
-		gs_free char *base = NULL;
-
-		base = g_path_get_basename (priv->rfcomm_iface);
-		if (!nm_streq (base, nm_modem_get_control_port (modem)))
-			return FALSE;
-	}
+	if (!priv->connect_rfcomm_iface)
+		return FALSE;
 
-	/* Got the modem */
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	rfcomm_base_name = g_path_get_basename (priv->connect_rfcomm_iface);
+	if (!nm_streq0 (rfcomm_base_name, nm_modem_get_control_port (modem)))
+		return FALSE;
 
-	/* Can only accept the modem in stage2, but since the interface matched
+	/* Can only accept the modem in stage1, but since the interface matched
 	 * what we were expecting, don't let anything else claim the modem either.
 	 */
 	state = nm_device_get_state (NM_DEVICE (self));
-	if (state != NM_DEVICE_STATE_CONFIG) {
-		_LOGW (LOGD_BT | LOGD_MB,
+	if (state != NM_DEVICE_STATE_PREPARE) {
+		_LOGD (LOGD_BT | LOGD_MB,
 		       "modem found but device not in correct state (%d)",
 		       nm_device_get_state (NM_DEVICE (self)));
-		return TRUE;
+		return FALSE;
 	}
 
-	_LOGI (LOGD_BT | LOGD_MB,
-	       "Activation: (bluetooth) Stage 2 of 5 (Device Configure) modem found.");
+	priv->modem = nm_modem_claim (modem);
+	priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT;
 
-	if (priv->modem) {
-		g_warn_if_reached ();
-		modem_cleanup (self);
-	}
-
-	priv->modem = g_object_ref (modem);
 	g_signal_connect (modem, NM_MODEM_PPP_STATS, G_CALLBACK (ppp_stats), self);
 	g_signal_connect (modem, NM_MODEM_PPP_FAILED, G_CALLBACK (ppp_failed), self);
 	g_signal_connect (modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK (modem_prepare_result), self);
@@ -705,92 +697,187 @@ component_added (NMDevice *device, GObject *component)
 	g_signal_connect (modem, NM_MODEM_AUTH_RESULT, G_CALLBACK (modem_auth_result), self);
 	g_signal_connect (modem, NM_MODEM_STATE_CHANGED, G_CALLBACK (modem_state_cb), self);
 	g_signal_connect (modem, NM_MODEM_REMOVED, G_CALLBACK (modem_removed_cb), self);
-
 	g_signal_connect (modem, "notify::" NM_MODEM_IP_IFINDEX, G_CALLBACK (ip_ifindex_changed_cb), self);
 
-	/* Kick off the modem connection */
-	if (!modem_stage1 (self, modem, &failure_reason))
-		nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, failure_reason);
+	_LOGD (LOGD_BT | LOGD_MB,
+	       "modem found");
 
 	return TRUE;
 }
 
-static gboolean
-modem_find_timeout (gpointer user_data)
+static void
+mm_modem_added_cb (NMModemManager *manager,
+                   NMModem *modem,
+                   gpointer user_data)
+{
+	NMDeviceBt *self = user_data;
+	NMDeviceBtPrivate *priv;
+
+	if (!modem_try_claim (user_data, modem))
+		return;
+
+	priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED)
+		nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+}
+
+/*****************************************************************************/
+
+void
+_nm_device_bt_notify_set_connected (NMDeviceBt *self,
+                                    gboolean connected)
 {
-	NMDeviceBt *self = NM_DEVICE_BT (user_data);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
-	priv->timeout_id = 0;
-	nm_clear_g_cancellable (&priv->cancellable);
+	connected = !!connected;
+	if (priv->is_connected == connected)
+		return;
+
+	priv->is_connected = connected;
 
+	if (   connected
+	    || priv->stage1_bt_state != NM_DEVICE_STAGE_STATE_COMPLETED
+	    || nm_device_get_state (NM_DEVICE (self)) > NM_DEVICE_STATE_ACTIVATED) {
+		_LOGT (LOGD_BT, "set-connected: %d", connected);
+		return;
+	}
+
+	_LOGT (LOGD_BT, "set-connected: %d (disconnecting device...)", connected);
 	nm_device_state_changed (NM_DEVICE (self),
 	                         NM_DEVICE_STATE_FAILED,
-	                         NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND);
-	return FALSE;
+	                         NM_DEVICE_STATE_REASON_CARRIER);
 }
 
-static void
-check_connect_continue (NMDeviceBt *self)
+static gboolean
+connect_watch_link_idle_cb (gpointer user_data)
 {
-	NMDevice *device = NM_DEVICE (self);
+	NMDeviceBt *self = user_data;
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	gboolean pan = (priv->bt_type == NM_BT_CAPABILITY_NAP);
-	gboolean dun = (priv->bt_type == NM_BT_CAPABILITY_DUN);
+	int ifindex;
 
-	if (!priv->connected || !priv->have_iface)
-		return;
+	priv->connect_watch_link_idle_id = 0;
+
+	if (nm_device_get_state (NM_DEVICE (self)) <= NM_DEVICE_STATE_ACTIVATED) {
+		ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+		if (   ifindex > 0
+		    && !nm_platform_link_get (nm_device_get_platform (NM_DEVICE (self)), ifindex)) {
+			_LOGT (LOGD_BT, "device disappeared");
+			nm_device_state_changed (NM_DEVICE (self),
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_BT_FAILED);
+		}
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+connect_watch_link_cb (NMPlatform *platform,
+                       int obj_type_i,
+                       int ifindex,
+                       NMPlatformLink *info,
+                       int change_type_i,
+                       NMDevice *self)
+{
+	const NMPlatformSignalChangeType change_type = change_type_i;
+	NMDeviceBtPrivate *priv;
+
+	/* bluez doesn't notify us when the connection disconnects.
+	 * Neither does NMManager (or NMDevice) tell us when the ip-ifindex goes away.
+	 * This is horrible, and should be improved. For now, watch the link ourself... */
+
+	if (NM_IN_SET (change_type, NM_PLATFORM_SIGNAL_CHANGED,
+	                            NM_PLATFORM_SIGNAL_REMOVED)) {
+		priv = NM_DEVICE_BT_GET_PRIVATE (self);
+		if (priv->connect_watch_link_idle_id == 0)
+			priv->connect_watch_link_idle_id = g_idle_add (connect_watch_link_idle_cb, self);
+	}
+}
 
-	_LOGI (LOGD_BT,
-	       "Activation: (bluetooth) Stage 2 of 5 (Device Configure) successful. Will connect via %s.",
-	       dun ? "DUN" : (pan ? "PAN" : "unknown"));
+static gboolean
+connect_wait_modem_timeout (gpointer user_data)
+{
+	NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	/* since this timeout is longer than the connect timeout, we must have already
+	 * hit the connect-timeout first or being connected. */
+	nm_assert (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED);
 
-	if (pan) {
-		/* Bluez says we're connected now.  Start IP config. */
-		nm_device_activate_schedule_stage3_ip_config_start (device);
-	} else if (dun) {
-		/* Wait for ModemManager to find the modem */
-		priv->timeout_id = g_timeout_add_seconds (30, modem_find_timeout, self);
+	priv->connect_wait_modem_id = 0;
+	nm_clear_g_cancellable (&priv->connect_bz_cancellable);
 
-		_LOGI (LOGD_BT | LOGD_MB,
-		       "Activation: (bluetooth) Stage 2 of 5 (Device Configure) waiting for modem to appear.");
-	} else
-		g_assert_not_reached ();
+	if (priv->modem)
+		_LOGD (LOGD_BT, "timeout connecting modem for DUN connection");
+	else
+		_LOGD (LOGD_BT, "timeout finding modem for DUN connection");
+
+	nm_device_state_changed (NM_DEVICE (self),
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND);
+	return G_SOURCE_REMOVE;
 }
 
 static void
-bluez_connect_cb (NMBluezDevice *bt_device,
-                  const char *device_name,
-                  GError *error,
-                  gpointer user_data)
+connect_bz_cb (NMBluezManager *bz_mgr,
+               gboolean is_complete,
+               const char *device_name,
+               GError *error,
+               gpointer user_data)
 {
-	gs_unref_object NMDeviceBt *self = user_data;
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	NMDeviceBt *self;
+	NMDeviceBtPrivate *priv;
+	char sbuf[100];
 
 	if (nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
-	nm_clear_g_source (&priv->timeout_id);
-	g_clear_object (&priv->cancellable);
+	self = user_data;
+	priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	nm_assert (nm_device_is_activating (NM_DEVICE (self)));
+	nm_assert (NM_IN_SET ((NMBluetoothCapabilities) priv->connect_bt_type, NM_BT_CAPABILITY_DUN,
+	                                                                       NM_BT_CAPABILITY_NAP));
 
-	if (!nm_device_is_activating (NM_DEVICE (self)))
+	if (!is_complete) {
+		nm_assert (priv->connect_bt_type == NM_BT_CAPABILITY_DUN);
+		nm_assert (device_name);
+		nm_assert (!error);
+
+		if (!nm_streq0 (priv->connect_rfcomm_iface, device_name)) {
+			nm_assert (!priv->connect_rfcomm_iface);
+			_LOGD (LOGD_BT, "DUN is still connecting but got serial port \"%s\" to claim modem", device_name);
+			g_free (priv->connect_rfcomm_iface);
+			priv->connect_rfcomm_iface = g_strdup (device_name);
+		}
 		return;
+	}
+
+	g_clear_object (&priv->connect_bz_cancellable);
 
 	if (!device_name) {
-		_LOGW (LOGD_BT, "Error connecting with bluez: %s", error->message);
+		_LOGW (LOGD_BT, "%s connect request failed: %s",
+		       nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf)),
+		       error->message);
 		nm_device_state_changed (NM_DEVICE (self),
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_BT_FAILED);
 		return;
 	}
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
-		g_free (priv->rfcomm_iface);
-		priv->rfcomm_iface = g_strdup (device_name);
-	} else if (priv->bt_type == NM_BT_CAPABILITY_NAP) {
+	_LOGD (LOGD_BT, "%s connect request successful (%s)",
+	       nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf)),
+	       device_name);
+
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
+		if (!nm_streq0 (priv->connect_rfcomm_iface, device_name)) {
+			nm_assert_not_reached ();
+			g_free (priv->connect_rfcomm_iface);
+			priv->connect_rfcomm_iface = g_strdup (device_name);
+		}
+	} else {
+		nm_assert (priv->connect_bt_type == NM_BT_CAPABILITY_NAP);
 		if (!nm_device_set_ip_iface (NM_DEVICE (self), device_name)) {
 			_LOGW (LOGD_BT, "Error connecting with bluez: cannot find device %s", device_name);
 			nm_device_state_changed (NM_DEVICE (self),
@@ -798,107 +885,121 @@ bluez_connect_cb (NMBluezDevice *bt_device,
 			                         NM_DEVICE_STATE_REASON_BT_FAILED);
 			return;
 		}
+		priv->connect_watch_link_id = g_signal_connect (nm_device_get_platform (NM_DEVICE (self)),
+		                                                NM_PLATFORM_SIGNAL_LINK_CHANGED,
+		                                                G_CALLBACK (connect_watch_link_cb),
+		                                                self);
 	}
 
-	_LOGD (LOGD_BT, "connect request successful");
-
-	/* Stage 3 gets scheduled when Bluez says we're connected */
-	priv->have_iface = TRUE;
-	check_connect_continue (self);
-}
-
-static void
-bluez_connected_changed (NMBluezDevice *bt_device,
-                         GParamSpec *pspec,
-                         NMDevice *device)
-{
-	NMDeviceBt *self = NM_DEVICE_BT (device);
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	gboolean connected;
-	NMDeviceState state;
-
-	state = nm_device_get_state (device);
-	connected = nm_bluez_device_get_connected (bt_device);
-	if (connected) {
-		if (state == NM_DEVICE_STATE_CONFIG) {
-			_LOGD (LOGD_BT, "connected to the device");
-
-			priv->connected = TRUE;
-			check_connect_continue (self);
-		}
-	} else {
-		gboolean fail = FALSE;
-
-		/* Bluez says we're disconnected from the device.  Suck. */
-
-		if (nm_device_is_activating (device)) {
-			_LOGI (LOGD_BT, "Activation: (bluetooth) bluetooth link disconnected.");
-			fail = TRUE;
-		} else if (state == NM_DEVICE_STATE_ACTIVATED) {
-			_LOGI (LOGD_BT, "bluetooth link disconnected.");
-			fail = TRUE;
-		}
-
-		if (fail) {
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CARRIER);
-			priv->connected = FALSE;
-		}
+	if (!priv->is_connected) {
+		/* we got the callback from NMBluezManager with succes. We actually should be
+		 * connected and this line shouldn't be reached. */
+		nm_assert_not_reached ();
+		_LOGE (LOGD_BT, "bluetooth is unexpectedly not in connected state");
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_BT_FAILED);
+		return;
 	}
-}
 
-static gboolean
-bt_connect_timeout (gpointer user_data)
-{
-	NMDeviceBt *self = NM_DEVICE_BT (user_data);
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-
-	_LOGD (LOGD_BT, "initial connection timed out");
-
-	priv->timeout_id = 0;
-	nm_clear_g_cancellable (&priv->cancellable);
-
-	nm_device_state_changed (NM_DEVICE (self),
-	                         NM_DEVICE_STATE_FAILED,
-	                         NM_DEVICE_STATE_REASON_BT_FAILED);
-	return FALSE;
+	priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
 }
 
 static NMActStageReturn
-act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+act_stage1_prepare (NMDevice *device,
+                    NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceBt *self = NM_DEVICE_BT (device);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	gs_free_error GError *error = NULL;
 	NMConnection *connection;
 
 	connection = nm_device_get_applied_connection (device);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-	priv->bt_type = get_connection_bt_type (connection);
-	if (priv->bt_type == NM_BT_CAPABILITY_NONE) {
-		// FIXME: set a reason code
+	priv->connect_bt_type = get_connection_bt_type (connection);
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_NONE) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_BT_FAILED);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN && !priv->mm_running) {
+	if (   priv->connect_bt_type == NM_BT_CAPABILITY_DUN
+	    && !priv->mm_running) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	_LOGD (LOGD_BT, "requesting connection to the device");
+	if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_PENDING)
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	else if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_INIT) {
+		gs_unref_object GCancellable *cancellable = NULL;
+		char sbuf[100];
+
+		_LOGD (LOGD_BT, "connecting to %s bluetooth device",
+		       nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf)));
+
+		cancellable = g_cancellable_new ();
+
+		if (!nm_bluez_manager_connect (priv->bz_mgr,
+		                               priv->dbus_path,
+		                               priv->connect_bt_type,
+		                               30000,
+		                               cancellable,
+		                               connect_bz_cb,
+		                               self,
+		                               &error)) {
+			_LOGD (LOGD_BT, "cannot connect to bluetooth device: %s", error->message);
+			*out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+
+		priv->connect_bz_cancellable = g_steal_pointer (&cancellable);
+		priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_PENDING;
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
+		if (!priv->modem) {
+			gs_free NMModem **modems = NULL;
+			guint i, n;
+
+			if (priv->connect_wait_modem_id == 0)
+				priv->connect_wait_modem_id = g_timeout_add_seconds (30, connect_wait_modem_timeout, self);
+
+			modems = nm_modem_manager_get_modems (priv->modem_manager, &n);
+			for (i = 0; i < n; i++) {
+				if (modem_try_claim (self, modems[i]))
+					break;
+			}
+			if (!priv->modem)
+				return NM_ACT_STAGE_RETURN_POSTPONE;
+		}
+
+		if (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_PENDING)
+			return NM_ACT_STAGE_RETURN_POSTPONE;
+		if (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_INIT) {
+			priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_PENDING;
+			return nm_modem_act_stage1_prepare (priv->modem,
+			                                    nm_device_get_act_request (NM_DEVICE (self)),
+			                                    out_failure_reason);
+		}
+	}
 
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
 
-	priv->timeout_id = g_timeout_add_seconds (30, bt_connect_timeout, device);
-	priv->cancellable = g_cancellable_new ();
+static NMActStageReturn
+act_stage2_config (NMDevice *device,
+                   NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceBt *self = NM_DEVICE_BT (device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
-	nm_bluez_device_connect_async (priv->bt_device,
-	                               priv->bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP),
-	                               priv->cancellable,
-	                               bluez_connect_cb,
-	                               g_object_ref (self));
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN)
+		nm_modem_act_stage2_config (priv->modem);
 
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 static NMActStageReturn
@@ -907,11 +1008,11 @@ act_stage3_ip_config_start (NMDevice *device,
                             gpointer *out_config,
                             NMDeviceStateReason *out_failure_reason)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
 
 	nm_assert_addr_family (addr_family);
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
 		if (addr_family == AF_INET) {
 			return nm_modem_stage3_ip4_config_start (priv->modem,
 			                                         device,
@@ -930,15 +1031,17 @@ act_stage3_ip_config_start (NMDevice *device,
 static void
 deactivate (NMDevice *device)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
 
-	priv->have_iface = FALSE;
-	priv->connected = FALSE;
+	nm_clear_g_signal_handler (nm_device_get_platform (device), &priv->connect_watch_link_id);
+	nm_clear_g_source (&priv->connect_watch_link_idle_id);
+	priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_INIT;
+	nm_clear_g_source (&priv->connect_wait_modem_id);
+	nm_clear_g_cancellable (&priv->connect_bz_cancellable);
 
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_INIT;
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
 		if (priv->modem) {
 			nm_modem_deactivate (priv->modem, device);
 
@@ -952,22 +1055,53 @@ deactivate (NMDevice *device)
 		}
 	}
 
-	if (priv->bt_type != NM_BT_CAPABILITY_NONE)
-		nm_bluez_device_disconnect (priv->bt_device);
-
-	priv->bt_type = NM_BT_CAPABILITY_NONE;
+	if (priv->connect_bt_type != NM_BT_CAPABILITY_NONE) {
+		priv->connect_bt_type = NM_BT_CAPABILITY_NONE;
+		nm_bluez_manager_disconnect (priv->bz_mgr, priv->dbus_path);
+	}
 
-	g_free (priv->rfcomm_iface);
-	priv->rfcomm_iface = NULL;
+	nm_clear_g_free (&priv->connect_rfcomm_iface);
 
 	if (NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate)
 		NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate (device);
 }
 
-static void
-bluez_device_removed (NMBluezDevice *bdev, gpointer user_data)
+void
+_nm_device_bt_notify_removed (NMDeviceBt *self)
+{
+	g_signal_emit_by_name (self, NM_DEVICE_REMOVED);
+}
+
+/*****************************************************************************/
+
+gboolean
+_nm_device_bt_for_same_device (NMDeviceBt *self,
+                               const char *dbus_path,
+                               const char *bdaddr,
+                               const char *name,
+                               NMBluetoothCapabilities capabilities)
 {
-	g_signal_emit_by_name (NM_DEVICE_BT (user_data), NM_DEVICE_REMOVED);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	return    nm_streq (priv->dbus_path, dbus_path)
+	       && nm_streq (priv->bdaddr, bdaddr)
+	       && capabilities == priv->capabilities
+	       && (!name || nm_streq (priv->name, name));
+}
+
+void
+_nm_device_bt_notify_set_name (NMDeviceBt *self, const char *name)
+{
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	nm_assert (name);
+
+	if (!nm_streq (priv->name, name)) {
+		_LOGT (LOGD_BT, "set-name: %s", name);
+		g_free (priv->name);
+		priv->name = g_strdup (name);
+		_notify (self, PROP_BT_NAME);
+	}
 }
 
 /*****************************************************************************/
@@ -1028,9 +1162,6 @@ get_property (GObject *object, guint prop_id,
 	case PROP_BT_CAPABILITIES:
 		g_value_set_uint (value, priv->capabilities);
 		break;
-	case PROP_BT_DEVICE:
-		g_value_set_object (value, priv->bt_device);
-		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1044,19 +1175,32 @@ set_property (GObject *object, guint prop_id,
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
 
 	switch (prop_id) {
+	case PROP_BT_BZ_MGR:
+		/* construct-only */
+		priv->bz_mgr = g_object_ref (g_value_get_pointer (value));
+		nm_assert (NM_IS_BLUEZ_MANAGER (priv->bz_mgr));
+		break;
+	case PROP_BT_DBUS_PATH:
+		/* construct-only */
+		priv->dbus_path = g_value_dup_string (value);
+		nm_assert (priv->dbus_path);
+		break;
+	case PROP_BT_BDADDR:
+		/* construct-only */
+		priv->bdaddr = g_value_dup_string (value);
+		nm_assert (priv->bdaddr);
+		break;
 	case PROP_BT_NAME:
 		/* construct-only */
 		priv->name = g_value_dup_string (value);
+		nm_assert (priv->name);
 		break;
 	case PROP_BT_CAPABILITIES:
 		/* construct-only */
 		priv->capabilities = g_value_get_uint (value);
-		break;
-	case PROP_BT_DEVICE:
-		/* construct-only */
-		priv->bt_device = g_value_dup_object (value);
-		if (!priv->bt_device)
-			g_return_if_reached ();
+		nm_assert (NM_IN_SET ((NMBluetoothCapabilities) priv->capabilities, NM_BT_CAPABILITY_DUN,
+		                                                                    NM_BT_CAPABILITY_NAP,
+		                                                                    NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1076,7 +1220,6 @@ constructed (GObject *object)
 {
 	NMDeviceBt *self = NM_DEVICE_BT (object);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	const char *my_hwaddr;
 
 	G_OBJECT_CLASS (nm_device_bt_parent_class)->constructed (object);
 
@@ -1085,74 +1228,69 @@ constructed (GObject *object)
 	nm_modem_manager_name_owner_ref (priv->modem_manager);
 
 	g_signal_connect (priv->modem_manager,
+	                  NM_MODEM_MANAGER_MODEM_ADDED,
+	                  G_CALLBACK (mm_modem_added_cb),
+	                  self);
+
+	g_signal_connect (priv->modem_manager,
 	                  "notify::"NM_MODEM_MANAGER_NAME_OWNER,
 	                  G_CALLBACK (mm_name_owner_changed_cb),
 	                  self);
 
-	if (priv->bt_device) {
-		/* Watch for BT device property changes */
-		g_signal_connect (priv->bt_device, "notify::" NM_BLUEZ_DEVICE_CONNECTED,
-		                  G_CALLBACK (bluez_connected_changed),
-		                  object);
-		g_signal_connect (priv->bt_device, NM_BLUEZ_DEVICE_REMOVED,
-		                  G_CALLBACK (bluez_device_removed), object);
-	}
-
-	my_hwaddr = nm_device_get_hw_address (NM_DEVICE (object));
-	if (my_hwaddr)
-		priv->bdaddr = g_strdup (my_hwaddr);
-	else
-		g_warn_if_reached ();
-
 	set_mm_running (self);
 }
 
-NMDevice *
-nm_device_bt_new (NMBluezDevice *bt_device,
-                  const char *udi,
+NMDeviceBt *
+nm_device_bt_new (NMBluezManager *bz_mgr,
+                  const char *dbus_path,
                   const char *bdaddr,
                   const char *name,
-                  guint32 capabilities)
+                  NMBluetoothCapabilities capabilities)
 {
-	g_return_val_if_fail (udi != NULL, NULL);
-	g_return_val_if_fail (bdaddr != NULL, NULL);
-	g_return_val_if_fail (name != NULL, NULL);
+	g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (bz_mgr), NULL);
+	g_return_val_if_fail (dbus_path, NULL);
+	g_return_val_if_fail (bdaddr, NULL);
+	g_return_val_if_fail (name, NULL);
 	g_return_val_if_fail (capabilities != NM_BT_CAPABILITY_NONE, NULL);
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (bt_device), NULL);
-
-	return (NMDevice *) g_object_new (NM_TYPE_DEVICE_BT,
-	                                  NM_DEVICE_UDI, udi,
-	                                  NM_DEVICE_IFACE, bdaddr,
-	                                  NM_DEVICE_DRIVER, "bluez",
-	                                  NM_DEVICE_PERM_HW_ADDRESS, bdaddr,
-	                                  NM_DEVICE_BT_DEVICE, bt_device,
-	                                  NM_DEVICE_BT_NAME, name,
-	                                  NM_DEVICE_BT_CAPABILITIES, capabilities,
-	                                  NM_DEVICE_TYPE_DESC, "Bluetooth",
-	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BT,
-	                                  NULL);
+
+	return g_object_new (NM_TYPE_DEVICE_BT,
+	                     NM_DEVICE_UDI, dbus_path,
+	                     NM_DEVICE_IFACE, bdaddr,
+	                     NM_DEVICE_DRIVER, "bluez",
+	                     NM_DEVICE_PERM_HW_ADDRESS, bdaddr,
+	                     NM_DEVICE_BT_BDADDR, bdaddr,
+	                     NM_DEVICE_BT_BZ_MGR, bz_mgr,
+	                     NM_DEVICE_BT_CAPABILITIES, (guint) capabilities,
+	                     NM_DEVICE_BT_DBUS_PATH, dbus_path,
+	                     NM_DEVICE_BT_NAME, name,
+	                     NM_DEVICE_TYPE_DESC, "Bluetooth",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BT,
+	                     NULL);
 }
 
 static void
 dispose (GObject *object)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
+	NMDeviceBt *self = NM_DEVICE_BT (object);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	nm_clear_g_signal_handler (nm_device_get_platform (NM_DEVICE (self)), &priv->connect_watch_link_id);
+	nm_clear_g_source (&priv->connect_watch_link_idle_id);
 
-	g_signal_handlers_disconnect_matched (priv->bt_device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
+	nm_clear_g_source (&priv->connect_wait_modem_id);
+	nm_clear_g_cancellable (&priv->connect_bz_cancellable);
 
 	if (priv->modem_manager) {
-		g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), object);
+		g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), self);
 		nm_modem_manager_name_owner_unref (priv->modem_manager);
 		g_clear_object (&priv->modem_manager);
 	}
 
-	modem_cleanup (NM_DEVICE_BT (object));
-	g_clear_object (&priv->bt_device);
+	modem_cleanup (self);
 
 	G_OBJECT_CLASS (nm_device_bt_parent_class)->dispose (object);
+
+	g_clear_object (&priv->bz_mgr);
 }
 
 static void
@@ -1160,7 +1298,8 @@ finalize (GObject *object)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
 
-	g_free (priv->rfcomm_iface);
+	g_free (priv->connect_rfcomm_iface);
+	g_free (priv->dbus_path);
 	g_free (priv->name);
 	g_free (priv->bdaddr);
 
@@ -1202,17 +1341,34 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->can_auto_connect = can_auto_connect;
 	device_class->deactivate = deactivate;
+	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->check_connection_available = check_connection_available;
 	device_class->complete_connection = complete_connection;
 	device_class->is_available = is_available;
-	device_class->component_added = component_added;
 	device_class->get_configured_mtu = nm_modem_get_configured_mtu;
 
 	device_class->state_changed = device_state_changed;
 
+	obj_properties[PROP_BT_BZ_MGR] =
+	     g_param_spec_pointer (NM_DEVICE_BT_BZ_MGR, "", "",
+	                           G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                           G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_BT_BDADDR] =
+	     g_param_spec_string (NM_DEVICE_BT_BDADDR, "", "",
+	                          NULL,
+	                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_BT_DBUS_PATH] =
+	     g_param_spec_string (NM_DEVICE_BT_DBUS_PATH, "", "",
+	                          NULL,
+	                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_BT_NAME] =
 	     g_param_spec_string (NM_DEVICE_BT_NAME, "", "",
 	                          NULL,
@@ -1225,12 +1381,6 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_BT_DEVICE] =
-	     g_param_spec_object (NM_DEVICE_BT_DEVICE, "", "",
-	                          NM_TYPE_BLUEZ_DEVICE,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[PPP_STATS] =
diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/devices/bluetooth/nm-device-bt.h
index 6c8a5773..ecb5781b 100644
--- a/src/devices/bluetooth/nm-device-bt.h
+++ b/src/devices/bluetooth/nm-device-bt.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 Red Hat, Inc.
  */
 
@@ -21,7 +7,6 @@
 #define __NETWORKMANAGER_DEVICE_BT_H__
 
 #include "devices/nm-device.h"
-#include "nm-bluez-device.h"
 
 #define NM_TYPE_DEVICE_BT                   (nm_device_bt_get_type ())
 #define NM_DEVICE_BT(obj)                   (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BT, NMDeviceBt))
@@ -30,9 +15,11 @@
 #define NM_IS_DEVICE_BT_CLASS(klass)        (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_BT))
 #define NM_DEVICE_BT_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_BT, NMDeviceBtClass))
 
-#define NM_DEVICE_BT_NAME         "name"
+#define NM_DEVICE_BT_BDADDR       "bt-bdaddr"
+#define NM_DEVICE_BT_BZ_MGR       "bt-bz-mgr"
 #define NM_DEVICE_BT_CAPABILITIES "bt-capabilities"
-#define NM_DEVICE_BT_DEVICE       "bt-device"
+#define NM_DEVICE_BT_DBUS_PATH    "bt-dbus-path"
+#define NM_DEVICE_BT_NAME         "bt-name"
 
 #define NM_DEVICE_BT_PPP_STATS    "ppp-stats"
 
@@ -41,13 +28,21 @@ typedef struct _NMDeviceBtClass NMDeviceBtClass;
 
 GType nm_device_bt_get_type (void);
 
-NMDevice *nm_device_bt_new (NMBluezDevice *bt_device,
-                            const char *udi,
-                            const char *bdaddr,
-                            const char *name,
-                            guint32 capabilities);
+struct _NMBluezManager;
 
-guint32 nm_device_bt_get_capabilities (NMDeviceBt *device);
+NMDeviceBt *nm_device_bt_new (struct _NMBluezManager *bz_mgr,
+                              const char *dbus_path,
+                              const char *bdaddr,
+                              const char *name,
+                              NMBluetoothCapabilities capabilities);
+
+gboolean _nm_device_bt_for_same_device (NMDeviceBt *device,
+                                        const char *dbus_path,
+                                        const char *bdaddr,
+                                        const char *name,
+                                        NMBluetoothCapabilities capabilities);
+
+NMBluetoothCapabilities nm_device_bt_get_capabilities (NMDeviceBt *device);
 
 struct _NMModem;
 
@@ -55,4 +50,11 @@ gboolean nm_device_bt_modem_added (NMDeviceBt *device,
                                    struct _NMModem *modem,
                                    const char *driver);
 
+void _nm_device_bt_notify_removed (NMDeviceBt *self);
+
+void _nm_device_bt_notify_set_name (NMDeviceBt *self, const char *name);
+
+void _nm_device_bt_notify_set_connected (NMDeviceBt *self,
+                                         gboolean connected);
+
 #endif /* __NETWORKMANAGER_DEVICE_BT_H__ */
diff --git a/src/devices/bluetooth/tests/nm-bt-test.c b/src/devices/bluetooth/tests/nm-bt-test.c
new file mode 100644
index 00000000..02cfd228
--- /dev/null
+++ b/src/devices/bluetooth/tests/nm-bt-test.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#include "nm-default.h"
+
+#include <glib-unix.h>
+
+#include "devices/bluetooth/nm-bluez5-dun.h"
+
+#include "nm-test-utils-core.h"
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN LOGD_BT
+#define _NMLOG(level, ...) \
+	nm_log ((level), _NMLOG_DOMAIN, \
+	        NULL, NULL, \
+	        "bt%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+	        NM_PRINT_FMT_QUOTED (gl.argv_cmd, "[", gl.argv_cmd, "]", "") \
+	        _NM_UTILS_MACRO_REST (__VA_ARGS__))
+
+/*****************************************************************************/
+
+struct {
+	int argc;
+	const char *const*argv;
+	const char *argv_cmd;
+	GMainLoop *loop;
+} gl;
+
+typedef struct _MainCmdInfo {
+	const char *name;
+	int (*main_func) (const struct _MainCmdInfo *main_cmd_info);
+} MainCmdInfo;
+
+/*****************************************************************************/
+
+#if WITH_BLUEZ5_DUN
+
+typedef struct {
+	NMBluez5DunContext *dun_context;
+	GCancellable *cancellable;
+	guint timeout_id;
+	guint sig_term_id;
+	guint sig_int_id;
+} DunConnectData;
+
+static void
+_dun_connect_cb (NMBluez5DunContext *context,
+                 const char *rfcomm_dev,
+                 GError *error,
+                 gpointer user_data)
+{
+	DunConnectData *dun_connect_data = user_data;
+
+	g_assert (dun_connect_data);
+	g_assert (!dun_connect_data->dun_context);
+	g_assert ((!!error) != (!!rfcomm_dev));
+
+	if (rfcomm_dev && !context) {
+		_LOGI ("dun-connect notifies path \"%s\". Wait longer...", rfcomm_dev);
+		return;
+	}
+
+	if (rfcomm_dev) {
+		g_assert (context);
+		_LOGI ("dun-connect completed with path \"%s\"", rfcomm_dev);
+	} else {
+		g_assert (!context);
+		_LOGI ("dun-connect failed with error: %s", error->message);
+	}
+
+	dun_connect_data->dun_context = context;
+
+	g_main_loop_quit (gl.loop);
+}
+
+static void
+_dun_notify_tty_hangup_cb (NMBluez5DunContext *context,
+                           gpointer user_data)
+{
+	_LOGI ("dun-connect: notified TTY hangup");
+}
+
+static gboolean
+_timeout_cb (gpointer user_data)
+{
+	DunConnectData *dun_connect_data = user_data;
+
+	_LOGI ("timeout");
+	dun_connect_data->timeout_id = 0;
+	if (dun_connect_data->cancellable)
+		g_cancellable_cancel (dun_connect_data->cancellable);
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_sig_xxx_cb (DunConnectData *dun_connect_data, int sigid)
+{
+	_LOGI ("signal %s received", sigid == SIGTERM ? "SIGTERM" : "SIGINT");
+	g_main_loop_quit (gl.loop);
+	return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+_sig_term_cb (gpointer user_data)
+{
+	return _sig_xxx_cb (user_data, SIGTERM);
+}
+
+static gboolean
+_sig_int_cb (gpointer user_data)
+{
+	return _sig_xxx_cb (user_data, SIGINT);
+}
+#endif
+
+static int
+do_dun_connect (const MainCmdInfo *main_cmd_info)
+{
+#if WITH_BLUEZ5_DUN
+	gs_unref_object GCancellable *cancellable = NULL;
+	gs_free_error GError *error = NULL;
+	const char *adapter;
+	const char *remote;
+	DunConnectData dun_connect_data = { };
+
+	if (gl.argc < 4) {
+		_LOGE ("missing arguments \"adapter\" and \"remote\"");
+		return -1;
+	}
+
+	adapter = gl.argv[2];
+	remote = gl.argv[3];
+
+	cancellable = g_cancellable_new ();
+	dun_connect_data.cancellable = cancellable;
+
+	if (!nm_bluez5_dun_connect (adapter,
+	                            remote,
+	                            cancellable,
+	                            _dun_connect_cb,
+	                            &dun_connect_data,
+	                            _dun_notify_tty_hangup_cb,
+	                            &dun_connect_data,
+	                            &error)) {
+		_LOGE ("connect failed to start: %s", error->message);
+		return -1;
+	}
+
+	dun_connect_data.timeout_id = g_timeout_add (60000, _timeout_cb, &dun_connect_data);
+
+	g_main_loop_run (gl.loop);
+
+	nm_clear_g_source (&dun_connect_data.timeout_id);
+
+	if (dun_connect_data.dun_context) {
+
+		dun_connect_data.sig_term_id = g_unix_signal_add (SIGTERM, _sig_term_cb, &dun_connect_data);
+		dun_connect_data.sig_int_id = g_unix_signal_add (SIGINT, _sig_int_cb, &dun_connect_data);
+
+		g_main_loop_run (gl.loop);
+
+		nm_clear_g_source (&dun_connect_data.sig_term_id);
+		nm_clear_g_source (&dun_connect_data.sig_int_id);
+
+		nm_bluez5_dun_disconnect (g_steal_pointer (&dun_connect_data.dun_context));
+	}
+
+	return 0;
+#else
+	_LOGE ("compiled without bluetooth DUN support");
+	return 1;
+#endif
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	static const MainCmdInfo main_cmd_infos[] = {
+		{ .name = "dun-connect", .main_func = do_dun_connect, },
+	};
+	int exit_code = 0;
+	guint i;
+
+	if (!g_getenv ("G_MESSAGES_DEBUG"))
+		g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+	nmtst_init_with_logging (&argc, &argv, "DEBUG", "ALL");
+
+	nm_logging_init (NULL, TRUE);
+
+	gl.argv = (const char *const*) argv;
+	gl.argc = argc;
+	gl.loop = g_main_loop_new (NULL, FALSE);
+
+	_LOGI ("bluetooth test util start");
+
+	gl.argv_cmd = argc >= 2 ? argv[1] : NULL;
+
+	for (i = 0; i < G_N_ELEMENTS (main_cmd_infos); i++) {
+		if (nm_streq0 (main_cmd_infos[i].name, gl.argv_cmd)) {
+			_LOGD ("start \"%s\"", gl.argv_cmd);
+			exit_code = main_cmd_infos[i].main_func (&main_cmd_infos[i]);
+			_LOGD ("completed with %d", exit_code);
+			break;
+		}
+	}
+	if (gl.argv_cmd && i >= G_N_ELEMENTS (main_cmd_infos)) {
+		nm_log_err (LOGD_BT, "invalid command \"%s\"", gl.argv_cmd);
+		exit_code = -1;
+	}
+
+	nm_clear_pointer (&gl.loop, g_main_loop_unref);
+
+	return exit_code;
+}