Bug 800695 - Final version: Audio route is set to sco before connecting to a bluetooth headset, r=qdot
authorGina Yeh <gyeh@mozilla.com>
Mon, 22 Oct 2012 18:34:15 +0800
changeset 111153 c42c9ad0d5b4dd1374d3fca6ef10f77f38c89bca
parent 111152 aaa5820e6d4f881446b29e50e9879eeec6bc3109
child 111154 4f3a1bcdc429425a6edbd1f680ce6fa17ff67c4e
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersqdot
bugs800695
milestone19.0a1
Bug 800695 - Final version: Audio route is set to sco before connecting to a bluetooth headset, r=qdot
dom/bluetooth/BluetoothHfpManager.cpp
dom/bluetooth/BluetoothHfpManager.h
dom/bluetooth/BluetoothScoManager.cpp
dom/bluetooth/BluetoothScoManager.h
dom/bluetooth/BluetoothUnixSocketConnector.cpp
dom/bluetooth/linux/BluetoothDBusService.cpp
--- a/dom/bluetooth/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/BluetoothHfpManager.cpp
@@ -16,30 +16,88 @@
 
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "nsContentUtils.h"
 #include "nsIAudioManager.h"
 #include "nsIObserverService.h"
 #include "nsIRadioInterfaceLayer.h"
-#include "nsVariant.h"
 
 #include <unistd.h> /* usleep() */
 
 #define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
-#define BLUETOOTH_SCO_STATUS_CHANGED "bluetooth-sco-status-changed"
 #define AUDIO_VOLUME_MASTER "audio.volume.master"
 #define HANDSFREE_UUID mozilla::dom::bluetooth::BluetoothServiceUuidStr::Handsfree
 #define HEADSET_UUID mozilla::dom::bluetooth::BluetoothServiceUuidStr::Headset
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
 
+/* CallState for sCINDItems[CINDType::CALL].value
+ * - NO_CALL: there are no calls in progress
+ * - IN_PROGRESS: at least one call is in progress
+ */
+enum CallState {
+  NO_CALL,
+  IN_PROGRESS
+};
+
+/* CallSetupState for sCINDItems[CINDType::CALLSETUP].value
+ * - NO_CALLSETUP: not currently in call set up
+ * - INCOMING: an incoming call process ongoing
+ * - OUTGOING: an outgoing call set up is ongoing
+ * - OUTGOING_ALERTING: remote party being alerted in an outgoing call
+ */
+enum CallSetupState {
+  NO_CALLSETUP,
+  INCOMING,
+  OUTGOING,
+  OUTGOING_ALERTING
+};
+
+/* CallHeldState for sCINDItems[CINDType::CALLHELD].value
+ * - NO_CALLHELD: no calls held
+ * - ONHOLD_ACTIVE: both an active and a held call
+ * - ONHOLD_NOACTIVE: call on hold, no active call
+ */
+enum CallHeldState {
+  NO_CALLHELD,
+  ONHOLD_ACTIVE,
+  ONHOLD_NOACTIVE
+};
+
+typedef struct {
+  const char* name;
+  const char* range;
+  int value;
+} CINDItem;
+
+enum CINDType {
+  BATTCHG = 1,
+  SIGNAL,
+  SERVICE,
+  CALL,
+  CALLSETUP,
+  CALLHELD,
+  ROAM,
+};
+
+static CINDItem sCINDItems[] = {
+  {},
+  {"battchg", "0-5", 5},
+  {"signal", "0-5", 5},
+  {"service", "0,1", 1},
+  {"call", "0,1", CallState::NO_CALL},
+  {"callsetup", "0-3", CallSetupState::NO_CALLSETUP},
+  {"callheld", "0-2", CallHeldState::NO_CALLHELD},
+  {"roam", "0,1", 0}
+};
+
 class mozilla::dom::bluetooth::BluetoothHfpManagerObserver : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   BluetoothHfpManagerObserver()
   {
@@ -93,16 +151,17 @@ namespace {
 NS_IMPL_ISUPPORTS1(BluetoothHfpManagerObserver, nsIObserver)
 
 NS_IMETHODIMP
 BluetoothHfpManagerObserver::Observe(nsISupports* aSubject,
                                      const char* aTopic,
                                      const PRUnichar* aData)
 {
   MOZ_ASSERT(gBluetoothHfpManager);
+
   if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
     return gBluetoothHfpManager->HandleVolumeChanged(nsDependentString(aData));
   } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     return gBluetoothHfpManager->HandleShutdown();
   }
 
   MOZ_ASSERT(false, "BluetoothHfpManager got unexpected topic!");
   return NS_ERROR_UNEXPECTED;
@@ -126,69 +185,52 @@ public:
       usleep(kRingInterval);
     }
 
     return NS_OK;
   }
 };
 
 void
-OpenScoSocket(const nsAString& aDevicePath)
+OpenScoSocket(const nsAString& aDeviceAddress)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothScoManager* sco = BluetoothScoManager::Get();
   if (!sco) {
     NS_WARNING("BluetoothScoManager is not available!");
     return;
   }
 
-  if (!sco->Connect(aDevicePath)) {
+  if (!sco->Connect(aDeviceAddress)) {
     NS_WARNING("Failed to create a sco socket!");
   }
 }
 
 void
 CloseScoSocket()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsCOMPtr<nsIAudioManager> am = do_GetService("@mozilla.org/telephony/audiomanager;1");
