1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1106 @@ 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 <hardware/bluetooth.h> 1.15 +#include <hardware/bt_av.h> 1.16 +#if ANDROID_VERSION > 17 1.17 +#include <hardware/bt_rc.h> 1.18 +#endif 1.19 + 1.20 +#include "BluetoothCommon.h" 1.21 +#include "BluetoothService.h" 1.22 +#include "BluetoothSocket.h" 1.23 +#include "BluetoothUtils.h" 1.24 + 1.25 +#include "mozilla/dom/bluetooth/BluetoothTypes.h" 1.26 +#include "mozilla/Services.h" 1.27 +#include "mozilla/StaticPtr.h" 1.28 +#include "MainThreadUtils.h" 1.29 +#include "nsIObserverService.h" 1.30 +#include "nsThreadUtils.h" 1.31 + 1.32 +using namespace mozilla; 1.33 +USING_BLUETOOTH_NAMESPACE 1.34 +// AVRC_ID op code follows bluedroid avrc_defs.h 1.35 +#define AVRC_ID_REWIND 0x48 1.36 +#define AVRC_ID_FAST_FOR 0x49 1.37 +#define AVRC_KEY_PRESS_STATE 1 1.38 +#define AVRC_KEY_RELEASE_STATE 0 1.39 + 1.40 +namespace { 1.41 + StaticRefPtr<BluetoothA2dpManager> sBluetoothA2dpManager; 1.42 + bool sInShutdown = false; 1.43 + static const btav_interface_t* sBtA2dpInterface; 1.44 +#if ANDROID_VERSION > 17 1.45 + static const btrc_interface_t* sBtAvrcpInterface; 1.46 +#endif 1.47 +} // anonymous namespace 1.48 + 1.49 +class SinkPropertyChangedHandler : public nsRunnable 1.50 +{ 1.51 +public: 1.52 + SinkPropertyChangedHandler(const BluetoothSignal& aSignal) 1.53 + : mSignal(aSignal) 1.54 + { 1.55 + } 1.56 + 1.57 + NS_IMETHOD 1.58 + Run() 1.59 + { 1.60 + MOZ_ASSERT(NS_IsMainThread()); 1.61 + 1.62 + BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); 1.63 + NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE); 1.64 + a2dp->HandleSinkPropertyChanged(mSignal); 1.65 + 1.66 + return NS_OK; 1.67 + } 1.68 + 1.69 +private: 1.70 + BluetoothSignal mSignal; 1.71 +}; 1.72 + 1.73 +class RequestPlayStatusTask : public nsRunnable 1.74 +{ 1.75 +public: 1.76 + RequestPlayStatusTask() 1.77 + { 1.78 + MOZ_ASSERT(!NS_IsMainThread()); 1.79 + } 1.80 + 1.81 + nsresult Run() 1.82 + { 1.83 + MOZ_ASSERT(NS_IsMainThread()); 1.84 + 1.85 + BluetoothSignal signal(NS_LITERAL_STRING(REQUEST_MEDIA_PLAYSTATUS_ID), 1.86 + NS_LITERAL_STRING(KEY_ADAPTER), 1.87 + InfallibleTArray<BluetoothNamedValue>()); 1.88 + 1.89 + BluetoothService* bs = BluetoothService::Get(); 1.90 + NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE); 1.91 + bs->DistributeSignal(signal); 1.92 + 1.93 + return NS_OK; 1.94 + } 1.95 +}; 1.96 + 1.97 +#if ANDROID_VERSION > 17 1.98 +class UpdateRegisterNotificationTask : public nsRunnable 1.99 +{ 1.100 +public: 1.101 + UpdateRegisterNotificationTask(btrc_event_id_t aEventId, uint32_t aParam) 1.102 + : mEventId(aEventId) 1.103 + , mParam(aParam) 1.104 + { 1.105 + MOZ_ASSERT(!NS_IsMainThread()); 1.106 + } 1.107 + 1.108 + nsresult Run() 1.109 + { 1.110 + MOZ_ASSERT(NS_IsMainThread()); 1.111 + 1.112 + BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); 1.113 + NS_ENSURE_TRUE(a2dp, NS_OK); 1.114 + a2dp->UpdateRegisterNotification(mEventId, mParam); 1.115 + return NS_OK; 1.116 + } 1.117 +private: 1.118 + btrc_event_id_t mEventId; 1.119 + uint32_t mParam; 1.120 +}; 1.121 + 1.122 +/* 1.123 + * This function maps attribute id and returns corresponding values 1.124 + * Attribute id refers to btrc_media_attr_t in bt_rc.h 1.125 + */ 1.126 +static void 1.127 +ConvertAttributeString(int aAttrId, nsAString& aAttrStr) 1.128 +{ 1.129 + BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); 1.130 + NS_ENSURE_TRUE_VOID(a2dp); 1.131 + 1.132 + switch (aAttrId) { 1.133 + case BTRC_MEDIA_ATTR_TITLE: 1.134 + a2dp->GetTitle(aAttrStr); 1.135 + break; 1.136 + case BTRC_MEDIA_ATTR_ARTIST: 1.137 + a2dp->GetArtist(aAttrStr); 1.138 + break; 1.139 + case BTRC_MEDIA_ATTR_ALBUM: 1.140 + a2dp->GetAlbum(aAttrStr); 1.141 + break; 1.142 + case BTRC_MEDIA_ATTR_TRACK_NUM: 1.143 + aAttrStr.AppendInt(a2dp->GetMediaNumber()); 1.144 + break; 1.145 + case BTRC_MEDIA_ATTR_NUM_TRACKS: 1.146 + aAttrStr.AppendInt(a2dp->GetTotalMediaNumber()); 1.147 + break; 1.148 + case BTRC_MEDIA_ATTR_GENRE: 1.149 + // TODO: we currently don't support genre from music player 1.150 + aAttrStr.Truncate(); 1.151 + break; 1.152 + case BTRC_MEDIA_ATTR_PLAYING_TIME: 1.153 + aAttrStr.AppendInt(a2dp->GetDuration()); 1.154 + break; 1.155 + } 1.156 +} 1.157 + 1.158 +class UpdateElementAttrsTask : public nsRunnable 1.159 +{ 1.160 +public: 1.161 + UpdateElementAttrsTask(uint8_t aNumAttr, btrc_media_attr_t* aPlayerAttrs) 1.162 + : mNumAttr(aNumAttr) 1.163 + , mPlayerAttrs(aPlayerAttrs) 1.164 + { 1.165 + MOZ_ASSERT(!NS_IsMainThread()); 1.166 + } 1.167 + 1.168 + nsresult Run() 1.169 + { 1.170 + MOZ_ASSERT(NS_IsMainThread()); 1.171 + 1.172 + btrc_element_attr_val_t* attrs = new btrc_element_attr_val_t[mNumAttr]; 1.173 + for (int i = 0; i < mNumAttr; i++) { 1.174 + nsAutoString attrText; 1.175 + attrs[i].attr_id = mPlayerAttrs[i]; 1.176 + ConvertAttributeString(mPlayerAttrs[i], attrText); 1.177 + strcpy((char *)attrs[i].text, NS_ConvertUTF16toUTF8(attrText).get()); 1.178 + } 1.179 + 1.180 + NS_ENSURE_TRUE(sBtAvrcpInterface, NS_OK); 1.181 + sBtAvrcpInterface->get_element_attr_rsp(mNumAttr, attrs); 1.182 + 1.183 + return NS_OK; 1.184 + } 1.185 +private: 1.186 + uint8_t mNumAttr; 1.187 + btrc_media_attr_t* mPlayerAttrs; 1.188 +}; 1.189 + 1.190 +class UpdatePassthroughCmdTask : public nsRunnable 1.191 +{ 1.192 +public: 1.193 + UpdatePassthroughCmdTask(const nsAString& aName) 1.194 + : mName(aName) 1.195 + { 1.196 + MOZ_ASSERT(!NS_IsMainThread()); 1.197 + } 1.198 + 1.199 + nsresult Run() 1.200 + { 1.201 + MOZ_ASSERT(NS_IsMainThread()); 1.202 + 1.203 + NS_NAMED_LITERAL_STRING(type, "media-button"); 1.204 + BroadcastSystemMessage(type, BluetoothValue(mName)); 1.205 + 1.206 + return NS_OK; 1.207 + } 1.208 +private: 1.209 + nsString mName; 1.210 +}; 1.211 + 1.212 +#endif 1.213 + 1.214 +NS_IMETHODIMP 1.215 +BluetoothA2dpManager::Observe(nsISupports* aSubject, 1.216 + const char* aTopic, 1.217 + const char16_t* aData) 1.218 +{ 1.219 + MOZ_ASSERT(sBluetoothA2dpManager); 1.220 + 1.221 + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 1.222 + HandleShutdown(); 1.223 + return NS_OK; 1.224 + } 1.225 + 1.226 + MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!"); 1.227 + return NS_ERROR_UNEXPECTED; 1.228 +} 1.229 + 1.230 +BluetoothA2dpManager::BluetoothA2dpManager() 1.231 +{ 1.232 + Reset(); 1.233 +} 1.234 + 1.235 +void 1.236 +BluetoothA2dpManager::Reset() 1.237 +{ 1.238 + ResetA2dp(); 1.239 + ResetAvrcp(); 1.240 +} 1.241 + 1.242 +static void 1.243 +AvStatusToSinkString(btav_connection_state_t aStatus, nsAString& aState) 1.244 +{ 1.245 + nsAutoString state; 1.246 + if (aStatus == BTAV_CONNECTION_STATE_DISCONNECTED) { 1.247 + aState = NS_LITERAL_STRING("disconnected"); 1.248 + } else if (aStatus == BTAV_CONNECTION_STATE_CONNECTING) { 1.249 + aState = NS_LITERAL_STRING("connecting"); 1.250 + } else if (aStatus == BTAV_CONNECTION_STATE_CONNECTED) { 1.251 + aState = NS_LITERAL_STRING("connected"); 1.252 + } else if (aStatus == BTAV_CONNECTION_STATE_DISCONNECTING) { 1.253 + aState = NS_LITERAL_STRING("disconnecting"); 1.254 + } else { 1.255 + BT_WARNING("Unknown sink state"); 1.256 + } 1.257 +} 1.258 + 1.259 +static void 1.260 +A2dpConnectionStateCallback(btav_connection_state_t aState, 1.261 + bt_bdaddr_t* aBdAddress) 1.262 +{ 1.263 + MOZ_ASSERT(!NS_IsMainThread()); 1.264 + 1.265 + nsString remoteDeviceBdAddress; 1.266 + BdAddressTypeToString(aBdAddress, remoteDeviceBdAddress); 1.267 + 1.268 + nsString a2dpState; 1.269 + AvStatusToSinkString(aState, a2dpState); 1.270 + 1.271 + InfallibleTArray<BluetoothNamedValue> props; 1.272 + BT_APPEND_NAMED_VALUE(props, "State", a2dpState); 1.273 + 1.274 + BluetoothSignal signal(NS_LITERAL_STRING("AudioSink"), 1.275 + remoteDeviceBdAddress, props); 1.276 + NS_DispatchToMainThread(new SinkPropertyChangedHandler(signal)); 1.277 +} 1.278 + 1.279 +static void 1.280 +A2dpAudioStateCallback(btav_audio_state_t aState, 1.281 + bt_bdaddr_t* aBdAddress) 1.282 +{ 1.283 + MOZ_ASSERT(!NS_IsMainThread()); 1.284 + 1.285 + nsString remoteDeviceBdAddress; 1.286 + BdAddressTypeToString(aBdAddress, remoteDeviceBdAddress); 1.287 + 1.288 + nsString a2dpState; 1.289 + 1.290 + if (aState == BTAV_AUDIO_STATE_STARTED) { 1.291 + a2dpState = NS_LITERAL_STRING("playing"); 1.292 + } else if (aState == BTAV_AUDIO_STATE_STOPPED) { 1.293 + // for avdtp state stop stream 1.294 + a2dpState = NS_LITERAL_STRING("connected"); 1.295 + } else if (aState == BTAV_AUDIO_STATE_REMOTE_SUSPEND) { 1.296 + // for avdtp state suspend stream from remote side 1.297 + a2dpState = NS_LITERAL_STRING("connected"); 1.298 + } 1.299 + 1.300 + InfallibleTArray<BluetoothNamedValue> props; 1.301 + BT_APPEND_NAMED_VALUE(props, "State", a2dpState); 1.302 + 1.303 + BluetoothSignal signal(NS_LITERAL_STRING("AudioSink"), 1.304 + remoteDeviceBdAddress, props); 1.305 + NS_DispatchToMainThread(new SinkPropertyChangedHandler(signal)); 1.306 +} 1.307 + 1.308 +#if ANDROID_VERSION > 17 1.309 +/* 1.310 + * Avrcp 1.3 callbacks 1.311 + */ 1.312 + 1.313 +/* 1.314 + * This function is to request Gaia player application to update 1.315 + * current play status. 1.316 + * Callback for play status request 1.317 + */ 1.318 +static void 1.319 +AvrcpGetPlayStatusCallback() 1.320 +{ 1.321 + MOZ_ASSERT(!NS_IsMainThread()); 1.322 + 1.323 + NS_DispatchToMainThread(new RequestPlayStatusTask()); 1.324 +} 1.325 + 1.326 +/* 1.327 + * This function is trying to get element attributes, which request from CT 1.328 + * Unlike BlueZ only calls UpdateMetaData, bluedroid does not cache meta data 1.329 + * information, but instead uses callback AvrcpGetElementAttrCallback and 1.330 + * call get_element_attr_rsp() to reply request. 1.331 + * 1.332 + * Callback to fetch the get element attributes of the current song 1.333 + * aNumAttr: It represents the number of attributes requested in aPlayerAttrs 1.334 + * aPlayerAttrs: It represents Attribute Ids 1.335 + */ 1.336 +static void 1.337 +AvrcpGetElementAttrCallback(uint8_t aNumAttr, btrc_media_attr_t* aPlayerAttrs) 1.338 +{ 1.339 + MOZ_ASSERT(!NS_IsMainThread()); 1.340 + 1.341 + NS_DispatchToMainThread(new UpdateElementAttrsTask(aNumAttr, aPlayerAttrs)); 1.342 +} 1.343 + 1.344 +/* 1.345 + * Callback for register notification (Play state change/track change/...) 1.346 + * To reply RegisterNotification INTERIM response 1.347 + * See AVRCP 1.3 Spec 25.2 1.348 + * aParam: It only valids if event_id is BTRC_EVT_PLAY_POS_CHANGED, 1.349 + * which is playback interval time 1.350 + */ 1.351 +static void 1.352 +AvrcpRegisterNotificationCallback(btrc_event_id_t aEventId, uint32_t aParam) 1.353 +{ 1.354 + MOZ_ASSERT(!NS_IsMainThread()); 1.355 + 1.356 + NS_DispatchToMainThread(new UpdateRegisterNotificationTask(aEventId, aParam)); 1.357 +} 1.358 + 1.359 +/* 1.360 + * Player application settings is optional for Avrcp 1.3 1.361 + * B2G 1.3 currently does not support Player application setting 1.362 + * related functions. Support Player Setting in the future version 1.363 + */ 1.364 +static void 1.365 +AvrcpListPlayerAppAttributeCallback() 1.366 +{ 1.367 + MOZ_ASSERT(!NS_IsMainThread()); 1.368 + 1.369 +// TODO: Support avrcp application setting related functions 1.370 +} 1.371 + 1.372 +static void 1.373 +AvrcpListPlayerAppValuesCallback(btrc_player_attr_t aAttrId) 1.374 +{ 1.375 + MOZ_ASSERT(!NS_IsMainThread()); 1.376 + 1.377 +// TODO: Support avrcp application setting related functions 1.378 +} 1.379 + 1.380 +static void 1.381 +AvrcpGetPlayerAppValueCallback(uint8_t aNumAttr, 1.382 + btrc_player_attr_t* aPlayerAttrs) 1.383 +{ 1.384 + MOZ_ASSERT(!NS_IsMainThread()); 1.385 + 1.386 +// TODO: Support avrcp application setting related functions 1.387 +} 1.388 + 1.389 +static void 1.390 +AvrcpGetPlayerAppAttrsTextCallback(uint8_t aNumAttr, 1.391 + btrc_player_attr_t* PlayerAttrs) 1.392 +{ 1.393 + MOZ_ASSERT(!NS_IsMainThread()); 1.394 + 1.395 +// TODO: Support avrcp application setting related functions 1.396 +} 1.397 + 1.398 +static void 1.399 +AvrcpGetPlayerAppValuesTextCallback(uint8_t aAttrId, uint8_t aNumVal, 1.400 + uint8_t* PlayerVals) 1.401 +{ 1.402 + MOZ_ASSERT(!NS_IsMainThread()); 1.403 + 1.404 +// TODO: Support avrcp application setting related functions 1.405 +} 1.406 + 1.407 +static void 1.408 +AvrcpSetPlayerAppValueCallback(btrc_player_settings_t* aPlayerVals) 1.409 +{ 1.410 + MOZ_ASSERT(!NS_IsMainThread()); 1.411 + 1.412 +// TODO: Support avrcp application setting related functions 1.413 +} 1.414 +#endif 1.415 + 1.416 +#if ANDROID_VERSION > 18 1.417 +/* 1.418 + * This callback function is to get CT features from Feature Bit Mask. 1.419 + * If Advanced Control Player bit is set, CT supports 1.420 + * volume sync (absolute volume feature). If Browsing bit is set, Avrcp 1.4 1.421 + * Browse feature will be supported 1.422 + */ 1.423 +static void 1.424 +AvrcpRemoteFeaturesCallback(bt_bdaddr_t* aBdAddress, 1.425 + btrc_remote_features_t aFeatures) 1.426 +{ 1.427 +// TODO: Support avrcp 1.4 absolute volume/browse 1.428 +} 1.429 + 1.430 +/* 1.431 + * This callback function is to get notification that volume changed on the 1.432 + * remote car kit (if it supports Avrcp 1.4), not notification from phone. 1.433 + */ 1.434 +static void 1.435 +AvrcpRemoteVolumeChangedCallback(uint8_t aVolume, uint8_t aCType) 1.436 +{ 1.437 +// TODO: Support avrcp 1.4 absolute volume/browse 1.438 +} 1.439 + 1.440 +/* 1.441 + * This callback function is to handle passthrough commands. 1.442 + */ 1.443 +static void 1.444 +AvrcpPassThroughCallback(int aId, int aKeyState) 1.445 +{ 1.446 + // Fast-forward and rewind key events won't be generated from bluedroid 1.447 + // stack after ANDROID_VERSION > 18, but via passthrough callback. 1.448 + nsAutoString name; 1.449 + NS_ENSURE_TRUE_VOID(aKeyState == AVRC_KEY_PRESS_STATE || 1.450 + aKeyState == AVRC_KEY_RELEASE_STATE); 1.451 + switch (aId) { 1.452 + case AVRC_ID_FAST_FOR: 1.453 + if (aKeyState == AVRC_KEY_PRESS_STATE) { 1.454 + name.AssignLiteral("media-fast-forward-button-press"); 1.455 + } else { 1.456 + name.AssignLiteral("media-fast-forward-button-release"); 1.457 + } 1.458 + break; 1.459 + case AVRC_ID_REWIND: 1.460 + if (aKeyState == AVRC_KEY_PRESS_STATE) { 1.461 + name.AssignLiteral("media-rewind-button-press"); 1.462 + } else { 1.463 + name.AssignLiteral("media-rewind-button-release"); 1.464 + } 1.465 + break; 1.466 + default: 1.467 + BT_WARNING("Unable to handle the unknown PassThrough command %d", aId); 1.468 + break; 1.469 + } 1.470 + if (!name.IsEmpty()) { 1.471 + NS_DispatchToMainThread(new UpdatePassthroughCmdTask(name)); 1.472 + } 1.473 +} 1.474 +#endif 1.475 + 1.476 +static btav_callbacks_t sBtA2dpCallbacks = { 1.477 + sizeof(sBtA2dpCallbacks), 1.478 + A2dpConnectionStateCallback, 1.479 + A2dpAudioStateCallback 1.480 +}; 1.481 + 1.482 +#if ANDROID_VERSION > 17 1.483 +static btrc_callbacks_t sBtAvrcpCallbacks = { 1.484 + sizeof(sBtAvrcpCallbacks), 1.485 +#if ANDROID_VERSION > 18 1.486 + AvrcpRemoteFeaturesCallback, 1.487 +#endif 1.488 + AvrcpGetPlayStatusCallback, 1.489 + AvrcpListPlayerAppAttributeCallback, 1.490 + AvrcpListPlayerAppValuesCallback, 1.491 + AvrcpGetPlayerAppValueCallback, 1.492 + AvrcpGetPlayerAppAttrsTextCallback, 1.493 + AvrcpGetPlayerAppValuesTextCallback, 1.494 + AvrcpSetPlayerAppValueCallback, 1.495 + AvrcpGetElementAttrCallback, 1.496 + AvrcpRegisterNotificationCallback, 1.497 +#if ANDROID_VERSION > 18 1.498 + AvrcpRemoteVolumeChangedCallback, 1.499 + AvrcpPassThroughCallback 1.500 +#endif 1.501 +}; 1.502 +#endif 1.503 + 1.504 +/* 1.505 + * This function will be only called when Bluetooth is turning on. 1.506 + * It is important to register a2dp callbacks before enable() gets called. 1.507 + * It is required to register a2dp callbacks before a2dp media task 1.508 + * starts up. 1.509 + */ 1.510 +bool 1.511 +BluetoothA2dpManager::Init() 1.512 +{ 1.513 + const bt_interface_t* btInf = GetBluetoothInterface(); 1.514 + NS_ENSURE_TRUE(btInf, false); 1.515 + 1.516 + sBtA2dpInterface = (btav_interface_t *)btInf-> 1.517 + get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID); 1.518 + NS_ENSURE_TRUE(sBtA2dpInterface, false); 1.519 + 1.520 + int ret = sBtA2dpInterface->init(&sBtA2dpCallbacks); 1.521 + if (ret != BT_STATUS_SUCCESS) { 1.522 + BT_LOGR("Warning: failed to init a2dp module"); 1.523 + return false; 1.524 + } 1.525 + 1.526 +#if ANDROID_VERSION > 17 1.527 + sBtAvrcpInterface = (btrc_interface_t *)btInf-> 1.528 + get_profile_interface(BT_PROFILE_AV_RC_ID); 1.529 + NS_ENSURE_TRUE(sBtAvrcpInterface, false); 1.530 + 1.531 + ret = sBtAvrcpInterface->init(&sBtAvrcpCallbacks); 1.532 + if (ret != BT_STATUS_SUCCESS) { 1.533 + BT_LOGR("Warning: failed to init avrcp module"); 1.534 + return false; 1.535 + } 1.536 +#endif 1.537 + 1.538 + return true; 1.539 +} 1.540 + 1.541 +BluetoothA2dpManager::~BluetoothA2dpManager() 1.542 +{ 1.543 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.544 + NS_ENSURE_TRUE_VOID(obs); 1.545 + if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { 1.546 + BT_WARNING("Failed to remove shutdown observer!"); 1.547 + } 1.548 +} 1.549 + 1.550 +void 1.551 +BluetoothA2dpManager::ResetA2dp() 1.552 +{ 1.553 + mA2dpConnected = false; 1.554 + mSinkState = SinkState::SINK_DISCONNECTED; 1.555 + mController = nullptr; 1.556 +} 1.557 + 1.558 +void 1.559 +BluetoothA2dpManager::ResetAvrcp() 1.560 +{ 1.561 + mAvrcpConnected = false; 1.562 + mDuration = 0; 1.563 + mMediaNumber = 0; 1.564 + mTotalMediaCount = 0; 1.565 + mPosition = 0; 1.566 + mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN; 1.567 +} 1.568 + 1.569 +/* 1.570 + * Static functions 1.571 + */ 1.572 + 1.573 +static BluetoothA2dpManager::SinkState 1.574 +StatusStringToSinkState(const nsAString& aStatus) 1.575 +{ 1.576 + BluetoothA2dpManager::SinkState state = 1.577 + BluetoothA2dpManager::SinkState::SINK_UNKNOWN; 1.578 + if (aStatus.EqualsLiteral("disconnected")) { 1.579 + state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED; 1.580 + } else if (aStatus.EqualsLiteral("connecting")) { 1.581 + state = BluetoothA2dpManager::SinkState::SINK_CONNECTING; 1.582 + } else if (aStatus.EqualsLiteral("connected")) { 1.583 + state = BluetoothA2dpManager::SinkState::SINK_CONNECTED; 1.584 + } else if (aStatus.EqualsLiteral("playing")) { 1.585 + state = BluetoothA2dpManager::SinkState::SINK_PLAYING; 1.586 + } else { 1.587 + BT_WARNING("Unknown sink state"); 1.588 + } 1.589 + return state; 1.590 +} 1.591 + 1.592 +//static 1.593 +BluetoothA2dpManager* 1.594 +BluetoothA2dpManager::Get() 1.595 +{ 1.596 + MOZ_ASSERT(NS_IsMainThread()); 1.597 + 1.598 + // If sBluetoothA2dpManager already exists, exit early 1.599 + if (sBluetoothA2dpManager) { 1.600 + return sBluetoothA2dpManager; 1.601 + } 1.602 + 1.603 + // If we're in shutdown, don't create a new instance 1.604 + NS_ENSURE_FALSE(sInShutdown, nullptr); 1.605 + 1.606 + // Create a new instance, register, and return 1.607 + BluetoothA2dpManager* manager = new BluetoothA2dpManager(); 1.608 + NS_ENSURE_TRUE(manager->Init(), nullptr); 1.609 + 1.610 + sBluetoothA2dpManager = manager; 1.611 + return sBluetoothA2dpManager; 1.612 +} 1.613 + 1.614 +void 1.615 +BluetoothA2dpManager::HandleShutdown() 1.616 +{ 1.617 + MOZ_ASSERT(NS_IsMainThread()); 1.618 + sInShutdown = true; 1.619 + Disconnect(nullptr); 1.620 + sBluetoothA2dpManager = nullptr; 1.621 +} 1.622 + 1.623 +void 1.624 +BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress, 1.625 + BluetoothProfileController* aController) 1.626 +{ 1.627 + MOZ_ASSERT(NS_IsMainThread()); 1.628 + MOZ_ASSERT(!aDeviceAddress.IsEmpty()); 1.629 + MOZ_ASSERT(aController && !mController); 1.630 + 1.631 + BluetoothService* bs = BluetoothService::Get(); 1.632 + if (!bs || sInShutdown) { 1.633 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.634 + return; 1.635 + } 1.636 + 1.637 + if (mA2dpConnected) { 1.638 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED)); 1.639 + return; 1.640 + } 1.641 + 1.642 + mDeviceAddress = aDeviceAddress; 1.643 + mController = aController; 1.644 + 1.645 + if (!sBtA2dpInterface) { 1.646 + BT_LOGR("sBluetoothA2dpInterface is null"); 1.647 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.648 + return; 1.649 + } 1.650 + 1.651 + bt_bdaddr_t remoteAddress; 1.652 + StringToBdAddressType(aDeviceAddress, &remoteAddress); 1.653 + 1.654 + bt_status_t result = sBtA2dpInterface->connect(&remoteAddress); 1.655 + if (BT_STATUS_SUCCESS != result) { 1.656 + BT_LOGR("Failed to connect: %x", result); 1.657 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); 1.658 + return; 1.659 + } 1.660 +} 1.661 + 1.662 +void 1.663 +BluetoothA2dpManager::Disconnect(BluetoothProfileController* aController) 1.664 +{ 1.665 + MOZ_ASSERT(NS_IsMainThread()); 1.666 + MOZ_ASSERT(!mController); 1.667 + 1.668 + BluetoothService* bs = BluetoothService::Get(); 1.669 + if (!bs) { 1.670 + if (aController) { 1.671 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.672 + } 1.673 + return; 1.674 + } 1.675 + 1.676 + if (!mA2dpConnected) { 1.677 + if (aController) { 1.678 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED)); 1.679 + } 1.680 + return; 1.681 + } 1.682 + 1.683 + MOZ_ASSERT(!mDeviceAddress.IsEmpty()); 1.684 + 1.685 + mController = aController; 1.686 + 1.687 + if (!sBtA2dpInterface) { 1.688 + BT_LOGR("sBluetoothA2dpInterface is null"); 1.689 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.690 + return; 1.691 + } 1.692 + 1.693 + bt_bdaddr_t remoteAddress; 1.694 + StringToBdAddressType(mDeviceAddress, &remoteAddress); 1.695 + 1.696 + bt_status_t result = sBtA2dpInterface->disconnect(&remoteAddress); 1.697 + if (BT_STATUS_SUCCESS != result) { 1.698 + BT_LOGR("Failed to disconnect: %x", result); 1.699 + aController->NotifyCompletion(NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED)); 1.700 + return; 1.701 + } 1.702 +} 1.703 + 1.704 +void 1.705 +BluetoothA2dpManager::OnConnect(const nsAString& aErrorStr) 1.706 +{ 1.707 + MOZ_ASSERT(NS_IsMainThread()); 1.708 + 1.709 + /** 1.710 + * On the one hand, notify the controller that we've done for outbound 1.711 + * connections. On the other hand, we do nothing for inbound connections. 1.712 + */ 1.713 + NS_ENSURE_TRUE_VOID(mController); 1.714 + 1.715 + nsRefPtr<BluetoothProfileController> controller = mController.forget(); 1.716 + controller->NotifyCompletion(aErrorStr); 1.717 +} 1.718 + 1.719 +void 1.720 +BluetoothA2dpManager::OnDisconnect(const nsAString& aErrorStr) 1.721 +{ 1.722 + MOZ_ASSERT(NS_IsMainThread()); 1.723 + 1.724 + /** 1.725 + * On the one hand, notify the controller that we've done for outbound 1.726 + * connections. On the other hand, we do nothing for inbound connections. 1.727 + */ 1.728 + NS_ENSURE_TRUE_VOID(mController); 1.729 + 1.730 + nsRefPtr<BluetoothProfileController> controller = mController.forget(); 1.731 + controller->NotifyCompletion(aErrorStr); 1.732 + 1.733 + Reset(); 1.734 +} 1.735 + 1.736 +/* HandleSinkPropertyChanged update sink state in A2dp 1.737 + * 1.738 + * Possible values: "disconnected", "connecting", "connected", "playing" 1.739 + * 1.740 + * 1. "disconnected" -> "connecting" 1.741 + * Either an incoming or outgoing connection attempt ongoing 1.742 + * 2. "connecting" -> "disconnected" 1.743 + * Connection attempt failed 1.744 + * 3. "connecting" -> "connected" 1.745 + * Successfully connected 1.746 + * 4. "connected" -> "playing" 1.747 + * Audio stream active 1.748 + * 5. "playing" -> "connected" 1.749 + * Audio stream suspended 1.750 + * 6. "connected" -> "disconnected" 1.751 + * "playing" -> "disconnected" 1.752 + * Disconnected from local or the remote device 1.753 + */ 1.754 +void 1.755 +BluetoothA2dpManager::HandleSinkPropertyChanged(const BluetoothSignal& aSignal) 1.756 +{ 1.757 + MOZ_ASSERT(NS_IsMainThread()); 1.758 + MOZ_ASSERT(aSignal.value().type() == 1.759 + BluetoothValue::TArrayOfBluetoothNamedValue); 1.760 + 1.761 + const nsString& address = aSignal.path(); 1.762 + /** 1.763 + * Update sink property only if 1.764 + * - mDeviceAddress is empty (A2dp is disconnected), or 1.765 + * - this property change is from the connected sink. 1.766 + */ 1.767 + NS_ENSURE_TRUE_VOID(mDeviceAddress.IsEmpty() || 1.768 + mDeviceAddress.Equals(address)); 1.769 + 1.770 + const InfallibleTArray<BluetoothNamedValue>& arr = 1.771 + aSignal.value().get_ArrayOfBluetoothNamedValue(); 1.772 + MOZ_ASSERT(arr.Length() == 1); 1.773 + 1.774 + /** 1.775 + * There are three properties: 1.776 + * - "State": a string 1.777 + * - "Connected": a boolean value 1.778 + * - "Playing": a boolean value 1.779 + * 1.780 + * Note that only "State" is handled in this function. 1.781 + */ 1.782 + 1.783 + const nsString& name = arr[0].name(); 1.784 + NS_ENSURE_TRUE_VOID(name.EqualsLiteral("State")); 1.785 + 1.786 + const BluetoothValue& value = arr[0].value(); 1.787 + MOZ_ASSERT(value.type() == BluetoothValue::TnsString); 1.788 + SinkState newState = StatusStringToSinkState(value.get_nsString()); 1.789 + NS_ENSURE_TRUE_VOID((newState != SinkState::SINK_UNKNOWN) && 1.790 + (newState != mSinkState)); 1.791 + 1.792 + SinkState prevState = mSinkState; 1.793 + mSinkState = newState; 1.794 + 1.795 + switch(mSinkState) { 1.796 + case SinkState::SINK_CONNECTING: 1.797 + // case 1: Either an incoming or outgoing connection attempt ongoing 1.798 + MOZ_ASSERT(prevState == SinkState::SINK_DISCONNECTED); 1.799 + break; 1.800 + case SinkState::SINK_PLAYING: 1.801 + // case 4: Audio stream active 1.802 + MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED); 1.803 + break; 1.804 + case SinkState::SINK_CONNECTED: 1.805 + // case 5: Audio stream suspended 1.806 + if (prevState == SinkState::SINK_PLAYING || 1.807 + prevState == SinkState::SINK_CONNECTED) { 1.808 + break; 1.809 + } 1.810 + 1.811 + // case 3: Successfully connected 1.812 + mA2dpConnected = true; 1.813 + mDeviceAddress = address; 1.814 + NotifyConnectionStatusChanged(); 1.815 + 1.816 + OnConnect(EmptyString()); 1.817 + break; 1.818 + case SinkState::SINK_DISCONNECTED: 1.819 + // case 2: Connection attempt failed 1.820 + if (prevState == SinkState::SINK_CONNECTING) { 1.821 + OnConnect(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); 1.822 + break; 1.823 + } 1.824 + 1.825 + // case 6: Disconnected from the remote device 1.826 + MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED || 1.827 + prevState == SinkState::SINK_PLAYING) ; 1.828 + 1.829 + mA2dpConnected = false; 1.830 + NotifyConnectionStatusChanged(); 1.831 + mDeviceAddress.Truncate(); 1.832 + OnDisconnect(EmptyString()); 1.833 + break; 1.834 + default: 1.835 + break; 1.836 + } 1.837 +} 1.838 + 1.839 +void 1.840 +BluetoothA2dpManager::NotifyConnectionStatusChanged() 1.841 +{ 1.842 + MOZ_ASSERT(NS_IsMainThread()); 1.843 + 1.844 + // Notify Gecko observers 1.845 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.846 + NS_ENSURE_TRUE_VOID(obs); 1.847 + 1.848 + if (NS_FAILED(obs->NotifyObservers(this, 1.849 + BLUETOOTH_A2DP_STATUS_CHANGED_ID, 1.850 + mDeviceAddress.get()))) { 1.851 + BT_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!"); 1.852 + } 1.853 + 1.854 + // Dispatch an event of status change 1.855 + DispatchStatusChangedEvent( 1.856 + NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected); 1.857 +} 1.858 + 1.859 +void 1.860 +BluetoothA2dpManager::OnGetServiceChannel(const nsAString& aDeviceAddress, 1.861 + const nsAString& aServiceUuid, 1.862 + int aChannel) 1.863 +{ 1.864 +} 1.865 + 1.866 +void 1.867 +BluetoothA2dpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) 1.868 +{ 1.869 +} 1.870 + 1.871 +void 1.872 +BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress) 1.873 +{ 1.874 + aDeviceAddress = mDeviceAddress; 1.875 +} 1.876 + 1.877 +bool 1.878 +BluetoothA2dpManager::IsConnected() 1.879 +{ 1.880 + return mA2dpConnected; 1.881 +} 1.882 + 1.883 +/* 1.884 + * In bluedroid stack case, there is no interface to know exactly 1.885 + * avrcp connection status. All connection are managed by bluedroid stack. 1.886 + */ 1.887 +void 1.888 +BluetoothA2dpManager::SetAvrcpConnected(bool aConnected) 1.889 +{ 1.890 + mAvrcpConnected = aConnected; 1.891 + if (!aConnected) { 1.892 + ResetAvrcp(); 1.893 + } 1.894 +} 1.895 + 1.896 +bool 1.897 +BluetoothA2dpManager::IsAvrcpConnected() 1.898 +{ 1.899 + return mAvrcpConnected; 1.900 +} 1.901 + 1.902 +/* 1.903 + * This function only updates meta data in BluetoothA2dpManager 1.904 + * Send "Get Element Attributes response" in AvrcpGetElementAttrCallback 1.905 + */ 1.906 +void 1.907 +BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle, 1.908 + const nsAString& aArtist, 1.909 + const nsAString& aAlbum, 1.910 + uint64_t aMediaNumber, 1.911 + uint64_t aTotalMediaCount, 1.912 + uint32_t aDuration) 1.913 +{ 1.914 + MOZ_ASSERT(NS_IsMainThread()); 1.915 + 1.916 +#if ANDROID_VERSION > 17 1.917 + NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); 1.918 + 1.919 + // Send track changed and position changed if track num is not the same. 1.920 + // See also AVRCP 1.3 Spec 5.4.2 1.921 + if (mMediaNumber != aMediaNumber && 1.922 + mTrackChangedNotifyType == BTRC_NOTIFICATION_TYPE_INTERIM) { 1.923 + btrc_register_notification_t param; 1.924 + // convert to network big endian format 1.925 + // since track stores as uint8[8] 1.926 + // 56 = 8 * (BTRC_UID_SIZE -1) 1.927 + for (int i = 0; i < BTRC_UID_SIZE; ++i) { 1.928 + param.track[i] = (aMediaNumber >> (56 - 8 * i)); 1.929 + } 1.930 + mTrackChangedNotifyType = BTRC_NOTIFICATION_TYPE_CHANGED; 1.931 + sBtAvrcpInterface->register_notification_rsp(BTRC_EVT_TRACK_CHANGE, 1.932 + BTRC_NOTIFICATION_TYPE_CHANGED, 1.933 + ¶m); 1.934 + if (mPlayPosChangedNotifyType == BTRC_NOTIFICATION_TYPE_INTERIM) { 1.935 + param.song_pos = mPosition; 1.936 + // EVENT_PLAYBACK_POS_CHANGED shall be notified if changed current track 1.937 + mPlayPosChangedNotifyType = BTRC_NOTIFICATION_TYPE_CHANGED; 1.938 + sBtAvrcpInterface->register_notification_rsp( 1.939 + BTRC_EVT_PLAY_POS_CHANGED, 1.940 + BTRC_NOTIFICATION_TYPE_CHANGED, 1.941 + ¶m); 1.942 + } 1.943 + } 1.944 + 1.945 + mTitle.Assign(aTitle); 1.946 + mArtist.Assign(aArtist); 1.947 + mAlbum.Assign(aAlbum); 1.948 + mMediaNumber = aMediaNumber; 1.949 + mTotalMediaCount = aTotalMediaCount; 1.950 + mDuration = aDuration; 1.951 +#endif 1.952 +} 1.953 + 1.954 +/* 1.955 + * This function is to reply AvrcpGetPlayStatusCallback (play-status-request) 1.956 + * from media player application (Gaia side) 1.957 + */ 1.958 +void 1.959 +BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration, 1.960 + uint32_t aPosition, 1.961 + ControlPlayStatus aPlayStatus) 1.962 +{ 1.963 + MOZ_ASSERT(NS_IsMainThread()); 1.964 + 1.965 +#if ANDROID_VERSION > 17 1.966 + NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); 1.967 + // always update playstatus first 1.968 + sBtAvrcpInterface->get_play_status_rsp((btrc_play_status_t)aPlayStatus, 1.969 + aDuration, aPosition); 1.970 + // when play status changed, send both play status and position 1.971 + if (mPlayStatus != aPlayStatus && 1.972 + mPlayStatusChangedNotifyType == BTRC_NOTIFICATION_TYPE_INTERIM) { 1.973 + btrc_register_notification_t param; 1.974 + param.play_status = (btrc_play_status_t)aPlayStatus; 1.975 + mPlayStatusChangedNotifyType = BTRC_NOTIFICATION_TYPE_CHANGED; 1.976 + sBtAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_STATUS_CHANGED, 1.977 + BTRC_NOTIFICATION_TYPE_CHANGED, 1.978 + ¶m); 1.979 + } 1.980 + 1.981 + if (mPosition != aPosition && 1.982 + mPlayPosChangedNotifyType == BTRC_NOTIFICATION_TYPE_INTERIM) { 1.983 + btrc_register_notification_t param; 1.984 + param.song_pos = aPosition; 1.985 + mPlayPosChangedNotifyType = BTRC_NOTIFICATION_TYPE_CHANGED; 1.986 + sBtAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_POS_CHANGED, 1.987 + BTRC_NOTIFICATION_TYPE_CHANGED, 1.988 + ¶m); 1.989 + } 1.990 + 1.991 + mDuration = aDuration; 1.992 + mPosition = aPosition; 1.993 + mPlayStatus = aPlayStatus; 1.994 +#endif 1.995 +} 1.996 + 1.997 +/* 1.998 + * This function handles RegisterNotification request from 1.999 + * AvrcpRegisterNotificationCallback, which updates current 1.1000 + * track/status/position status in the INTERRIM response. 1.1001 + * 1.1002 + * aParam is only valid when position changed 1.1003 + */ 1.1004 +void 1.1005 +BluetoothA2dpManager::UpdateRegisterNotification(int aEventId, int aParam) 1.1006 +{ 1.1007 + MOZ_ASSERT(NS_IsMainThread()); 1.1008 + 1.1009 +#if ANDROID_VERSION > 17 1.1010 + NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); 1.1011 + 1.1012 + btrc_register_notification_t param; 1.1013 + 1.1014 + switch (aEventId) { 1.1015 + case BTRC_EVT_PLAY_STATUS_CHANGED: 1.1016 + mPlayStatusChangedNotifyType = BTRC_NOTIFICATION_TYPE_INTERIM; 1.1017 + param.play_status = (btrc_play_status_t)mPlayStatus; 1.1018 + break; 1.1019 + case BTRC_EVT_TRACK_CHANGE: 1.1020 + // In AVRCP 1.3 and 1.4, the identifier parameter of EVENT_TRACK_CHANGED 1.1021 + // is different. 1.1022 + // AVRCP 1.4: If no track is selected, we shall return 0xFFFFFFFFFFFFFFFF, 1.1023 + // otherwise return 0x0 in the INTERRIM response. The expanded text in 1.1024 + // version 1.4 is to allow for new UID feature. As for AVRCP 1.3, we shall 1.1025 + // return 0xFFFFFFFF. Since PTS enforces to check this part to comply with 1.1026 + // the most updated spec. 1.1027 + mTrackChangedNotifyType = BTRC_NOTIFICATION_TYPE_INTERIM; 1.1028 + // needs to convert to network big endian format since track stores 1.1029 + // as uint8[8]. 56 = 8 * (BTRC_UID_SIZE -1). 1.1030 + for (int index = 0; index < BTRC_UID_SIZE; ++index) { 1.1031 + // We cannot easily check if a track is selected, so whenever A2DP is 1.1032 + // streaming, we assume a track is selected. 1.1033 + if (mSinkState == BluetoothA2dpManager::SinkState::SINK_PLAYING) { 1.1034 + param.track[index] = 0x0; 1.1035 + } else { 1.1036 + param.track[index] = 0xFF; 1.1037 + } 1.1038 + } 1.1039 + break; 1.1040 + case BTRC_EVT_PLAY_POS_CHANGED: 1.1041 + // If no track is selected, return 0xFFFFFFFF in the INTERIM response 1.1042 + mPlayPosChangedNotifyType = BTRC_NOTIFICATION_TYPE_INTERIM; 1.1043 + if (mSinkState == BluetoothA2dpManager::SinkState::SINK_PLAYING) { 1.1044 + param.song_pos = mPosition; 1.1045 + } else { 1.1046 + param.song_pos = 0xFFFFFFFF; 1.1047 + } 1.1048 + mPlaybackInterval = aParam; 1.1049 + break; 1.1050 + default: 1.1051 + break; 1.1052 + } 1.1053 + 1.1054 + sBtAvrcpInterface->register_notification_rsp((btrc_event_id_t)aEventId, 1.1055 + BTRC_NOTIFICATION_TYPE_INTERIM, 1.1056 + ¶m); 1.1057 +#endif 1.1058 +} 1.1059 + 1.1060 +void 1.1061 +BluetoothA2dpManager::GetAlbum(nsAString& aAlbum) 1.1062 +{ 1.1063 + aAlbum.Assign(mAlbum); 1.1064 +} 1.1065 + 1.1066 +uint32_t 1.1067 +BluetoothA2dpManager::GetDuration() 1.1068 +{ 1.1069 + return mDuration; 1.1070 +} 1.1071 + 1.1072 +ControlPlayStatus 1.1073 +BluetoothA2dpManager::GetPlayStatus() 1.1074 +{ 1.1075 + return mPlayStatus; 1.1076 +} 1.1077 + 1.1078 +uint32_t 1.1079 +BluetoothA2dpManager::GetPosition() 1.1080 +{ 1.1081 + return mPosition; 1.1082 +} 1.1083 + 1.1084 +uint64_t 1.1085 +BluetoothA2dpManager::GetMediaNumber() 1.1086 +{ 1.1087 + return mMediaNumber; 1.1088 +} 1.1089 + 1.1090 +uint64_t 1.1091 +BluetoothA2dpManager::GetTotalMediaNumber() 1.1092 +{ 1.1093 + return mTotalMediaCount; 1.1094 +} 1.1095 + 1.1096 +void 1.1097 +BluetoothA2dpManager::GetTitle(nsAString& aTitle) 1.1098 +{ 1.1099 + aTitle.Assign(mTitle); 1.1100 +} 1.1101 + 1.1102 +void 1.1103 +BluetoothA2dpManager::GetArtist(nsAString& aArtist) 1.1104 +{ 1.1105 + aArtist.Assign(mArtist); 1.1106 +} 1.1107 + 1.1108 +NS_IMPL_ISUPPORTS(BluetoothA2dpManager, nsIObserver) 1.1109 +