/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "base/basictypes.h" #include "BluetoothA2dpManager.h" #include "BluetoothCommon.h" #include "BluetoothService.h" #include "BluetoothSocket.h" #include "BluetoothUtils.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "nsIAudioManager.h" #include "nsIObserverService.h" using namespace mozilla; USING_BLUETOOTH_NAMESPACE namespace { StaticRefPtr sBluetoothA2dpManager; bool sInShutdown = false; } // anonymous namespace NS_IMETHODIMP BluetoothA2dpManager::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) { MOZ_ASSERT(sBluetoothA2dpManager); if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { HandleShutdown(); return NS_OK; } MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!"); return NS_ERROR_UNEXPECTED; } BluetoothA2dpManager::BluetoothA2dpManager() { ResetA2dp(); ResetAvrcp(); } bool BluetoothA2dpManager::Init() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = services::GetObserverService(); NS_ENSURE_TRUE(obs, false); if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { BT_WARNING("Failed to add shutdown observer!"); return false; } return true; } BluetoothA2dpManager::~BluetoothA2dpManager() { nsCOMPtr obs = services::GetObserverService(); NS_ENSURE_TRUE_VOID(obs); if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { BT_WARNING("Failed to remove shutdown observer!"); } } void BluetoothA2dpManager::ResetA2dp() { mA2dpConnected = false; mPlaying = false; mSinkState = SinkState::SINK_DISCONNECTED; } void BluetoothA2dpManager::ResetAvrcp() { mAvrcpConnected = false; mDuration = 0; mMediaNumber = 0; mTotalMediaCount = 0; mPosition = 0; mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN; } static BluetoothA2dpManager::SinkState StatusStringToSinkState(const nsAString& aStatus) { BluetoothA2dpManager::SinkState state; if (aStatus.EqualsLiteral("disconnected")) { state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED; } else if (aStatus.EqualsLiteral("connecting")) { state = BluetoothA2dpManager::SinkState::SINK_CONNECTING; } else if (aStatus.EqualsLiteral("connected")) { state = BluetoothA2dpManager::SinkState::SINK_CONNECTED; } else if (aStatus.EqualsLiteral("playing")) { state = BluetoothA2dpManager::SinkState::SINK_PLAYING; } else if (aStatus.EqualsLiteral("disconnecting")) { state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTING; } else { MOZ_ASSERT(false, "Unknown sink state"); } return state; } //static BluetoothA2dpManager* BluetoothA2dpManager::Get() { MOZ_ASSERT(NS_IsMainThread()); // If sBluetoothA2dpManager already exists, exit early if (sBluetoothA2dpManager) { return sBluetoothA2dpManager; } // If we're in shutdown, don't create a new instance NS_ENSURE_FALSE(sInShutdown, nullptr); // Create a new instance, register, and return BluetoothA2dpManager* manager = new BluetoothA2dpManager(); NS_ENSURE_TRUE(manager->Init(), nullptr); sBluetoothA2dpManager = manager; return sBluetoothA2dpManager; } void BluetoothA2dpManager::HandleShutdown() { MOZ_ASSERT(NS_IsMainThread()); sInShutdown = true; Disconnect(); sBluetoothA2dpManager = nullptr; } bool BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aDeviceAddress.IsEmpty()); NS_ENSURE_FALSE(sInShutdown, false); NS_ENSURE_FALSE(mA2dpConnected, false); mDeviceAddress = aDeviceAddress; BluetoothService* bs = BluetoothService::Get(); NS_ENSURE_TRUE(bs, false); nsresult rv = bs->SendSinkMessage(aDeviceAddress, NS_LITERAL_STRING("Connect")); return NS_SUCCEEDED(rv); } void BluetoothA2dpManager::Disconnect() { NS_ENSURE_TRUE_VOID(mA2dpConnected); MOZ_ASSERT(!mDeviceAddress.IsEmpty()); BluetoothService* bs = BluetoothService::Get(); NS_ENSURE_TRUE_VOID(bs); bs->SendSinkMessage(mDeviceAddress, NS_LITERAL_STRING("Disconnect")); } void BluetoothA2dpManager::HandleSinkPropertyChanged(const BluetoothSignal& aSignal) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue); const InfallibleTArray& arr = aSignal.value().get_ArrayOfBluetoothNamedValue(); MOZ_ASSERT(arr.Length() == 1); const nsString& name = arr[0].name(); const BluetoothValue& value = arr[0].value(); if (name.EqualsLiteral("Connected")) { // Indicates if a stream is setup to a A2DP sink on the remote device. MOZ_ASSERT(value.type() == BluetoothValue::Tbool); MOZ_ASSERT(mA2dpConnected != value.get_bool()); mA2dpConnected = value.get_bool(); NotifyConnectionStatusChanged(); DispatchConnectionStatusChanged(); } else if (name.EqualsLiteral("Playing")) { // Indicates if a stream is active to a A2DP sink on the remote device. MOZ_ASSERT(value.type() == BluetoothValue::Tbool); MOZ_ASSERT(mPlaying != value.get_bool()); mPlaying = value.get_bool(); } else if (name.EqualsLiteral("State")) { MOZ_ASSERT(value.type() == BluetoothValue::TnsString); MOZ_ASSERT(mSinkState != StatusStringToSinkState(value.get_nsString())); HandleSinkStateChanged(StatusStringToSinkState(value.get_nsString())); } else { NS_WARNING("Unknown sink property"); } } /* HandleSinkStateChanged updates sink state in A2dp * * Possible values: "disconnected", "connecting", "connected", "playing" * * 1. "disconnected" -> "connecting" * Either an incoming or outgoing connection attempt ongoing * 2. "connecting" -> "disconnected" * Connection attempt failed * 3. "connecting" -> "connected" * Successfully connected * 4. "connected" -> "playing" * Audio stream active * 5. "playing" -> "connected" * Audio stream suspended * 6. "connected" -> "disconnected" * "playing" -> "disconnected" * Disconnected from the remote device * 7. "disconnecting" -> "disconnected" * Disconnected from local */ void BluetoothA2dpManager::HandleSinkStateChanged(SinkState aState) { MOZ_ASSERT_IF(aState == SinkState::SINK_CONNECTED, mSinkState == SinkState::SINK_CONNECTING || mSinkState == SinkState::SINK_PLAYING); MOZ_ASSERT_IF(aState == SinkState::SINK_PLAYING, mSinkState == SinkState::SINK_CONNECTED); if (aState == SinkState::SINK_DISCONNECTED) { mDeviceAddress.Truncate(); } mSinkState = aState; } void BluetoothA2dpManager::DispatchConnectionStatusChanged() { MOZ_ASSERT(NS_IsMainThread()); DispatchStatusChangedEvent( NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected); } void BluetoothA2dpManager::NotifyConnectionStatusChanged() { MOZ_ASSERT(NS_IsMainThread()); // Broadcast system message to Gaia NS_NAMED_LITERAL_STRING(type, BLUETOOTH_A2DP_STATUS_CHANGED_ID); InfallibleTArray parameters; BluetoothValue v = mA2dpConnected; parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("connected"), v)); v = mDeviceAddress; parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("address"), v)); if (!BroadcastSystemMessage(type, parameters)) { NS_WARNING("Failed to broadcast system message to settings"); } // Notify Gecko observers nsCOMPtr obs = do_GetService("@mozilla.org/observer-service;1"); NS_ENSURE_TRUE_VOID(obs); if (NS_FAILED(obs->NotifyObservers(this, BLUETOOTH_A2DP_STATUS_CHANGED_ID, mDeviceAddress.get()))) { NS_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!"); } } void BluetoothA2dpManager::OnGetServiceChannel(const nsAString& aDeviceAddress, const nsAString& aServiceUuid, int aChannel) { } void BluetoothA2dpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) { } void BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress) { aDeviceAddress = mDeviceAddress; } bool BluetoothA2dpManager::IsConnected() { return mA2dpConnected; } void BluetoothA2dpManager::SetAvrcpConnected(bool aConnected) { mAvrcpConnected = aConnected; if (!aConnected) { ResetAvrcp(); } } bool BluetoothA2dpManager::IsAvrcpConnected() { return mAvrcpConnected; } void BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle, const nsAString& aArtist, const nsAString& aAlbum, uint32_t aMediaNumber, uint32_t aTotalMediaCount, uint32_t aDuration) { mTitle.Assign(aTitle); mArtist.Assign(aArtist); mAlbum.Assign(aAlbum); mMediaNumber = aMediaNumber; mTotalMediaCount = aTotalMediaCount; mDuration = aDuration; } void BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration, uint32_t aPosition, ControlPlayStatus aPlayStatus) { mDuration = aDuration; mPosition = aPosition; mPlayStatus = aPlayStatus; } void BluetoothA2dpManager::GetAlbum(nsAString& aAlbum) { aAlbum.Assign(mAlbum); } uint32_t BluetoothA2dpManager::GetDuration() { return mDuration; } ControlPlayStatus BluetoothA2dpManager::GetPlayStatus() { return mPlayStatus; } uint32_t BluetoothA2dpManager::GetPosition() { return mPosition; } uint32_t BluetoothA2dpManager::GetMediaNumber() { return mMediaNumber; } void BluetoothA2dpManager::GetTitle(nsAString& aTitle) { aTitle.Assign(mTitle); } NS_IMPL_ISUPPORTS1(BluetoothA2dpManager, nsIObserver)