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 "BluetoothA2dpManager.h" michael@0: michael@0: #include "BluetoothCommon.h" michael@0: #include "BluetoothService.h" michael@0: #include "BluetoothSocket.h" michael@0: #include "BluetoothUtils.h" michael@0: michael@0: #include "mozilla/dom/bluetooth/BluetoothTypes.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "nsIObserverService.h" michael@0: #include "MainThreadUtils.h" michael@0: michael@0: michael@0: using namespace mozilla; michael@0: USING_BLUETOOTH_NAMESPACE michael@0: michael@0: namespace { michael@0: StaticRefPtr sBluetoothA2dpManager; michael@0: bool sInShutdown = false; michael@0: } // anonymous namespace michael@0: michael@0: NS_IMETHODIMP michael@0: BluetoothA2dpManager::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: MOZ_ASSERT(sBluetoothA2dpManager); michael@0: michael@0: if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { michael@0: HandleShutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: BluetoothA2dpManager::BluetoothA2dpManager() michael@0: { michael@0: Reset(); michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::Reset() michael@0: { michael@0: ResetA2dp(); michael@0: ResetAvrcp(); michael@0: } michael@0: michael@0: bool michael@0: BluetoothA2dpManager::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: if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { michael@0: BT_WARNING("Failed to add shutdown observer!"); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: BluetoothA2dpManager::~BluetoothA2dpManager() michael@0: { michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(obs); michael@0: if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { michael@0: BT_WARNING("Failed to remove shutdown observer!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::ResetA2dp() michael@0: { michael@0: mA2dpConnected = false; michael@0: mSinkState = SinkState::SINK_DISCONNECTED; michael@0: mController = nullptr; michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::ResetAvrcp() michael@0: { michael@0: mAvrcpConnected = false; michael@0: mDuration = 0; michael@0: mMediaNumber = 0; michael@0: mTotalMediaCount = 0; michael@0: mPosition = 0; michael@0: mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN; michael@0: } michael@0: michael@0: static BluetoothA2dpManager::SinkState michael@0: StatusStringToSinkState(const nsAString& aStatus) michael@0: { michael@0: BluetoothA2dpManager::SinkState state = michael@0: BluetoothA2dpManager::SinkState::SINK_UNKNOWN; michael@0: if (aStatus.EqualsLiteral("disconnected")) { michael@0: state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED; michael@0: } else if (aStatus.EqualsLiteral("connecting")) { michael@0: state = BluetoothA2dpManager::SinkState::SINK_CONNECTING; michael@0: } else if (aStatus.EqualsLiteral("connected")) { michael@0: state = BluetoothA2dpManager::SinkState::SINK_CONNECTED; michael@0: } else if (aStatus.EqualsLiteral("playing")) { michael@0: state = BluetoothA2dpManager::SinkState::SINK_PLAYING; michael@0: } else { michael@0: BT_WARNING("Unknown sink state"); michael@0: } michael@0: return state; michael@0: } michael@0: michael@0: //static michael@0: BluetoothA2dpManager* michael@0: BluetoothA2dpManager::Get() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // If sBluetoothA2dpManager already exists, exit early michael@0: if (sBluetoothA2dpManager) { michael@0: return sBluetoothA2dpManager; 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: BluetoothA2dpManager* manager = new BluetoothA2dpManager(); michael@0: NS_ENSURE_TRUE(manager->Init(), nullptr); michael@0: michael@0: sBluetoothA2dpManager = manager; michael@0: return sBluetoothA2dpManager; michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::HandleShutdown() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: sInShutdown = true; michael@0: Disconnect(nullptr); michael@0: sBluetoothA2dpManager = nullptr; michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress, michael@0: BluetoothProfileController* aController) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!aDeviceAddress.IsEmpty()); 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 (mA2dpConnected) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED)); michael@0: return; michael@0: } michael@0: michael@0: mDeviceAddress = aDeviceAddress; michael@0: mController = aController; michael@0: michael@0: if (NS_FAILED(bs->SendSinkMessage(aDeviceAddress, michael@0: NS_LITERAL_STRING("Connect")))) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::Disconnect(BluetoothProfileController* aController) michael@0: { michael@0: BluetoothService* bs = BluetoothService::Get(); michael@0: if (!bs) { michael@0: if (aController) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (!mA2dpConnected) { 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(!mDeviceAddress.IsEmpty()); michael@0: MOZ_ASSERT(!mController); michael@0: michael@0: mController = aController; michael@0: michael@0: if (NS_FAILED(bs->SendSinkMessage(mDeviceAddress, michael@0: NS_LITERAL_STRING("Disconnect")))) { michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::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: nsRefPtr controller = mController.forget(); michael@0: controller->NotifyCompletion(aErrorStr); michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::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: nsRefPtr controller = mController.forget(); michael@0: controller->NotifyCompletion(aErrorStr); michael@0: michael@0: Reset(); michael@0: } michael@0: michael@0: /* HandleSinkPropertyChanged update sink state in A2dp michael@0: * michael@0: * Possible values: "disconnected", "connecting", "connected", "playing" michael@0: * michael@0: * 1. "disconnected" -> "connecting" michael@0: * Either an incoming or outgoing connection attempt ongoing michael@0: * 2. "connecting" -> "disconnected" michael@0: * Connection attempt failed michael@0: * 3. "connecting" -> "connected" michael@0: * Successfully connected michael@0: * 4. "connected" -> "playing" michael@0: * Audio stream active michael@0: * 5. "playing" -> "connected" michael@0: * Audio stream suspended michael@0: * 6. "connected" -> "disconnected" michael@0: * "playing" -> "disconnected" michael@0: * Disconnected from local or the remote device michael@0: */ michael@0: void michael@0: BluetoothA2dpManager::HandleSinkPropertyChanged(const BluetoothSignal& aSignal) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue); michael@0: michael@0: const nsString& address = aSignal.path(); michael@0: /** michael@0: * Update sink property only if michael@0: * - mDeviceAddress is empty (A2dp is disconnected), or michael@0: * - this property change is from the connected sink. michael@0: */ michael@0: NS_ENSURE_TRUE_VOID(mDeviceAddress.IsEmpty() || michael@0: mDeviceAddress.Equals(address)); michael@0: michael@0: const InfallibleTArray& arr = michael@0: aSignal.value().get_ArrayOfBluetoothNamedValue(); michael@0: MOZ_ASSERT(arr.Length() == 1); michael@0: michael@0: /** michael@0: * There are three properties: michael@0: * - "State": a string michael@0: * - "Connected": a boolean value michael@0: * - "Playing": a boolean value michael@0: * michael@0: * Note that only "State" is handled in this function. michael@0: */ michael@0: michael@0: const nsString& name = arr[0].name(); michael@0: NS_ENSURE_TRUE_VOID(name.EqualsLiteral("State")); michael@0: michael@0: const BluetoothValue& value = arr[0].value(); michael@0: MOZ_ASSERT(value.type() == BluetoothValue::TnsString); michael@0: SinkState newState = StatusStringToSinkState(value.get_nsString()); michael@0: NS_ENSURE_TRUE_VOID((newState != SinkState::SINK_UNKNOWN) && michael@0: (newState != mSinkState)); michael@0: michael@0: /** michael@0: * Reject 'connected' state change if bluetooth is already disabled. michael@0: * Sink state would be reset to 'disconnected' when bluetooth is disabled. michael@0: * michael@0: * See bug 984284 for more information about the edge case. michael@0: */ michael@0: NS_ENSURE_FALSE_VOID(newState == SinkState::SINK_CONNECTED && michael@0: mSinkState == SinkState::SINK_DISCONNECTED); michael@0: michael@0: SinkState prevState = mSinkState; michael@0: mSinkState = newState; michael@0: michael@0: switch(mSinkState) { michael@0: case SinkState::SINK_CONNECTING: michael@0: // case 1: Either an incoming or outgoing connection attempt ongoing michael@0: MOZ_ASSERT(prevState == SinkState::SINK_DISCONNECTED); michael@0: break; michael@0: case SinkState::SINK_PLAYING: michael@0: // case 4: Audio stream active michael@0: MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED); michael@0: break; michael@0: case SinkState::SINK_CONNECTED: michael@0: // case 5: Audio stream suspended michael@0: if (prevState == SinkState::SINK_PLAYING) { michael@0: break; michael@0: } michael@0: michael@0: // case 3: Successfully connected michael@0: MOZ_ASSERT(prevState == SinkState::SINK_CONNECTING); michael@0: michael@0: mA2dpConnected = true; michael@0: mDeviceAddress = address; michael@0: NotifyConnectionStatusChanged(); michael@0: michael@0: OnConnect(EmptyString()); michael@0: break; michael@0: case SinkState::SINK_DISCONNECTED: michael@0: // case 2: Connection attempt failed michael@0: if (prevState == SinkState::SINK_CONNECTING) { michael@0: OnConnect(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); michael@0: break; michael@0: } michael@0: michael@0: // case 6: Disconnected from the remote device michael@0: MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED || michael@0: prevState == SinkState::SINK_PLAYING); michael@0: michael@0: mA2dpConnected = false; michael@0: NotifyConnectionStatusChanged(); michael@0: mDeviceAddress.Truncate(); michael@0: OnDisconnect(EmptyString()); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::NotifyConnectionStatusChanged() 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, michael@0: BLUETOOTH_A2DP_STATUS_CHANGED_ID, michael@0: mDeviceAddress.get()))) { michael@0: BT_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!"); michael@0: } michael@0: michael@0: // Dispatch an event of status change michael@0: DispatchStatusChangedEvent( michael@0: NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected); michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::OnGetServiceChannel(const nsAString& aDeviceAddress, michael@0: const nsAString& aServiceUuid, michael@0: int aChannel) michael@0: { michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) michael@0: { michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress) michael@0: { michael@0: aDeviceAddress = mDeviceAddress; michael@0: } michael@0: michael@0: bool michael@0: BluetoothA2dpManager::IsConnected() michael@0: { michael@0: return mA2dpConnected; michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::SetAvrcpConnected(bool aConnected) michael@0: { michael@0: mAvrcpConnected = aConnected; michael@0: if (!aConnected) { michael@0: ResetAvrcp(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BluetoothA2dpManager::IsAvrcpConnected() michael@0: { michael@0: return mAvrcpConnected; michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle, michael@0: const nsAString& aArtist, michael@0: const nsAString& aAlbum, michael@0: uint64_t aMediaNumber, michael@0: uint64_t aTotalMediaCount, michael@0: uint32_t aDuration) michael@0: { michael@0: mTitle.Assign(aTitle); michael@0: mArtist.Assign(aArtist); michael@0: mAlbum.Assign(aAlbum); michael@0: mMediaNumber = aMediaNumber; michael@0: mTotalMediaCount = aTotalMediaCount; michael@0: mDuration = aDuration; michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration, michael@0: uint32_t aPosition, michael@0: ControlPlayStatus aPlayStatus) michael@0: { michael@0: mDuration = aDuration; michael@0: mPosition = aPosition; michael@0: mPlayStatus = aPlayStatus; michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::GetAlbum(nsAString& aAlbum) michael@0: { michael@0: aAlbum.Assign(mAlbum); michael@0: } michael@0: michael@0: uint32_t michael@0: BluetoothA2dpManager::GetDuration() michael@0: { michael@0: return mDuration; michael@0: } michael@0: michael@0: ControlPlayStatus michael@0: BluetoothA2dpManager::GetPlayStatus() michael@0: { michael@0: return mPlayStatus; michael@0: } michael@0: michael@0: uint32_t michael@0: BluetoothA2dpManager::GetPosition() michael@0: { michael@0: return mPosition; michael@0: } michael@0: michael@0: uint64_t michael@0: BluetoothA2dpManager::GetMediaNumber() michael@0: { michael@0: return mMediaNumber; michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::GetTitle(nsAString& aTitle) michael@0: { michael@0: aTitle.Assign(mTitle); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(BluetoothA2dpManager, nsIObserver) michael@0: