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 "BluetoothProfileController.h" michael@0: #include "BluetoothReplyRunnable.h" michael@0: michael@0: #include "BluetoothA2dpManager.h" michael@0: #include "BluetoothHfpManager.h" michael@0: #include "BluetoothHidManager.h" michael@0: #include "BluetoothUtils.h" michael@0: michael@0: #include "mozilla/dom/bluetooth/BluetoothTypes.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: USING_BLUETOOTH_NAMESPACE michael@0: michael@0: #define BT_LOGR_PROFILE(mgr, msg, ...) \ michael@0: do { \ michael@0: nsCString name; \ michael@0: mgr->GetName(name); \ michael@0: BT_LOGR("[%s] " msg, name.get(), ##__VA_ARGS__); \ michael@0: } while(0) michael@0: michael@0: #define CONNECTION_TIMEOUT_MS 15000 michael@0: michael@0: class CheckProfileStatusCallback : public nsITimerCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSITIMERCALLBACK michael@0: michael@0: CheckProfileStatusCallback(BluetoothProfileController* aController) michael@0: : mController(aController) michael@0: { michael@0: MOZ_ASSERT(aController); michael@0: } michael@0: michael@0: virtual ~CheckProfileStatusCallback() michael@0: { michael@0: mController = nullptr; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mController; michael@0: }; michael@0: michael@0: BluetoothProfileController::BluetoothProfileController( michael@0: bool aConnect, michael@0: const nsAString& aDeviceAddress, michael@0: BluetoothReplyRunnable* aRunnable, michael@0: BluetoothProfileControllerCallback aCallback, michael@0: uint16_t aServiceUuid, michael@0: uint32_t aCod) michael@0: : mConnect(aConnect) michael@0: , mDeviceAddress(aDeviceAddress) michael@0: , mRunnable(aRunnable) michael@0: , mCallback(aCallback) michael@0: , mCurrentProfileFinished(false) michael@0: , mSuccess(false) michael@0: , mProfilesIndex(-1) michael@0: { michael@0: MOZ_ASSERT(!aDeviceAddress.IsEmpty()); michael@0: MOZ_ASSERT(aRunnable); michael@0: MOZ_ASSERT(aCallback); michael@0: michael@0: mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: MOZ_ASSERT(mTimer); michael@0: michael@0: mCheckProfileStatusCallback = new CheckProfileStatusCallback(this); michael@0: mProfiles.Clear(); michael@0: michael@0: /** michael@0: * If the service uuid is not specified, either connect multiple profiles michael@0: * based on Cod, or disconnect all connected profiles. michael@0: */ michael@0: if (!aServiceUuid) { michael@0: mTarget.cod = aCod; michael@0: SetupProfiles(false); michael@0: } else { michael@0: BluetoothServiceClass serviceClass = michael@0: BluetoothUuidHelper::GetBluetoothServiceClass(aServiceUuid); michael@0: mTarget.service = serviceClass; michael@0: SetupProfiles(true); michael@0: } michael@0: } michael@0: michael@0: BluetoothProfileController::~BluetoothProfileController() michael@0: { michael@0: mProfiles.Clear(); michael@0: mRunnable = nullptr; michael@0: mCallback = nullptr; michael@0: michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothProfileController::AddProfileWithServiceClass( michael@0: BluetoothServiceClass aClass) michael@0: { michael@0: BluetoothProfileManagerBase* profile; michael@0: switch (aClass) { michael@0: case BluetoothServiceClass::HANDSFREE: michael@0: case BluetoothServiceClass::HEADSET: michael@0: profile = BluetoothHfpManager::Get(); michael@0: break; michael@0: case BluetoothServiceClass::A2DP: michael@0: profile = BluetoothA2dpManager::Get(); michael@0: break; michael@0: case BluetoothServiceClass::HID: michael@0: profile = BluetoothHidManager::Get(); michael@0: break; michael@0: default: michael@0: DispatchBluetoothReply(mRunnable, BluetoothValue(), michael@0: NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE)); michael@0: mCallback(); michael@0: return; michael@0: } michael@0: michael@0: AddProfile(profile); michael@0: } michael@0: michael@0: void michael@0: BluetoothProfileController::AddProfile(BluetoothProfileManagerBase* aProfile, michael@0: bool aCheckConnected) michael@0: { michael@0: if (!aProfile) { michael@0: DispatchBluetoothReply(mRunnable, BluetoothValue(), michael@0: NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: mCallback(); michael@0: return; michael@0: } michael@0: michael@0: if (aCheckConnected && !aProfile->IsConnected()) { michael@0: BT_WARNING("The profile is not connected."); michael@0: return; michael@0: } michael@0: michael@0: mProfiles.AppendElement(aProfile); michael@0: } michael@0: michael@0: void michael@0: BluetoothProfileController::SetupProfiles(bool aAssignServiceClass) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: /** michael@0: * When a service class is assigned, only its corresponding profile is put michael@0: * into array. michael@0: */ michael@0: if (aAssignServiceClass) { michael@0: AddProfileWithServiceClass(mTarget.service); michael@0: return; michael@0: } michael@0: michael@0: // For a disconnect request, all connected profiles are put into array. michael@0: if (!mConnect) { michael@0: AddProfile(BluetoothHidManager::Get(), true); michael@0: AddProfile(BluetoothA2dpManager::Get(), true); michael@0: AddProfile(BluetoothHfpManager::Get(), true); michael@0: return; michael@0: } michael@0: michael@0: /** michael@0: * For a connect request, put multiple profiles into array and connect to michael@0: * all of them sequencely. michael@0: */ michael@0: bool hasAudio = HAS_AUDIO(mTarget.cod); michael@0: bool hasRendering = HAS_RENDERING(mTarget.cod); michael@0: bool isPeripheral = IS_PERIPHERAL(mTarget.cod); michael@0: bool isRemoteControl = IS_REMOTE_CONTROL(mTarget.cod); michael@0: bool isKeyboard = IS_KEYBOARD(mTarget.cod); michael@0: bool isPointingDevice = IS_POINTING_DEVICE(mTarget.cod); michael@0: michael@0: NS_ENSURE_TRUE_VOID(hasAudio || hasRendering || isPeripheral); michael@0: michael@0: // Audio bit should be set if remote device supports HFP/HSP. michael@0: if (hasAudio) { michael@0: AddProfile(BluetoothHfpManager::Get()); michael@0: } michael@0: michael@0: // Rendering bit should be set if remote device supports A2DP. michael@0: // A device which supports AVRCP should claim that it's a peripheral and it's michael@0: // a remote control. michael@0: if (hasRendering || (isPeripheral && isRemoteControl)) { michael@0: AddProfile(BluetoothA2dpManager::Get()); michael@0: } michael@0: michael@0: // A device which supports HID should claim that it's a peripheral and it's michael@0: // either a keyboard, a pointing device, or both. michael@0: if (isPeripheral && (isKeyboard || isPointingDevice)) { michael@0: AddProfile(BluetoothHidManager::Get()); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(CheckProfileStatusCallback, nsITimerCallback) michael@0: michael@0: NS_IMETHODIMP michael@0: CheckProfileStatusCallback::Notify(nsITimer* aTimer) michael@0: { michael@0: MOZ_ASSERT(mController); michael@0: // Continue on the next profile since we haven't got the callback after michael@0: // timeout. michael@0: mController->GiveupAndContinue(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: BluetoothProfileController::StartSession() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!mDeviceAddress.IsEmpty()); michael@0: MOZ_ASSERT(mProfilesIndex == -1); michael@0: MOZ_ASSERT(mTimer); michael@0: michael@0: if (mProfiles.Length() < 1) { michael@0: BT_LOGR("No queued profile."); michael@0: EndSession(); michael@0: return; michael@0: } michael@0: michael@0: if (mTimer) { michael@0: mTimer->InitWithCallback(mCheckProfileStatusCallback, CONNECTION_TIMEOUT_MS, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: BT_LOGR("%s", mConnect ? "connecting" : "disconnecting"); michael@0: michael@0: Next(); michael@0: } michael@0: michael@0: void michael@0: BluetoothProfileController::EndSession() michael@0: { michael@0: MOZ_ASSERT(mRunnable && mCallback); michael@0: michael@0: BT_LOGR("mSuccess %d", mSuccess); michael@0: michael@0: // The action has completed, so the DOM request should be replied then invoke michael@0: // the callback. michael@0: if (mSuccess) { michael@0: DispatchBluetoothReply(mRunnable, BluetoothValue(true), EmptyString()); michael@0: } else if (mConnect) { michael@0: DispatchBluetoothReply(mRunnable, BluetoothValue(true), michael@0: NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); michael@0: } else { michael@0: DispatchBluetoothReply(mRunnable, BluetoothValue(true), michael@0: NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED)); michael@0: } michael@0: michael@0: mCallback(); michael@0: } michael@0: michael@0: void michael@0: BluetoothProfileController::Next() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!mDeviceAddress.IsEmpty()); michael@0: MOZ_ASSERT(mProfilesIndex < (int)mProfiles.Length()); michael@0: MOZ_ASSERT(mTimer); michael@0: michael@0: mCurrentProfileFinished = false; michael@0: michael@0: if (++mProfilesIndex >= (int)mProfiles.Length()) { michael@0: EndSession(); michael@0: return; michael@0: } michael@0: michael@0: BT_LOGR_PROFILE(mProfiles[mProfilesIndex], ""); michael@0: michael@0: if (mConnect) { michael@0: mProfiles[mProfilesIndex]->Connect(mDeviceAddress, this); michael@0: } else { michael@0: mProfiles[mProfilesIndex]->Disconnect(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothProfileController::NotifyCompletion(const nsAString& aErrorStr) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mTimer); michael@0: MOZ_ASSERT(mProfiles.Length() > 0); michael@0: michael@0: BT_LOGR_PROFILE(mProfiles[mProfilesIndex], "<%s>", michael@0: NS_ConvertUTF16toUTF8(aErrorStr).get()); michael@0: michael@0: mCurrentProfileFinished = true; michael@0: michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: } michael@0: michael@0: mSuccess |= aErrorStr.IsEmpty(); michael@0: michael@0: Next(); michael@0: } michael@0: michael@0: void michael@0: BluetoothProfileController::GiveupAndContinue() michael@0: { michael@0: MOZ_ASSERT(!mCurrentProfileFinished); michael@0: MOZ_ASSERT(mProfilesIndex < (int)mProfiles.Length()); michael@0: michael@0: BT_LOGR_PROFILE(mProfiles[mProfilesIndex], ERR_OPERATION_TIMEOUT); michael@0: mProfiles[mProfilesIndex]->Reset(); michael@0: Next(); michael@0: } michael@0: