/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "base/basictypes.h" #include "BluetoothHfpManager.h" #include "BluetoothReplyRunnable.h" #include "BluetoothScoManager.h" #include "BluetoothService.h" #include "BluetoothServiceUuid.h" #include "BluetoothUtils.h" #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 /* usleep() */ #define MOZSETTINGS_CHANGED_ID "mozsettings-changed" #define AUDIO_VOLUME_MASTER "audio.volume.bt_sco" #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() { } bool Init() { nsCOMPtr obs = services::GetObserverService(); MOZ_ASSERT(obs); if (NS_FAILED(obs->AddObserver(this, MOZSETTINGS_CHANGED_ID, false))) { NS_WARNING("Failed to add settings change observer!"); return false; } if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { NS_WARNING("Failed to add shutdown observer!"); return false; } return true; } bool Shutdown() { nsCOMPtr obs = services::GetObserverService(); if (!obs || (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) || NS_FAILED(obs->RemoveObserver(this, MOZSETTINGS_CHANGED_ID)))) { NS_WARNING("Can't unregister observers, or already unregistered!"); return false; } return true; } ~BluetoothHfpManagerObserver() { Shutdown(); } }; namespace { StaticRefPtr gBluetoothHfpManager; StaticRefPtr sHfpObserver; bool gInShutdown = false; static bool sStopSendingRingFlag = true; static int sRingInterval = 3000; //unit: ms } // anonymous 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; } class SendRingIndicatorTask : public Task { public: SendRingIndicatorTask() { MOZ_ASSERT(NS_IsMainThread()); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(NS_IsMainThread()); if (sStopSendingRingFlag) { return; } if (!gBluetoothHfpManager) { NS_WARNING("BluetoothHfpManager no longer exists, cannot send ring!"); return; } gBluetoothHfpManager->SendLine("RING"); MessageLoop::current()-> PostDelayedTask(FROM_HERE, new SendRingIndicatorTask(), sRingInterval); return; } }; void OpenScoSocket(const nsAString& aDeviceAddress) { MOZ_ASSERT(NS_IsMainThread()); BluetoothScoManager* sco = BluetoothScoManager::Get(); if (!sco) { NS_WARNING("BluetoothScoManager is not available!"); return; } if (!sco->Connect(aDeviceAddress)) { NS_WARNING("Failed to create a sco socket!"); } } void CloseScoSocket() { MOZ_ASSERT(NS_IsMainThread()); BluetoothScoManager* sco = BluetoothScoManager::Get(); if (!sco) { NS_WARNING("BluetoothScoManager is not available!"); return; } sco->Disconnect(); } BluetoothHfpManager::BluetoothHfpManager() : mCurrentCallIndex(0) , mReceiveVgsFlag(false) { sCINDItems[CINDType::CALL].value = CallState::NO_CALL; sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP; sCINDItems[CINDType::CALLHELD].value = CallHeldState::NO_CALLHELD; mCurrentCallStateArray.AppendElement((int)nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED); } bool BluetoothHfpManager::Init() { mSocketStatus = GetConnectionStatus(); sHfpObserver = new BluetoothHfpManagerObserver(); if (!sHfpObserver->Init()) { NS_WARNING("Cannot set up Hfp Observers!"); } mListener = new BluetoothRilListener(); if (!mListener->StartListening()) { NS_WARNING("Failed to start listening RIL"); return false; } float volume; nsCOMPtr am = do_GetService("@mozilla.org/telephony/audiomanager;1"); if (!am) { NS_WARNING("Failed to get AudioManager Service!"); return false; } am->GetMasterVolume(&volume); // AG volume range: [0.0, 1.0] // HS volume range: [0, 15] mCurrentVgs = floor(volume * 15); return true; } BluetoothHfpManager::~BluetoothHfpManager() { Cleanup(); } void BluetoothHfpManager::Cleanup() { if (!mListener->StopListening()) { NS_WARNING("Failed to stop listening RIL"); } mListener = nullptr; sHfpObserver->Shutdown(); sHfpObserver = nullptr; } //static BluetoothHfpManager* BluetoothHfpManager::Get() { MOZ_ASSERT(NS_IsMainThread()); // If we already exist, exit early if (gBluetoothHfpManager) { return gBluetoothHfpManager; } // If we're in shutdown, don't create a new instance if (gInShutdown) { NS_WARNING("BluetoothHfpManager can't be created during shutdown"); return nullptr; } // Create new instance, register, return nsRefPtr manager = new BluetoothHfpManager(); NS_ENSURE_TRUE(manager, nullptr); if (!manager->Init()) { return nullptr; } gBluetoothHfpManager = manager; return gBluetoothHfpManager; } void BluetoothHfpManager::NotifySettings() { nsString type, name; BluetoothValue v; InfallibleTArray parameters; type.AssignLiteral("bluetooth-hfp-status-changed"); name.AssignLiteral("connected"); if (GetConnectionStatus() == SocketConnectionStatus::SOCKET_CONNECTED) { v = true; } else { v = false; } parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("address"); v = mDevicePath; parameters.AppendElement(BluetoothNamedValue(name, v)); if (!BroadcastSystemMessage(type, parameters)) { NS_WARNING("Failed to broadcast system message to dialer"); return; } } void BluetoothHfpManager::NotifyDialer(const nsAString& aCommand) { nsString type, name, command; command = aCommand; InfallibleTArray parameters; type.AssignLiteral("bluetooth-dialer-command"); BluetoothValue v(command); parameters.AppendElement(BluetoothNamedValue(type, v)); if (!BroadcastSystemMessage(type, parameters)) { NS_WARNING("Failed to broadcast system message to dialer"); return; } } nsresult BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData) { MOZ_ASSERT(NS_IsMainThread()); // 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; } JS::Value val; if (!JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val)) { return JS_ReportPendingException(cx) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } if (!val.isObject()) { return NS_OK; } JSObject& obj(val.toObject()); JS::Value key; if (!JS_GetProperty(cx, &obj, "key", &key)) { MOZ_ASSERT(!JS_IsExceptionPending(cx)); return NS_ERROR_OUT_OF_MEMORY; } if (!key.isString()) { return NS_OK; } JSBool match; if (!JS_StringEqualsAscii(cx, key.toString(), AUDIO_VOLUME_MASTER, &match)) { MOZ_ASSERT(!JS_IsExceptionPending(cx)); return NS_ERROR_OUT_OF_MEMORY; } if (!match) { return NS_OK; } JS::Value value; if (!JS_GetProperty(cx, &obj, "value", &value)) { MOZ_ASSERT(!JS_IsExceptionPending(cx)); 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(); mCurrentVgs = floor(volume * 15); if (mReceiveVgsFlag) { mReceiveVgsFlag = false; return NS_OK; } if (GetConnectionStatus() != SocketConnectionStatus::SOCKET_CONNECTED) { return NS_OK; } SendCommand("+VGS: ", mCurrentVgs); return NS_OK; } nsresult BluetoothHfpManager::HandleShutdown() { MOZ_ASSERT(NS_IsMainThread()); gInShutdown = true; CloseSocket(); gBluetoothHfpManager = nullptr; return NS_OK; } // Virtual function of class SocketConsumer void BluetoothHfpManager::ReceiveSocketData(UnixSocketRawData* aMessage) { MOZ_ASSERT(NS_IsMainThread()); const char* msg = (const char*)aMessage->mData; int currentCallState = mCurrentCallStateArray[mCurrentCallIndex]; // 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)) { SendCommand("+BRSF: ", 23); SendLine("OK"); } else if (!strncmp(msg, "AT+CIND=?", 9)) { // Asking for CIND range SendCommand("+CIND: ", 0); SendLine("OK"); } else if (!strncmp(msg, "AT+CIND?", 8)) { // Asking for CIND value SendCommand("+CIND: ", 1); SendLine("OK"); } else if (!strncmp(msg, "AT+CMER=", 8)) { // SLC establishment SendLine("OK"); } else if (!strncmp(msg, "AT+CHLD=?", 9)) { SendLine("+CHLD: (1,2)"); SendLine("OK"); } else if (!strncmp(msg, "AT+CHLD=", 8)) { int length = strlen(msg) - 9; nsAutoCString chldString(nsDependentCSubstring(msg+8, length)); nsresult rv; int chld = chldString.ToInteger(&rv); if (NS_FAILED(rv)) { NS_WARNING("Failed to extract volume value from bluetooth headset!"); } switch(chld) { case 1: // Releases active calls and accepts the other (held or waiting) call NotifyDialer(NS_LITERAL_STRING("CHUP+ATA")); break; case 2: // Places active calls on hold and accepts the other (held or waiting) call NotifyDialer(NS_LITERAL_STRING("CHLD+ATA")); break; default: #ifdef DEBUG NS_WARNING("Not handling chld value"); #endif break; } SendLine("OK"); } else if (!strncmp(msg, "AT+VGS=", 7)) { mReceiveVgsFlag = true; int length = strlen(msg) - 8; nsAutoCString vgsString(nsDependentCSubstring(msg+7, length)); nsresult rv; int newVgs = vgsString.ToInteger(&rv); if (NS_FAILED(rv)) { NS_WARNING("Failed to extract volume value from bluetooth headset!"); } if (newVgs == mCurrentVgs) { SendLine("OK"); return; } #ifdef DEBUG NS_ASSERTION(newVgs >= 0 && newVgs <= 15, "Received invalid VGS value"); #endif // HS volume range: [0, 15] // sound_manager volume range: [0, 10] nsString data; int volume; nsCOMPtr os = mozilla::services::GetObserverService(); volume = ceil((float)newVgs / 15.0 * 10.0); data.AppendInt(volume); os->NotifyObservers(nullptr, "bluetooth-volume-change", data.get()); SendLine("OK"); } else if (!strncmp(msg, "AT+BLDN", 7)) { NotifyDialer(NS_LITERAL_STRING("BLDN")); SendLine("OK"); } else if (!strncmp(msg, "ATA", 3)) { NotifyDialer(NS_LITERAL_STRING("ATA")); SendLine("OK"); } else if (!strncmp(msg, "AT+CHUP", 7)) { NotifyDialer(NS_LITERAL_STRING("CHUP")); SendLine("OK"); } else if (!strncmp(msg, "AT+CKPD", 7)) { // For Headset switch (currentCallState) { case nsIRadioInterfaceLayer::CALL_STATE_INCOMING: NotifyDialer(NS_LITERAL_STRING("ATA")); break; case nsIRadioInterfaceLayer::CALL_STATE_CONNECTED: case nsIRadioInterfaceLayer::CALL_STATE_DIALING: case nsIRadioInterfaceLayer::CALL_STATE_ALERTING: NotifyDialer(NS_LITERAL_STRING("CHUP")); break; case nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED: NotifyDialer(NS_LITERAL_STRING("BLDN")); break; default: #ifdef DEBUG NS_WARNING("Not handling state changed"); #endif break; } SendLine("OK"); } else { #ifdef DEBUG nsCString warningMsg; warningMsg.AssignLiteral("Not handling HFP message, reply ok: "); warningMsg.Append(msg); NS_WARNING(warningMsg.get()); #endif SendLine("OK"); } } bool 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; } nsString serviceUuidStr; if (aIsHandsfree) { serviceUuidStr = NS_ConvertUTF8toUTF16(HANDSFREE_UUID); } else { serviceUuidStr = NS_ConvertUTF8toUTF16(HEADSET_UUID); } nsRefPtr runnable = aRunnable; nsresult rv = bs->GetSocketViaService(aDevicePath, serviceUuidStr, BluetoothSocketType::RFCOMM, true, true, this, runnable); runnable.forget(); return NS_FAILED(rv) ? false : true; } bool BluetoothHfpManager::Listen() { MOZ_ASSERT(NS_IsMainThread()); if (gInShutdown) { MOZ_ASSERT(false, "Listen called while in shutdown!"); return false; } CloseSocket(); BluetoothService* bs = BluetoothService::Get(); if (!bs) { NS_WARNING("BluetoothService not available!"); return false; } nsresult rv = bs->ListenSocketViaService(BluetoothReservedChannels::HANDSFREE_AG, BluetoothSocketType::RFCOMM, true, true, this); mSocketStatus = GetConnectionStatus(); return NS_FAILED(rv) ? false : true; } void BluetoothHfpManager::Disconnect() { if (GetConnectionStatus() == SocketConnectionStatus::SOCKET_DISCONNECTED) { NS_WARNING("BluetoothHfpManager has disconnected!"); return; } CloseSocket(); } 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 sendRingTask; nsString address; while (aCallIndex >= mCurrentCallStateArray.Length()) { mCurrentCallStateArray.AppendElement((int)nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED); } int currentCallState = mCurrentCallStateArray[aCallIndex]; switch (aCallState) { case nsIRadioInterfaceLayer::CALL_STATE_INCOMING: sCINDItems[CINDType::CALLSETUP].value = CallSetupState::INCOMING; if (!aInitial) { SendCommand("+CIEV: ", CINDType::CALLSETUP); } if (!mCurrentCallIndex) { // Start sending RING indicator to HF sStopSendingRingFlag = false; MessageLoop::current()->PostTask(FROM_HERE, new SendRingIndicatorTask()); } 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: mCurrentCallIndex = aCallIndex; if (aInitial) { sCINDItems[CINDType::CALL].value = CallState::IN_PROGRESS; sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP; } else { switch (currentCallState) { case nsIRadioInterfaceLayer::CALL_STATE_INCOMING: // Incoming call, no break sStopSendingRingFlag = true; GetSocketAddr(address); OpenScoSocket(address); case nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED: 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 (currentCallState) { 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; case nsIRadioInterfaceLayer::CALL_STATE_HELD: sCINDItems[CINDType::CALLHELD].value = NO_CALLHELD; SendCommand("+CIEV: ", CINDType::CALLHELD); default: #ifdef DEBUG NS_WARNING("Not handling state changed"); #endif break; } if (aCallIndex == mCurrentCallIndex) { #ifdef DEBUG NS_ASSERTION(mCurrentCallStateArray.Length() > aCallIndex, "Call index out of bounds!"); #endif mCurrentCallStateArray[aCallIndex] = aCallState; // Find the first non-disconnected call (like connected, held), // and update mCurrentCallIndex int c; for (c = 1; c < mCurrentCallStateArray.Length(); c++) { if (mCurrentCallStateArray[c] != nsIRadioInterfaceLayer::CALL_STATE_DISCONNECTED) { mCurrentCallIndex = c; break; } } // There is no call if (c == mCurrentCallStateArray.Length()) { mCurrentCallIndex = 0; CloseScoSocket(); } } } break; case nsIRadioInterfaceLayer::CALL_STATE_HELD: sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE; if (!aInitial) { SendCommand("+CIEV: ", CINDType::CALLHELD); } break; default: #ifdef DEBUG NS_WARNING("Not handling state changed"); sCINDItems[CINDType::CALL].value = CallState::NO_CALL; sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP; sCINDItems[CINDType::CALLHELD].value = CallHeldState::NO_CALLHELD; #endif break; } mCurrentCallStateArray[aCallIndex] = aCallState; } /* * EnumerateCallState will be called for each call */ void BluetoothHfpManager::EnumerateCallState(int aCallIndex, int aCallState, const char* aNumber, bool aIsActive) { SetupCIND(aCallIndex, aCallState, true); if (sCINDItems[CINDType::CALL].value == CallState::IN_PROGRESS || sCINDItems[CINDType::CALLSETUP].value == CallSetupState::OUTGOING || sCINDItems[CINDType::CALLSETUP].value == CallSetupState::OUTGOING_ALERTING) { nsString address; GetSocketAddr(address); OpenScoSocket(address); } } /* * 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) { if (GetConnectionStatus() != SocketConnectionStatus::SOCKET_CONNECTED) { return; } SetupCIND(aCallIndex, aCallState, false); } void BluetoothHfpManager::OnConnectSuccess() { // Cache device path for NotifySettings() since we can't get socket address // when a headset disconnect with us GetSocketAddr(mDevicePath); mSocketStatus = GetConnectionStatus(); nsCOMPtr ril = do_GetService("@mozilla.org/ril/content-helper;1"); if (!ril) { MOZ_ASSERT("Failed to get RIL Content Helper"); } ril->EnumerateCalls(mListener->GetCallback()); NotifySettings(); } void BluetoothHfpManager::OnConnectError() { CloseSocket(); mSocketStatus = GetConnectionStatus(); // If connecting for some reason didn't work, restart listening Listen(); } void BluetoothHfpManager::OnDisconnect() { if (mSocketStatus == SocketConnectionStatus::SOCKET_CONNECTED) { Listen(); NotifySettings(); } sCINDItems[CINDType::CALL].value = CallState::NO_CALL; sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP; sCINDItems[CINDType::CALLHELD].value = CallHeldState::NO_CALLHELD; }