-  if (!am) {
-    NS_WARNING("Failed to get AudioManager Service!");
-    return;
-  }
-  am->SetForceForUse(am->USE_COMMUNICATION, am->FORCE_NONE);
-
   BluetoothScoManager* sco = BluetoothScoManager::Get();
   if (!sco) {
     NS_WARNING("BluetoothScoManager is not available!");
     return;
   }
-
-  if (sco->GetConnected()) {
-    nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
-    if (obs) {
-      if (NS_FAILED(obs->NotifyObservers(nullptr, BLUETOOTH_SCO_STATUS_CHANGED, nullptr))) {
-        NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
-        return;
-      }
-    }
-    sco->Disconnect();
-  }
+  sco->Disconnect();
 }
 
 BluetoothHfpManager::BluetoothHfpManager()
   : mCurrentVgs(-1)
   , mCurrentCallIndex(0)
   , mCurrentCallState(nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED)
-  , mCall(0)
-  , mCallSetup(0)
-  , mCallHeld(0)
 {
+  sCINDItems[CINDType::CALL].value = CallState::NO_CALL;
+  sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP;
+  sCINDItems[CINDType::CALLHELD].value = CallHeldState::NO_CALLHELD;
 }
 
 bool
 BluetoothHfpManager::Init()
 {
   sHfpObserver = new BluetoothHfpManagerObserver();
   if (!sHfpObserver->Init()) {
     NS_WARNING("Cannot set up Hfp Observers!");
@@ -261,29 +303,32 @@ BluetoothHfpManager::Get()
     return nullptr;
   }
 
   gBluetoothHfpManager = manager;
   return gBluetoothHfpManager;
 }
 
 void
-BluetoothHfpManager::NotifySettings(const bool aConnected)
+BluetoothHfpManager::NotifySettings()
 {
   nsString type, name;
   BluetoothValue v;
   InfallibleTArray<BluetoothNamedValue> parameters;
   type.AssignLiteral("bluetooth-hfp-status-changed");
 
   name.AssignLiteral("connected");
-  v = aConnected;
+  uint32_t status = GetConnectionStatus();
+  v = status;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("address");
-  v = GetAddressFromObjectPath(mDevicePath);
+  nsString address;
+  GetSocketAddr(address);
+  v = address;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   if (!BroadcastSystemMessage(type, parameters)) {
     NS_WARNING("Failed to broadcast system message to dialer");
     return;
   }
 }
 
@@ -304,16 +349,20 @@ BluetoothHfpManager::NotifyDialer(const 
   }
 }
 
 nsresult
 BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (GetConnectionStatus() != SocketConnectionStatus::SOCKET_CONNECTED) {
+    return NS_OK;
+  }
+
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"volumeup", "value":1.0}
   //  {"key":"volumedown", "value":0.2}
 
   JSContext* cx = nsContentUtils::GetSafeJSContext();
   if (!cx) {
     return NS_OK;
   }
@@ -355,118 +404,66 @@ BluetoothHfpManager::HandleVolumeChanged
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (!value.isNumber()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   // AG volume range: [0.0, 1.0]
+  // HS volume range: [0, 15]
   float volume = value.toNumber();
-
-  // HS volume range: [0, 15]
   mCurrentVgs = ceil(volume * 15);
 
-  nsDiscriminatedUnion du;
-  du.mType = 0;
-  du.u.mInt8Value = mCurrentVgs;
-
-  nsCString vgs;
-  if (NS_FAILED(nsVariant::ConvertToACString(du, vgs))) {
-    NS_WARNING("Failed to convert volume to string");
-    return NS_ERROR_FAILURE;
-  }
-
-  nsAutoCString newVgs;
-  newVgs += "+VGS: ";
-  newVgs += vgs;
-
-  SendLine(newVgs.get());
+  SendCommand("+VGS: ", mCurrentVgs);
 
   return NS_OK;
 }
 
 nsresult
 BluetoothHfpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   gInShutdown = true;
   CloseSocket();
   gBluetoothHfpManager = nullptr;
   return NS_OK;
 }
 
-bool
-AppendIntegerToString(nsAutoCString& aString, int aValue)
-{
-  nsDiscriminatedUnion du;
-  du.mType = 0;
-  du.u.mInt8Value = aValue;
-
-  nsCString temp;
-  if (NS_FAILED(nsVariant::ConvertToACString(du, temp))) {
-    NS_WARNING("Failed to convert mCall/mCallSetup/mCallHeld to string");
-    return false;
-  }
-  aString += temp;
-  aString += ",";
-  return true;
-}
-
 // Virtual function of class SocketConsumer
 void
 BluetoothHfpManager::ReceiveSocketData(UnixSocketRawData* aMessage)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   const char* msg = (const char*)aMessage->mData;
 
   // For more information, please refer to 4.34.1 "Bluetooth Defined AT
   // Capabilities" in Bluetooth hands-free profile 1.6
   if (!strncmp(msg, "AT+BRSF=", 8)) {
-    SendLine("+BRSF: 23");
+    SendCommand("+BRSF: ", 23);
     SendLine("OK");
   } else if (!strncmp(msg, "AT+CIND=?", 9)) {
-    nsAutoCString cindRange;
-
-    cindRange += "+CIND: ";
-    cindRange += "(\"battchg\",(0-5)),";
-    cindRange += "(\"signal\",(0-5)),";
-    cindRange += "(\"service\",(0,1)),";
-    cindRange += "(\"call\",(0,1)),";
-    cindRange += "(\"callsetup\",(0-3)),";
-    cindRange += "(\"callheld\",(0-2)),";
-    cindRange += "(\"roam\",(0,1))";
-
-    SendLine(cindRange.get());
+    // Asking for CIND range
+    SendCommand("+CIND: ", 0);
     SendLine("OK");
   } else if (!strncmp(msg, "AT+CIND?", 8)) {
-    nsAutoCString cind;
-    cind += "+CIND: 5,5,1,";
-
-    if (!AppendIntegerToString(cind, mCall) ||
-        !AppendIntegerToString(cind, mCallSetup) ||
-        !AppendIntegerToString(cind, mCallHeld)) {
-      NS_WARNING("Failed to convert mCall/mCallSetup/mCallHeld to string");
-    } 
-    cind += "0";
-
-    SendLine(cind.get());
+    // Asking for CIND value
+    SendCommand("+CIND: ", 1);
     SendLine("OK");
   } else if (!strncmp(msg, "AT+CMER=", 8)) {
     // SLC establishment
-    NotifySettings(true);
     SendLine("OK");
   } else if (!strncmp(msg, "AT+CHLD=?", 9)) {
     SendLine("+CHLD: (0,1,2,3)");
     SendLine("OK");
   } else if (!strncmp(msg, "AT+CHLD=", 8)) {
     SendLine("OK");
   } else if (!strncmp(msg, "AT+VGS=", 7)) {
-    // VGS range: [0, 15]
+    // HS volume range: [0, 15]
     int newVgs = msg[7] - '0';
 
     if (strlen(msg) > 8) {
       newVgs *= 10;
       newVgs += (msg[8] - '0');
     }
 
 #ifdef DEBUG
@@ -524,51 +521,57 @@ BluetoothHfpManager::ReceiveSocketData(U
     warningMsg.Append(msg);
     NS_WARNING(warningMsg.get());
 #endif
     SendLine("OK");
   }
 }
 
 bool
-BluetoothHfpManager::Connect(const nsAString& aDeviceObjectPath,
+BluetoothHfpManager::Connect(const nsAString& aDevicePath,
                              const bool aIsHandsfree,
                              BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gInShutdown) {
     MOZ_ASSERT(false, "Connect called while in shutdown!");
     return false;
   }
 
+  if (GetConnectionStatus() == SocketConnectionStatus::SOCKET_CONNECTED) {
+    NS_WARNING("BluetoothHfpManager has connected to a headset/handsfree!");
+    return false;
+  }
+
   CloseSocket();
 
   BluetoothService* bs = BluetoothService::Get();
   if (!bs) {
     NS_WARNING("BluetoothService not available!");
     return false;
   }
-  mDevicePath = aDeviceObjectPath;
 
   nsString serviceUuidStr;
   if (aIsHandsfree) {
     serviceUuidStr = NS_ConvertUTF8toUTF16(HANDSFREE_UUID);
   } else {
     serviceUuidStr = NS_ConvertUTF8toUTF16(HEADSET_UUID);
   }
 
   nsCOMPtr<nsIRILContentHelper> ril =
     do_GetService("@mozilla.org/ril/content-helper;1");
-  NS_ENSURE_TRUE(ril, false);
+  if (!ril) {
+    MOZ_ASSERT("Failed to get RIL Content Helper");
+  }
   ril->EnumerateCalls(mListener->GetCallback());
 
   nsRefPtr<BluetoothReplyRunnable> runnable = aRunnable;
 
-  nsresult rv = bs->GetSocketViaService(aDeviceObjectPath,
+  nsresult rv = bs->GetSocketViaService(aDevicePath,
                                         serviceUuidStr,
                                         BluetoothSocketType::RFCOMM,
                                         true,
                                         false,
                                         this,
                                         runnable);
 
   runnable.forget();
@@ -599,193 +602,253 @@ BluetoothHfpManager::Listen()
                                            false,
                                            this);
   return NS_FAILED(rv) ? false : true;
 }
 
 void
 BluetoothHfpManager::Disconnect()
 {
-  NotifySettings(false);
+  if (GetConnectionStatus() == SocketConnectionStatus::SOCKET_DISCONNECTED) {
+    NS_WARNING("BluetoothHfpManager has disconnected!");
+    return;
+  }
+
   CloseSocket();
-  mCall = 0;
-  mCallSetup = 0;
-  mCallHeld = 0;
   Listen();
 }
 
 bool
 BluetoothHfpManager::SendLine(const char* aMessage)
 {
   const char* kHfpCrlf = "\xd\xa";
   nsAutoCString msg;
 
   msg += kHfpCrlf;
   msg += aMessage;
   msg += kHfpCrlf;
 
   return SendSocketData(msg);
 }
 
