text: /* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #ifndef _GNU_SOURCE
  #define _GNU_SOURCE /* for ppoll */
  #endif
  
  #include <dbus/dbus.h>
  
  #include <errno.h>
  #include <poll.h>
  #include <stdbool.h>
  #include <stdint.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/socket.h>
  #include <syslog.h>
  
  #include "bluetooth.h"
  #include "cras_a2dp_endpoint.h"
  #include "cras_bt_adapter.h"
  #include "cras_bt_device.h"
  #include "cras_bt_constants.h"
  #include "cras_bt_log.h"
  #include "cras_bt_io.h"
  #include "cras_bt_profile.h"
  #include "cras_hfp_ag_profile.h"
  #include "cras_hfp_slc.h"
  #include "cras_iodev.h"
  #include "cras_iodev_list.h"
  #include "cras_main_message.h"
  #include "cras_server_metrics.h"
  #include "cras_system_state.h"
  #include "cras_tm.h"
  #include "utlist.h"
  
  /*
   * Bluetooth Core 5.0 spec, vol 4, part B, section 2 describes
   * the recommended HCI packet size in one USB transfer for CVSD
   * and MSBC codec.
   */
  #define USB_MSBC_PKT_SIZE 60
  #define USB_CVSD_PKT_SIZE 48
  #define DEFAULT_SCO_PKT_SIZE USB_CVSD_PKT_SIZE
  
  static const unsigned int PROFILE_SWITCH_DELAY_MS = 500;
  static const unsigned int PROFILE_DROP_SUSPEND_DELAY_MS = 5000;
  
  /* Check profile connections every 2 seconds and rerty 30 times maximum.
   * Attemp to connect profiles which haven't been ready every 3 retries.
   */
  static const unsigned int CONN_WATCH_PERIOD_MS = 2000;
  static const unsigned int CONN_WATCH_MAX_RETRIES = 30;
  
  /* This is used when a critical SCO failure happens and is worth scheduling a
   * suspend in case for some reason BT headset stays connected in baseband and
   * confuses user.
   */
  static const unsigned int SCO_SUSPEND_DELAY_MS = 5000;
  
  static const unsigned int CRAS_SUPPORTED_PROFILES =
  	CRAS_BT_DEVICE_PROFILE_A2DP_SINK |
  	CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE |
  	CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY;
  
  /* Object to represent a general bluetooth device, and used to
   * associate with some CRAS modules if it supports audio.
   * Members:
   *    conn - The dbus connection object used to send message to bluetoothd.
   *    object_path - Object path of the bluetooth device.
   *    adapter - The object path of the adapter associates with this device.
   *    address - The BT address of this device.
   *    name - The readable name of this device.
   *    bluetooth_class - The bluetooth class of this device.
   *    paired - If this device is paired.
   *    trusted - If this device is trusted.
   *    connected - If this devices is connected.
   *    connected_profiles - OR'ed all connected audio profiles.
   *    profiles - OR'ed by all audio profiles this device supports.
   *    bt_iodevs - The pointer to the cras_iodevs of this device.
   *    active_profile - The flag to indicate the active audio profile this
   *        device is currently using.
   *    conn_watch_retries - The retry count for conn_watch_timer.
   *    conn_watch_timer - The timer used to watch connected profiles and start
   *        BT audio input/ouput when all profiles are ready.
   *    suspend_timer - The timer used to suspend device.
   *    switch_profile_timer - The timer used to delay enabling iodev after
   *        profile switch.
   *    sco_fd - The file descriptor of the SCO connection.
   *    sco_ref_count - The reference counts of the SCO connection.
   *    suspend_reason - The reason code for why suspend is scheduled.
   */
  struct cras_bt_device {
  	DBusConnection *conn;
  	char *object_path;
  	char *adapter_obj_path;
  	char *address;
  	char *name;
  	uint32_t bluetooth_class;
  	int paired;
  	int trusted;
  	int connected;
  	enum cras_bt_device_profile connected_profiles;
  	enum cras_bt_device_profile profiles;
  	struct cras_iodev *bt_iodevs[CRAS_NUM_DIRECTIONS];
  	unsigned int active_profile;
  	int use_hardware_volume;
  	int conn_watch_retries;
  	struct cras_timer *conn_watch_timer;
  	struct cras_timer *suspend_timer;
  	struct cras_timer *switch_profile_timer;
  	int sco_fd;
  	size_t sco_ref_count;
  	enum cras_bt_device_suspend_reason suspend_reason;
  
  	struct cras_bt_device *prev, *next;
  };
  
  enum BT_DEVICE_COMMAND {
  	BT_DEVICE_CANCEL_SUSPEND,
  	BT_DEVICE_SCHEDULE_SUSPEND,
  	BT_DEVICE_SWITCH_PROFILE,
  	BT_DEVICE_SWITCH_PROFILE_ENABLE_DEV,
  };
  
  struct bt_device_msg {
  	struct cras_main_message header;
  	enum BT_DEVICE_COMMAND cmd;
  	struct cras_bt_device *device;
  	struct cras_iodev *dev;
  	unsigned int arg1;
  	unsigned int arg2;
  };
  
  static struct cras_bt_device *devices;
  
  enum cras_bt_device_profile cras_bt_device_profile_from_uuid(const char *uuid)
  {
  	if (strcmp(uuid, HSP_HS_UUID) == 0)
  		return CRAS_BT_DEVICE_PROFILE_HSP_HEADSET;
  	else if (strcmp(uuid, HSP_AG_UUID) == 0)
  		return CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY;
  	else if (strcmp(uuid, HFP_HF_UUID) == 0)
  		return CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE;
  	else if (strcmp(uuid, HFP_AG_UUID) == 0)
  		return CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY;
  	else if (strcmp(uuid, A2DP_SOURCE_UUID) == 0)
  		return CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE;
  	else if (strcmp(uuid, A2DP_SINK_UUID) == 0)
  		return CRAS_BT_DEVICE_PROFILE_A2DP_SINK;
  	else if (strcmp(uuid, AVRCP_REMOTE_UUID) == 0)
  		return CRAS_BT_DEVICE_PROFILE_AVRCP_REMOTE;
  	else if (strcmp(uuid, AVRCP_TARGET_UUID) == 0)
  		return CRAS_BT_DEVICE_PROFILE_AVRCP_TARGET;
  	else
  		return 0;
  }
  
  struct cras_bt_device *cras_bt_device_create(DBusConnection *conn,
  					     const char *object_path)
  {
  	struct cras_bt_device *device;
  
  	device = calloc(1, sizeof(*device));
  	if (device == NULL)
  		return NULL;
  
  	device->conn = conn;
  	device->object_path = strdup(object_path);
  	if (device->object_path == NULL) {
  		free(device);
  		return NULL;
  	}
  
  	DL_APPEND(devices, device);
  
  	return device;
  }
  
  static void on_connect_profile_reply(DBusPendingCall *pending_call, void *data)
  {
  	DBusMessage *reply;
  
  	reply = dbus_pending_call_steal_reply(pending_call);
  	dbus_pending_call_unref(pending_call);
  
  	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR)
  		syslog(LOG_ERR, "Connect profile message replied error: %s",
  		       dbus_message_get_error_name(reply));
  
  	dbus_message_unref(reply);
  }
  
  static void on_disconnect_reply(DBusPendingCall *pending_call, void *data)
  {
  	DBusMessage *reply;
  
  	reply = dbus_pending_call_steal_reply(pending_call);
  	dbus_pending_call_unref(pending_call);
  
  	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR)
  		syslog(LOG_ERR, "Disconnect message replied error");
  
  	dbus_message_unref(reply);
  }
  
  int cras_bt_device_connect_profile(DBusConnection *conn,
  				   struct cras_bt_device *device,
  				   const char *uuid)
  {
  	DBusMessage *method_call;
  	DBusError dbus_error;
  	DBusPendingCall *pending_call;
  
  	method_call =
  		dbus_message_new_method_call(BLUEZ_SERVICE, device->object_path,
  					     BLUEZ_INTERFACE_DEVICE,
  					     "ConnectProfile");
  	if (!method_call)
  		return -ENOMEM;
  
  	if (!dbus_message_append_args(method_call, DBUS_TYPE_STRING, &uuid,
  				      DBUS_TYPE_INVALID))
  		return -ENOMEM;
  
  	dbus_error_init(&dbus_error);
  
  	pending_call = NULL;
  	if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
  					     DBUS_TIMEOUT_USE_DEFAULT)) {
  		dbus_message_unref(method_call);
  		syslog(LOG_ERR, "Failed to send Disconnect message");
  		return -EIO;
  	}
  
  	dbus_message_unref(method_call);
  	if (!dbus_pending_call_set_notify(
  		    pending_call, on_connect_profile_reply, conn, NULL)) {
  		dbus_pending_call_cancel(pending_call);
  		dbus_pending_call_unref(pending_call);
  		return -EIO;
  	}
  	return 0;
  }
  
  int cras_bt_device_disconnect(DBusConnection *conn,
  			      struct cras_bt_device *device)
  {
  	DBusMessage *method_call;
  	DBusError dbus_error;
  	DBusPendingCall *pending_call;
  
  	method_call =
  		dbus_message_new_method_call(BLUEZ_SERVICE, device->object_path,
  					     BLUEZ_INTERFACE_DEVICE,
  					     "Disconnect");
  	if (!method_call)
  		return -ENOMEM;
  
  	dbus_error_init(&dbus_error);
  
  	pending_call = NULL;
  	if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
  					     DBUS_TIMEOUT_USE_DEFAULT)) {
  		dbus_message_unref(method_call);
  		syslog(LOG_ERR, "Failed to send Disconnect message");
  		return -EIO;
  	}
  
  	dbus_message_unref(method_call);
  	if (!dbus_pending_call_set_notify(pending_call, on_disconnect_reply,
  					  conn, NULL)) {
  		dbus_pending_call_cancel(pending_call);
  		dbus_pending_call_unref(pending_call);
  		return -EIO;
  	}
  	return 0;
  }
  
  static void cras_bt_device_destroy(struct cras_bt_device *device)
  {
  	struct cras_tm *tm = cras_system_state_get_tm();
  	DL_DELETE(devices, device);
  
  	if (device->conn_watch_timer)
  		cras_tm_cancel_timer(tm, device->conn_watch_timer);
  	if (device->switch_profile_timer)
  		cras_tm_cancel_timer(tm, device->switch_profile_timer);
  	if (device->suspend_timer)
  		cras_tm_cancel_timer(tm, device->suspend_timer);
  	free(device->adapter_obj_path);
  	free(device->object_path);
  	free(device->address);
  	free(device->name);
  	free(device);
  }
  
  void cras_bt_device_remove(struct cras_bt_device *device)
  {
  	/*
  	 * We expect BT stack to disconnect this device before removing it,
  	 * but it may not the case if there's issue at BT side. Print error
  	 * log whenever this happens.
  	 */
  	if (device->connected)
  		syslog(LOG_ERR, "Removing dev with connected profiles %u",
  		       device->connected_profiles);
  	/*
  	 * Possibly clean up the associated A2DP and HFP AG iodevs that are
  	 * still accessing this device.
  	 */
  	cras_a2dp_suspend_connected_device(device);
  	cras_hfp_ag_suspend_connected_device(device);
  	cras_bt_device_destroy(device);
  }
  
  void cras_bt_device_reset()
  {
  	while (devices) {
  		syslog(LOG_INFO, "Bluetooth Device: %s removed",
  		       devices->address);
  		cras_bt_device_destroy(devices);
  	}
  }
  
  struct cras_bt_device *cras_bt_device_get(const char *object_path)
  {
  	struct cras_bt_device *device;
  
  	DL_FOREACH (devices, device) {
  		if (strcmp(device->object_path, object_path) == 0)
  			return device;
  	}
  
  	return NULL;
  }
  
  const char *cras_bt_device_object_path(const struct cras_bt_device *device)
  {
  	return device->object_path;
  }
  
  struct cras_bt_adapter *
  cras_bt_device_adapter(const struct cras_bt_device *device)
  {
  	return cras_bt_adapter_get(device->adapter_obj_path);
  }
  
  const char *cras_bt_device_address(const struct cras_bt_device *device)
  {
  	return device->address;
  }
  
  const char *cras_bt_device_name(const struct cras_bt_device *device)
  {
  	return device->name;
  }
  
  int cras_bt_device_paired(const struct cras_bt_device *device)
  {
  	return device->paired;
  }
  
  int cras_bt_device_trusted(const struct cras_bt_device *device)
  {
  	return device->trusted;
  }
  
  int cras_bt_device_connected(const struct cras_bt_device *device)
  {
  	return device->connected;
  }
  
  int cras_bt_device_supports_profile(const struct cras_bt_device *device,
  				    enum cras_bt_device_profile profile)
  {
  	return !!(device->profiles & profile);
  }
  
  void cras_bt_device_append_iodev(struct cras_bt_device *device,
  				 struct cras_iodev *iodev,
  				 enum cras_bt_device_profile profile)
  {
  	struct cras_iodev *bt_iodev;
  
  	bt_iodev = device->bt_iodevs[iodev->direction];
  
  	if (bt_iodev) {
  		cras_bt_io_append(bt_iodev, iodev, profile);
  	} else {
  		device->bt_iodevs[iodev->direction] =
  			cras_bt_io_create(device, iodev, profile);
  	}
  }
  
  /*
   * Sets the audio nodes to 'plugged' means UI can select it and open it
   * for streams. Sets to 'unplugged' to hide these nodes from UI, when device
   * disconnects in progress.
   */
  static void bt_device_set_nodes_plugged(struct cras_bt_device *device,
  					int plugged)
  {
  	struct cras_iodev *iodev;
  
  	iodev = device->bt_iodevs[CRAS_STREAM_INPUT];
  	if (iodev)
  		cras_iodev_set_node_plugged(iodev->active_node, plugged);
  
  	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
  	if (iodev)
  		cras_iodev_set_node_plugged(iodev->active_node, plugged);
  }
  
  static void bt_device_switch_profile(struct cras_bt_device *device,
  				     struct cras_iodev *bt_iodev,
  				     int enable_dev);
  
  void cras_bt_device_rm_iodev(struct cras_bt_device *device,
  			     struct cras_iodev *iodev)
  {
  	struct cras_iodev *bt_iodev;
  	int rc;
  
  	bt_device_set_nodes_plugged(device, 0);
  
  	bt_iodev = device->bt_iodevs[iodev->direction];
  	if (bt_iodev) {
  		unsigned try_profile;
  
  		/* Check what will the preffered profile be if we remove dev. */
  		try_profile = cras_bt_io_try_remove(bt_iodev, iodev);
  		if (!try_profile)
  			goto destroy_bt_io;
  
  		/* If the check result doesn't match with the active
  		 * profile we are currently using, switch to the
  		 * preffered profile before actually remove the iodev.
  		 */
  		if (!cras_bt_io_on_profile(bt_iodev, try_profile)) {
  			device->active_profile = try_profile;
  			bt_device_switch_profile(device, bt_iodev, 0);
  		}
  		rc = cras_bt_io_remove(bt_iodev, iodev);
  		if (rc) {
  			syslog(LOG_ERR, "Fail to fallback to profile %u",
  			       try_profile);
  			goto destroy_bt_io;
  		}
  	}
  	return;
  
  destroy_bt_io:
  	device->bt_iodevs[iodev->direction] = NULL;
  	cras_bt_io_destroy(bt_iodev);
  
  	if (!device->bt_iodevs[CRAS_STREAM_INPUT] &&
  	    !device->bt_iodevs[CRAS_STREAM_OUTPUT])
  		cras_bt_device_set_active_profile(device, 0);
  }
  
  void cras_bt_device_a2dp_configured(struct cras_bt_device *device)
  {
  	BTLOG(btlog, BT_A2DP_CONFIGURED, device->connected_profiles, 0);
  	device->connected_profiles |= CRAS_BT_DEVICE_PROFILE_A2DP_SINK;
  }
  
  int cras_bt_device_has_a2dp(struct cras_bt_device *device)
  {
  	struct cras_iodev *odev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
  
  	/* Check if there is an output iodev with A2DP node attached. */
  	return odev &&
  	       cras_bt_io_get_profile(odev, CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
  }
  
  int cras_bt_device_can_switch_to_a2dp(struct cras_bt_device *device)
  {
  	struct cras_iodev *idev = device->bt_iodevs[CRAS_STREAM_INPUT];
  
  	return cras_bt_device_has_a2dp(device) &&
  	       (!idev || !cras_iodev_is_open(idev));
  }
  
  static void bt_device_remove_conflict(struct cras_bt_device *device)
  {
  	struct cras_bt_device *connected;
  
  	/* Suspend other HFP audio gateways that conflict with device. */
  	cras_hfp_ag_remove_conflict(device);
  
  	/* Check if there's conflict A2DP headset and suspend it. */
  	connected = cras_a2dp_connected_device();
  	if (connected && (connected != device))
  		cras_a2dp_suspend_connected_device(connected);
  }
  
  static void bt_device_conn_watch_cb(struct cras_timer *timer, void *arg);
  
  int cras_bt_device_audio_gateway_initialized(struct cras_bt_device *device)
  {
  	BTLOG(btlog, BT_AUDIO_GATEWAY_INIT, device->profiles, 0);
  	/* Marks HFP/HSP as connected. This is what connection watcher
  	 * checks. */
  	device->connected_profiles |= (CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE |
  				       CRAS_BT_DEVICE_PROFILE_HSP_HEADSET);
  
  	/* If device connects HFP but not reporting correct UUID, manually add
  	 * it to allow CRAS to enumerate audio node for it. We're seeing this
  	 * behavior on qualification test software. */
  	if (!cras_bt_device_supports_profile(
  		    device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE)) {
  		cras_bt_device_add_supported_profiles(device, HFP_HF_UUID);
  		bt_device_conn_watch_cb(NULL, (void *)device);
  	}
  
  	return 0;
  }
  
  int cras_bt_device_get_active_profile(const struct cras_bt_device *device)
  {
  	return device->active_profile;
  }
  
  void cras_bt_device_set_active_profile(struct cras_bt_device *device,
  				       unsigned int profile)
  {
  	device->active_profile = profile;
  }
  
  static void cras_bt_device_log_profile(const struct cras_bt_device *device,
  				       enum cras_bt_device_profile profile)
  {
  	switch (profile) {
  	case CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE:
  		syslog(LOG_DEBUG, "Bluetooth Device: %s is HFP handsfree",
  		       device->address);
  		break;
  	case CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY:
  		syslog(LOG_DEBUG, "Bluetooth Device: %s is HFP audio gateway",
  		       device->address);
  		break;
  	case CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE:
  		syslog(LOG_DEBUG, "Bluetooth Device: %s is A2DP source",
  		       device->address);
  		break;
  	case CRAS_BT_DEVICE_PROFILE_A2DP_SINK:
  		syslog(LOG_DEBUG, "Bluetooth Device: %s is A2DP sink",
  		       device->address);
  		break;
  	case CRAS_BT_DEVICE_PROFILE_AVRCP_REMOTE:
  		syslog(LOG_DEBUG, "Bluetooth Device: %s is AVRCP remote",
  		       device->address);
  		break;
  	case CRAS_BT_DEVICE_PROFILE_AVRCP_TARGET:
  		syslog(LOG_DEBUG, "Bluetooth Device: %s is AVRCP target",
  		       device->address);
  		break;
  	case CRAS_BT_DEVICE_PROFILE_HSP_HEADSET:
  		syslog(LOG_DEBUG, "Bluetooth Device: %s is HSP headset",
  		       device->address);
  		break;
  	case CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY:
  		syslog(LOG_DEBUG, "Bluetooth Device: %s is HSP audio gateway",
  		       device->address);
  		break;
  	}
  }
  
  static int
  cras_bt_device_is_profile_connected(const struct cras_bt_device *device,
  				    enum cras_bt_device_profile profile)
  {
  	return !!(device->connected_profiles & profile);
  }
  
  static void
  bt_device_schedule_suspend(struct cras_bt_device *device, unsigned int msec,
  			   enum cras_bt_device_suspend_reason suspend_reason);
  
  /* Callback used to periodically check if supported profiles are connected. */
  static void bt_device_conn_watch_cb(struct cras_timer *timer, void *arg)
  {
  	struct cras_tm *tm;
  	struct cras_bt_device *device = (struct cras_bt_device *)arg;
  	int rc;
  	bool a2dp_supported;
  	bool a2dp_connected;
  	bool hfp_supported;
  	bool hfp_connected;
  
  	BTLOG(btlog, BT_DEV_CONN_WATCH_CB, device->conn_watch_retries,
  	      device->profiles);
  	device->conn_watch_timer = NULL;
  
  	/* Skip the callback if it is not an audio device. */
  	if (!device->profiles)
  		return;
  
  	a2dp_supported = cras_bt_device_supports_profile(
  		device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK);
  	a2dp_connected = cras_bt_device_is_profile_connected(
  		device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK);
  	hfp_supported = cras_bt_device_supports_profile(
  		device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
  	hfp_connected = cras_bt_device_is_profile_connected(
  		device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
  
  	/* If not both A2DP and HFP are supported, simply wait for BlueZ
  	 * to notify us about the new connection.
  	 * Otherwise, when seeing one but not the other profile is connected,
  	 * send message to ask BlueZ to connect the pending one.
  	 */
  	if (a2dp_supported && hfp_supported) {
  		/* If both a2dp and hfp are not connected, do nothing. BlueZ
  		 * should be responsible to notify connection of one profile.
  		 */
  		if (!a2dp_connected && hfp_connected)
  			cras_bt_device_connect_profile(device->conn, device,
  						       A2DP_SINK_UUID);
  		if (a2dp_connected && !hfp_connected)
  			cras_bt_device_connect_profile(device->conn, device,
  						       HFP_HF_UUID);
  	}
  
  	if (a2dp_supported != a2dp_connected || hfp_supported != hfp_connected)
  		goto arm_retry_timer;
  
  	/* Expected profiles are all connected, no more connection watch
  	 * callback will be scheduled.
  	 * Base on the decision that we expose only the latest connected
  	 * BT audio device to user, treat all other connected devices as
  	 * conflict and remove them before we start A2DP/HFP of this device.
  	 */
  	bt_device_remove_conflict(device);
  
  	if (cras_bt_device_is_profile_connected(
  		    device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK))
  		cras_a2dp_start(device);
  
  	if (cras_bt_device_is_profile_connected(
  		    device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE)) {
  		rc = cras_hfp_ag_start(device);
  		if (rc) {
  			syslog(LOG_ERR, "Start audio gateway failed, rc %d",
  			       rc);
  			bt_device_schedule_suspend(device, 0,
  						   HFP_AG_START_FAILURE);
  		}
  	}
  	bt_device_set_nodes_plugged(device, 1);
  	return;
  
  arm_retry_timer:
  
  	syslog(LOG_DEBUG, "conn_watch_retries: %d", device->conn_watch_retries);
  
  	if (--device->conn_watch_retries) {
  		tm = cras_system_state_get_tm();
  		device->conn_watch_timer =
  			cras_tm_create_timer(tm, CONN_WATCH_PERIOD_MS,
  					     bt_device_conn_watch_cb, device);
  	} else {
  		syslog(LOG_ERR, "Connection watch timeout.");
  		bt_device_schedule_suspend(device, 0, CONN_WATCH_TIME_OUT);
  	}
  }
  
  static void
  cras_bt_device_start_new_conn_watch_timer(struct cras_bt_device *device)
  {
  	struct cras_tm *tm = cras_system_state_get_tm();
  
  	if (device->conn_watch_timer) {
  		cras_tm_cancel_timer(tm, device->conn_watch_timer);
  	}
  	device->conn_watch_retries = CONN_WATCH_MAX_RETRIES;
  	device->conn_watch_timer = cras_tm_create_timer(
  		tm, CONN_WATCH_PERIOD_MS, bt_device_conn_watch_cb, device);
  }
  
  static void bt_device_cancel_suspend(struct cras_bt_device *device);
  
  void cras_bt_device_set_connected(struct cras_bt_device *device, int value)
  {
  	struct cras_tm *tm = cras_system_state_get_tm();
  	if (device->connected || value)
  		BTLOG(btlog, BT_DEV_CONNECTED_CHANGE, device->profiles, value);
  
  	if (device->connected && !value) {
  		cras_bt_profile_on_device_disconnected(device);
  		/* Device is disconnected, resets connected profiles and the
  		 * suspend timer which scheduled earlier. */
  		device->connected_profiles = 0;
  		bt_device_cancel_suspend(device);
  	}
  
  	device->connected = value;
  
  	if (!device->connected && device->conn_watch_timer) {
  		cras_tm_cancel_timer(tm, device->conn_watch_timer);
  		device->conn_watch_timer = NULL;
  	}
  }
  
  void cras_bt_device_notify_profile_dropped(struct cras_bt_device *device,
  					   enum cras_bt_device_profile profile)
  {
  	device->connected_profiles &= !profile;
  
  	/* Do nothing if device already disconnected. */
  	if (!device->connected)
  		return;
  
  	/* If any profile, a2dp or hfp/hsp, has dropped for some reason,
  	 * we shall make sure this device is fully disconnected within
  	 * given time so that user does not see a headset stay connected
  	 * but works with partial function.
  	 */
  	bt_device_schedule_suspend(device, PROFILE_DROP_SUSPEND_DELAY_MS,
  				   UNEXPECTED_PROFILE_DROP);
  }
  
  /*
   * Check if the uuid is of a new audio profile that isn't listed
   * as supported by device.
   * Args:
   *    device - The BT device holding supported profiles bitmap.
   *    uuid - UUID string from the device properties notified by BlueZ.
   * Returns:
   *    True if uuid is a new audio profiles not already supported by device.
   */
  int cras_bt_device_add_supported_profiles(struct cras_bt_device *device,
  					  const char *uuid)
  {
  	enum cras_bt_device_profile profile =
  		cras_bt_device_profile_from_uuid(uuid);
  
  	if (profile == 0)
  		return 0;
  
  	/* Do nothing if this profile is not new. */
  	if (device->profiles & profile)
  		return 0;
  
  	/* Log this event as we might need to re-intialize the BT audio nodes
  	 * if new audio profile is reported for already connected device. */
  	if (device->connected && (profile & CRAS_SUPPORTED_PROFILES))
  		BTLOG(btlog, BT_NEW_AUDIO_PROFILE_AFTER_CONNECT,
  		      device->profiles, profile);
  	device->profiles |= profile;
  	cras_bt_device_log_profile(device, profile);
  
  	return (profile & CRAS_SUPPORTED_PROFILES);
  }
  
  void cras_bt_device_update_properties(struct cras_bt_device *device,
  				      DBusMessageIter *properties_array_iter,
  				      DBusMessageIter *invalidated_array_iter)
  {
  	int watch_needed = 0;
  	while (dbus_message_iter_get_arg_type(properties_array_iter) !=
  	       DBUS_TYPE_INVALID) {
  		DBusMessageIter properties_dict_iter, variant_iter;
  		const char *key;
  		int type;
  
  		dbus_message_iter_recurse(properties_array_iter,
  					  &properties_dict_iter);
  
  		dbus_message_iter_get_basic(&properties_dict_iter, &key);
  		dbus_message_iter_next(&properties_dict_iter);
  
  		dbus_message_iter_recurse(&properties_dict_iter, &variant_iter);
  		type = dbus_message_iter_get_arg_type(&variant_iter);
  
  		if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) {
  			const char *value;
  
  			dbus_message_iter_get_basic(&variant_iter, &value);
  
  			if (strcmp(key, "Adapter") == 0) {
  				free(device->adapter_obj_path);
  				device->adapter_obj_path = strdup(value);
  			} else if (strcmp(key, "Address") == 0) {
  				free(device->address);
  				device->address = strdup(value);
  			} else if (strcmp(key, "Alias") == 0) {
  				free(device->name);
  				device->name = strdup(value);
  			}
  
  		} else if (type == DBUS_TYPE_UINT32) {
  			uint32_t value;
  
  			dbus_message_iter_get_basic(&variant_iter, &value);
  
  			if (strcmp(key, "Class") == 0)
  				device->bluetooth_class = value;
  
  		} else if (type == DBUS_TYPE_BOOLEAN) {
  			int value;
  
  			dbus_message_iter_get_basic(&variant_iter, &value);
  
  			if (strcmp(key, "Paired") == 0) {
  				device->paired = value;
  			} else if (strcmp(key, "Trusted") == 0) {
  				device->trusted = value;
  			} else if (strcmp(key, "Connected") == 0) {
  				cras_bt_device_set_connected(device, value);
  				watch_needed = device->connected &&
  					       cras_bt_device_supports_profile(
  						       device,
  						       CRAS_SUPPORTED_PROFILES);
  			}
  
  		} else if (strcmp(dbus_message_iter_get_signature(&variant_iter),
  				  "as") == 0 &&
  			   strcmp(key, "UUIDs") == 0) {
  			DBusMessageIter uuid_array_iter;
  
  			dbus_message_iter_recurse(&variant_iter,
  						  &uuid_array_iter);
  			while (dbus_message_iter_get_arg_type(
  				       &uuid_array_iter) != DBUS_TYPE_INVALID) {
  				const char *uuid;
  
  				dbus_message_iter_get_basic(&uuid_array_iter,
  							    &uuid);
  
  				/*
  				 * If updated properties includes new audio
  				 * profile, and device is connected, we need
  				 * to start connection watcher. This is needed
  				 * because on some bluetooth device, supported
  				 * profiles do not present when device
  				 * interface is added and they are updated
  				 * later.
  				 */
  				if (cras_bt_device_add_supported_profiles(
  					    device, uuid))
  					watch_needed = device->connected;
  
  				dbus_message_iter_next(&uuid_array_iter);
  			}
  		}
  
  		dbus_message_iter_next(properties_array_iter);
  	}
  
  	while (invalidated_array_iter &&
  	       dbus_message_iter_get_arg_type(invalidated_array_iter) !=
  		       DBUS_TYPE_INVALID) {
  		const char *key;
  
  		dbus_message_iter_get_basic(invalidated_array_iter, &key);
  
  		if (strcmp(key, "Adapter") == 0) {
  			free(device->adapter_obj_path);
  			device->adapter_obj_path = NULL;
  		} else if (strcmp(key, "Address") == 0) {
  			free(device->address);
  			device->address = NULL;
  		} else if (strcmp(key, "Alias") == 0) {
  			free(device->name);
  			device->name = NULL;
  		} else if (strcmp(key, "Class") == 0) {
  			device->bluetooth_class = 0;
  		} else if (strcmp(key, "Paired") == 0) {
  			device->paired = 0;
  		} else if (strcmp(key, "Trusted") == 0) {
  			device->trusted = 0;
  		} else if (strcmp(key, "Connected") == 0) {
  			device->connected = 0;
  		} else if (strcmp(key, "UUIDs") == 0) {
  			device->profiles = 0;
  		}
  
  		dbus_message_iter_next(invalidated_array_iter);
  	}
  
  	if (watch_needed)
  		cras_bt_device_start_new_conn_watch_timer(device);
  }
  
  /* Converts bluetooth address string into sockaddr structure. The address
   * string is expected of the form 1A:2B:3C:4D:5E:6F, and each of the six
   * hex values will be parsed into sockaddr in inverse order.
   * Args:
   *    str - The string version of bluetooth address
   *    addr - The struct to be filled with converted address
   */
  static int bt_address(const char *str, struct sockaddr *addr)
  {
  	int i;
  
  	if (strlen(str) != 17) {
  		syslog(LOG_ERR, "Invalid bluetooth address %s", str);
  		return -1;
  	}
  
  	memset(addr, 0, sizeof(*addr));
  	addr->sa_family = AF_BLUETOOTH;
  	for (i = 5; i >= 0; i--) {
  		addr->sa_data[i] = (unsigned char)strtol(str, NULL, 16);
  		str += 3;
  	}
  
  	return 0;
  }
  
  /* Apply codec specific settings to the socket fd. */
  static int apply_codec_settings(int fd, uint8_t codec)
  {
  	struct bt_voice voice;
  	uint32_t pkt_status;
  
  	memset(&voice, 0, sizeof(voice));
  	if (codec == HFP_CODEC_ID_CVSD)
  		return 0;
  
  	if (codec != HFP_CODEC_ID_MSBC) {
  		syslog(LOG_ERR, "Unsupported codec %d", codec);
  		return -1;
  	}
  
  	voice.setting = BT_VOICE_TRANSPARENT;
  
  	if (setsockopt(fd, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)) <
  	    0) {
  		syslog(LOG_ERR, "Failed to apply voice setting");
  		return -1;
  	}
  
  	pkt_status = 1;
  	if (setsockopt(fd, SOL_BLUETOOTH, BT_PKT_STATUS, &pkt_status,
  		       sizeof(pkt_status))) {
  		syslog(LOG_ERR, "Failed to enable BT_PKT_STATUS");
  	}
  	return 0;
  }
  
  int cras_bt_device_sco_connect(struct cras_bt_device *device, int codec)
  {
  	int sk = 0, err;
  	struct sockaddr addr;
  	struct cras_bt_adapter *adapter;
  	struct timespec timeout = { 1, 0 };
  	struct pollfd pollfd;
  
  	adapter = cras_bt_device_adapter(device);
  	if (!adapter) {
  		syslog(LOG_ERR, "No adapter found for device %s at SCO connect",
  		       cras_bt_device_object_path(device));
  		goto error;
  	}
  
  	sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET | O_NONBLOCK | SOCK_CLOEXEC,
  		    BTPROTO_SCO);
  	if (sk < 0) {
  		syslog(LOG_ERR, "Failed to create socket: %s (%d)",
  		       strerror(errno), errno);
  		cras_server_metrics_hfp_sco_connection_error(
  			CRAS_METRICS_SCO_SKT_OPEN_ERROR);
  		return -errno;
  	}
  
  	/* Bind to local address */
  	if (bt_address(cras_bt_adapter_address(adapter), &addr))
  		goto error;
  	if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
  		syslog(LOG_ERR, "Failed to bind socket: %s (%d)",
  		       strerror(errno), errno);
  		goto error;
  	}
  
  	/* Connect to remote in nonblocking mode */
  	fcntl(sk, F_SETFL, O_NONBLOCK);
  
  	if (bt_address(cras_bt_device_address(device), &addr))
  		goto error;
  
  	err = apply_codec_settings(sk, codec);
  	if (err)
  		goto error;
  
  	err = connect(sk, (struct sockaddr *)&addr, sizeof(addr));
  	if (err && errno != EINPROGRESS) {
  		syslog(LOG_ERR, "Failed to connect: %s (%d)", strerror(errno),
  		       errno);
  		cras_server_metrics_hfp_sco_connection_error(
  			CRAS_METRICS_SCO_SKT_CONNECT_ERROR);
  		goto error;
  	}
  
  	pollfd.fd = sk;
  	pollfd.events = POLLOUT;
  
  	err = ppoll(&pollfd, 1, &timeout, NULL);
  	if (err <= 0) {
  		syslog(LOG_ERR, "Connect SCO: poll for writable timeout");
  		cras_server_metrics_hfp_sco_connection_error(
  			CRAS_METRICS_SCO_SKT_POLL_TIMEOUT);
  		goto error;
  	}
  
  	if (pollfd.revents & (POLLERR | POLLHUP)) {
  		syslog(LOG_ERR,
  		       "SCO socket error, revents: %u. Suspend in %u seconds",
  		       pollfd.revents, SCO_SUSPEND_DELAY_MS);
  		cras_server_metrics_hfp_sco_connection_error(
  			CRAS_METRICS_SCO_SKT_POLL_ERR_HUP);
  		bt_device_schedule_suspend(device, SCO_SUSPEND_DELAY_MS,
  					   HFP_SCO_SOCKET_ERROR);
  		goto error;
  	}
  
  	cras_server_metrics_hfp_sco_connection_error(
  		CRAS_METRICS_SCO_SKT_SUCCESS);
  	BTLOG(btlog, BT_SCO_CONNECT, 1, sk);
  	return sk;
  
  error:
  	BTLOG(btlog, BT_SCO_CONNECT, 0, sk);
  	if (sk)
  		close(sk);
  	return -1;
  }
  
  int cras_bt_device_sco_packet_size(struct cras_bt_device *device,
  				   int sco_socket, int codec)
  {
  	struct sco_options so;
  	socklen_t len = sizeof(so);
  	struct cras_bt_adapter *adapter;
  	uint32_t wbs_pkt_len = 0;
  	socklen_t optlen = sizeof(wbs_pkt_len);
  
  	adapter = cras_bt_adapter_get(device->adapter_obj_path);
  
  	if (cras_bt_adapter_on_usb(adapter)) {
  		if (codec == HFP_CODEC_ID_MSBC) {
  			/* BT_SNDMTU and BT_RCVMTU return the same value. */
  			if (getsockopt(sco_socket, SOL_BLUETOOTH, BT_SNDMTU,
  				       &wbs_pkt_len, &optlen))
  				syslog(LOG_ERR, "Failed to get BT_SNDMTU");
  
  			return (wbs_pkt_len > 0) ? wbs_pkt_len :
  						   USB_MSBC_PKT_SIZE;
  		} else {
  			return USB_CVSD_PKT_SIZE;
  		}
  	}
  
  	/* For non-USB cases, query the SCO MTU from driver. */
  	if (getsockopt(sco_socket, SOL_SCO, SCO_OPTIONS, &so, &len) < 0) {
  		syslog(LOG_ERR, "Get SCO options error: %s", strerror(errno));
  		return DEFAULT_SCO_PKT_SIZE;
  	}
  	return so.mtu;
  }
  
  void cras_bt_device_set_use_hardware_volume(struct cras_bt_device *device,
  					    int use_hardware_volume)
  {
  	struct cras_iodev *iodev;
  
  	device->use_hardware_volume = use_hardware_volume;
  	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
  	if (iodev)
  		iodev->software_volume_needed = !use_hardware_volume;
  }
  
  int cras_bt_device_get_use_hardware_volume(struct cras_bt_device *device)
  {
  	return device->use_hardware_volume;
  }
  
  static void init_bt_device_msg(struct bt_device_msg *msg,
  			       enum BT_DEVICE_COMMAND cmd,
  			       struct cras_bt_device *device,
  			       struct cras_iodev *dev, unsigned int arg1,
  			       unsigned int arg2)
  {
  	memset(msg, 0, sizeof(*msg));
  	msg->header.type = CRAS_MAIN_BT;
  	msg->header.length = sizeof(*msg);
  	msg->cmd = cmd;
  	msg->device = device;
  	msg->dev = dev;
  	msg->arg1 = arg1;
  	msg->arg2 = arg2;
  }
  
  int cras_bt_device_cancel_suspend(struct cras_bt_device *device)
  {
  	struct bt_device_msg msg;
  	int rc;
  
  	init_bt_device_msg(&msg, BT_DEVICE_CANCEL_SUSPEND, device, NULL, 0, 0);
  	rc = cras_main_message_send((struct cras_main_message *)&msg);
  	return rc;
  }
  
  int cras_bt_device_schedule_suspend(
  	struct cras_bt_device *device, unsigned int msec,
  	enum cras_bt_device_suspend_reason suspend_reason)
  {
  	struct bt_device_msg msg;
  	int rc;
  
  	init_bt_device_msg(&msg, BT_DEVICE_SCHEDULE_SUSPEND, device, NULL, msec,
  			   suspend_reason);
  	rc = cras_main_message_send((struct cras_main_message *)&msg);
  	return rc;
  }
  
  /* This diagram describes how the profile switching happens. When
   * certain conditions met, bt iodev will call the APIs below to interact
   * with main thread to switch to another active profile.
   *
   * Audio thread:
   *  +--------------------------------------------------------------+
   *  | bt iodev                                                     |
   *  |              +------------------+    +-----------------+     |
   *  |              | condition met to |    | open, close, or |     |
   *  |           +--| change profile   |<---| append profile  |<--+ |
   *  |           |  +------------------+    +-----------------+   | |
   *  +-----------|------------------------------------------------|-+
   *              |                                                |
   * Main thread: |
   *  +-----------|------------------------------------------------|-+
   *  |           |                                                | |
   *  |           |      +------------+     +----------------+     | |
   *  |           +----->| set active |---->| switch profile |-----+ |
   *  |                  | profile    |     +----------------+       |
   *  | bt device        +------------+                              |
   *  +--------------------------------------------------------------+
   */
  int cras_bt_device_switch_profile_enable_dev(struct cras_bt_device *device,
  					     struct cras_iodev *bt_iodev)
  {
  	struct bt_device_msg msg;
  	int rc;
  
  	init_bt_device_msg(&msg, BT_DEVICE_SWITCH_PROFILE_ENABLE_DEV, device,
  			   bt_iodev, 0, 0);
  	rc = cras_main_message_send((struct cras_main_message *)&msg);
  	return rc;
  }
  
  int cras_bt_device_switch_profile(struct cras_bt_device *device,
  				  struct cras_iodev *bt_iodev)
  {
  	struct bt_device_msg msg;
  	int rc;
  
  	init_bt_device_msg(&msg, BT_DEVICE_SWITCH_PROFILE, device, bt_iodev, 0,
  			   0);
  	rc = cras_main_message_send((struct cras_main_message *)&msg);
  	return rc;
  }
  
  static void profile_switch_delay_cb(struct cras_timer *timer, void *arg)
  {
  	struct cras_bt_device *device = (struct cras_bt_device *)arg;
  	struct cras_iodev *iodev;
  
  	device->switch_profile_timer = NULL;
  	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
  	if (!iodev)
  		return;
  
  	/*
  	 * During the |PROFILE_SWITCH_DELAY_MS| time interval, BT iodev could
  	 * have been enabled by others, and its active profile may have changed.
  	 * If iodev has been enabled, that means it has already picked up a
  	 * reasonable profile to use and audio thread is accessing iodev now.
  	 * We should NOT call into update_active_node from main thread
  	 * because that may mess up the active node content.
  	 */
  	iodev->update_active_node(iodev, 0, 1);
  	cras_iodev_list_resume_dev(iodev->info.idx);
  }
  
  static void bt_device_switch_profile_with_delay(struct cras_bt_device *device,
  						unsigned int delay_ms)
  {
  	struct cras_tm *tm = cras_system_state_get_tm();
  
  	if (device->switch_profile_timer) {
  		cras_tm_cancel_timer(tm, device->switch_profile_timer);
  		device->switch_profile_timer = NULL;
  	}
  	device->switch_profile_timer = cras_tm_create_timer(
  		tm, delay_ms, profile_switch_delay_cb, device);
  }
  
  /* Switches associated bt iodevs to use the active profile. This is
   * achieved by close the iodevs, update their active nodes, and then
   * finally reopen them. */
  static void bt_device_switch_profile(struct cras_bt_device *device,
  				     struct cras_iodev *bt_iodev,
  				     int enable_dev)
  {
  	struct cras_iodev *iodev;
  	int dir;
  
  	/* If a bt iodev is active, temporarily force close it.
  	 * Note that we need to check all bt_iodevs for the situation that both
  	 * input and output are active while switches from HFP/HSP to A2DP.
  	 */
  	for (dir = 0; dir < CRAS_NUM_DIRECTIONS; dir++) {
  		iodev = device->bt_iodevs[dir];
  		if (!iodev)
  			continue;
  		cras_iodev_list_suspend_dev(iodev->info.idx);
  	}
  
  	for (dir = 0; dir < CRAS_NUM_DIRECTIONS; dir++) {
  		iodev = device->bt_iodevs[dir];
  		if (!iodev)
  			continue;
  
  		/* If the iodev was active or this profile switching is
  		 * triggered at opening iodev, add it to active dev list.
  		 * However for the output iodev, adding it back to active dev
  		 * list could cause immediate switching from HFP to A2DP if
  		 * there exists an output stream. Certain headset/speaker
  		 * would fail to playback afterwards when the switching happens
  		 * too soon, so put this task in a delayed callback.
  		 */
  		if (dir == CRAS_STREAM_INPUT) {
  			iodev->update_active_node(iodev, 0, 1);
  			cras_iodev_list_resume_dev(iodev->info.idx);
  		} else {
  			bt_device_switch_profile_with_delay(
  				device, PROFILE_SWITCH_DELAY_MS);
  		}
  	}
  }
  
  static void bt_device_suspend_cb(struct cras_timer *timer, void *arg)
  {
  	struct cras_bt_device *device = (struct cras_bt_device *)arg;
  
  	BTLOG(btlog, BT_DEV_SUSPEND_CB, device->profiles,
  	      device->suspend_reason);
  	device->suspend_timer = NULL;
  
  	/* Error log the reason so we can track them in user reports. */
  	switch (device->suspend_reason) {
  	case A2DP_LONG_TX_FAILURE:
  		syslog(LOG_ERR, "Suspend dev: A2DP long Tx failure");
  		break;
  	case A2DP_TX_FATAL_ERROR:
  		syslog(LOG_ERR, "Suspend dev: A2DP Tx fatal error");
  		break;
  	case CONN_WATCH_TIME_OUT:
  		syslog(LOG_ERR, "Suspend dev: Conn watch times out");
  		break;
  	case HFP_SCO_SOCKET_ERROR:
  		syslog(LOG_ERR, "Suspend dev: SCO socket error");
  		break;
  	case HFP_AG_START_FAILURE:
  		syslog(LOG_ERR, "Suspend dev: HFP AG start failure");
  		break;
  	case UNEXPECTED_PROFILE_DROP:
  		syslog(LOG_ERR, "Suspend dev: Unexpected profile drop");
  		break;
  	}
  
  	cras_a2dp_suspend_connected_device(device);
  	cras_hfp_ag_suspend_connected_device(device);
  	cras_bt_device_disconnect(device->conn, device);
  }
  
  static void
  bt_device_schedule_suspend(struct cras_bt_device *device, unsigned int msec,
  			   enum cras_bt_device_suspend_reason suspend_reason)
  {
  	struct cras_tm *tm = cras_system_state_get_tm();
  
  	if (device->suspend_timer)
  		return;
  	device->suspend_reason = suspend_reason;
  	device->suspend_timer =
  		cras_tm_create_timer(tm, msec, bt_device_suspend_cb, device);
  }
  
  static void bt_device_cancel_suspend(struct cras_bt_device *device)
  {
  	struct cras_tm *tm = cras_system_state_get_tm();
  	if (device->suspend_timer == NULL)
  		return;
  	cras_tm_cancel_timer(tm, device->suspend_timer);
  	device->suspend_timer = NULL;
  }
  
  static void bt_device_process_msg(struct cras_main_message *msg, void *arg)
  {
  	struct bt_device_msg *bt_msg = (struct bt_device_msg *)msg;
  	struct cras_bt_device *device = NULL;
  
  	DL_FOREACH (devices, device) {
  		if (device == bt_msg->device)
  			break;
  	}
  
  	/* Do nothing if target device no longer exists. */
  	if (device == NULL)
  		return;
  
  	switch (bt_msg->cmd) {
  	case BT_DEVICE_SWITCH_PROFILE:
  		bt_device_switch_profile(bt_msg->device, bt_msg->dev, 0);
  		break;
  	case BT_DEVICE_SWITCH_PROFILE_ENABLE_DEV:
  		bt_device_switch_profile(bt_msg->device, bt_msg->dev, 1);
  		break;
  	case BT_DEVICE_SCHEDULE_SUSPEND:
  		bt_device_schedule_suspend(bt_msg->device, bt_msg->arg1,
  					   bt_msg->arg2);
  		break;
  	case BT_DEVICE_CANCEL_SUSPEND:
  		bt_device_cancel_suspend(bt_msg->device);
  		break;
  	default:
  		break;
  	}
  }
  
  void cras_bt_device_start_monitor()
  {
  	cras_main_message_add_handler(CRAS_MAIN_BT, bt_device_process_msg,
  				      NULL);
  }
  
  void cras_bt_device_update_hardware_volume(struct cras_bt_device *device,
  					   int volume)
  {
  	struct cras_iodev *iodev;
  
  	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
  	if (iodev == NULL)
  		return;
  
  	/* Check if this BT device is okay to use hardware volume. If not
  	 * then ignore the reported volume change event.
  	 */
  	if (!cras_bt_device_get_use_hardware_volume(device))
  		return;
  
  	iodev->active_node->volume = volume;
  	cras_iodev_list_notify_node_volume(iodev->active_node);
  }
  
  int cras_bt_device_get_sco(struct cras_bt_device *device, int codec)
  {
  	if (device->sco_ref_count == 0) {
  		device->sco_fd = cras_bt_device_sco_connect(device, codec);
  		if (device->sco_fd < 0)
  			return device->sco_fd;
  	}
  
  	++device->sco_ref_count;
  	return 0;
  }
  
  void cras_bt_device_put_sco(struct cras_bt_device *device)
  {
  	if (device->sco_ref_count == 0)
  		return;
  
  	if (--device->sco_ref_count == 0)
  		close(device->sco_fd);
  }
  