1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/bluetooth/bluez/BluetoothA2dpManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,477 @@ 1.4 +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "base/basictypes.h" 1.11 + 1.12 +#include "BluetoothA2dpManager.h" 1.13 + 1.14 +#include "BluetoothCommon.h" 1.15 +#include "BluetoothService.h" 1.16 +#include "BluetoothSocket.h" 1.17 +#include "BluetoothUtils.h" 1.18 + 1.19 +#include "mozilla/dom/bluetooth/BluetoothTypes.h" 1.20 +#include "mozilla/Services.h" 1.21 +#include "mozilla/StaticPtr.h" 1.22 +#include "nsIObserverService.h" 1.23 +#include "MainThreadUtils.h" 1.24 + 1.25 + 1.26 +using namespace mozilla; 1.27 +USING_BLUETOOTH_NAMESPACE 1.28 + 1.29 +namespace { 1.30 + StaticRefPtr<BluetoothA2dpManager> sBluetoothA2dpManager; 1.31 + bool sInShutdown = false; 1.32 +} // anonymous namespace 1.33 + 1.34 +NS_IMETHODIMP 1.35 +BluetoothA2dpManager::Observe(nsISupports* aSubject, 1.36 + const char* aTopic, 1.37 + const char16_t* aData) 1.38 +{ 1.39 + MOZ_ASSERT(sBluetoothA2dpManager); 1.40 + 1.41 + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 1.42 + HandleShutdown(); 1.43 + return NS_OK; 1.44 + } 1.45 + 1.46 + MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!"); 1.47 + return NS_ERROR_UNEXPECTED; 1.48 +} 1.49 + 1.50 +BluetoothA2dpManager::BluetoothA2dpManager() 1.51 +{ 1.52 + Reset(); 1.53 +} 1.54 + 1.55 +void 1.56 +BluetoothA2dpManager::Reset() 1.57 +{ 1.58 + ResetA2dp(); 1.59 + ResetAvrcp(); 1.60 +} 1.61 + 1.62 +bool 1.63 +BluetoothA2dpManager::Init() 1.64 +{ 1.65 + MOZ_ASSERT(NS_IsMainThread()); 1.66 + 1.67 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.68 + NS_ENSURE_TRUE(obs, false); 1.69 + if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { 1.70 + BT_WARNING("Failed to add shutdown observer!"); 1.71 + return false; 1.72 + } 1.73 + 1.74 + return true; 1.75 +} 1.76 + 1.77 +BluetoothA2dpManager::~BluetoothA2dpManager() 1.78 +{ 1.79 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.80 + NS_ENSURE_TRUE_VOID(obs); 1.81 + if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { 1.82 + BT_WARNING("Failed to remove shutdown observer!"); 1.83 + } 1.84 +} 1.85 + 1.86 +void 1.87 +BluetoothA2dpManager::ResetA2dp() 1.88 +{ 1.89 + mA2dpConnected = false; 1.90 + mSinkState = SinkState::SINK_DISCONNECTED; 1.91 + mController = nullptr; 1.92 +} 1.93 + 1.94 +void 1.95 +BluetoothA2dpManager::ResetAvrcp() 1.96 +{ 1.97 + mAvrcpConnected = false; 1.98 + mDuration = 0; 1.99 + mMediaNumber = 0; 1.100 + mTotalMediaCount = 0; 1.101 + mPosition = 0; 1.102 + mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN; 1.103 +} 1.104 + 1.105 +static BluetoothA2dpManager::SinkState 1.106 +StatusStringToSinkState(const nsAString& aStatus) 1.107 +{ 1.108 + BluetoothA2dpManager::SinkState state = 1.109 + BluetoothA2dpManager::SinkState::SINK_UNKNOWN; 1.110 + if (aStatus.EqualsLiteral("disconnected")) { 1.111 + state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED; 1.112 + } else if (aStatus.EqualsLiteral("connecting")) { 1.113 + state = BluetoothA2dpManager::SinkState::SINK_CONNECTING; 1.114 + } else if (aStatus.EqualsLiteral("connected")) { 1.115 + state = BluetoothA2dpManager::SinkState::SINK_CONNECTED; 1.116 + } else if (aStatus.EqualsLiteral("playing")) { 1.117 + state = BluetoothA2dpManager::SinkState::SINK_PLAYING; 1.118 + } else { 1.119 + BT_WARNING("Unknown sink state"); 1.120 + } 1.121 + return state; 1.122 +} 1.123 + 1.124 +//static 1.125 +BluetoothA2dpManager* 1.126 +BluetoothA2dpManager::Get() 1.127 +{ 1.128 + MOZ_ASSERT(NS_IsMainThread()); 1.129 + 1.130 + // If sBluetoothA2dpManager already exists, exit early 1.131 + if (sBluetoothA2dpManager) { 1.132 + return sBluetoothA2dpManager; 1.133 + } 1.134 + 1.135 + // If we're in shutdown, don't create a new instance 1.136 + NS_ENSURE_FALSE(sInShutdown, nullptr); 1.137 + 1.138 + // Create a new instance, register, and return 1.139 + BluetoothA2dpManager* manager = new BluetoothA2dpManager(); 1.140 + NS_ENSURE_TRUE(manager->Init(), nullptr); 1.141 + 1.142 + sBluetoothA2dpManager = manager; 1.143 + return sBluetoothA2dpManager; 1.144 +} 1.145 + 1.146 +void 1.147 +BluetoothA2dpManager::HandleShutdown() 1.148 +{ 1.149 + MOZ_ASSERT(NS_IsMainThread()); 1.150 + sInShutdown = true; 1.151 + Disconnect(nullptr); 1.152 + sBluetoothA2dpManager = nullptr; 1.153 +} 1.154 + 1.155 +void 1.156 +BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress, 1.157 + BluetoothProfileController* aController) 1.158 +{ 1.159 + MOZ_ASSERT(NS_IsMainThread()); 1.160 + MOZ_ASSERT(!aDeviceAddress.IsEmpty()); 1.161 + MOZ_ASSERT(aController && !mController); 1.162 + 1.163 + BluetoothService* bs = BluetoothService::Get(); 1.164 + if (!bs || sInShutdown) { 1.165 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.166 + return; 1.167 + } 1.168 + 1.169 + if (mA2dpConnected) { 1.170 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED)); 1.171 + return; 1.172 + } 1.173 + 1.174 + mDeviceAddress = aDeviceAddress; 1.175 + mController = aController; 1.176 + 1.177 + if (NS_FAILED(bs->SendSinkMessage(aDeviceAddress, 1.178 + NS_LITERAL_STRING("Connect")))) { 1.179 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.180 + return; 1.181 + } 1.182 +} 1.183 + 1.184 +void 1.185 +BluetoothA2dpManager::Disconnect(BluetoothProfileController* aController) 1.186 +{ 1.187 + BluetoothService* bs = BluetoothService::Get(); 1.188 + if (!bs) { 1.189 + if (aController) { 1.190 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.191 + } 1.192 + return; 1.193 + } 1.194 + 1.195 + if (!mA2dpConnected) { 1.196 + if (aController) { 1.197 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED)); 1.198 + } 1.199 + return; 1.200 + } 1.201 + 1.202 + MOZ_ASSERT(!mDeviceAddress.IsEmpty()); 1.203 + MOZ_ASSERT(!mController); 1.204 + 1.205 + mController = aController; 1.206 + 1.207 + if (NS_FAILED(bs->SendSinkMessage(mDeviceAddress, 1.208 + NS_LITERAL_STRING("Disconnect")))) { 1.209 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.210 + return; 1.211 + } 1.212 +} 1.213 + 1.214 +void 1.215 +BluetoothA2dpManager::OnConnect(const nsAString& aErrorStr) 1.216 +{ 1.217 + MOZ_ASSERT(NS_IsMainThread()); 1.218 + 1.219 + /** 1.220 + * On the one hand, notify the controller that we've done for outbound 1.221 + * connections. On the other hand, we do nothing for inbound connections. 1.222 + */ 1.223 + NS_ENSURE_TRUE_VOID(mController); 1.224 + 1.225 + nsRefPtr<BluetoothProfileController> controller = mController.forget(); 1.226 + controller->NotifyCompletion(aErrorStr); 1.227 +} 1.228 + 1.229 +void 1.230 +BluetoothA2dpManager::OnDisconnect(const nsAString& aErrorStr) 1.231 +{ 1.232 + MOZ_ASSERT(NS_IsMainThread()); 1.233 + 1.234 + /** 1.235 + * On the one hand, notify the controller that we've done for outbound 1.236 + * connections. On the other hand, we do nothing for inbound connections. 1.237 + */ 1.238 + NS_ENSURE_TRUE_VOID(mController); 1.239 + 1.240 + nsRefPtr<BluetoothProfileController> controller = mController.forget(); 1.241 + controller->NotifyCompletion(aErrorStr); 1.242 + 1.243 + Reset(); 1.244 +} 1.245 + 1.246 +/* HandleSinkPropertyChanged update sink state in A2dp 1.247 + * 1.248 + * Possible values: "disconnected", "connecting", "connected", "playing" 1.249 + * 1.250 + * 1. "disconnected" -> "connecting" 1.251 + * Either an incoming or outgoing connection attempt ongoing 1.252 + * 2. "connecting" -> "disconnected" 1.253 + * Connection attempt failed 1.254 + * 3. "connecting" -> "connected" 1.255 + * Successfully connected 1.256 + * 4. "connected" -> "playing" 1.257 + * Audio stream active 1.258 + * 5. "playing" -> "connected" 1.259 + * Audio stream suspended 1.260 + * 6. "connected" -> "disconnected" 1.261 + * "playing" -> "disconnected" 1.262 + * Disconnected from local or the remote device 1.263 + */ 1.264 +void 1.265 +BluetoothA2dpManager::HandleSinkPropertyChanged(const BluetoothSignal& aSignal) 1.266 +{ 1.267 + MOZ_ASSERT(NS_IsMainThread()); 1.268 + MOZ_ASSERT(aSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue); 1.269 + 1.270 + const nsString& address = aSignal.path(); 1.271 + /** 1.272 + * Update sink property only if 1.273 + * - mDeviceAddress is empty (A2dp is disconnected), or 1.274 + * - this property change is from the connected sink. 1.275 + */ 1.276 + NS_ENSURE_TRUE_VOID(mDeviceAddress.IsEmpty() || 1.277 + mDeviceAddress.Equals(address)); 1.278 + 1.279 + const InfallibleTArray<BluetoothNamedValue>& arr = 1.280 + aSignal.value().get_ArrayOfBluetoothNamedValue(); 1.281 + MOZ_ASSERT(arr.Length() == 1); 1.282 + 1.283 + /** 1.284 + * There are three properties: 1.285 + * - "State": a string 1.286 + * - "Connected": a boolean value 1.287 + * - "Playing": a boolean value 1.288 + * 1.289 + * Note that only "State" is handled in this function. 1.290 + */ 1.291 + 1.292 + const nsString& name = arr[0].name(); 1.293 + NS_ENSURE_TRUE_VOID(name.EqualsLiteral("State")); 1.294 + 1.295 + const BluetoothValue& value = arr[0].value(); 1.296 + MOZ_ASSERT(value.type() == BluetoothValue::TnsString); 1.297 + SinkState newState = StatusStringToSinkState(value.get_nsString()); 1.298 + NS_ENSURE_TRUE_VOID((newState != SinkState::SINK_UNKNOWN) && 1.299 + (newState != mSinkState)); 1.300 + 1.301 + /** 1.302 + * Reject 'connected' state change if bluetooth is already disabled. 1.303 + * Sink state would be reset to 'disconnected' when bluetooth is disabled. 1.304 + * 1.305 + * See bug 984284 for more information about the edge case. 1.306 + */ 1.307 + NS_ENSURE_FALSE_VOID(newState == SinkState::SINK_CONNECTED && 1.308 + mSinkState == SinkState::SINK_DISCONNECTED); 1.309 + 1.310 + SinkState prevState = mSinkState; 1.311 + mSinkState = newState; 1.312 + 1.313 + switch(mSinkState) { 1.314 + case SinkState::SINK_CONNECTING: 1.315 + // case 1: Either an incoming or outgoing connection attempt ongoing 1.316 + MOZ_ASSERT(prevState == SinkState::SINK_DISCONNECTED); 1.317 + break; 1.318 + case SinkState::SINK_PLAYING: 1.319 + // case 4: Audio stream active 1.320 + MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED); 1.321 + break; 1.322 + case SinkState::SINK_CONNECTED: 1.323 + // case 5: Audio stream suspended 1.324 + if (prevState == SinkState::SINK_PLAYING) { 1.325 + break; 1.326 + } 1.327 + 1.328 + // case 3: Successfully connected 1.329 + MOZ_ASSERT(prevState == SinkState::SINK_CONNECTING); 1.330 + 1.331 + mA2dpConnected = true; 1.332 + mDeviceAddress = address; 1.333 + NotifyConnectionStatusChanged(); 1.334 + 1.335 + OnConnect(EmptyString()); 1.336 + break; 1.337 + case SinkState::SINK_DISCONNECTED: 1.338 + // case 2: Connection attempt failed 1.339 + if (prevState == SinkState::SINK_CONNECTING) { 1.340 + OnConnect(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); 1.341 + break; 1.342 + } 1.343 + 1.344 + // case 6: Disconnected from the remote device 1.345 + MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED || 1.346 + prevState == SinkState::SINK_PLAYING); 1.347 + 1.348 + mA2dpConnected = false; 1.349 + NotifyConnectionStatusChanged(); 1.350 + mDeviceAddress.Truncate(); 1.351 + OnDisconnect(EmptyString()); 1.352 + break; 1.353 + default: 1.354 + break; 1.355 + } 1.356 +} 1.357 + 1.358 +void 1.359 +BluetoothA2dpManager::NotifyConnectionStatusChanged() 1.360 +{ 1.361 + MOZ_ASSERT(NS_IsMainThread()); 1.362 + 1.363 + // Notify Gecko observers 1.364 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.365 + NS_ENSURE_TRUE_VOID(obs); 1.366 + 1.367 + if (NS_FAILED(obs->NotifyObservers(this, 1.368 + BLUETOOTH_A2DP_STATUS_CHANGED_ID, 1.369 + mDeviceAddress.get()))) { 1.370 + BT_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!"); 1.371 + } 1.372 + 1.373 + // Dispatch an event of status change 1.374 + DispatchStatusChangedEvent( 1.375 + NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected); 1.376 +} 1.377 + 1.378 +void 1.379 +BluetoothA2dpManager::OnGetServiceChannel(const nsAString& aDeviceAddress, 1.380 + const nsAString& aServiceUuid, 1.381 + int aChannel) 1.382 +{ 1.383 +} 1.384 + 1.385 +void 1.386 +BluetoothA2dpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) 1.387 +{ 1.388 +} 1.389 + 1.390 +void 1.391 +BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress) 1.392 +{ 1.393 + aDeviceAddress = mDeviceAddress; 1.394 +} 1.395 + 1.396 +bool 1.397 +BluetoothA2dpManager::IsConnected() 1.398 +{ 1.399 + return mA2dpConnected; 1.400 +} 1.401 + 1.402 +void 1.403 +BluetoothA2dpManager::SetAvrcpConnected(bool aConnected) 1.404 +{ 1.405 + mAvrcpConnected = aConnected; 1.406 + if (!aConnected) { 1.407 + ResetAvrcp(); 1.408 + } 1.409 +} 1.410 + 1.411 +bool 1.412 +BluetoothA2dpManager::IsAvrcpConnected() 1.413 +{ 1.414 + return mAvrcpConnected; 1.415 +} 1.416 + 1.417 +void 1.418 +BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle, 1.419 + const nsAString& aArtist, 1.420 + const nsAString& aAlbum, 1.421 + uint64_t aMediaNumber, 1.422 + uint64_t aTotalMediaCount, 1.423 + uint32_t aDuration) 1.424 +{ 1.425 + mTitle.Assign(aTitle); 1.426 + mArtist.Assign(aArtist); 1.427 + mAlbum.Assign(aAlbum); 1.428 + mMediaNumber = aMediaNumber; 1.429 + mTotalMediaCount = aTotalMediaCount; 1.430 + mDuration = aDuration; 1.431 +} 1.432 + 1.433 +void 1.434 +BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration, 1.435 + uint32_t aPosition, 1.436 + ControlPlayStatus aPlayStatus) 1.437 +{ 1.438 + mDuration = aDuration; 1.439 + mPosition = aPosition; 1.440 + mPlayStatus = aPlayStatus; 1.441 +} 1.442 + 1.443 +void 1.444 +BluetoothA2dpManager::GetAlbum(nsAString& aAlbum) 1.445 +{ 1.446 + aAlbum.Assign(mAlbum); 1.447 +} 1.448 + 1.449 +uint32_t 1.450 +BluetoothA2dpManager::GetDuration() 1.451 +{ 1.452 + return mDuration; 1.453 +} 1.454 + 1.455 +ControlPlayStatus 1.456 +BluetoothA2dpManager::GetPlayStatus() 1.457 +{ 1.458 + return mPlayStatus; 1.459 +} 1.460 + 1.461 +uint32_t 1.462 +BluetoothA2dpManager::GetPosition() 1.463 +{ 1.464 + return mPosition; 1.465 +} 1.466 + 1.467 +uint64_t 1.468 +BluetoothA2dpManager::GetMediaNumber() 1.469 +{ 1.470 + return mMediaNumber; 1.471 +} 1.472 + 1.473 +void 1.474 +BluetoothA2dpManager::GetTitle(nsAString& aTitle) 1.475 +{ 1.476 + aTitle.Assign(mTitle); 1.477 +} 1.478 + 1.479 +NS_IMPL_ISUPPORTS(BluetoothA2dpManager, nsIObserver) 1.480 +