+bool
+BluetoothHfpManager::SendCommand(const char* aCommand, const int aValue)
+{
+  nsAutoCString message;
+  int value = aValue;
+  message += aCommand;
+
+  if (!strcmp(aCommand, "+CIEV: ")) {
+    message.AppendInt(aValue);
+    message += ",";
+    switch (aValue) {
+      case CINDType::CALL:
+        message.AppendInt(sCINDItems[CINDType::CALL].value);
+        break;
+      case CINDType::CALLSETUP:
+        message.AppendInt(sCINDItems[CINDType::CALLSETUP].value);
+        break;
+      case CINDType::CALLHELD:
+        message.AppendInt(sCINDItems[CINDType::CALLHELD].value);
+        break;
+      default:
+#ifdef DEBUG
+        NS_WARNING("unexpected CINDType for CIEV command");
+#endif
+        return false;
+    }
+  } else if (!strcmp(aCommand, "+CIND: ")) {
+    if (!aValue) {
+      for (uint8_t i = 1; i < ArrayLength(sCINDItems); i++) {
+        message += "(\"";
+        message += sCINDItems[i].name;
+        message += "\",(";
+        message += sCINDItems[i].range;
+        message += ")";
+        if (i == (ArrayLength(sCINDItems) - 1)) {
+          message +=")";
+          break;
+        }
+        message += "),";
+      }
+    } else {
+      for (uint8_t i = 1; i < ArrayLength(sCINDItems); i++) {
+        message.AppendInt(sCINDItems[i].value);
+        if (i == (ArrayLength(sCINDItems) - 1)) {
+          break;
+        }
+        message += ",";
+      }
+    }
+  } else {
+    message.AppendInt(value);
+  }
+
+  return SendLine(message.get());
+}
+
+void
+BluetoothHfpManager::SetupCIND(int aCallIndex, int aCallState, bool aInitial)
+{
+  nsRefPtr<nsRunnable> sendRingTask;
+  nsString address;
+
+  switch (aCallState) {
+    case nsIRadioInterfaceLayer::CALL_STATE_INCOMING:
+      sCINDItems[CINDType::CALLSETUP].value = CallSetupState::INCOMING;
+      if (!aInitial) {
+        SendCommand("+CIEV: ", CINDType::CALLSETUP);
+      }
+
+      // Start sending RING indicator to HF
+      sStopSendingRingFlag = false;
+      sendRingTask = new SendRingIndicatorTask();
+
+      if (NS_FAILED(sHfpCommandThread->Dispatch(sendRingTask, NS_DISPATCH_NORMAL))) {
+        NS_WARNING("Cannot dispatch ring task!");
+        return;
+      };
+      break;
+    case nsIRadioInterfaceLayer::CALL_STATE_DIALING:
+      sCINDItems[CINDType::CALLSETUP].value = CallSetupState::OUTGOING;
+      if (!aInitial) {
+        SendCommand("+CIEV: ", CINDType::CALLSETUP);
+
+        GetSocketAddr(address);
+        OpenScoSocket(address);
+      }
+      break;
+    case nsIRadioInterfaceLayer::CALL_STATE_ALERTING:
+      sCINDItems[CINDType::CALLSETUP].value = CallSetupState::OUTGOING_ALERTING;
+      if (!aInitial) {
+        SendCommand("+CIEV: ", CINDType::CALLSETUP);
+      }
+      break;
+    case nsIRadioInterfaceLayer::CALL_STATE_CONNECTED:
+      if (aInitial) {
+        sCINDItems[CINDType::CALL].value = CallState::IN_PROGRESS;
+        sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP;
+      } else {
+        switch (mCurrentCallState) {
+          case nsIRadioInterfaceLayer::CALL_STATE_INCOMING:
+            // Incoming call, no break
+            sStopSendingRingFlag = true;
+
+            GetSocketAddr(address);
+            OpenScoSocket(address);
+          case nsIRadioInterfaceLayer::CALL_STATE_ALERTING:
+            // Outgoing call
+            sCINDItems[CINDType::CALL].value = CallState::IN_PROGRESS;
+            SendCommand("+CIEV: ", CINDType::CALL);
+            sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP;
+            SendCommand("+CIEV: ", CINDType::CALLSETUP);
+            break;
+          default:
+#ifdef DEBUG
+            NS_WARNING("Not handling state changed");
+#endif
+            break;
+        }
+      }
+      break;
+    case nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED:
+      if (!aInitial) {
+        switch (mCurrentCallState) {
+          case nsIRadioInterfaceLayer::CALL_STATE_INCOMING:
+            // Incoming call, no break
+            sStopSendingRingFlag = true;
+          case nsIRadioInterfaceLayer::CALL_STATE_DIALING:
+          case nsIRadioInterfaceLayer::CALL_STATE_ALERTING:
+            // Outgoing call
+            sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP;
+            SendCommand("+CIEV: ", CINDType::CALLSETUP);
+            break;
+          case nsIRadioInterfaceLayer::CALL_STATE_CONNECTED:
+            sCINDItems[CINDType::CALL].value = CallState::NO_CALL;
+            SendCommand("+CIEV: ", CINDType::CALL);
+            break;
+          default:
+#ifdef DEBUG
+            NS_WARNING("Not handling state changed");
+#endif
+            break;
+        }
+        CloseScoSocket();
+      }
+      break;
+    default:
+#ifdef DEBUG
+      NS_WARNING("Not handling state changed");
+      sCINDItems[CINDType::CALL].value = CallState::NO_CALL;
+      sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP;
+#endif
+      break;
+  }
+
+  mCurrentCallIndex = aCallIndex;
+  mCurrentCallState = aCallState;
+}
+
 /*
  * EnumerateCallState will be called for each call
  */
 void
 BluetoothHfpManager::EnumerateCallState(int aCallIndex, int aCallState,
                                         const char* aNumber, bool aIsActive)
 {
-  // TODO: enums for mCall, mCallSetup, mCallHeld
-  /* mCall - 0: there are no calls in progress
-   *       - 1: at least one call is in progress
-   * mCallSetup - 0: not currently in call set up
-   *            - 1: an incoming call process ongoing
-   *            - 2: an outgoing call set up is ongoing
-   *            - 3: remote party being alerted in an outgoing call
-   * mCallHeld - 0: no calls held
-   *           - 1: call is placed on hold or active/held calls swapped
-   *           - 2: call o hold, no active call
-   */
-
-  if (mCallHeld == 2 && aIsActive) {
-    mCallHeld = 1;
+  if (aIsActive &&
+      (sCINDItems[CINDType::CALLHELD].value == CallHeldState::ONHOLD_NOACTIVE)) {
+    sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE;
   }
 
-  if (mCall || mCallSetup) {
+  if (sCINDItems[CINDType::CALL].value || sCINDItems[CINDType::CALLSETUP].value) {
     return;
   }
 
-  switch (aCallState) {
-    case nsIRadioInterfaceLayer::CALL_STATE_ALERTING:
-      mCall = 1;
-      mCallSetup = 3;
-      break;
-    case nsIRadioInterfaceLayer::CALL_STATE_INCOMING:
-      mCall = 1;
-      mCallSetup = 1;
-      break;
-    case nsIRadioInterfaceLayer::CALL_STATE_CONNECTING:
-    case nsIRadioInterfaceLayer::CALL_STATE_CONNECTED:
-    case nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTING:
-    case nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED:
-    case nsIRadioInterfaceLayer::CALL_STATE_BUSY:
-      mCall = 1;
-      mCallSetup = 2;
-      break;
-    case nsIRadioInterfaceLayer::CALL_STATE_HOLDING:
-    case nsIRadioInterfaceLayer::CALL_STATE_HELD:
-    case nsIRadioInterfaceLayer::CALL_STATE_RESUMING:
-      mCall = 1;
-      mCallHeld = 2;
-    default:
-      mCall = 0;
-      mCallSetup = 0;
-  }
+  SetupCIND(aCallIndex, aCallState, true);
 }
 
 /*
  * CallStateChanged will be called whenever call status is changed, and it
  * also means we need to notify HS about the change. For more information, 
  * please refer to 4.13 ~ 4.15 in Bluetooth hands-free profile 1.6.
  */
 void
 BluetoothHfpManager::CallStateChanged(int aCallIndex, int aCallState,
                                       const char* aNumber, bool aIsActive)
 {
-  nsRefPtr<nsRunnable> sendRingTask;
-
-  switch (aCallState) {
-    case nsIRadioInterfaceLayer::CALL_STATE_INCOMING:
-      // Send "CallSetup = 1"
-      SendLine("+CIEV: 5,1");
-
-      // Start sending RING indicator to HF
-      sStopSendingRingFlag = false;
-      sendRingTask = new SendRingIndicatorTask();
-
-      if (NS_FAILED(sHfpCommandThread->Dispatch(sendRingTask, NS_DISPATCH_NORMAL))) {
-        NS_WARNING("Cannot dispatch ring task!");
-        return;
-      };
-      break;
-    case nsIRadioInterfaceLayer::CALL_STATE_DIALING:
-      // Send "CallSetup = 2"
-      SendLine("+CIEV: 5,2");
-      break;
-    case nsIRadioInterfaceLayer::CALL_STATE_ALERTING:
-      // Send "CallSetup = 3"
-      if (mCurrentCallIndex == nsIRadioInterfaceLayer::CALL_STATE_DIALING) {
-        SendLine("+CIEV: 5,3");
-      } else {
-#ifdef DEBUG
-        NS_WARNING("Not handling state changed");
-#endif
-      }
-      break;
-    case nsIRadioInterfaceLayer::CALL_STATE_CONNECTED:
-      switch (mCurrentCallState) {
-        case nsIRadioInterfaceLayer::CALL_STATE_INCOMING:
-          sStopSendingRingFlag = true;
-          // Continue executing, no break
-        case nsIRadioInterfaceLayer::CALL_STATE_DIALING:
-          // Send "Call = 1, CallSetup = 0"
-          SendLine("+CIEV: 4,1");
-          SendLine("+CIEV: 5,0");
-          break;
-        default:
-#ifdef DEBUG
-          NS_WARNING("Not handling state changed");
-#endif
-          break;
-      }
-      OpenScoSocket(mDevicePath);
-      break;
-    case nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED:
-      switch (mCurrentCallState) {
-        case nsIRadioInterfaceLayer::CALL_STATE_INCOMING:
-          sStopSendingRingFlag = true;
-          // Continue executing, no break
-        case nsIRadioInterfaceLayer::CALL_STATE_DIALING:
-        case nsIRadioInterfaceLayer::CALL_STATE_ALERTING:
-          // Send "CallSetup = 0"
-          SendLine("+CIEV: 5,0");
-          break;
-        case nsIRadioInterfaceLayer::CALL_STATE_CONNECTED:
-          // Send "Call = 0"
-          SendLine("+CIEV: 4,0");
-          break;
-        default:
-#ifdef DEBUG
-          NS_WARNING("Not handling state changed");
-#endif
-          break;
-      }
-      CloseScoSocket();
-      break;
-
-    default:
-#ifdef DEBUG
-      NS_WARNING("Not handling state changed");
-#endif
-      break;
+  if (GetConnectionStatus() != SocketConnectionStatus::SOCKET_CONNECTED) {
+    return;
   }
 
-  mCurrentCallIndex = aCallIndex;
-  mCurrentCallState = aCallState;
+  SetupCIND(aCallIndex, aCallState, false);
 }
 
 void
 BluetoothHfpManager::OnConnectSuccess()
 {
+  if (mCurrentCallState == nsIRadioInterfaceLayer::CALL_STATE_CONNECTED ||
+      mCurrentCallState == nsIRadioInterfaceLayer::CALL_STATE_DIALING ||
+      mCurrentCallState == nsIRadioInterfaceLayer::CALL_STATE_ALERTING) {
+    nsString address;
+    GetSocketAddr(address);
+    OpenScoSocket(address);
+  }
+
+  NotifySettings();
 }
 
 void
 BluetoothHfpManager::OnConnectError()
 {
   CloseSocket();
   // If connecting for some reason didn't work, restart listening
   Listen();
 }
 
 void
 BluetoothHfpManager::OnDisconnect()
 {
-  NS_WARNING("GOT DISCONNECT!");
+  NotifySettings();
+  sCINDItems[CINDType::CALL].value = CallState::NO_CALL;
+  sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP;
+  sCINDItems[CINDType::CALLHELD].value = CallHeldState::NO_CALLHELD;
 }
--- a/dom/bluetooth/BluetoothHfpManager.h
+++ b/dom/bluetooth/BluetoothHfpManager.h
@@ -24,40 +24,38 @@ public:
   static BluetoothHfpManager* Get();
   virtual void ReceiveSocketData(mozilla::ipc::UnixSocketRawData* aMessage)
     MOZ_OVERRIDE;
   bool Connect(const nsAString& aDeviceObjectPath,
                const bool aIsHandsfree,
                BluetoothReplyRunnable* aRunnable);
   void Disconnect();
   bool SendLine(const char* aMessage);
+  bool SendCommand(const char* aCommand, const int aValue);
   void CallStateChanged(int aCallIndex, int aCallState,
                         const char* aNumber, bool aIsActive);
   void EnumerateCallState(int aCallIndex, int aCallState,
                           const char* aNumber, bool aIsActive);
+  void SetupCIND(int aCallIndex, int aCallState, bool aInitial);
   bool Listen();
 
 private:
   friend class BluetoothHfpManagerObserver;
   BluetoothHfpManager();
   nsresult HandleVolumeChanged(const nsAString& aData);
   nsresult HandleShutdown();
   bool Init();
   void Cleanup();
   void NotifyDialer(const nsAString& aCommand);
-  void NotifySettings(const bool aConnected);
+  void NotifySettings();
   virtual void OnConnectSuccess() MOZ_OVERRIDE;
   virtual void OnConnectError() MOZ_OVERRIDE;
   virtual void OnDisconnect() MOZ_OVERRIDE;
 
   int mCurrentVgs;
   int mCurrentCallIndex;
   int mCurrentCallState;
-  int mCall;
-  int mCallSetup;
-  int mCallHeld;
   nsAutoPtr<BluetoothRilListener> mListener;
-  nsString mDevicePath;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/BluetoothScoManager.cpp
+++ b/dom/bluetooth/BluetoothScoManager.cpp
@@ -6,25 +6,26 @@
 
 #include "base/basictypes.h"
 
 #include "BluetoothScoManager.h"
 
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothServiceUuid.h"
+#include "BluetoothUtils.h"
 
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "nsContentUtils.h"
-#include "nsIDOMDOMRequest.h"
+#include "nsIAudioManager.h"
 #include "nsIObserverService.h"
-#include "nsISystemMessagesInternal.h"
-#include "nsVariant.h"
+
+#define BLUETOOTH_SCO_STATUS_CHANGED "bluetooth-sco-status-changed"
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
 
 class mozilla::dom::bluetooth::BluetoothScoManagerObserver : public nsIObserver
 {
 public:
@@ -59,16 +60,46 @@ public:
   }
 
   ~BluetoothScoManagerObserver()
   {
     Shutdown();
   }
 };
 
+void
+BluetoothScoManager::NotifyAudioManager(const nsAString& aAddress) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
+  if (!obs) {
+    NS_WARNING("Failed to get observser service!");
+    return;
+  }
+
+  if (aAddress.IsEmpty()) {
+    if (NS_FAILED(obs->NotifyObservers(nullptr, BLUETOOTH_SCO_STATUS_CHANGED, nullptr))) {
+      NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
+      return;
+    }
+  } else {
+    if (NS_FAILED(obs->NotifyObservers(nullptr, BLUETOOTH_SCO_STATUS_CHANGED, aAddress.BeginReading()))) {
+      NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
+      return;
+    }
+  }
+
+  nsCOMPtr<nsIAudioManager> am = do_GetService("@mozilla.org/telephony/audiomanager;1");
+  if (!am) {
+    NS_WARNING("Failed to get AudioManager service!");
+    return;
+  }
+  am->SetForceForUse(am->USE_COMMUNICATION, am->FORCE_BT_SCO);
+}
+
 NS_IMPL_ISUPPORTS1(BluetoothScoManagerObserver, nsIObserver)
 
 namespace {
 StaticRefPtr<BluetoothScoManager> gBluetoothScoManager;
 StaticRefPtr<BluetoothScoManagerObserver> sScoObserver;
 bool gInShutdown = false;
 } // anonymous namespace
 
@@ -82,17 +113,16 @@ BluetoothScoManagerObserver::Observe(nsI
     return gBluetoothScoManager->HandleShutdown();
   }
 
   MOZ_ASSERT(false, "BluetoothScoManager got unexpected topic!");
   return NS_ERROR_UNEXPECTED;
 }
 
 BluetoothScoManager::BluetoothScoManager()
-  : mConnected(false)
 {
 }
 
 bool
 BluetoothScoManager::Init()
 {
   sScoObserver = new BluetoothScoManagerObserver();
   if (sScoObserver->Init()) {
@@ -156,70 +186,66 @@ BluetoothScoManager::HandleShutdown()
   MOZ_ASSERT(NS_IsMainThread());
   gInShutdown = true;
   CloseSocket();
   gBluetoothScoManager = nullptr;
   return NS_OK;
 }
 
 bool
-BluetoothScoManager::Connect(const nsAString& aDeviceObjectPath)
+BluetoothScoManager::Connect(const nsAString& aDeviceAddress)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gInShutdown) {
     MOZ_ASSERT(false, "Connect called while in shutdown!");
     return false;
   }
 
-  if (mConnected) {
-    NS_WARNING("Sco socket has been ready");
-    return true;
+  if (GetConnectionStatus() == SocketConnectionStatus::SOCKET_CONNECTED) {
+    NS_WARNING("Sco socket has been connected");
+    return false;
   }
 
   BluetoothService* bs = BluetoothService::Get();
   if (!bs) {
     NS_WARNING("BluetoothService not available!");
     return false;
   }
 
-  nsresult rv = bs->GetScoSocket(aDeviceObjectPath,
+  nsresult rv = bs->GetScoSocket(aDeviceAddress,
                                  true,
                                  false,
                                  this);
 
   return NS_FAILED(rv) ? false : true;
 }
 
 void
 BluetoothScoManager::Disconnect()
 {
-  CloseSocket();
-  mConnected = false;
-}
+  if (GetConnectionStatus() == SocketConnectionStatus::SOCKET_DISCONNECTED) {
+    return;
+  }
 
-// FIXME: detect connection in UnixSocketConsumer
-bool
-BluetoothScoManager::GetConnected()
-{
-  return mConnected;
-}
-
-void
-BluetoothScoManager::SetConnected(bool aConnected)
-{
-  mConnected = aConnected;
+  CloseSocket();
 }
 
 void
 BluetoothScoManager::OnConnectSuccess()
 {
+  nsString address;
+  GetSocketAddr(address);
+  NotifyAudioManager(address);
 }
 
 void
 BluetoothScoManager::OnConnectError()
 {
+  CloseSocket();
 }
 
 void
 BluetoothScoManager::OnDisconnect()
 {
+  nsString address = NS_LITERAL_STRING("");
+  NotifyAudioManager(address);
 }
--- a/dom/bluetooth/BluetoothScoManager.h
+++ b/dom/bluetooth/BluetoothScoManager.h
@@ -22,27 +22,24 @@ public:
   ~BluetoothScoManager();
 
   static BluetoothScoManager* Get();
   void ReceiveSocketData(mozilla::ipc::UnixSocketRawData* aMessage)
     MOZ_OVERRIDE;
 
   bool Connect(const nsAString& aDeviceObjectPath);
   void Disconnect();
-  void SetConnected(bool aConnected);
-  bool GetConnected();
 
 private:
   friend class BluetoothScoManagerObserver;
   BluetoothScoManager();
   bool Init();
   void Cleanup();
   nsresult HandleShutdown();
-  void CreateScoSocket(const nsAString& aDevicePath);
+  void NotifyAudioManager(const nsAString& aAddress);
   virtual void OnConnectSuccess() MOZ_OVERRIDE;
   virtual void OnConnectError() MOZ_OVERRIDE;
   virtual void OnDisconnect() MOZ_OVERRIDE;
-  bool mConnected;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/BluetoothUnixSocketConnector.cpp
+++ b/dom/bluetooth/BluetoothUnixSocketConnector.cpp
@@ -186,11 +186,11 @@ BluetoothUnixSocketConnector::CreateAddr
   }
 }
 
 void
 BluetoothUnixSocketConnector::GetSocketAddr(const sockaddr& aAddr,
                                             nsAString& aAddrStr)
 {
   char addr[18];
-  get_bdaddr_as_string((bdaddr_t*)&aAddr, addr);
+  get_bdaddr_as_string((bdaddr_t*)aAddr.sa_data, addr);
   aAddrStr.AssignASCII(addr);
 }
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -70,17 +70,16 @@ USING_BLUETOOTH_NAMESPACE
 #define B2G_AGENT_CAPABILITIES "DisplayYesNo"
 #define DBUS_MANAGER_IFACE BLUEZ_DBUS_BASE_IFC ".Manager"
 #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter"
 #define DBUS_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".Device"
 #define DBUS_AGENT_IFACE BLUEZ_DBUS_BASE_IFC ".Agent"
 #define BLUEZ_DBUS_BASE_PATH      "/org/bluez"
 #define BLUEZ_DBUS_BASE_IFC       "org.bluez"
 #define BLUEZ_ERROR_IFC           "org.bluez.Error"
