summary refs log tree commit diff
path: root/src/devices/bluetooth/nm-device-bt.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-12-18 18:29:24 +0100
committerMichael Biebl <biebl@debian.org>2019-12-18 18:29:24 +0100
commit28028b26b3371756811e95d894f709f4b1207c00 (patch)
tree6fe7316fd743b51042db47601a8ef8814b3134ac /src/devices/bluetooth/nm-device-bt.c
parente22609983008e1a669196ad64ba3a59ae8c76e0d (diff)
New upstream version 1.22.0 upstream/1.22.0
Diffstat (limited to 'src/devices/bluetooth/nm-device-bt.c')
-rw-r--r--src/devices/bluetooth/nm-device-bt.c898
1 files changed, 524 insertions, 374 deletions
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] =