michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "base/basictypes.h" michael@0: michael@0: #include "BluetoothHfpManager.h" michael@0: michael@0: #include "BluetoothProfileController.h" michael@0: #include "BluetoothReplyRunnable.h" michael@0: #include "BluetoothService.h" michael@0: #include "BluetoothSocket.h" michael@0: #include "BluetoothUtils.h" michael@0: #include "BluetoothUuid.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "mozilla/dom/bluetooth/BluetoothTypes.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsISettingsService.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: #include "nsIDOMIccInfo.h" michael@0: #include "nsIDOMMobileConnection.h" michael@0: #include "nsIIccProvider.h" michael@0: #include "nsIMobileConnectionProvider.h" michael@0: #include "nsITelephonyProvider.h" michael@0: #include "nsRadioInterfaceLayer.h" michael@0: #endif michael@0: michael@0: /** michael@0: * BRSF bitmask of AG supported features. See 4.34.1 "Bluetooth Defined AT michael@0: * Capabilities" in Bluetooth hands-free profile 1.6 michael@0: */ michael@0: #define BRSF_BIT_THREE_WAY_CALLING 1 michael@0: #define BSRF_BIT_EC_NR_FUNCTION (1 << 1) michael@0: #define BRSF_BIT_VOICE_RECOGNITION (1 << 2) michael@0: #define BRSF_BIT_IN_BAND_RING_TONE (1 << 3) michael@0: #define BRSF_BIT_ATTACH_NUM_TO_VOICE_TAG (1 << 4) michael@0: #define BRSF_BIT_ABILITY_TO_REJECT_CALL (1 << 5) michael@0: #define BRSF_BIT_ENHANCED_CALL_STATUS (1 << 6) michael@0: #define BRSF_BIT_ENHANCED_CALL_CONTROL (1 << 7) michael@0: #define BRSF_BIT_EXTENDED_ERR_RESULT_CODES (1 << 8) michael@0: #define BRSF_BIT_CODEC_NEGOTIATION (1 << 9) michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: /** michael@0: * These constants are used in result code such as +CLIP and +CCWA. The value michael@0: * of these constants is the same as TOA_INTERNATIONAL/TOA_UNKNOWN defined in michael@0: * ril_consts.js michael@0: */ michael@0: #define TOA_UNKNOWN 0x81 michael@0: #define TOA_INTERNATIONAL 0x91 michael@0: #endif michael@0: michael@0: #define CR_LF "\xd\xa"; michael@0: michael@0: #define MOZSETTINGS_CHANGED_ID "mozsettings-changed" michael@0: #define AUDIO_VOLUME_BT_SCO_ID "audio.volume.bt_sco" michael@0: michael@0: #define RESPONSE_CIEV "+CIEV: " michael@0: #define RESPONSE_CIND "+CIND: " michael@0: #define RESPONSE_CLCC "+CLCC: " michael@0: #define RESPONSE_BRSF "+BRSF: " michael@0: #define RESPONSE_VGS "+VGS: " michael@0: #define RESPONSE_CME_ERROR "+CME ERROR: " michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::ipc; michael@0: USING_BLUETOOTH_NAMESPACE michael@0: michael@0: namespace { michael@0: StaticRefPtr sBluetoothHfpManager; michael@0: bool sInShutdown = false; michael@0: static const char kHfpCrlf[] = "\xd\xa"; michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: // Sending ringtone related michael@0: static bool sStopSendingRingFlag = true; michael@0: static int sRingInterval = 3000; //unit: ms michael@0: michael@0: // Wait for 2 seconds for Dialer processing event 'BLDN'. '2' seconds is a michael@0: // magic number. The mechanism should be revised once we can get call history. michael@0: static int sWaitingForDialingInterval = 2000; //unit: ms michael@0: michael@0: // Wait 3.7 seconds until Dialer stops playing busy tone. '3' seconds is the michael@0: // time window set in Dialer and the extra '0.7' second is a magic number. michael@0: // The mechanism should be revised once we know the exact time at which michael@0: // Dialer stops playing. michael@0: static int sBusyToneInterval = 3700; //unit: ms michael@0: #endif // MOZ_B2G_RIL michael@0: } // anonymous namespace michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: /* CallState for sCINDItems[CINDType::CALL].value michael@0: * - NO_CALL: there are no calls in progress michael@0: * - IN_PROGRESS: at least one call is in progress michael@0: */ michael@0: enum CallState { michael@0: NO_CALL, michael@0: IN_PROGRESS michael@0: }; michael@0: michael@0: /* CallSetupState for sCINDItems[CINDType::CALLSETUP].value michael@0: * - NO_CALLSETUP: not currently in call set up michael@0: * - INCOMING: an incoming call process ongoing michael@0: * - OUTGOING: an outgoing call set up is ongoing michael@0: * - OUTGOING_ALERTING: remote party being alerted in an outgoing call michael@0: */ michael@0: enum CallSetupState { michael@0: NO_CALLSETUP, michael@0: INCOMING, michael@0: OUTGOING, michael@0: OUTGOING_ALERTING michael@0: }; michael@0: michael@0: /* CallHeldState for sCINDItems[CINDType::CALLHELD].value michael@0: * - NO_CALLHELD: no calls held michael@0: * - ONHOLD_ACTIVE: both an active and a held call michael@0: * - ONHOLD_NOACTIVE: call on hold, no active call michael@0: */ michael@0: enum CallHeldState { michael@0: NO_CALLHELD, michael@0: ONHOLD_ACTIVE, michael@0: ONHOLD_NOACTIVE michael@0: }; michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: typedef struct { michael@0: const char* name; michael@0: const char* range; michael@0: int value; michael@0: bool activated; michael@0: } CINDItem; michael@0: michael@0: enum CINDType { michael@0: BATTCHG = 1, michael@0: #ifdef MOZ_B2G_RIL michael@0: CALL, michael@0: CALLHELD, michael@0: CALLSETUP, michael@0: SERVICE, michael@0: SIGNAL, michael@0: ROAM michael@0: #endif michael@0: }; michael@0: michael@0: static CINDItem sCINDItems[] = { michael@0: {}, michael@0: {"battchg", "0-5", 5, true}, michael@0: #ifdef MOZ_B2G_RIL michael@0: {"call", "0,1", CallState::NO_CALL, true}, michael@0: {"callheld", "0-2", CallHeldState::NO_CALLHELD, true}, michael@0: {"callsetup", "0-3", CallSetupState::NO_CALLSETUP, true}, michael@0: {"service", "0,1", 0, true}, michael@0: {"signal", "0-5", 0, true}, michael@0: {"roam", "0,1", 0, true} michael@0: #endif michael@0: }; michael@0: michael@0: class BluetoothHfpManager::GetVolumeTask : public nsISettingsServiceCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD michael@0: Handle(const nsAString& aName, JS::Handle aResult) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: JSContext *cx = nsContentUtils::GetCurrentJSContext(); michael@0: NS_ENSURE_TRUE(cx, NS_OK); michael@0: michael@0: if (!aResult.isNumber()) { michael@0: BT_WARNING("'" AUDIO_VOLUME_BT_SCO_ID "' is not a number!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); michael@0: hfp->mCurrentVgs = aResult.toNumber(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: HandleError(const nsAString& aName) michael@0: { michael@0: BT_WARNING("Unable to get value for '" AUDIO_VOLUME_BT_SCO_ID "'"); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(BluetoothHfpManager::GetVolumeTask, michael@0: nsISettingsServiceCallback); michael@0: michael@0: NS_IMETHODIMP michael@0: BluetoothHfpManager::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) { michael@0: HandleVolumeChanged(nsDependentString(aData)); michael@0: } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { michael@0: HandleShutdown(); michael@0: } else { michael@0: MOZ_ASSERT(false, "BluetoothHfpManager got unexpected topic!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::Notify(const hal::BatteryInformation& aBatteryInfo) michael@0: { michael@0: // Range of battery level: [0, 1], double michael@0: // Range of CIND::BATTCHG: [0, 5], int michael@0: int level = ceil(aBatteryInfo.level() * 5.0); michael@0: if (level != sCINDItems[CINDType::BATTCHG].value) { michael@0: sCINDItems[CINDType::BATTCHG].value = level; michael@0: SendCommand(RESPONSE_CIEV, CINDType::BATTCHG); michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: class BluetoothHfpManager::RespondToBLDNTask : public Task michael@0: { michael@0: private: michael@0: void Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(sBluetoothHfpManager); michael@0: michael@0: if (!sBluetoothHfpManager->mDialingRequestProcessed) { michael@0: sBluetoothHfpManager->mDialingRequestProcessed = true; michael@0: sBluetoothHfpManager->SendLine("ERROR"); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: class BluetoothHfpManager::SendRingIndicatorTask : public Task michael@0: { michael@0: public: michael@0: SendRingIndicatorTask(const nsAString& aNumber, int aType) michael@0: : mNumber(aNumber) michael@0: , mType(aType) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: void Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Stop sending RING indicator michael@0: if (sStopSendingRingFlag) { michael@0: return; michael@0: } michael@0: michael@0: if (!sBluetoothHfpManager) { michael@0: BT_WARNING("BluetoothHfpManager no longer exists, cannot send ring!"); michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString ringMsg("RING"); michael@0: sBluetoothHfpManager->SendLine(ringMsg.get()); michael@0: michael@0: if (!mNumber.IsEmpty()) { michael@0: nsAutoCString clipMsg("+CLIP: \""); michael@0: clipMsg.Append(NS_ConvertUTF16toUTF8(mNumber).get()); michael@0: clipMsg.AppendLiteral("\","); michael@0: clipMsg.AppendInt(mType); michael@0: sBluetoothHfpManager->SendLine(clipMsg.get()); michael@0: } michael@0: michael@0: MessageLoop::current()-> michael@0: PostDelayedTask(FROM_HERE, michael@0: new SendRingIndicatorTask(mNumber, mType), michael@0: sRingInterval); michael@0: } michael@0: michael@0: private: michael@0: nsString mNumber; michael@0: int mType; michael@0: }; michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: class BluetoothHfpManager::CloseScoTask : public Task michael@0: { michael@0: private: michael@0: void Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(sBluetoothHfpManager); michael@0: michael@0: sBluetoothHfpManager->DisconnectSco(); michael@0: } michael@0: }; michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: static bool michael@0: IsValidDtmf(const char aChar) { michael@0: // Valid DTMF: [*#0-9ABCD] michael@0: if (aChar == '*' || aChar == '#') { michael@0: return true; michael@0: } else if (aChar >= '0' && aChar <= '9') { michael@0: return true; michael@0: } else if (aChar >= 'A' && aChar <= 'D') { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: IsMandatoryIndicator(const CINDType aType) { michael@0: return (aType == CINDType::CALL) || michael@0: (aType == CINDType::CALLHELD) || michael@0: (aType == CINDType::CALLSETUP); michael@0: } michael@0: michael@0: /** michael@0: * Call michael@0: */ michael@0: Call::Call() michael@0: { michael@0: Reset(); michael@0: } michael@0: michael@0: void michael@0: Call::Reset() michael@0: { michael@0: mState = nsITelephonyProvider::CALL_STATE_DISCONNECTED; michael@0: mDirection = false; michael@0: mIsConference = false; michael@0: mNumber.Truncate(); michael@0: mType = TOA_UNKNOWN; michael@0: } michael@0: michael@0: bool michael@0: Call::IsActive() michael@0: { michael@0: return (mState == nsITelephonyProvider::CALL_STATE_CONNECTED); michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: /** michael@0: * BluetoothHfpManager michael@0: */ michael@0: BluetoothHfpManager::BluetoothHfpManager() michael@0: { michael@0: #ifdef MOZ_B2G_RIL michael@0: mPhoneType = PhoneType::NONE; michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: Reset(); michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: void michael@0: BluetoothHfpManager::ResetCallArray() michael@0: { michael@0: mCurrentCallArray.Clear(); michael@0: // Append a call object at the beginning of mCurrentCallArray since call michael@0: // index from RIL starts at 1. michael@0: Call call; michael@0: mCurrentCallArray.AppendElement(call); michael@0: michael@0: if (mPhoneType == PhoneType::CDMA) { michael@0: mCdmaSecondCall.Reset(); michael@0: } michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: void michael@0: BluetoothHfpManager::Reset() michael@0: { michael@0: #ifdef MOZ_B2G_RIL michael@0: sStopSendingRingFlag = true; michael@0: sCINDItems[CINDType::CALL].value = CallState::NO_CALL; michael@0: sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP; michael@0: sCINDItems[CINDType::CALLHELD].value = CallHeldState::NO_CALLHELD; michael@0: #endif michael@0: for (uint8_t i = 1; i < ArrayLength(sCINDItems); i++) { michael@0: sCINDItems[i].activated = true; michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: mCCWA = false; michael@0: mCLIP = false; michael@0: mDialingRequestProcessed = true; michael@0: michael@0: // We disable BSIR by default as it requires OEM implement BT SCO + SPEAKER michael@0: // output audio path in audio driver. OEM can enable BSIR by setting michael@0: // mBSIR=true here. michael@0: // michael@0: // Please see Bug 878728 for more information. michael@0: mBSIR = false; michael@0: michael@0: ResetCallArray(); michael@0: #endif michael@0: mCMEE = false; michael@0: mCMER = false; michael@0: mConnectScoRequest = false; michael@0: mSlcConnected = false; michael@0: mIsHsp = false; michael@0: mReceiveVgsFlag = false; michael@0: mController = nullptr; michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::Init() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE(obs, false); michael@0: michael@0: if (NS_FAILED(obs->AddObserver(this, MOZSETTINGS_CHANGED_ID, false)) || michael@0: NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { michael@0: BT_WARNING("Failed to add observers!"); michael@0: return false; michael@0: } michael@0: michael@0: hal::RegisterBatteryObserver(this); michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: mListener = new BluetoothRilListener(); michael@0: if (!mListener->Listen(true)) { michael@0: BT_WARNING("Failed to start listening RIL"); michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr settings = michael@0: do_GetService("@mozilla.org/settingsService;1"); michael@0: NS_ENSURE_TRUE(settings, false); michael@0: michael@0: nsCOMPtr settingsLock; michael@0: nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: nsRefPtr callback = new GetVolumeTask(); michael@0: rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO_ID, callback); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: Listen(); michael@0: michael@0: mScoSocket = new BluetoothSocket(this, michael@0: BluetoothSocketType::SCO, michael@0: true, michael@0: false); michael@0: mScoSocketStatus = mScoSocket->GetConnectionStatus(); michael@0: ListenSco(); michael@0: return true; michael@0: } michael@0: michael@0: BluetoothHfpManager::~BluetoothHfpManager() michael@0: { michael@0: #ifdef MOZ_B2G_RIL michael@0: if (!mListener->Listen(false)) { michael@0: BT_WARNING("Failed to stop listening RIL"); michael@0: } michael@0: mListener = nullptr; michael@0: #endif michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(obs); michael@0: michael@0: if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) || michael@0: NS_FAILED(obs->RemoveObserver(this, MOZSETTINGS_CHANGED_ID))) { michael@0: BT_WARNING("Failed to remove observers!"); michael@0: } michael@0: michael@0: hal::UnregisterBatteryObserver(this); michael@0: } michael@0: michael@0: //static michael@0: BluetoothHfpManager* michael@0: BluetoothHfpManager::Get() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // If sBluetoothHfpManager already exists, exit early michael@0: if (sBluetoothHfpManager) { michael@0: return sBluetoothHfpManager; michael@0: } michael@0: michael@0: // If we're in shutdown, don't create a new instance michael@0: NS_ENSURE_FALSE(sInShutdown, nullptr); michael@0: michael@0: // Create a new instance, register, and return michael@0: BluetoothHfpManager* manager = new BluetoothHfpManager(); michael@0: NS_ENSURE_TRUE(manager->Init(), nullptr); michael@0: michael@0: sBluetoothHfpManager = manager; michael@0: return sBluetoothHfpManager; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::NotifyConnectionStatusChanged(const nsAString& aType) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Notify Gecko observers michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(obs); michael@0: michael@0: if (NS_FAILED(obs->NotifyObservers(this, NS_ConvertUTF16toUTF8(aType).get(), michael@0: mDeviceAddress.get()))) { michael@0: BT_WARNING("Failed to notify observsers!"); michael@0: } michael@0: michael@0: // Dispatch an event of status change michael@0: bool status; michael@0: nsAutoString eventName; michael@0: if (aType.EqualsLiteral(BLUETOOTH_HFP_STATUS_CHANGED_ID)) { michael@0: status = IsConnected(); michael@0: eventName.AssignLiteral(HFP_STATUS_CHANGED_ID); michael@0: } else if (aType.EqualsLiteral(BLUETOOTH_SCO_STATUS_CHANGED_ID)) { michael@0: status = IsScoConnected(); michael@0: eventName.AssignLiteral(SCO_STATUS_CHANGED_ID); michael@0: } else { michael@0: MOZ_ASSERT(false); michael@0: return; michael@0: } michael@0: michael@0: DispatchStatusChangedEvent(eventName, mDeviceAddress, status); michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: void michael@0: BluetoothHfpManager::NotifyDialer(const nsAString& aCommand) michael@0: { michael@0: nsString type, name; michael@0: BluetoothValue v; michael@0: InfallibleTArray parameters; michael@0: type.AssignLiteral("bluetooth-dialer-command"); michael@0: michael@0: name.AssignLiteral("command"); michael@0: v = nsString(aCommand); michael@0: parameters.AppendElement(BluetoothNamedValue(name, v)); michael@0: michael@0: if (!BroadcastSystemMessage(type, parameters)) { michael@0: BT_WARNING("Failed to broadcast system message to dialer"); michael@0: } michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: void michael@0: BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // The string that we're interested in will be a JSON string that looks like: michael@0: // {"key":"volumeup", "value":10} michael@0: // {"key":"volumedown", "value":2} michael@0: michael@0: JSContext* cx = nsContentUtils::GetSafeJSContext(); michael@0: NS_ENSURE_TRUE_VOID(cx); michael@0: michael@0: JS::Rooted val(cx); michael@0: NS_ENSURE_TRUE_VOID(JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val)); michael@0: NS_ENSURE_TRUE_VOID(val.isObject()); michael@0: michael@0: JS::Rooted obj(cx, &val.toObject()); michael@0: JS::Rooted key(cx); michael@0: if (!JS_GetProperty(cx, obj, "key", &key) || !key.isString()) { michael@0: return; michael@0: } michael@0: michael@0: bool match; michael@0: if (!JS_StringEqualsAscii(cx, key.toString(), AUDIO_VOLUME_BT_SCO_ID, &match) || michael@0: !match) { michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted value(cx); michael@0: if (!JS_GetProperty(cx, obj, "value", &value)|| michael@0: !value.isNumber()) { michael@0: return; michael@0: } michael@0: michael@0: mCurrentVgs = value.toNumber(); michael@0: michael@0: // Adjust volume by headset and we don't have to send volume back to headset michael@0: if (mReceiveVgsFlag) { michael@0: mReceiveVgsFlag = false; michael@0: return; michael@0: } michael@0: michael@0: // Only send volume back when there's a connected headset michael@0: if (IsConnected()) { michael@0: SendCommand(RESPONSE_VGS, mCurrentVgs); michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: void michael@0: BluetoothHfpManager::HandleVoiceConnectionChanged(uint32_t aClientId) michael@0: { michael@0: nsCOMPtr connection = michael@0: do_GetService(NS_RILCONTENTHELPER_CONTRACTID); michael@0: NS_ENSURE_TRUE_VOID(connection); michael@0: michael@0: nsCOMPtr voiceInfo; michael@0: connection->GetVoiceConnectionInfo(aClientId, getter_AddRefs(voiceInfo)); michael@0: NS_ENSURE_TRUE_VOID(voiceInfo); michael@0: michael@0: nsString type; michael@0: voiceInfo->GetType(type); michael@0: mPhoneType = GetPhoneType(type); michael@0: michael@0: bool roaming; michael@0: voiceInfo->GetRoaming(&roaming); michael@0: UpdateCIND(CINDType::ROAM, roaming); michael@0: michael@0: nsString regState; michael@0: voiceInfo->GetState(regState); michael@0: bool service = regState.EqualsLiteral("registered"); michael@0: if (service != sCINDItems[CINDType::SERVICE].value) { michael@0: // Notify BluetoothRilListener of service change michael@0: mListener->ServiceChanged(aClientId, service); michael@0: } michael@0: UpdateCIND(CINDType::SERVICE, service); michael@0: michael@0: JSContext* cx = nsContentUtils::GetSafeJSContext(); michael@0: NS_ENSURE_TRUE_VOID(cx); michael@0: JS::Rooted value(cx); michael@0: voiceInfo->GetRelSignalStrength(&value); michael@0: NS_ENSURE_TRUE_VOID(value.isNumber()); michael@0: uint8_t signal = ceil(value.toNumber() / 20.0); michael@0: UpdateCIND(CINDType::SIGNAL, signal); michael@0: michael@0: /** michael@0: * Possible return values for mode are: michael@0: * - null (unknown): set mNetworkSelectionMode to 0 (auto) michael@0: * - automatic: set mNetworkSelectionMode to 0 (auto) michael@0: * - manual: set mNetworkSelectionMode to 1 (manual) michael@0: */ michael@0: nsString mode; michael@0: connection->GetNetworkSelectionMode(aClientId, mode); michael@0: if (mode.EqualsLiteral("manual")) { michael@0: mNetworkSelectionMode = 1; michael@0: } else { michael@0: mNetworkSelectionMode = 0; michael@0: } michael@0: michael@0: nsCOMPtr network; michael@0: voiceInfo->GetNetwork(getter_AddRefs(network)); michael@0: NS_ENSURE_TRUE_VOID(network); michael@0: network->GetLongName(mOperatorName); michael@0: michael@0: // According to GSM 07.07, " indicates if the format is alphanumeric michael@0: // or numeric; long alphanumeric format can be upto 16 characters long and michael@0: // short format up to 8 characters (refer GSM MoU SE.13 [9])..." michael@0: // However, we found that the operator name may sometimes be longer than 16 michael@0: // characters. After discussion, we decided to fix this here but not in RIL michael@0: // or modem. michael@0: // michael@0: // Please see Bug 871366 for more information. michael@0: if (mOperatorName.Length() > 16) { michael@0: BT_WARNING("The operator name was longer than 16 characters. We cut it."); michael@0: mOperatorName.Left(mOperatorName, 16); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::HandleIccInfoChanged(uint32_t aClientId) michael@0: { michael@0: nsCOMPtr icc = michael@0: do_GetService(NS_RILCONTENTHELPER_CONTRACTID); michael@0: NS_ENSURE_TRUE_VOID(icc); michael@0: michael@0: nsCOMPtr iccInfo; michael@0: icc->GetIccInfo(aClientId, getter_AddRefs(iccInfo)); michael@0: NS_ENSURE_TRUE_VOID(iccInfo); michael@0: michael@0: nsCOMPtr gsmIccInfo = do_QueryInterface(iccInfo); michael@0: NS_ENSURE_TRUE_VOID(gsmIccInfo); michael@0: gsmIccInfo->GetMsisdn(mMsisdn); michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: void michael@0: BluetoothHfpManager::HandleShutdown() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: sInShutdown = true; michael@0: Disconnect(nullptr); michael@0: DisconnectSco(); michael@0: sBluetoothHfpManager = nullptr; michael@0: } michael@0: michael@0: // Virtual function of class SocketConsumer michael@0: void michael@0: BluetoothHfpManager::ReceiveSocketData(BluetoothSocket* aSocket, michael@0: nsAutoPtr& aMessage) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aSocket); michael@0: michael@0: nsAutoCString msg((const char*)aMessage->mData.get(), aMessage->mSize); michael@0: msg.StripWhitespace(); michael@0: michael@0: nsTArray atCommandValues; michael@0: michael@0: // For more information, please refer to 4.34.1 "Bluetooth Defined AT michael@0: // Capabilities" in Bluetooth hands-free profile 1.6 michael@0: if (msg.Find("AT+BRSF=") != -1) { michael@0: #ifdef MOZ_B2G_RIL michael@0: uint32_t brsf = BRSF_BIT_ABILITY_TO_REJECT_CALL | michael@0: BRSF_BIT_ENHANCED_CALL_STATUS; michael@0: michael@0: // No support for three way calling in CDMA since michael@0: // CDMA disallows to hang existing call for CHLD=1 michael@0: if (mPhoneType != PhoneType::CDMA) { michael@0: brsf |= BRSF_BIT_THREE_WAY_CALLING; michael@0: } michael@0: michael@0: if (mBSIR) { michael@0: brsf |= BRSF_BIT_IN_BAND_RING_TONE; michael@0: } michael@0: #else michael@0: uint32_t brsf = 0; michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: SendCommand(RESPONSE_BRSF, brsf); michael@0: } else if (msg.Find("AT+CIND=?") != -1) { michael@0: // Asking for CIND range michael@0: SendCommand(RESPONSE_CIND, 0); michael@0: } else if (msg.Find("AT+CIND?") != -1) { michael@0: // Asking for CIND value michael@0: SendCommand(RESPONSE_CIND, 1); michael@0: } else if (msg.Find("AT+CMER=") != -1) { michael@0: /** michael@0: * SLC establishment is done when AT+CMER has been received. michael@0: * Do nothing but respond with "OK". michael@0: */ michael@0: ParseAtCommand(msg, 8, atCommandValues); michael@0: michael@0: if (atCommandValues.Length() < 4) { michael@0: BT_WARNING("Could't get the value of command [AT+CMER=]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: if (!atCommandValues[0].EqualsLiteral("3") || michael@0: !atCommandValues[1].EqualsLiteral("0") || michael@0: !atCommandValues[2].EqualsLiteral("0")) { michael@0: BT_WARNING("Wrong value of CMER"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: mCMER = atCommandValues[3].EqualsLiteral("1"); michael@0: michael@0: /** michael@0: * SLC is connected once the "indicator status update" is enabled by michael@0: * AT+CMER command. See 4.2.1 in Bluetooth hands-free profile 1.6 michael@0: * for more details. michael@0: */ michael@0: if (mCMER) { michael@0: mSlcConnected = true; michael@0: } michael@0: michael@0: // If we get internal request for SCO connection, michael@0: // setup SCO after Service Level Connection established. michael@0: if (mConnectScoRequest) { michael@0: mConnectScoRequest = false; michael@0: ConnectSco(); michael@0: } michael@0: } else if (msg.Find("AT+CMEE=") != -1) { michael@0: ParseAtCommand(msg, 8, atCommandValues); michael@0: michael@0: if (atCommandValues.IsEmpty()) { michael@0: BT_WARNING("Could't get the value of command [AT+CMEE=]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: // AT+CMEE = 0: +CME ERROR shall not be used michael@0: // AT+CMEE = 1: use numeric michael@0: // AT+CMEE = 2: use verbose michael@0: mCMEE = !atCommandValues[0].EqualsLiteral("0"); michael@0: #ifdef MOZ_B2G_RIL michael@0: } else if (msg.Find("AT+COPS=") != -1) { michael@0: ParseAtCommand(msg, 8, atCommandValues); michael@0: michael@0: if (atCommandValues.Length() != 2) { michael@0: BT_WARNING("Could't get the value of command [AT+COPS=]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: // Handsfree only support AT+COPS=3,0 michael@0: if (!atCommandValues[0].EqualsLiteral("3") || michael@0: !atCommandValues[1].EqualsLiteral("0")) { michael@0: if (mCMEE) { michael@0: SendCommand(RESPONSE_CME_ERROR, BluetoothCmeError::OPERATION_NOT_SUPPORTED); michael@0: } else { michael@0: SendLine("ERROR"); michael@0: } michael@0: return; michael@0: } michael@0: } else if (msg.Find("AT+COPS?") != -1) { michael@0: nsAutoCString message("+COPS: "); michael@0: message.AppendInt(mNetworkSelectionMode); michael@0: message.AppendLiteral(",0,\""); michael@0: message.Append(NS_ConvertUTF16toUTF8(mOperatorName)); michael@0: message.AppendLiteral("\""); michael@0: SendLine(message.get()); michael@0: } else if (msg.Find("AT+VTS=") != -1) { michael@0: ParseAtCommand(msg, 7, atCommandValues); michael@0: michael@0: if (atCommandValues.Length() != 1) { michael@0: BT_WARNING("Couldn't get the value of command [AT+VTS=]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: if (IsValidDtmf(atCommandValues[0].get()[0])) { michael@0: nsAutoCString message("VTS="); michael@0: message += atCommandValues[0].get()[0]; michael@0: NotifyDialer(NS_ConvertUTF8toUTF16(message)); michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: } else if (msg.Find("AT+VGM=") != -1) { michael@0: ParseAtCommand(msg, 7, atCommandValues); michael@0: michael@0: if (atCommandValues.IsEmpty()) { michael@0: BT_WARNING("Couldn't get the value of command [AT+VGM]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: nsresult rv; michael@0: int vgm = atCommandValues[0].ToInteger(&rv); michael@0: if (NS_FAILED(rv)) { michael@0: BT_WARNING("Failed to extract microphone volume from bluetooth headset!"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: if (vgm < 0 || vgm > 15) { michael@0: BT_WARNING("Received invalid VGM value"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: mCurrentVgm = vgm; michael@0: #ifdef MOZ_B2G_RIL michael@0: } else if (msg.Find("AT+CHLD=?") != -1) { michael@0: SendLine("+CHLD: (0,1,2,3)"); michael@0: } else if (msg.Find("AT+CHLD=") != -1) { michael@0: ParseAtCommand(msg, 8, atCommandValues); michael@0: michael@0: if (atCommandValues.IsEmpty()) { michael@0: BT_WARNING("Could't get the value of command [AT+CHLD=]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: /** michael@0: * The following three cases are supported: michael@0: * AT+CHLD=0 - Releases all held calls or sets User Determined User Busy michael@0: * (UDUB) for a waiting call michael@0: * AT+CHLD=1 - Releases active calls and accepts the other (held or michael@0: * waiting) call michael@0: * AT+CHLD=2 - Places active calls on hold and accepts the other (held michael@0: * or waiting) call michael@0: * AT+CHLD=3 - Adds a held call to the conversation. michael@0: * michael@0: * The following cases are NOT supported yet: michael@0: * AT+CHLD=1, AT+CHLD=2, AT+CHLD=4 michael@0: * Please see 4.33.2 in Bluetooth hands-free profile 1.6 for more michael@0: * information. michael@0: */ michael@0: char chld = atCommandValues[0][0]; michael@0: bool valid = true; michael@0: if (atCommandValues[0].Length() > 1) { michael@0: BT_WARNING("No index should be included in command [AT+CHLD]"); michael@0: valid = false; michael@0: } else if (chld == '4') { michael@0: BT_WARNING("The value of command [AT+CHLD] is not supported"); michael@0: valid = false; michael@0: } else if (chld == '0') { michael@0: // We need to rename these dialer commands for better readability michael@0: // and expandability. michael@0: // See bug 884190 for more information. michael@0: NotifyDialer(NS_LITERAL_STRING("CHLD=0")); michael@0: } else if (chld == '1') { michael@0: NotifyDialer(NS_LITERAL_STRING("CHLD=1")); michael@0: } else if (chld == '2') { michael@0: NotifyDialer(NS_LITERAL_STRING("CHLD=2")); michael@0: } else if (chld == '3') { michael@0: NotifyDialer(NS_LITERAL_STRING("CHLD=3")); michael@0: } else { michael@0: BT_WARNING("Wrong value of command [AT+CHLD]"); michael@0: valid = false; michael@0: } michael@0: michael@0: if (!valid) { michael@0: SendLine("ERROR"); michael@0: return; michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: } else if (msg.Find("AT+VGS=") != -1) { michael@0: // Adjust volume by headset michael@0: mReceiveVgsFlag = true; michael@0: ParseAtCommand(msg, 7, atCommandValues); michael@0: michael@0: if (atCommandValues.IsEmpty()) { michael@0: BT_WARNING("Could't get the value of command [AT+VGS=]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: nsresult rv; michael@0: int newVgs = atCommandValues[0].ToInteger(&rv); michael@0: if (NS_FAILED(rv)) { michael@0: BT_WARNING("Failed to extract volume value from bluetooth headset!"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: if (newVgs == mCurrentVgs) { michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: if (newVgs < 0 || newVgs > 15) { michael@0: BT_WARNING("Received invalid VGS value"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (!os) { michael@0: BT_WARNING("Failed to get observer service!"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: nsString data; michael@0: data.AppendInt(newVgs); michael@0: os->NotifyObservers(nullptr, "bluetooth-volume-change", data.get()); michael@0: #ifdef MOZ_B2G_RIL michael@0: } else if ((msg.Find("AT+BLDN") != -1) || (msg.Find("ATD>") != -1)) { michael@0: // Dialer app of FFOS v1 does not have plan to support Memory Dailing. michael@0: // However, in order to pass Bluetooth HFP certification, we still have to michael@0: // make a call when we receive AT command 'ATD>n'. michael@0: mDialingRequestProcessed = false; michael@0: michael@0: if (msg.Find("AT+BLDN") != -1) { michael@0: NotifyDialer(NS_LITERAL_STRING("BLDN")); michael@0: } else { michael@0: NotifyDialer(NS_ConvertUTF8toUTF16(msg)); michael@0: } michael@0: michael@0: MessageLoop::current()-> michael@0: PostDelayedTask(FROM_HERE, new RespondToBLDNTask(), michael@0: sWaitingForDialingInterval); michael@0: michael@0: // Don't send response 'OK' here because we'll respond later in either michael@0: // RespondToBLDNTask or HandleCallStateChanged() michael@0: return; michael@0: } else if (msg.Find("ATA") != -1) { michael@0: NotifyDialer(NS_LITERAL_STRING("ATA")); michael@0: } else if (msg.Find("AT+CHUP") != -1) { michael@0: NotifyDialer(NS_LITERAL_STRING("CHUP")); michael@0: } else if (msg.Find("AT+CLCC") != -1) { michael@0: SendCommand(RESPONSE_CLCC); michael@0: } else if (msg.Find("ATD") != -1) { michael@0: nsAutoCString message(msg), newMsg; michael@0: int end = message.FindChar(';'); michael@0: if (end < 0) { michael@0: BT_WARNING("Could't get the value of command [ATD]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: newMsg += nsDependentCSubstring(message, 0, end); michael@0: NotifyDialer(NS_ConvertUTF8toUTF16(newMsg)); michael@0: } else if (msg.Find("AT+CLIP=") != -1) { michael@0: ParseAtCommand(msg, 8, atCommandValues); michael@0: michael@0: if (atCommandValues.IsEmpty()) { michael@0: BT_WARNING("Could't get the value of command [AT+CLIP=]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: mCLIP = atCommandValues[0].EqualsLiteral("1"); michael@0: } else if (msg.Find("AT+CCWA=") != -1) { michael@0: ParseAtCommand(msg, 8, atCommandValues); michael@0: michael@0: if (atCommandValues.IsEmpty()) { michael@0: BT_WARNING("Could't get the value of command [AT+CCWA=]"); michael@0: goto respond_with_ok; michael@0: } michael@0: michael@0: mCCWA = atCommandValues[0].EqualsLiteral("1"); michael@0: } else if (msg.Find("AT+CKPD") != -1) { michael@0: if (!sStopSendingRingFlag) { michael@0: // Bluetooth HSP spec 4.2.2 michael@0: // There is an incoming call, notify Dialer to pick up the phone call michael@0: // and SCO will be established after we get the CallStateChanged event michael@0: // indicating the call is answered successfully. michael@0: NotifyDialer(NS_LITERAL_STRING("ATA")); michael@0: } else { michael@0: if (!IsScoConnected()) { michael@0: // Bluetooth HSP spec 4.3 michael@0: // If there's no SCO, set up a SCO link. michael@0: ConnectSco(); michael@0: } else if (!mFirstCKPD) { michael@0: // Bluetooth HSP spec 4.5 michael@0: // There are two ways to release SCO: sending CHUP to dialer or closing michael@0: // SCO socket directly. We notify dialer only if there is at least one michael@0: // active call. michael@0: if (mCurrentCallArray.Length() > 1) { michael@0: NotifyDialer(NS_LITERAL_STRING("CHUP")); michael@0: } else { michael@0: DisconnectSco(); michael@0: } michael@0: } else { michael@0: // Three conditions have to be matched to come in here: michael@0: // (1) Not sending RING indicator michael@0: // (2) A SCO link exists michael@0: // (3) This is the very first AT+CKPD=200 of this session michael@0: // It is the case of Figure 4.3, Bluetooth HSP spec. Do nothing. michael@0: BT_WARNING("AT+CKPD=200: Do nothing"); michael@0: } michael@0: } michael@0: michael@0: mFirstCKPD = false; michael@0: } else if (msg.Find("AT+CNUM") != -1) { michael@0: if (!mMsisdn.IsEmpty()) { michael@0: nsAutoCString message("+CNUM: ,\""); michael@0: message.Append(NS_ConvertUTF16toUTF8(mMsisdn).get()); michael@0: message.AppendLiteral("\","); michael@0: message.AppendInt(TOA_UNKNOWN); michael@0: message.AppendLiteral(",,4"); michael@0: SendLine(message.get()); michael@0: } michael@0: } else if (msg.Find("AT+BIA=") != -1) { michael@0: ParseAtCommand(msg, 7, atCommandValues); michael@0: michael@0: for (uint8_t i = 0; i < atCommandValues.Length(); i++) { michael@0: CINDType indicatorType = (CINDType) (i + 1); michael@0: if (indicatorType >= (int)ArrayLength(sCINDItems)) { michael@0: // Ignore excess parameters at the end michael@0: break; michael@0: } michael@0: michael@0: if (!IsMandatoryIndicator(indicatorType)) { michael@0: /** michael@0: * Accept only following indicator states: michael@0: * - "1": activate michael@0: * - "0": deactivate michael@0: * - "" : maintain current state michael@0: * Otherwise we regard the command incorrectly formatted. michael@0: */ michael@0: if (atCommandValues[i].EqualsLiteral("1")) { michael@0: sCINDItems[indicatorType].activated = 1; michael@0: } else if (atCommandValues[i].EqualsLiteral("0")) { michael@0: sCINDItems[indicatorType].activated = 0; michael@0: } else if (!atCommandValues[i].EqualsLiteral("")) { michael@0: SendLine("ERROR"); michael@0: return; michael@0: } michael@0: } else { michael@0: // Ignore requests to activate/deactivate mandatory indicators michael@0: } michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: } else { michael@0: nsCString warningMsg; michael@0: warningMsg.Append(NS_LITERAL_CSTRING("Unsupported AT command: ")); michael@0: warningMsg.Append(msg); michael@0: warningMsg.Append(NS_LITERAL_CSTRING(", reply with ERROR")); michael@0: BT_WARNING(warningMsg.get()); michael@0: michael@0: SendLine("ERROR"); michael@0: return; michael@0: } michael@0: michael@0: respond_with_ok: michael@0: // We always respond to remote device with "OK" in general cases. michael@0: SendLine("OK"); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::Connect(const nsAString& aDeviceAddress, michael@0: BluetoothProfileController* aController) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aController && !mController); michael@0: michael@0: BluetoothService* bs = BluetoothService::Get(); michael@0: if (!bs || sInShutdown) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: michael@0: if (mSocket) { michael@0: if (mDeviceAddress == aDeviceAddress) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED)); michael@0: } else { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_REACHED_CONNECTION_LIMIT)); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: nsString uuid; michael@0: BluetoothUuidHelper::GetString(BluetoothServiceClass::HANDSFREE, uuid); michael@0: michael@0: if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: michael@0: // Stop listening because currently we only support one connection at a time. michael@0: if (mHandsfreeSocket) { michael@0: mHandsfreeSocket->Disconnect(); michael@0: mHandsfreeSocket = nullptr; michael@0: } michael@0: michael@0: if (mHeadsetSocket) { michael@0: mHeadsetSocket->Disconnect(); michael@0: mHeadsetSocket = nullptr; michael@0: } michael@0: michael@0: mController = aController; michael@0: mSocket = michael@0: new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true); michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::Listen() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (sInShutdown) { michael@0: BT_WARNING("Listen called while in shutdown!"); michael@0: return false; michael@0: } michael@0: michael@0: if (mSocket) { michael@0: BT_WARNING("mSocket exists. Failed to listen."); michael@0: return false; michael@0: } michael@0: michael@0: if (!mHandsfreeSocket) { michael@0: mHandsfreeSocket = michael@0: new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true); michael@0: michael@0: if (!mHandsfreeSocket->Listen( michael@0: BluetoothReservedChannels::CHANNEL_HANDSFREE_AG)) { michael@0: BT_WARNING("[HFP] Can't listen on RFCOMM socket!"); michael@0: mHandsfreeSocket = nullptr; michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!mHeadsetSocket) { michael@0: mHeadsetSocket = michael@0: new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true); michael@0: michael@0: if (!mHeadsetSocket->Listen( michael@0: BluetoothReservedChannels::CHANNEL_HEADSET_AG)) { michael@0: BT_WARNING("[HSP] Can't listen on RFCOMM socket!"); michael@0: mHandsfreeSocket->Disconnect(); michael@0: mHandsfreeSocket = nullptr; michael@0: mHeadsetSocket = nullptr; michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::Disconnect(BluetoothProfileController* aController) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!mSocket) { michael@0: if (aController) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED)); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(!mController); michael@0: michael@0: mController = aController; michael@0: mSocket->Disconnect(); michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: void michael@0: BluetoothHfpManager::SendCCWA(const nsAString& aNumber, int aType) michael@0: { michael@0: if (mCCWA) { michael@0: nsAutoCString ccwaMsg("+CCWA: \""); michael@0: ccwaMsg.Append(NS_ConvertUTF16toUTF8(aNumber)); michael@0: ccwaMsg.AppendLiteral("\","); michael@0: ccwaMsg.AppendInt(aType); michael@0: SendLine(ccwaMsg.get()); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::SendCLCC(const Call& aCall, int aIndex) michael@0: { michael@0: if (aCall.mState == nsITelephonyProvider::CALL_STATE_DISCONNECTED) { michael@0: return true; michael@0: } michael@0: michael@0: nsAutoCString message(RESPONSE_CLCC); michael@0: message.AppendInt(aIndex); michael@0: message.AppendLiteral(","); michael@0: message.AppendInt(aCall.mDirection); michael@0: message.AppendLiteral(","); michael@0: michael@0: int status = 0; michael@0: switch (aCall.mState) { michael@0: case nsITelephonyProvider::CALL_STATE_CONNECTED: michael@0: if (mPhoneType == PhoneType::CDMA && aIndex == 1) { michael@0: status = (mCdmaSecondCall.IsActive()) ? 1 : 0; michael@0: } michael@0: message.AppendInt(status); michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_HELD: michael@0: message.AppendInt(1); michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_DIALING: michael@0: message.AppendInt(2); michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_ALERTING: michael@0: message.AppendInt(3); michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_INCOMING: michael@0: if (!FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) { michael@0: message.AppendInt(4); michael@0: } else { michael@0: message.AppendInt(5); michael@0: } michael@0: break; michael@0: default: michael@0: BT_WARNING("Not handling call status for CLCC"); michael@0: break; michael@0: } michael@0: michael@0: message.AppendLiteral(",0,0,\""); michael@0: message.Append(NS_ConvertUTF16toUTF8(aCall.mNumber)); michael@0: message.AppendLiteral("\","); michael@0: message.AppendInt(aCall.mType); michael@0: michael@0: return SendLine(message.get()); michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: bool michael@0: BluetoothHfpManager::SendLine(const char* aMessage) michael@0: { michael@0: MOZ_ASSERT(mSocket); michael@0: michael@0: nsAutoCString msg; michael@0: michael@0: msg.AppendLiteral(kHfpCrlf); michael@0: msg.Append(aMessage); michael@0: msg.AppendLiteral(kHfpCrlf); michael@0: michael@0: return mSocket->SendSocketData(msg); michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::SendCommand(const char* aCommand, uint32_t aValue) michael@0: { michael@0: if (!IsConnected()) { michael@0: BT_WARNING("Trying to SendCommand() without a SLC"); michael@0: return false; michael@0: } michael@0: michael@0: nsAutoCString message; michael@0: message += aCommand; michael@0: michael@0: if (!strcmp(aCommand, RESPONSE_CIEV)) { michael@0: if (!mCMER || !sCINDItems[aValue].activated) { michael@0: // Indicator status update is disabled michael@0: return true; michael@0: } michael@0: michael@0: if ((aValue < 1) || (aValue > ArrayLength(sCINDItems) - 1)) { michael@0: BT_WARNING("unexpected CINDType for CIEV command"); michael@0: return false; michael@0: } michael@0: michael@0: message.AppendInt(aValue); michael@0: message.AppendLiteral(","); michael@0: message.AppendInt(sCINDItems[aValue].value); michael@0: } else if (!strcmp(aCommand, RESPONSE_CIND)) { michael@0: if (!aValue) { michael@0: // Query for range michael@0: for (uint8_t i = 1; i < ArrayLength(sCINDItems); i++) { michael@0: message.AppendLiteral("(\""); michael@0: message.Append(sCINDItems[i].name); michael@0: message.AppendLiteral("\",("); michael@0: message.Append(sCINDItems[i].range); michael@0: message.AppendLiteral(")"); michael@0: if (i == (ArrayLength(sCINDItems) - 1)) { michael@0: message.AppendLiteral(")"); michael@0: break; michael@0: } michael@0: message.AppendLiteral("),"); michael@0: } michael@0: } else { michael@0: // Query for value michael@0: for (uint8_t i = 1; i < ArrayLength(sCINDItems); i++) { michael@0: message.AppendInt(sCINDItems[i].value); michael@0: if (i == (ArrayLength(sCINDItems) - 1)) { michael@0: break; michael@0: } michael@0: message.AppendLiteral(","); michael@0: } michael@0: } michael@0: #ifdef MOZ_B2G_RIL michael@0: } else if (!strcmp(aCommand, RESPONSE_CLCC)) { michael@0: bool rv = true; michael@0: uint32_t callNumbers = mCurrentCallArray.Length(); michael@0: uint32_t i; michael@0: for (i = 1; i < callNumbers; i++) { michael@0: rv &= SendCLCC(mCurrentCallArray[i], i); michael@0: } michael@0: michael@0: if (!mCdmaSecondCall.mNumber.IsEmpty()) { michael@0: MOZ_ASSERT(mPhoneType == PhoneType::CDMA); michael@0: MOZ_ASSERT(i == 2); michael@0: michael@0: rv &= SendCLCC(mCdmaSecondCall, 2); michael@0: } michael@0: michael@0: return rv; michael@0: #endif // MOZ_B2G_RIL michael@0: } else { michael@0: message.AppendInt(aValue); michael@0: } michael@0: michael@0: return SendLine(message.get()); michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: void michael@0: BluetoothHfpManager::UpdateCIND(uint8_t aType, uint8_t aValue, bool aSend) michael@0: { michael@0: if (sCINDItems[aType].value != aValue) { michael@0: sCINDItems[aType].value = aValue; michael@0: if (aSend) { michael@0: SendCommand(RESPONSE_CIEV, aType); michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: BluetoothHfpManager::FindFirstCall(uint16_t aState) michael@0: { michael@0: uint32_t callLength = mCurrentCallArray.Length(); michael@0: michael@0: for (uint32_t i = 1; i < callLength; ++i) { michael@0: if (mCurrentCallArray[i].mState == aState) { michael@0: return i; michael@0: } michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: uint32_t michael@0: BluetoothHfpManager::GetNumberOfCalls(uint16_t aState) michael@0: { michael@0: uint32_t num = 0; michael@0: uint32_t callLength = mCurrentCallArray.Length(); michael@0: michael@0: for (uint32_t i = 1; i < callLength; ++i) { michael@0: if (mCurrentCallArray[i].mState == aState) { michael@0: ++num; michael@0: } michael@0: } michael@0: michael@0: return num; michael@0: } michael@0: michael@0: uint32_t michael@0: BluetoothHfpManager::GetNumberOfConCalls() michael@0: { michael@0: uint32_t num = 0; michael@0: uint32_t callLength = mCurrentCallArray.Length(); michael@0: michael@0: for (uint32_t i = 1; i < callLength; ++i) { michael@0: if (mCurrentCallArray[i].mIsConference) { michael@0: ++num; michael@0: } michael@0: } michael@0: michael@0: return num; michael@0: } michael@0: michael@0: uint32_t michael@0: BluetoothHfpManager::GetNumberOfConCalls(uint16_t aState) michael@0: { michael@0: uint32_t num = 0; michael@0: uint32_t callLength = mCurrentCallArray.Length(); michael@0: michael@0: for (uint32_t i = 1; i < callLength; ++i) { michael@0: if (mCurrentCallArray[i].mIsConference michael@0: && mCurrentCallArray[i].mState == aState) { michael@0: ++num; michael@0: } michael@0: } michael@0: michael@0: return num; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex, michael@0: uint16_t aCallState, michael@0: const nsAString& aError, michael@0: const nsAString& aNumber, michael@0: const bool aIsOutgoing, michael@0: const bool aIsConference, michael@0: bool aSend) michael@0: { michael@0: if (!IsConnected()) { michael@0: // Normal case. No need to print out warnings. michael@0: return; michael@0: } michael@0: michael@0: // aCallIndex can be UINT32_MAX for the pending outgoing call state update. michael@0: // aCallIndex will be updated again after real call state changes. See Bug michael@0: // 990467. michael@0: if (aCallIndex == UINT32_MAX) { michael@0: return; michael@0: } michael@0: michael@0: while (aCallIndex >= mCurrentCallArray.Length()) { michael@0: Call call; michael@0: mCurrentCallArray.AppendElement(call); michael@0: } michael@0: michael@0: uint16_t prevCallState = mCurrentCallArray[aCallIndex].mState; michael@0: mCurrentCallArray[aCallIndex].mState = aCallState; michael@0: mCurrentCallArray[aCallIndex].mDirection = !aIsOutgoing; michael@0: michael@0: bool prevCallIsConference = mCurrentCallArray[aCallIndex].mIsConference; michael@0: mCurrentCallArray[aCallIndex].mIsConference = aIsConference; michael@0: michael@0: // Same logic as implementation in ril_worker.js michael@0: if (aNumber.Length() && aNumber[0] == '+') { michael@0: mCurrentCallArray[aCallIndex].mType = TOA_INTERNATIONAL; michael@0: } michael@0: mCurrentCallArray[aCallIndex].mNumber = aNumber; michael@0: michael@0: nsRefPtr sendRingTask; michael@0: nsString address; michael@0: michael@0: switch (aCallState) { michael@0: case nsITelephonyProvider::CALL_STATE_HELD: michael@0: switch (prevCallState) { michael@0: case nsITelephonyProvider::CALL_STATE_CONNECTED: { michael@0: uint32_t numActive = GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_CONNECTED); michael@0: uint32_t numHeld = GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_HELD); michael@0: uint32_t numConCalls = GetNumberOfConCalls(); michael@0: michael@0: /** michael@0: * An active call becomes a held call. michael@0: * michael@0: * If this call is not a conference call, michael@0: * - callheld state = ONHOLD_NOACTIVE if no active call remains; michael@0: * - callheld state = ONHOLD_ACTIVE otherwise. michael@0: * If this call belongs to a conference call and all other members of michael@0: * the conference call have become held calls, michael@0: * - callheld state = ONHOLD_NOACTIVE if no active call remains; michael@0: * - callheld state = ONHOLD_ACTIVE otherwise. michael@0: * michael@0: * Note number of active calls may be 0 in-between state transition michael@0: * (c1 has become held but c2 has not become active yet), so we regard michael@0: * no active call remains if there is no other active/held call michael@0: * besides this changed call/group of conference call. michael@0: */ michael@0: if (!aIsConference) { michael@0: if (numActive + numHeld == 1) { michael@0: // A single active call is put on hold. michael@0: sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_NOACTIVE; michael@0: } else { michael@0: // An active call is placed on hold or active/held calls swapped. michael@0: sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE; michael@0: } michael@0: SendCommand(RESPONSE_CIEV, CINDType::CALLHELD); michael@0: } else if (GetNumberOfConCalls(nsITelephonyProvider::CALL_STATE_HELD) michael@0: == numConCalls) { michael@0: if (numActive + numHeld == numConCalls) { michael@0: // An active conference call is put on hold. michael@0: sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_NOACTIVE; michael@0: } else { michael@0: // Active calls are placed on hold or active/held calls swapped. michael@0: sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE; michael@0: } michael@0: SendCommand(RESPONSE_CIEV, CINDType::CALLHELD); michael@0: } michael@0: break; michael@0: } michael@0: case nsITelephonyProvider::CALL_STATE_DISCONNECTED: michael@0: // The call state changed from DISCONNECTED to HELD. It could happen michael@0: // when user held a call before Bluetooth got connected. michael@0: if (FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) { michael@0: // callheld = ONHOLD_ACTIVE if an active call already exists. michael@0: sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE; michael@0: SendCommand(RESPONSE_CIEV, CINDType::CALLHELD); michael@0: } michael@0: break; michael@0: } michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_INCOMING: michael@0: if (FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) { michael@0: SendCCWA(aNumber, mCurrentCallArray[aCallIndex].mType); michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::INCOMING, aSend); michael@0: } else { michael@0: // Start sending RING indicator to HF michael@0: sStopSendingRingFlag = false; michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::INCOMING, aSend); michael@0: michael@0: if (mBSIR) { michael@0: // Setup audio connection for in-band ring tone michael@0: ConnectSco(); michael@0: } michael@0: michael@0: nsAutoString number(aNumber); michael@0: if (!mCLIP) { michael@0: number.AssignLiteral(""); michael@0: } michael@0: michael@0: MessageLoop::current()->PostDelayedTask( michael@0: FROM_HERE, michael@0: new SendRingIndicatorTask(number, michael@0: mCurrentCallArray[aCallIndex].mType), michael@0: sRingInterval); michael@0: } michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_DIALING: michael@0: if (!mDialingRequestProcessed) { michael@0: SendLine("OK"); michael@0: mDialingRequestProcessed = true; michael@0: } michael@0: michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::OUTGOING, aSend); michael@0: ConnectSco(); michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_ALERTING: michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::OUTGOING_ALERTING, aSend); michael@0: michael@0: // If there's an ongoing call when the headset is just connected, we have michael@0: // to open a sco socket here. michael@0: ConnectSco(); michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_CONNECTED: michael@0: /** michael@0: * A call becomes active because: michael@0: * - user answers an incoming call, michael@0: * - user dials a outgoing call and it is answered, or michael@0: * - SLC is connected when a call is active. michael@0: */ michael@0: switch (prevCallState) { michael@0: case nsITelephonyProvider::CALL_STATE_INCOMING: michael@0: case nsITelephonyProvider::CALL_STATE_DISCONNECTED: michael@0: // Incoming call, no break michael@0: sStopSendingRingFlag = true; michael@0: ConnectSco(); michael@0: // NO BREAK HERE. continue to next statement michael@0: case nsITelephonyProvider::CALL_STATE_DIALING: michael@0: case nsITelephonyProvider::CALL_STATE_ALERTING: michael@0: // Outgoing call michael@0: UpdateCIND(CINDType::CALL, CallState::IN_PROGRESS, aSend); michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, aSend); michael@0: michael@0: if (FindFirstCall(nsITelephonyProvider::CALL_STATE_HELD)) { michael@0: // callheld state = ONHOLD_ACTIVE if a held call already exists. michael@0: UpdateCIND(CINDType::CALLHELD, CallHeldState::ONHOLD_ACTIVE, aSend); michael@0: } michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_CONNECTED: michael@0: // User wants to add a held call to the conversation. michael@0: // The original connected call becomes a conference call here. michael@0: if (aIsConference) { michael@0: UpdateCIND(CINDType::CALLHELD, CallHeldState::NO_CALLHELD, aSend); michael@0: } michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_HELD: michael@0: if (!FindFirstCall(nsITelephonyProvider::CALL_STATE_HELD)) { michael@0: if (aIsConference && !prevCallIsConference) { michael@0: // The held call was merged and becomes a conference call. michael@0: UpdateCIND(CINDType::CALLHELD, CallHeldState::NO_CALLHELD, aSend); michael@0: } else if (sCINDItems[CINDType::CALLHELD].value == michael@0: CallHeldState::ONHOLD_NOACTIVE) { michael@0: // The held call(s) become connected call(s). michael@0: UpdateCIND(CINDType::CALLHELD, CallHeldState::NO_CALLHELD, aSend); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: BT_WARNING("Not handling state changed"); michael@0: } michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_DISCONNECTED: michael@0: switch (prevCallState) { michael@0: case nsITelephonyProvider::CALL_STATE_INCOMING: michael@0: // Incoming call, no break michael@0: sStopSendingRingFlag = true; michael@0: case nsITelephonyProvider::CALL_STATE_DIALING: michael@0: case nsITelephonyProvider::CALL_STATE_ALERTING: michael@0: // Outgoing call michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, aSend); michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_CONNECTED: michael@0: // No call is ongoing michael@0: if (sCINDItems[CINDType::CALLHELD].value == michael@0: CallHeldState::NO_CALLHELD) { michael@0: UpdateCIND(CINDType::CALL, CallState::NO_CALL, aSend); michael@0: } michael@0: break; michael@0: default: michael@0: BT_WARNING("Not handling state changed"); michael@0: } michael@0: michael@0: // Handle held calls separately michael@0: if (!FindFirstCall(nsITelephonyProvider::CALL_STATE_HELD)) { michael@0: UpdateCIND(CINDType::CALLHELD, CallHeldState::NO_CALLHELD, aSend); michael@0: } else if (!FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) { michael@0: UpdateCIND(CINDType::CALLHELD, CallHeldState::ONHOLD_NOACTIVE, aSend); michael@0: } else { michael@0: UpdateCIND(CINDType::CALLHELD, CallHeldState::ONHOLD_ACTIVE, aSend); michael@0: } michael@0: michael@0: // -1 is necessary because call 0 is an invalid (padding) call object. michael@0: if (mCurrentCallArray.Length() - 1 == michael@0: GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_DISCONNECTED)) { michael@0: // In order to let user hear busy tone via connected Bluetooth headset, michael@0: // we postpone the timing of dropping SCO. michael@0: if (!(aError.Equals(NS_LITERAL_STRING("BusyError")))) { michael@0: DisconnectSco(); michael@0: } else { michael@0: // Close Sco later since Dialer is still playing busy tone via HF. michael@0: MessageLoop::current()->PostDelayedTask(FROM_HERE, michael@0: new CloseScoTask(), michael@0: sBusyToneInterval); michael@0: } michael@0: michael@0: ResetCallArray(); michael@0: } michael@0: break; michael@0: default: michael@0: BT_WARNING("Not handling state changed"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: PhoneType michael@0: BluetoothHfpManager::GetPhoneType(const nsAString& aType) michael@0: { michael@0: // FIXME: Query phone type from RIL after RIL implements new API (bug 912019) michael@0: if (aType.EqualsLiteral("gsm") || aType.EqualsLiteral("gprs") || michael@0: aType.EqualsLiteral("edge") || aType.EqualsLiteral("umts") || michael@0: aType.EqualsLiteral("hspa") || aType.EqualsLiteral("hsdpa") || michael@0: aType.EqualsLiteral("hsupa") || aType.EqualsLiteral("hspa+")) { michael@0: return PhoneType::GSM; michael@0: } else if (aType.EqualsLiteral("is95a") || aType.EqualsLiteral("is95b") || michael@0: aType.EqualsLiteral("1xrtt") || aType.EqualsLiteral("evdo0") || michael@0: aType.EqualsLiteral("evdoa") || aType.EqualsLiteral("evdob")) { michael@0: return PhoneType::CDMA; michael@0: } michael@0: michael@0: return PhoneType::NONE; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::UpdateSecondNumber(const nsAString& aNumber) michael@0: { michael@0: MOZ_ASSERT(mPhoneType == PhoneType::CDMA); michael@0: michael@0: // Always regard second call as incoming call since v1.2 RIL michael@0: // doesn't support outgoing second call in CDMA. michael@0: mCdmaSecondCall.mDirection = true; michael@0: michael@0: mCdmaSecondCall.mNumber = aNumber; michael@0: mCdmaSecondCall.mType = (aNumber[0] == '+') ? TOA_INTERNATIONAL : michael@0: TOA_UNKNOWN; michael@0: michael@0: SendCCWA(aNumber, mCdmaSecondCall.mType); michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::INCOMING, true); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::AnswerWaitingCall() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mPhoneType == PhoneType::CDMA); michael@0: michael@0: // Pick up second call. First call is held now. michael@0: mCdmaSecondCall.mState = nsITelephonyProvider::CALL_STATE_CONNECTED; michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, true); michael@0: michael@0: sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE; michael@0: SendCommand(RESPONSE_CIEV, CINDType::CALLHELD); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::IgnoreWaitingCall() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mPhoneType == PhoneType::CDMA); michael@0: michael@0: mCdmaSecondCall.Reset(); michael@0: UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, true); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ToggleCalls() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mPhoneType == PhoneType::CDMA); michael@0: michael@0: // Toggle acitve and held calls michael@0: mCdmaSecondCall.mState = (mCdmaSecondCall.IsActive()) ? michael@0: nsITelephonyProvider::CALL_STATE_HELD : michael@0: nsITelephonyProvider::CALL_STATE_CONNECTED; michael@0: } michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: void michael@0: BluetoothHfpManager::OnSocketConnectSuccess(BluetoothSocket* aSocket) michael@0: { michael@0: MOZ_ASSERT(aSocket); michael@0: #ifdef MOZ_B2G_RIL michael@0: MOZ_ASSERT(mListener); michael@0: #endif michael@0: michael@0: // Success to create a SCO socket michael@0: if (aSocket == mScoSocket) { michael@0: OnScoConnectSuccess(); michael@0: return; michael@0: } michael@0: michael@0: /** michael@0: * If the created connection is an inbound connection, close another server michael@0: * socket because currently only one SLC is allowed. After that, we need to michael@0: * make sure that both server socket would be nulled out. As for outbound michael@0: * connections, we do nothing since sockets have been already handled in michael@0: * function Connect(). michael@0: */ michael@0: if (aSocket == mHandsfreeSocket) { michael@0: MOZ_ASSERT(!mSocket); michael@0: mIsHsp = false; michael@0: mHandsfreeSocket.swap(mSocket); michael@0: michael@0: mHeadsetSocket->Disconnect(); michael@0: mHeadsetSocket = nullptr; michael@0: } else if (aSocket == mHeadsetSocket) { michael@0: MOZ_ASSERT(!mSocket); michael@0: mIsHsp = true; michael@0: mHeadsetSocket.swap(mSocket); michael@0: michael@0: mHandsfreeSocket->Disconnect(); michael@0: mHandsfreeSocket = nullptr; michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: // Enumerate current calls michael@0: mListener->EnumerateCalls(); michael@0: michael@0: mFirstCKPD = true; michael@0: #endif michael@0: michael@0: // Cache device path for NotifySettings() since we can't get socket address michael@0: // when a headset disconnect with us michael@0: mSocket->GetAddress(mDeviceAddress); michael@0: NotifyConnectionStatusChanged( michael@0: NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID)); michael@0: michael@0: ListenSco(); michael@0: michael@0: OnConnect(EmptyString()); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnSocketConnectError(BluetoothSocket* aSocket) michael@0: { michael@0: // Failed to create a SCO socket michael@0: if (aSocket == mScoSocket) { michael@0: OnScoConnectError(); michael@0: return; michael@0: } michael@0: michael@0: mHandsfreeSocket = nullptr; michael@0: mHeadsetSocket = nullptr; michael@0: michael@0: OnConnect(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnSocketDisconnect(BluetoothSocket* aSocket) michael@0: { michael@0: MOZ_ASSERT(aSocket); michael@0: michael@0: if (aSocket == mScoSocket) { michael@0: // SCO socket is closed michael@0: OnScoDisconnect(); michael@0: return; michael@0: } michael@0: michael@0: if (aSocket != mSocket) { michael@0: // Do nothing when a listening server socket is closed. michael@0: return; michael@0: } michael@0: michael@0: DisconnectSco(); michael@0: michael@0: NotifyConnectionStatusChanged( michael@0: NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID)); michael@0: OnDisconnect(EmptyString()); michael@0: michael@0: Reset(); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) michael@0: { michael@0: // UpdateSdpRecord() is not called so this callback function should not michael@0: // be invoked. michael@0: MOZ_ASSUME_UNREACHABLE("UpdateSdpRecords() should be called somewhere"); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnGetServiceChannel(const nsAString& aDeviceAddress, michael@0: const nsAString& aServiceUuid, michael@0: int aChannel) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!aDeviceAddress.IsEmpty()); michael@0: michael@0: BluetoothService* bs = BluetoothService::Get(); michael@0: NS_ENSURE_TRUE_VOID(bs); michael@0: michael@0: if (aChannel < 0) { michael@0: // If we can't find Handsfree server channel number on the remote device, michael@0: // try to create HSP connection instead. michael@0: nsString hspUuid; michael@0: BluetoothUuidHelper::GetString(BluetoothServiceClass::HEADSET, hspUuid); michael@0: michael@0: if (aServiceUuid.Equals(hspUuid)) { michael@0: OnConnect(NS_LITERAL_STRING(ERR_SERVICE_CHANNEL_NOT_FOUND)); michael@0: } else if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, michael@0: hspUuid, this))) { michael@0: OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: } else { michael@0: mIsHsp = true; michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(mSocket); michael@0: michael@0: if (!mSocket->Connect(NS_ConvertUTF16toUTF8(aDeviceAddress), aChannel)) { michael@0: OnConnect(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnScoConnectSuccess() michael@0: { michael@0: // For active connection request, we need to reply the DOMRequest michael@0: if (mScoRunnable) { michael@0: DispatchBluetoothReply(mScoRunnable, michael@0: BluetoothValue(true), EmptyString()); michael@0: mScoRunnable = nullptr; michael@0: } michael@0: michael@0: NotifyConnectionStatusChanged( michael@0: NS_LITERAL_STRING(BLUETOOTH_SCO_STATUS_CHANGED_ID)); michael@0: michael@0: mScoSocketStatus = mScoSocket->GetConnectionStatus(); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnScoConnectError() michael@0: { michael@0: if (mScoRunnable) { michael@0: NS_NAMED_LITERAL_STRING(replyError, "Failed to create SCO socket!"); michael@0: DispatchBluetoothReply(mScoRunnable, BluetoothValue(), replyError); michael@0: michael@0: mScoRunnable = nullptr; michael@0: } michael@0: michael@0: ListenSco(); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnScoDisconnect() michael@0: { michael@0: if (mScoSocketStatus == SocketConnectionStatus::SOCKET_CONNECTED) { michael@0: ListenSco(); michael@0: NotifyConnectionStatusChanged( michael@0: NS_LITERAL_STRING(BLUETOOTH_SCO_STATUS_CHANGED_ID)); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::IsConnected() michael@0: { michael@0: if (mSocket) { michael@0: return mSocket->GetConnectionStatus() == michael@0: SocketConnectionStatus::SOCKET_CONNECTED; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::GetAddress(nsAString& aDeviceAddress) michael@0: { michael@0: return mSocket->GetAddress(aDeviceAddress); michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::ConnectSco(BluetoothReplyRunnable* aRunnable) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: NS_ENSURE_TRUE(!sInShutdown, false); michael@0: NS_ENSURE_TRUE(IsConnected(), false); michael@0: michael@0: SocketConnectionStatus status = mScoSocket->GetConnectionStatus(); michael@0: if (status == SocketConnectionStatus::SOCKET_CONNECTED || michael@0: status == SocketConnectionStatus::SOCKET_CONNECTING || michael@0: (mScoRunnable && (mScoRunnable != aRunnable))) { michael@0: BT_WARNING("SCO connection exists or is being established"); michael@0: return false; michael@0: } michael@0: michael@0: // If we are not using HSP, we have to make sure Service Level Connection michael@0: // established before we start to set up SCO (synchronous connection). michael@0: if (!mSlcConnected && !mIsHsp) { michael@0: mConnectScoRequest = true; michael@0: BT_WARNING("ConnectSco called before Service Level Connection established"); michael@0: return false; michael@0: } michael@0: michael@0: // Stop listening michael@0: mScoSocket->Disconnect(); michael@0: michael@0: mScoSocket->Connect(NS_ConvertUTF16toUTF8(mDeviceAddress), -1); michael@0: mScoSocketStatus = mScoSocket->GetConnectionStatus(); michael@0: michael@0: mScoRunnable = aRunnable; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::DisconnectSco() michael@0: { michael@0: if (!IsScoConnected()) { michael@0: BT_WARNING("SCO has been already disconnected."); michael@0: return false; michael@0: } michael@0: michael@0: mScoSocket->Disconnect(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::ListenSco() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (sInShutdown) { michael@0: BT_WARNING("ListenSco called while in shutdown!"); michael@0: return false; michael@0: } michael@0: michael@0: if (mScoSocket->GetConnectionStatus() == michael@0: SocketConnectionStatus::SOCKET_LISTENING) { michael@0: BT_WARNING("SCO socket has been already listening"); michael@0: return false; michael@0: } michael@0: michael@0: mScoSocket->Disconnect(); michael@0: michael@0: if (!mScoSocket->Listen(-1)) { michael@0: BT_WARNING("Can't listen on SCO socket!"); michael@0: return false; michael@0: } michael@0: michael@0: mScoSocketStatus = mScoSocket->GetConnectionStatus(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::IsScoConnected() michael@0: { michael@0: if (mScoSocket) { michael@0: return mScoSocket->GetConnectionStatus() == michael@0: SocketConnectionStatus::SOCKET_CONNECTED; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnConnect(const nsAString& aErrorStr) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // When we failed to create a socket, restart listening. michael@0: if (!aErrorStr.IsEmpty()) { michael@0: mSocket = nullptr; michael@0: Listen(); michael@0: } michael@0: michael@0: /** michael@0: * On the one hand, notify the controller that we've done for outbound michael@0: * connections. On the other hand, we do nothing for inbound connections. michael@0: */ michael@0: NS_ENSURE_TRUE_VOID(mController); michael@0: michael@0: nsRefPtr controller = mController.forget(); michael@0: controller->NotifyCompletion(aErrorStr); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnDisconnect(const nsAString& aErrorStr) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Start listening michael@0: mSocket = nullptr; michael@0: Listen(); michael@0: michael@0: /** michael@0: * On the one hand, notify the controller that we've done for outbound michael@0: * connections. On the other hand, we do nothing for inbound connections. michael@0: */ michael@0: NS_ENSURE_TRUE_VOID(mController); michael@0: michael@0: nsRefPtr controller = mController.forget(); michael@0: controller->NotifyCompletion(aErrorStr); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(BluetoothHfpManager, nsIObserver) michael@0: