/* -*- 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 "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" #define BLUETOOTH_A2DP_STATUS_CHANGED "bluetooth-a2dp-status-changed" using namespace mozilla; USING_BLUETOOTH_NAMESPACE namespace { StaticAutoPtr gBluetoothA2dpManager; StaticRefPtr sA2dpObserver; bool gInShutdown = false; } // anonymous namespace class mozilla::dom::bluetooth::BluetoothA2dpManagerObserver : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER BluetoothA2dpManagerObserver() { } bool Init() { nsCOMPtr obs = services::GetObserverService(); MOZ_ASSERT(obs); if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { NS_WARNING("Failed to add shutdown observer!"); return false; } return true; } bool Shutdown() { nsCOMPtr obs = services::GetObserverService(); if (!obs || NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { NS_WARNING("Can't unregister observers, or already unregistered!"); return false; } return true; } ~BluetoothA2dpManagerObserver() { Shutdown(); } }; NS_IMETHODIMP BluetoothA2dpManagerObserver::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) { MOZ_ASSERT(gBluetoothA2dpManager); if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { return gBluetoothA2dpManager->HandleShutdown(); } MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!"); return NS_ERROR_UNEXPECTED; } NS_IMPL_ISUPPORTS1(BluetoothA2dpManagerObserver, nsIObserver) BluetoothA2dpManager::BluetoothA2dpManager() : mConnected(false) , mPlaying(false) , mSinkState(SinkState::SINK_DISCONNECTED) { } bool BluetoothA2dpManager::Init() { MOZ_ASSERT(NS_IsMainThread()); sA2dpObserver = new BluetoothA2dpManagerObserver(); if (!sA2dpObserver->Init()) { NS_WARNING("Cannot set up A2dp Observers!"); sA2dpObserver = nullptr; } return true; } BluetoothA2dpManager::~BluetoothA2dpManager() { Cleanup(); } void BluetoothA2dpManager::Cleanup() { sA2dpObserver->Shutdown(); sA2dpObserver = nullptr; } static SinkState StatusStringToSinkState(const nsAString& aStatus) { SinkState state; if (aStatus.EqualsLiteral("disconnected")) { state = SinkState::SINK_DISCONNECTED; } else if (aStatus.EqualsLiteral("connecting")) { state = SinkState::SINK_CONNECTING; } else if (aStatus.EqualsLiteral("connected")) { state = SinkState::SINK_CONNECTED; } else if (aStatus.EqualsLiteral("playing")) { state = SinkState::SINK_PLAYING; } else if (aStatus.EqualsLiteral("disconnecting")) { state = SinkState::SINK_DISCONNECTING; } else { MOZ_ASSERT(false, "Unknown sink state"); } return state; } //static BluetoothA2dpManager* BluetoothA2dpManager::Get() { MOZ_ASSERT(NS_IsMainThread()); // If we already exist, exit early if (gBluetoothA2dpManager) { return gBluetoothA2dpManager; } // If we're in shutdown, don't create a new instance if (gInShutdown) { NS_WARNING("BluetoothA2dpManager can't be created during shutdown"); return nullptr; } // Create new instance, register, return BluetoothA2dpManager* manager = new BluetoothA2dpManager(); NS_ENSURE_TRUE(manager->Init(), nullptr); gBluetoothA2dpManager = manager; return gBluetoothA2dpManager; } nsresult BluetoothA2dpManager::HandleShutdown() { MOZ_ASSERT(NS_IsMainThread()); gInShutdown = true; Disconnect(); gBluetoothA2dpManager = nullptr; return NS_OK; } bool BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aDeviceAddress.IsEmpty()); if (gInShutdown) { NS_WARNING("Connect called while in shutdown!"); return false; } if (mConnected) { NS_WARNING("BluetoothA2dpManager is connected"); return 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() { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); if (!mConnected) { NS_WARNING("BluetoothA2dpManager has been disconnected"); return; } 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); mConnected = value.get_bool(); } 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); mPlaying = value.get_bool(); } else if (name.EqualsLiteral("State")) { MOZ_ASSERT(value.type() == BluetoothValue::TnsString); HandleSinkStateChanged(StatusStringToSinkState(value.get_nsString())); } else { NS_WARNING("Unknown sink property"); } } /* HandleSinkPropertyChanged update 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; }