-#define BLUETOOTH_SCO_STATUS_CHANGED "bluetooth-sco-status-changed"
 
 typedef struct {
   const char* name;
   int type;
 } Properties;
 
 static Properties sDeviceProperties[] = {
   {"Address", DBUS_TYPE_STRING},
@@ -169,47 +168,16 @@ public:
       NS_WARNING("BluetoothService not available!");
       return NS_ERROR_FAILURE;
     }
     bs->DistributeSignal(mSignal);
     return NS_OK;
   }
 };
 
-class NotifyAudioManagerTask : public nsRunnable {
-public:
-  NotifyAudioManagerTask(nsString aObjectPath) : 
-    mObjectPath(aObjectPath)
-  {
-  }
-
-  NS_IMETHOD
-  Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    nsCOMPtr<nsIAudioManager> am = do_GetService("@mozilla.org/telephony/audiomanager;1");
-    if (!am) {
-      NS_WARNING("Failed to get AudioManager service!");
-    }
-    am->SetForceForUse(am->USE_COMMUNICATION, am->FORCE_BT_SCO);
-
-    nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
-    if (obs) {
-      if (NS_FAILED(obs->NotifyObservers(nullptr, BLUETOOTH_SCO_STATUS_CHANGED, mObjectPath.get()))) {
-        NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
-        return NS_ERROR_FAILURE;
-      }
-    }
-    return NS_OK;
-  }
-private:
-  nsString mObjectPath;
-};
-
 class PrepareAdapterTask : public nsRunnable {
 public:
   PrepareAdapterTask(const nsAString& aPath) :
     mPath(aPath)
   {
   }
 
   NS_IMETHOD
@@ -2258,24 +2226,36 @@ BluetoothDBusService::PrepareAdapterInte
 bool
 BluetoothDBusService::Connect(const nsAString& aDeviceAddress,
                               const nsAString& aAdapterPath,
                               const uint16_t aProfileId,
                               BluetoothReplyRunnable* aRunnable)
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!");
 
+  nsString errorStr;
+  BluetoothValue v = true;
   if (aProfileId == (uint16_t)(BluetoothServiceUuid::Handsfree >> 32)) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-    return hfp->Connect(GetObjectPathFromAddress(aAdapterPath, aDeviceAddress), true,
-                        aRunnable);
+    if (!hfp->Connect(GetObjectPathFromAddress(aAdapterPath, aDeviceAddress),
+                      true, aRunnable)) {
+      errorStr.AssignLiteral("Failed to connect with device.");
+      DispatchBluetoothReply(aRunnable, v, errorStr);
+      return false;
+    }
+    return true;
   } else if (aProfileId == (uint16_t)(BluetoothServiceUuid::Headset >> 32)) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-    return hfp->Connect(GetObjectPathFromAddress(aAdapterPath, aDeviceAddress), false,
-                        aRunnable);
+    if (!hfp->Connect(GetObjectPathFromAddress(aAdapterPath, aDeviceAddress),
+                      false, aRunnable)) {
+      errorStr.AssignLiteral("Failed to connect with device.");
+      DispatchBluetoothReply(aRunnable, v, errorStr);
+      return false;
+    }
+    return true;
   } else if (aProfileId == (uint16_t)(BluetoothServiceUuid::ObjectPush >> 32)) {
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     return opp->Connect(GetObjectPathFromAddress(aAdapterPath, aDeviceAddress),
                         aRunnable);
   }
 
   NS_WARNING("Unknow Profile");
   return false;
