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: #include "BluetoothProfileController.h" michael@0: #include "BluetoothUtils.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 "nsIAudioManager.h" michael@0: #include "nsIDOMIccInfo.h" michael@0: #include "nsIDOMMobileConnection.h" michael@0: #include "nsIIccProvider.h" michael@0: #include "nsIMobileConnectionProvider.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsISettingsService.h" michael@0: #include "nsITelephonyProvider.h" michael@0: #include "nsRadioInterfaceLayer.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsThreadUtils.h" 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: /** michael@0: * Dispatch task with arguments to main thread. michael@0: */ michael@0: #define BT_HF_DISPATCH_MAIN(args...) \ michael@0: NS_DispatchToMainThread(new MainThreadTask(args)) michael@0: michael@0: /** michael@0: * Process bluedroid callbacks with corresponding handlers. michael@0: */ michael@0: #define BT_HF_PROCESS_CB(func, args...) \ michael@0: do { \ michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpManager); \ michael@0: sBluetoothHfpManager->func(args); \ michael@0: } while(0) 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: static const bthf_interface_t* sBluetoothHfpInterface = nullptr; michael@0: michael@0: bool sInShutdown = false; 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: } // anonymous namespace michael@0: michael@0: // Main thread task commands michael@0: enum MainThreadTaskCmd { michael@0: NOTIFY_CONN_STATE_CHANGED, michael@0: NOTIFY_DIALER, michael@0: NOTIFY_SCO_VOLUME_CHANGED, michael@0: POST_TASK_RESPOND_TO_BLDN, michael@0: POST_TASK_CLOSE_SCO michael@0: }; michael@0: michael@0: static void michael@0: ConnectionStateCallback(bthf_connection_state_t state, bt_bdaddr_t* bd_addr) michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessConnectionState, state, bd_addr); michael@0: } michael@0: michael@0: static void michael@0: AudioStateCallback(bthf_audio_state_t state, bt_bdaddr_t* bd_addr) michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessAudioState, state, bd_addr); michael@0: } michael@0: michael@0: static void michael@0: VoiceRecognitionCallback(bthf_vr_state_t state) michael@0: { michael@0: // No support michael@0: } michael@0: michael@0: static void michael@0: AnswerCallCallback() michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessAnswerCall); michael@0: } michael@0: michael@0: static void michael@0: HangupCallCallback() michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessHangupCall); michael@0: } michael@0: michael@0: static void michael@0: VolumeControlCallback(bthf_volume_type_t type, int volume) michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessVolumeControl, type, volume); michael@0: } michael@0: michael@0: static void michael@0: DialCallCallback(char *number) michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessDialCall, number); michael@0: } michael@0: michael@0: static void michael@0: DtmfCmdCallback(char dtmf) michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessDtmfCmd, dtmf); michael@0: } michael@0: michael@0: static void michael@0: NoiceReductionCallback(bthf_nrec_t nrec) michael@0: { michael@0: // No support michael@0: } michael@0: michael@0: static void michael@0: AtChldCallback(bthf_chld_type_t chld) michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessAtChld, chld); michael@0: } michael@0: michael@0: static void michael@0: AtCnumCallback() michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessAtCnum); michael@0: } michael@0: michael@0: static void michael@0: AtCindCallback() michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessAtCind); michael@0: } michael@0: michael@0: static void michael@0: AtCopsCallback() michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessAtCops); michael@0: } michael@0: michael@0: static void michael@0: AtClccCallback() michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessAtClcc); michael@0: } michael@0: michael@0: static void michael@0: UnknownAtCallback(char *at_string) michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessUnknownAt, at_string); michael@0: } michael@0: michael@0: static void michael@0: KeyPressedCallback() michael@0: { michael@0: BT_HF_PROCESS_CB(ProcessKeyPressed); michael@0: } michael@0: michael@0: static bthf_callbacks_t sBluetoothHfpCallbacks = { michael@0: sizeof(sBluetoothHfpCallbacks), michael@0: ConnectionStateCallback, michael@0: AudioStateCallback, michael@0: VoiceRecognitionCallback, michael@0: AnswerCallCallback, michael@0: HangupCallCallback, michael@0: VolumeControlCallback, michael@0: DialCallCallback, michael@0: DtmfCmdCallback, michael@0: NoiceReductionCallback, michael@0: AtChldCallback, michael@0: AtCnumCallback, michael@0: AtCindCallback, michael@0: AtCopsCallback, michael@0: AtClccCallback, michael@0: UnknownAtCallback, michael@0: KeyPressedCallback michael@0: }; michael@0: michael@0: static bool michael@0: IsValidDtmf(const char aChar) { michael@0: // Valid DTMF: [*#0-9ABCD] michael@0: return (aChar == '*' || aChar == '#') || michael@0: (aChar >= '0' && aChar <= '9') || michael@0: (aChar >= 'A' && aChar <= 'D'); 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: 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: sBluetoothHfpManager->DisconnectSco(); michael@0: } michael@0: }; michael@0: 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->SendResponse(BTHF_AT_RESPONSE_ERROR); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: class BluetoothHfpManager::MainThreadTask : public nsRunnable michael@0: { michael@0: public: michael@0: MainThreadTask(const int aCommand, michael@0: const nsAString& aParameter = EmptyString()) michael@0: : mCommand(aCommand), mParameter(aParameter) michael@0: { michael@0: } michael@0: michael@0: nsresult Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(sBluetoothHfpManager); michael@0: michael@0: switch (mCommand) { michael@0: case MainThreadTaskCmd::NOTIFY_CONN_STATE_CHANGED: michael@0: sBluetoothHfpManager->NotifyConnectionStateChanged(mParameter); michael@0: break; michael@0: case MainThreadTaskCmd::NOTIFY_DIALER: michael@0: sBluetoothHfpManager->NotifyDialer(mParameter); michael@0: break; michael@0: case MainThreadTaskCmd::NOTIFY_SCO_VOLUME_CHANGED: michael@0: { michael@0: nsCOMPtr os = michael@0: mozilla::services::GetObserverService(); michael@0: NS_ENSURE_TRUE(os, NS_OK); michael@0: michael@0: os->NotifyObservers(nullptr, "bluetooth-volume-change", michael@0: mParameter.get()); michael@0: } michael@0: break; michael@0: case MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN: michael@0: MessageLoop::current()-> michael@0: PostDelayedTask(FROM_HERE, new RespondToBLDNTask(), michael@0: sWaitingForDialingInterval); michael@0: break; michael@0: case MainThreadTaskCmd::POST_TASK_CLOSE_SCO: michael@0: MessageLoop::current()-> michael@0: PostDelayedTask(FROM_HERE, new CloseScoTask(), michael@0: sBusyToneInterval); michael@0: break; michael@0: default: michael@0: BT_WARNING("MainThreadTask: Unknown command %d", mCommand); michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: int mCommand; michael@0: nsString mParameter; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(BluetoothHfpManager::GetVolumeTask, michael@0: nsISettingsServiceCallback); 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::Set(const nsAString& aNumber, const bool aIsOutgoing) michael@0: { michael@0: mNumber = aNumber; michael@0: mDirection = (aIsOutgoing) ? BTHF_CALL_DIRECTION_OUTGOING : michael@0: BTHF_CALL_DIRECTION_INCOMING; michael@0: // Same logic as implementation in ril_worker.js michael@0: if (aNumber.Length() && aNumber[0] == '+') { michael@0: mType = BTHF_CALL_ADDRTYPE_INTERNATIONAL; michael@0: } michael@0: } michael@0: michael@0: void michael@0: Call::Reset() michael@0: { michael@0: mState = nsITelephonyProvider::CALL_STATE_DISCONNECTED; michael@0: mDirection = BTHF_CALL_DIRECTION_OUTGOING; michael@0: mNumber.Truncate(); michael@0: mType = BTHF_CALL_ADDRTYPE_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: michael@0: /** michael@0: * BluetoothHfpManager michael@0: */ michael@0: BluetoothHfpManager::BluetoothHfpManager() : mPhoneType(PhoneType::NONE) michael@0: { michael@0: Reset(); michael@0: } michael@0: 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: michael@0: void michael@0: BluetoothHfpManager::Reset() michael@0: { michael@0: mReceiveVgsFlag = false; michael@0: mDialingRequestProcessed = true; michael@0: michael@0: mConnectionState = BTHF_CONNECTION_STATE_DISCONNECTED; michael@0: mPrevConnectionState = BTHF_CONNECTION_STATE_DISCONNECTED; michael@0: mAudioState = BTHF_AUDIO_STATE_DISCONNECTED; michael@0: michael@0: // Phone & Device CIND michael@0: ResetCallArray(); michael@0: mBattChg = 5; michael@0: mService = 0; michael@0: mRoam = 0; michael@0: mSignal = 0; michael@0: 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: NS_ENSURE_TRUE(InitHfpInterface(), false); 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: mListener = new BluetoothRilListener(); michael@0: NS_ENSURE_TRUE(mListener->Listen(true), false); 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: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::InitHfpInterface() michael@0: { michael@0: const bt_interface_t* btInf = GetBluetoothInterface(); michael@0: NS_ENSURE_TRUE(btInf, false); michael@0: michael@0: if (sBluetoothHfpInterface) { michael@0: sBluetoothHfpInterface->cleanup(); michael@0: sBluetoothHfpInterface = nullptr; michael@0: } michael@0: michael@0: bthf_interface_t *interface = (bthf_interface_t *) michael@0: btInf->get_profile_interface(BT_PROFILE_HANDSFREE_ID); michael@0: NS_ENSURE_TRUE(interface, false); michael@0: michael@0: NS_ENSURE_TRUE(BT_STATUS_SUCCESS == michael@0: interface->init(&sBluetoothHfpCallbacks), false); michael@0: sBluetoothHfpInterface = interface; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: BluetoothHfpManager::~BluetoothHfpManager() michael@0: { michael@0: if (!mListener->Listen(false)) { michael@0: BT_WARNING("Failed to stop listening RIL"); michael@0: } michael@0: mListener = nullptr; 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: DeinitHfpInterface(); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::DeinitHfpInterface() michael@0: { michael@0: NS_ENSURE_TRUE_VOID(GetBluetoothInterface()); michael@0: michael@0: if (sBluetoothHfpInterface) { michael@0: sBluetoothHfpInterface->cleanup(); michael@0: sBluetoothHfpInterface = nullptr; michael@0: } 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: 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: mBattChg = (int) ceil(aBatteryInfo.level() * 5.0); michael@0: UpdateDeviceCIND(); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessConnectionState(bthf_connection_state_t aState, michael@0: bt_bdaddr_t* aBdAddress) michael@0: { michael@0: BT_LOGR("state %d", aState); michael@0: michael@0: mPrevConnectionState = mConnectionState; michael@0: mConnectionState = aState; michael@0: michael@0: if (aState == BTHF_CONNECTION_STATE_SLC_CONNECTED) { michael@0: BdAddressTypeToString(aBdAddress, mDeviceAddress); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_CONN_STATE_CHANGED, michael@0: NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID)); michael@0: } else if (aState == BTHF_CONNECTION_STATE_DISCONNECTED) { michael@0: DisconnectSco(); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_CONN_STATE_CHANGED, michael@0: NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessAudioState(bthf_audio_state_t aState, michael@0: bt_bdaddr_t* aBdAddress) michael@0: { michael@0: BT_LOGR("state %d", aState); michael@0: michael@0: mAudioState = aState; michael@0: michael@0: if (aState == BTHF_AUDIO_STATE_CONNECTED || michael@0: aState == BTHF_AUDIO_STATE_DISCONNECTED) { michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_CONN_STATE_CHANGED, michael@0: NS_LITERAL_STRING(BLUETOOTH_SCO_STATUS_CHANGED_ID)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessAnswerCall() michael@0: { michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER, michael@0: NS_LITERAL_STRING("ATA")); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessHangupCall() michael@0: { michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER, michael@0: NS_LITERAL_STRING("CHUP")); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessVolumeControl(bthf_volume_type_t aType, michael@0: int aVolume) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(aVolume >= 0 && aVolume <= 15); michael@0: michael@0: if (aType == BTHF_VOLUME_TYPE_MIC) { michael@0: mCurrentVgm = aVolume; michael@0: } else if (aType == BTHF_VOLUME_TYPE_SPK) { michael@0: mReceiveVgsFlag = true; michael@0: michael@0: if (aVolume == mCurrentVgs) { michael@0: // Keep current volume michael@0: return; michael@0: } michael@0: michael@0: nsString data; michael@0: data.AppendInt(aVolume); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_SCO_VOLUME_CHANGED, data); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessDtmfCmd(char aDtmf) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(IsValidDtmf(aDtmf)); michael@0: michael@0: nsAutoCString message("VTS="); michael@0: message += aDtmf; michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER, michael@0: NS_ConvertUTF8toUTF16(message)); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessAtChld(bthf_chld_type_t aChld) michael@0: { michael@0: nsAutoCString message("CHLD="); michael@0: message.AppendInt((int)aChld); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER, michael@0: NS_ConvertUTF8toUTF16(message)); michael@0: michael@0: SendResponse(BTHF_AT_RESPONSE_OK); michael@0: } michael@0: michael@0: void BluetoothHfpManager::ProcessDialCall(char *aNumber) michael@0: { michael@0: nsAutoCString message(aNumber); michael@0: michael@0: // There are three cases based on aNumber, michael@0: // 1) Empty value: Redial, BLDN michael@0: // 2) >xxx: Memory dial, ATD>xxx michael@0: // 3) xxx: Normal dial, ATDxxx michael@0: // We need to respond OK/Error for dial requests for every case listed above, michael@0: // 1) and 2): Respond in either RespondToBLDNTask or michael@0: // HandleCallStateChanged() michael@0: // 3): Respond here michael@0: if (message.IsEmpty()) { michael@0: mDialingRequestProcessed = false; michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER, michael@0: NS_LITERAL_STRING("BLDN")); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN); michael@0: } else if (message[0] == '>') { michael@0: mDialingRequestProcessed = false; michael@0: nsAutoCString newMsg("ATD"); michael@0: newMsg += StringHead(message, message.Length() - 1); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER, michael@0: NS_ConvertUTF8toUTF16(newMsg)); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN); michael@0: } else { michael@0: nsAutoCString newMsg("ATD"); michael@0: newMsg += StringHead(message, message.Length() - 1); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER, michael@0: NS_ConvertUTF8toUTF16(newMsg)); michael@0: SendResponse(BTHF_AT_RESPONSE_OK); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessAtCnum() michael@0: { 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(BTHF_CALL_ADDRTYPE_UNKNOWN); michael@0: message.AppendLiteral(",,4"); michael@0: michael@0: SendLine(message.get()); michael@0: } michael@0: michael@0: SendResponse(BTHF_AT_RESPONSE_OK); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessAtCind() michael@0: { michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: michael@0: int numActive = GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_CONNECTED); michael@0: int numHeld = GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_HELD); michael@0: michael@0: bt_status_t status = sBluetoothHfpInterface->cind_response( michael@0: mService, michael@0: numActive, michael@0: numHeld, michael@0: ConvertToBthfCallState(GetCallSetupState()), michael@0: mSignal, michael@0: mRoam, michael@0: mBattChg); michael@0: NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessAtCops() michael@0: { michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->cops_response( michael@0: NS_ConvertUTF16toUTF8(mOperatorName).get())); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessAtClcc() michael@0: { michael@0: uint32_t callNumbers = mCurrentCallArray.Length(); michael@0: uint32_t i; michael@0: for (i = 1; i < callNumbers; i++) { michael@0: 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: SendCLCC(mCdmaSecondCall, 2); michael@0: } michael@0: michael@0: SendResponse(BTHF_AT_RESPONSE_OK); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessUnknownAt(char *aAtString) michael@0: { michael@0: BT_LOGR("[%s]", aAtString); michael@0: michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->at_response(BTHF_AT_RESPONSE_ERROR, 0)); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::ProcessKeyPressed() michael@0: { michael@0: bool hasActiveCall = michael@0: (FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED) > 0); michael@0: michael@0: // Refer to AOSP HeadsetStateMachine.processKeyPressed michael@0: if (FindFirstCall(nsITelephonyProvider::CALL_STATE_INCOMING) michael@0: && !hasActiveCall) { michael@0: /** 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: */ michael@0: ProcessAnswerCall(); michael@0: } else if (hasActiveCall) { michael@0: if (!IsScoConnected()) { michael@0: /** michael@0: * Bluetooth HSP spec 4.3 michael@0: * If there's no SCO, set up a SCO link. michael@0: */ michael@0: ConnectSco(); michael@0: } else { michael@0: /** 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: */ michael@0: ProcessHangupCall(); michael@0: } michael@0: } else { michael@0: // BLDN michael@0: mDialingRequestProcessed = false; michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER, michael@0: NS_LITERAL_STRING("BLDN")); michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::NotifyConnectionStateChanged(const nsAString& aType) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Notify Gecko observers michael@0: nsCOMPtr obs = michael@0: do_GetService("@mozilla.org/observer-service;1"); 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: // Notify profile controller michael@0: if (aType.EqualsLiteral(BLUETOOTH_HFP_STATUS_CHANGED_ID)) { michael@0: if (IsConnected()) { michael@0: MOZ_ASSERT(mListener); michael@0: michael@0: // Enumerate current calls michael@0: mListener->EnumerateCalls(); michael@0: michael@0: OnConnect(EmptyString()); michael@0: } else if (mConnectionState == BTHF_CONNECTION_STATE_DISCONNECTED) { michael@0: mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); michael@0: if (mPrevConnectionState == BTHF_CONNECTION_STATE_DISCONNECTED) { michael@0: // Bug 979160: This implies the outgoing connection failure. michael@0: // When the outgoing hfp connection fails, state changes to disconnected michael@0: // state. Since bluedroid would not report connecting state, but only michael@0: // report connected/disconnected. michael@0: OnConnect(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); michael@0: } else { michael@0: OnDisconnect(EmptyString()); michael@0: } michael@0: Reset(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::NotifyDialer(const nsAString& aCommand) michael@0: { michael@0: NS_NAMED_LITERAL_STRING(type, "bluetooth-dialer-command"); michael@0: InfallibleTArray parameters; michael@0: michael@0: BT_APPEND_NAMED_VALUE(parameters, "command", nsString(aCommand)); michael@0: michael@0: BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters); michael@0: } 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: 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: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->volume_control(BTHF_VOLUME_TYPE_SPK, michael@0: mCurrentVgs)); michael@0: } michael@0: } michael@0: 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: mRoam = (roaming) ? 1 : 0; michael@0: michael@0: // Service michael@0: nsString regState; michael@0: voiceInfo->GetState(regState); michael@0: mService = (regState.EqualsLiteral("registered")) ? 1 : 0; michael@0: michael@0: // Signal 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: mSignal = (int)ceil(value.toNumber() / 20.0); michael@0: michael@0: UpdateDeviceCIND(); michael@0: michael@0: // Operator name 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: 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: void michael@0: BluetoothHfpManager::SendCLCC(Call& aCall, int aIndex) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(aCall.mState != michael@0: nsITelephonyProvider::CALL_STATE_DISCONNECTED); michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: michael@0: bthf_call_state_t callState = ConvertToBthfCallState(aCall.mState); michael@0: michael@0: if (mPhoneType == PhoneType::CDMA && aIndex == 1 && aCall.IsActive()) { michael@0: callState = (mCdmaSecondCall.IsActive()) ? BTHF_CALL_STATE_HELD : michael@0: BTHF_CALL_STATE_ACTIVE; michael@0: } michael@0: michael@0: if (callState == BTHF_CALL_STATE_INCOMING && michael@0: FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) { michael@0: callState = BTHF_CALL_STATE_WAITING; michael@0: } michael@0: michael@0: bt_status_t status = sBluetoothHfpInterface->clcc_response( michael@0: aIndex, michael@0: aCall.mDirection, michael@0: callState, michael@0: BTHF_CALL_TYPE_VOICE, michael@0: BTHF_CALL_MPTY_TYPE_SINGLE, michael@0: NS_ConvertUTF16toUTF8(aCall.mNumber).get(), michael@0: aCall.mType); michael@0: NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::SendLine(const char* aMessage) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->formatted_at_response(aMessage)); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::SendResponse(bthf_at_response_t aResponseCode) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->at_response(aResponseCode, 0)); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::UpdatePhoneCIND(uint32_t aCallIndex) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: michael@0: int numActive = GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_CONNECTED); michael@0: int numHeld = GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_HELD); michael@0: bthf_call_state_t callSetupState = michael@0: ConvertToBthfCallState(GetCallSetupState()); michael@0: nsAutoCString number = michael@0: NS_ConvertUTF16toUTF8(mCurrentCallArray[aCallIndex].mNumber); michael@0: bthf_call_addrtype_t type = mCurrentCallArray[aCallIndex].mType; michael@0: michael@0: BT_LOGR("[%d] state %d => BTHF: active[%d] held[%d] setupstate[%d]", michael@0: aCallIndex, mCurrentCallArray[aCallIndex].mState, michael@0: numActive, numHeld, callSetupState); michael@0: michael@0: NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->phone_state_change( michael@0: numActive, numHeld, callSetupState, number.get(), type)); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::UpdateDeviceCIND() michael@0: { michael@0: NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface); michael@0: NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->device_status_notification( michael@0: (bthf_network_state_t) mService, michael@0: (bthf_service_type_t) mRoam, michael@0: mSignal, michael@0: mBattChg)); 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: uint16_t michael@0: BluetoothHfpManager::GetCallSetupState() michael@0: { michael@0: uint32_t callLength = mCurrentCallArray.Length(); michael@0: michael@0: for (uint32_t i = 1; i < callLength; ++i) { michael@0: switch (mCurrentCallArray[i].mState) { michael@0: case nsITelephonyProvider::CALL_STATE_INCOMING: michael@0: case nsITelephonyProvider::CALL_STATE_DIALING: michael@0: case nsITelephonyProvider::CALL_STATE_ALERTING: michael@0: return mCurrentCallArray[i].mState; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return nsITelephonyProvider::CALL_STATE_DISCONNECTED; michael@0: } michael@0: michael@0: bthf_call_state_t michael@0: BluetoothHfpManager::ConvertToBthfCallState(int aCallState) michael@0: { michael@0: bthf_call_state_t state; michael@0: michael@0: // Refer to AOSP BluetoothPhoneService.convertCallState michael@0: if (aCallState == nsITelephonyProvider::CALL_STATE_INCOMING) { michael@0: state = BTHF_CALL_STATE_INCOMING; michael@0: } else if (aCallState == nsITelephonyProvider::CALL_STATE_DIALING) { michael@0: state = BTHF_CALL_STATE_DIALING; michael@0: } else if (aCallState == nsITelephonyProvider::CALL_STATE_ALERTING) { michael@0: state = BTHF_CALL_STATE_ALERTING; michael@0: } else if (aCallState == nsITelephonyProvider::CALL_STATE_CONNECTED) { michael@0: state = BTHF_CALL_STATE_ACTIVE; michael@0: } else if (aCallState == nsITelephonyProvider::CALL_STATE_HELD) { michael@0: state = BTHF_CALL_STATE_HELD; michael@0: } else { // disconnected michael@0: state = BTHF_CALL_STATE_IDLE; michael@0: } michael@0: michael@0: return state; michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::IsTransitionState(uint16_t aCallState, bool aIsConference) michael@0: { michael@0: /** michael@0: * Regard this callstate change as during CHLD=2 transition state if michael@0: * - the call becomes active, and numActive > 1 michael@0: * - the call becomes held, and numHeld > 1 or an incoming call exists michael@0: * michael@0: * TODO: michael@0: * 1) handle CHLD=1 transition state michael@0: * 2) handle conference call cases michael@0: */ michael@0: if (!aIsConference) { michael@0: switch (aCallState) { michael@0: case nsITelephonyProvider::CALL_STATE_CONNECTED: michael@0: return (GetNumberOfCalls(aCallState) > 1); michael@0: case nsITelephonyProvider::CALL_STATE_HELD: michael@0: return (GetNumberOfCalls(aCallState) > 1 || michael@0: FindFirstCall(nsITelephonyProvider::CALL_STATE_INCOMING)); michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return false; 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: // 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: // Update call state only michael@0: while (aCallIndex >= mCurrentCallArray.Length()) { michael@0: Call call; michael@0: mCurrentCallArray.AppendElement(call); michael@0: } michael@0: mCurrentCallArray[aCallIndex].mState = aCallState; michael@0: michael@0: // Return if SLC is disconnected michael@0: if (!IsConnected()) { michael@0: return; michael@0: } michael@0: michael@0: // Update call information besides call state michael@0: mCurrentCallArray[aCallIndex].Set(aNumber, aIsOutgoing); michael@0: michael@0: // Notify bluedroid of phone state change if this michael@0: // call state change is not during transition state michael@0: if (!IsTransitionState(aCallState, aIsConference)) { michael@0: UpdatePhoneCIND(aCallIndex); michael@0: } michael@0: michael@0: switch (aCallState) { michael@0: case nsITelephonyProvider::CALL_STATE_DIALING: michael@0: // We've send Dialer a dialing request and this is the response. michael@0: if (!mDialingRequestProcessed) { michael@0: SendResponse(BTHF_AT_RESPONSE_OK); michael@0: mDialingRequestProcessed = true; michael@0: } michael@0: break; michael@0: case nsITelephonyProvider::CALL_STATE_DISCONNECTED: 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: // FIXME: UpdatePhoneCIND later since it causes SCO close but michael@0: // Dialer is still playing busy tone via HF. michael@0: BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_CLOSE_SCO); michael@0: } michael@0: michael@0: ResetCallArray(); michael@0: } michael@0: break; michael@0: default: 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.Set(aNumber, false); michael@0: michael@0: // FIXME: check CDMA + bluedroid 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: // FIXME: check CDMA + bluedroid michael@0: //UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, true); michael@0: michael@0: //sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE; michael@0: //SendCommand("+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: // FIXME: check CDMA + bluedroid 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: michael@0: bool michael@0: BluetoothHfpManager::ConnectSco() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: NS_ENSURE_TRUE(!sInShutdown, false); michael@0: NS_ENSURE_TRUE(IsConnected() && !IsScoConnected(), false); michael@0: NS_ENSURE_TRUE(sBluetoothHfpInterface, false); michael@0: michael@0: bt_bdaddr_t deviceBdAddress; michael@0: StringToBdAddressType(mDeviceAddress, &deviceBdAddress); michael@0: NS_ENSURE_TRUE(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->connect_audio(&deviceBdAddress), false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::DisconnectSco() michael@0: { michael@0: NS_ENSURE_TRUE(IsScoConnected(), false); michael@0: NS_ENSURE_TRUE(sBluetoothHfpInterface, false); michael@0: michael@0: bt_bdaddr_t deviceBdAddress; michael@0: StringToBdAddressType(mDeviceAddress, &deviceBdAddress); michael@0: NS_ENSURE_TRUE(BT_STATUS_SUCCESS == michael@0: sBluetoothHfpInterface->disconnect_audio(&deviceBdAddress), false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::IsScoConnected() michael@0: { michael@0: return (mAudioState == BTHF_AUDIO_STATE_CONNECTED); michael@0: } michael@0: michael@0: bool michael@0: BluetoothHfpManager::IsConnected() michael@0: { michael@0: return (mConnectionState == BTHF_CONNECTION_STATE_SLC_CONNECTED); 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: if (sInShutdown) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: michael@0: if (!sBluetoothHfpInterface) { michael@0: BT_LOGR("sBluetoothHfpInterface is null"); michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: michael@0: bt_bdaddr_t deviceBdAddress; michael@0: StringToBdAddressType(aDeviceAddress, &deviceBdAddress); michael@0: michael@0: bt_status_t result = sBluetoothHfpInterface->connect(&deviceBdAddress); michael@0: if (BT_STATUS_SUCCESS != result) { michael@0: BT_LOGR("Failed to connect: %x", result); michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); michael@0: return; michael@0: } michael@0: michael@0: mDeviceAddress = aDeviceAddress; michael@0: mController = aController; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::Disconnect(BluetoothProfileController* aController) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!mController); michael@0: michael@0: if (!sBluetoothHfpInterface) { michael@0: BT_LOGR("sBluetoothHfpInterface is null"); michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: michael@0: bt_bdaddr_t deviceBdAddress; michael@0: StringToBdAddressType(mDeviceAddress, &deviceBdAddress); michael@0: michael@0: bt_status_t result = sBluetoothHfpInterface->disconnect(&deviceBdAddress); michael@0: if (BT_STATUS_SUCCESS != result) { michael@0: BT_LOGR("Failed to disconnect: %x", result); michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED)); michael@0: return; michael@0: } michael@0: michael@0: mController = aController; 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: /** 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: mController->NotifyCompletion(aErrorStr); michael@0: mController = nullptr; 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: /** 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: mController->NotifyCompletion(aErrorStr); michael@0: mController = nullptr; michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) michael@0: { michael@0: // Bluedroid handles this part michael@0: MOZ_ASSERT(false); 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: // Bluedroid handles this part michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: void michael@0: BluetoothHfpManager::GetAddress(nsAString& aDeviceAddress) michael@0: { michael@0: aDeviceAddress = mDeviceAddress; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(BluetoothHfpManager, nsIObserver)