@@ -2301,58 +2281,49 @@ BluetoothDBusService::Disconnect(const u
   // once Disconnect will fail.
   nsString replyError;
   BluetoothValue v = true;
   DispatchBluetoothReply(aRunnable, v, replyError);
 }
 
 class CreateBluetoothScoSocket : public nsRunnable
 {
-public: 
+public:
   CreateBluetoothScoSocket(UnixSocketConsumer* aConsumer,
-                           const nsAString& aObjectPath,
+                           const nsAString& aAddress,
                            bool aAuth,
                            bool aEncrypt)
     : mConsumer(aConsumer),
-      mObjectPath(aObjectPath),
+      mAddress(aAddress),
       mAuth(aAuth),
       mEncrypt(aEncrypt)
   {
   }
 
   nsresult
   Run()
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
-    nsString address = GetAddressFromObjectPath(mObjectPath);
     nsString replyError;
     BluetoothUnixSocketConnector* c =
       new BluetoothUnixSocketConnector(BluetoothSocketType::SCO, -1,
                                        mAuth, mEncrypt);
 
-    BluetoothScoManager* sco = BluetoothScoManager::Get();
-    if (!mConsumer->ConnectSocket(c, NS_ConvertUTF16toUTF8(address).get())) {
+    if (!mConsumer->ConnectSocket(c, NS_ConvertUTF16toUTF8(mAddress).get())) {
       replyError.AssignLiteral("SocketConnectionError");
-      sco->SetConnected(false); 
       return NS_ERROR_FAILURE;
     }
-    sco->SetConnected(true);
 
-    nsRefPtr<NotifyAudioManagerTask> task = new NotifyAudioManagerTask(address);
-    if (NS_FAILED(NS_DispatchToMainThread(task))) {
-      NS_WARNING("Failed to dispatch to main thread!");
-      return NS_ERROR_FAILURE;
-    }    
     return NS_OK;
   }
 
 private:
   nsRefPtr<UnixSocketConsumer> mConsumer;
-  nsString mObjectPath;
+  nsString mAddress;
   bool mAuth;
   bool mEncrypt;
 };
 
 class ConnectBluetoothSocketRunnable : public nsRunnable
 {
 public:
   ConnectBluetoothSocketRunnable(BluetoothReplyRunnable* aRunnable,
@@ -2493,29 +2464,29 @@ BluetoothDBusService::GetSocketViaServic
     return NS_ERROR_FAILURE;
   }
 
   runnable.forget();
   return NS_OK;
 }
 
 nsresult
-BluetoothDBusService::GetScoSocket(const nsAString& aObjectPath,
+BluetoothDBusService::GetScoSocket(const nsAString& aAddress,
                                    bool aAuth,
                                    bool aEncrypt,
                                    mozilla::ipc::UnixSocketConsumer* aConsumer)
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!");
   if (!mConnection || !gThreadConnection) {
     NS_ERROR("Bluetooth service not started yet!");
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<nsRunnable> func(new CreateBluetoothScoSocket(aConsumer,
-                                                         aObjectPath,
+                                                         aAddress,
                                                          aAuth,
                                                          aEncrypt));
   if (NS_FAILED(mBluetoothCommandThread->Dispatch(func, NS_DISPATCH_NORMAL))) {
     NS_WARNING("Cannot dispatch firmware loading task!");
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;