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 michael@0: #include michael@0: #if ANDROID_VERSION > 17 michael@0: #include michael@0: #endif 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 "MainThreadUtils.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: USING_BLUETOOTH_NAMESPACE michael@0: // AVRC_ID op code follows bluedroid avrc_defs.h michael@0: #define AVRC_ID_REWIND 0x48 michael@0: #define AVRC_ID_FAST_FOR 0x49 michael@0: #define AVRC_KEY_PRESS_STATE 1 michael@0: #define AVRC_KEY_RELEASE_STATE 0 michael@0: michael@0: namespace { michael@0: StaticRefPtr sBluetoothA2dpManager; michael@0: bool sInShutdown = false; michael@0: static const btav_interface_t* sBtA2dpInterface; michael@0: #if ANDROID_VERSION > 17 michael@0: static const btrc_interface_t* sBtAvrcpInterface; michael@0: #endif michael@0: } // anonymous namespace michael@0: michael@0: class SinkPropertyChangedHandler : public nsRunnable michael@0: { michael@0: public: michael@0: SinkPropertyChangedHandler(const BluetoothSignal& aSignal) michael@0: : mSignal(aSignal) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); michael@0: NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE); michael@0: a2dp->HandleSinkPropertyChanged(mSignal); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: BluetoothSignal mSignal; michael@0: }; michael@0: michael@0: class RequestPlayStatusTask : public nsRunnable michael@0: { michael@0: public: michael@0: RequestPlayStatusTask() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: } michael@0: michael@0: nsresult Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: BluetoothSignal signal(NS_LITERAL_STRING(REQUEST_MEDIA_PLAYSTATUS_ID), michael@0: NS_LITERAL_STRING(KEY_ADAPTER), michael@0: InfallibleTArray()); michael@0: michael@0: BluetoothService* bs = BluetoothService::Get(); michael@0: NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE); michael@0: bs->DistributeSignal(signal); michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: #if ANDROID_VERSION > 17 michael@0: class UpdateRegisterNotificationTask : public nsRunnable michael@0: { michael@0: public: michael@0: UpdateRegisterNotificationTask(btrc_event_id_t aEventId, uint32_t aParam) michael@0: : mEventId(aEventId) michael@0: , mParam(aParam) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: } michael@0: michael@0: nsresult Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); michael@0: NS_ENSURE_TRUE(a2dp, NS_OK); michael@0: a2dp->UpdateRegisterNotification(mEventId, mParam); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: btrc_event_id_t mEventId; michael@0: uint32_t mParam; michael@0: }; michael@0: michael@0: /* michael@0: * This function maps attribute id and returns corresponding values michael@0: * Attribute id refers to btrc_media_attr_t in bt_rc.h michael@0: */ michael@0: static void michael@0: ConvertAttributeString(int aAttrId, nsAString& aAttrStr) michael@0: { michael@0: BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); michael@0: NS_ENSURE_TRUE_VOID(a2dp); michael@0: michael@0: switch (aAttrId) { michael@0: case BTRC_MEDIA_ATTR_TITLE: michael@0: a2dp->GetTitle(aAttrStr); michael@0: break; michael@0: case BTRC_MEDIA_ATTR_ARTIST: michael@0: a2dp->GetArtist(aAttrStr); michael@0: break; michael@0: case BTRC_MEDIA_ATTR_ALBUM: michael@0: a2dp->GetAlbum(aAttrStr); michael@0: break; michael@0: case BTRC_MEDIA_ATTR_TRACK_NUM: michael@0: aAttrStr.AppendInt(a2dp->GetMediaNumber()); michael@0: break; michael@0: case BTRC_MEDIA_ATTR_NUM_TRACKS: michael@0: aAttrStr.AppendInt(a2dp->GetTotalMediaNumber()); michael@0: break; michael@0: case BTRC_MEDIA_ATTR_GENRE: michael@0: // TODO: we currently don't support genre from music player michael@0: aAttrStr.Truncate(); michael@0: break; michael@0: case BTRC_MEDIA_ATTR_PLAYING_TIME: michael@0: aAttrStr.AppendInt(a2dp->GetDuration()); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: class UpdateElementAttrsTask : public nsRunnable michael@0: { michael@0: public: michael@0: UpdateElementAttrsTask(uint8_t aNumAttr, btrc_media_attr_t* aPlayerAttrs) michael@0: : mNumAttr(aNumAttr) michael@0: , mPlayerAttrs(aPlayerAttrs) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: } michael@0: michael@0: nsresult Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: btrc_element_attr_val_t* attrs = new btrc_element_attr_val_t[mNumAttr]; michael@0: for (int i = 0; i < mNumAttr; i++) { michael@0: nsAutoString attrText; michael@0: attrs[i].attr_id = mPlayerAttrs[i]; michael@0: ConvertAttributeString(mPlayerAttrs[i], attrText); michael@0: strcpy((char *)attrs[i].text, NS_ConvertUTF16toUTF8(attrText).get()); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(sBtAvrcpInterface, NS_OK); michael@0: sBtAvrcpInterface->get_element_attr_rsp(mNumAttr, attrs); michael@0: michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: uint8_t mNumAttr; michael@0: btrc_media_attr_t* mPlayerAttrs; michael@0: }; michael@0: michael@0: class UpdatePassthroughCmdTask : public nsRunnable michael@0: { michael@0: public: michael@0: UpdatePassthroughCmdTask(const nsAString& aName) michael@0: : mName(aName) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: } michael@0: michael@0: nsresult Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: NS_NAMED_LITERAL_STRING(type, "media-button"); michael@0: BroadcastSystemMessage(type, BluetoothValue(mName)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsString mName; michael@0: }; michael@0: michael@0: #endif 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: static void michael@0: AvStatusToSinkString(btav_connection_state_t aStatus, nsAString& aState) michael@0: { michael@0: nsAutoString state; michael@0: if (aStatus == BTAV_CONNECTION_STATE_DISCONNECTED) { michael@0: aState = NS_LITERAL_STRING("disconnected"); michael@0: } else if (aStatus == BTAV_CONNECTION_STATE_CONNECTING) { michael@0: aState = NS_LITERAL_STRING("connecting"); michael@0: } else if (aStatus == BTAV_CONNECTION_STATE_CONNECTED) { michael@0: aState = NS_LITERAL_STRING("connected"); michael@0: } else if (aStatus == BTAV_CONNECTION_STATE_DISCONNECTING) { michael@0: aState = NS_LITERAL_STRING("disconnecting"); michael@0: } else { michael@0: BT_WARNING("Unknown sink state"); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: A2dpConnectionStateCallback(btav_connection_state_t aState, michael@0: bt_bdaddr_t* aBdAddress) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: nsString remoteDeviceBdAddress; michael@0: BdAddressTypeToString(aBdAddress, remoteDeviceBdAddress); michael@0: michael@0: nsString a2dpState; michael@0: AvStatusToSinkString(aState, a2dpState); michael@0: michael@0: InfallibleTArray props; michael@0: BT_APPEND_NAMED_VALUE(props, "State", a2dpState); michael@0: michael@0: BluetoothSignal signal(NS_LITERAL_STRING("AudioSink"), michael@0: remoteDeviceBdAddress, props); michael@0: NS_DispatchToMainThread(new SinkPropertyChangedHandler(signal)); michael@0: } michael@0: michael@0: static void michael@0: A2dpAudioStateCallback(btav_audio_state_t aState, michael@0: bt_bdaddr_t* aBdAddress) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: nsString remoteDeviceBdAddress; michael@0: BdAddressTypeToString(aBdAddress, remoteDeviceBdAddress); michael@0: michael@0: nsString a2dpState; michael@0: michael@0: if (aState == BTAV_AUDIO_STATE_STARTED) { michael@0: a2dpState = NS_LITERAL_STRING("playing"); michael@0: } else if (aState == BTAV_AUDIO_STATE_STOPPED) { michael@0: // for avdtp state stop stream michael@0: a2dpState = NS_LITERAL_STRING("connected"); michael@0: } else if (aState == BTAV_AUDIO_STATE_REMOTE_SUSPEND) { michael@0: // for avdtp state suspend stream from remote side michael@0: a2dpState = NS_LITERAL_STRING("connected"); michael@0: } michael@0: michael@0: InfallibleTArray props; michael@0: BT_APPEND_NAMED_VALUE(props, "State", a2dpState); michael@0: michael@0: BluetoothSignal signal(NS_LITERAL_STRING("AudioSink"), michael@0: remoteDeviceBdAddress, props); michael@0: NS_DispatchToMainThread(new SinkPropertyChangedHandler(signal)); michael@0: } michael@0: michael@0: #if ANDROID_VERSION > 17 michael@0: /* michael@0: * Avrcp 1.3 callbacks michael@0: */ michael@0: michael@0: /* michael@0: * This function is to request Gaia player application to update michael@0: * current play status. michael@0: * Callback for play status request michael@0: */ michael@0: static void michael@0: AvrcpGetPlayStatusCallback() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: NS_DispatchToMainThread(new RequestPlayStatusTask()); michael@0: } michael@0: michael@0: /* michael@0: * This function is trying to get element attributes, which request from CT michael@0: * Unlike BlueZ only calls UpdateMetaData, bluedroid does not cache meta data michael@0: * information, but instead uses callback AvrcpGetElementAttrCallback and michael@0: * call get_element_attr_rsp() to reply request. michael@0: * michael@0: * Callback to fetch the get element attributes of the current song michael@0: * aNumAttr: It represents the number of attributes requested in aPlayerAttrs michael@0: * aPlayerAttrs: It represents Attribute Ids michael@0: */ michael@0: static void michael@0: AvrcpGetElementAttrCallback(uint8_t aNumAttr, btrc_media_attr_t* aPlayerAttrs) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: NS_DispatchToMainThread(new UpdateElementAttrsTask(aNumAttr, aPlayerAttrs)); michael@0: } michael@0: michael@0: /* michael@0: * Callback for register notification (Play state change/track change/...) michael@0: * To reply RegisterNotification INTERIM response michael@0: * See AVRCP 1.3 Spec 25.2 michael@0: * aParam: It only valids if event_id is BTRC_EVT_PLAY_POS_CHANGED, michael@0: * which is playback interval time michael@0: */ michael@0: static void michael@0: AvrcpRegisterNotificationCallback(btrc_event_id_t aEventId, uint32_t aParam) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: NS_DispatchToMainThread(new UpdateRegisterNotificationTask(aEventId, aParam)); michael@0: } michael@0: michael@0: /* michael@0: * Player application settings is optional for Avrcp 1.3 michael@0: * B2G 1.3 currently does not support Player application setting michael@0: * related functions. Support Player Setting in the future version michael@0: */ michael@0: static void michael@0: AvrcpListPlayerAppAttributeCallback() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: // TODO: Support avrcp application setting related functions michael@0: } michael@0: michael@0: static void michael@0: AvrcpListPlayerAppValuesCallback(btrc_player_attr_t aAttrId) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: // TODO: Support avrcp application setting related functions michael@0: } michael@0: michael@0: static void michael@0: AvrcpGetPlayerAppValueCallback(uint8_t aNumAttr, michael@0: btrc_player_attr_t* aPlayerAttrs) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: // TODO: Support avrcp application setting related functions michael@0: } michael@0: michael@0: static void michael@0: AvrcpGetPlayerAppAttrsTextCallback(uint8_t aNumAttr, michael@0: btrc_player_attr_t* PlayerAttrs) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: // TODO: Support avrcp application setting related functions michael@0: } michael@0: michael@0: static void michael@0: AvrcpGetPlayerAppValuesTextCallback(uint8_t aAttrId, uint8_t aNumVal, michael@0: uint8_t* PlayerVals) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: // TODO: Support avrcp application setting related functions michael@0: } michael@0: michael@0: static void michael@0: AvrcpSetPlayerAppValueCallback(btrc_player_settings_t* aPlayerVals) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: // TODO: Support avrcp application setting related functions michael@0: } michael@0: #endif michael@0: michael@0: #if ANDROID_VERSION > 18 michael@0: /* michael@0: * This callback function is to get CT features from Feature Bit Mask. michael@0: * If Advanced Control Player bit is set, CT supports michael@0: * volume sync (absolute volume feature). If Browsing bit is set, Avrcp 1.4 michael@0: * Browse feature will be supported michael@0: */ michael@0: static void michael@0: AvrcpRemoteFeaturesCallback(bt_bdaddr_t* aBdAddress, michael@0: btrc_remote_features_t aFeatures) michael@0: { michael@0: // TODO: Support avrcp 1.4 absolute volume/browse michael@0: } michael@0: michael@0: /* michael@0: * This callback function is to get notification that volume changed on the michael@0: * remote car kit (if it supports Avrcp 1.4), not notification from phone. michael@0: */ michael@0: static void michael@0: AvrcpRemoteVolumeChangedCallback(uint8_t aVolume, uint8_t aCType) michael@0: { michael@0: // TODO: Support avrcp 1.4 absolute volume/browse michael@0: } michael@0: michael@0: /* michael@0: * This callback function is to handle passthrough commands. michael@0: */ michael@0: static void michael@0: AvrcpPassThroughCallback(int aId, int aKeyState) michael@0: { michael@0: // Fast-forward and rewind key events won't be generated from bluedroid michael@0: // stack after ANDROID_VERSION > 18, but via passthrough callback. michael@0: nsAutoString name; michael@0: NS_ENSURE_TRUE_VOID(aKeyState == AVRC_KEY_PRESS_STATE || michael@0: aKeyState == AVRC_KEY_RELEASE_STATE); michael@0: switch (aId) { michael@0: case AVRC_ID_FAST_FOR: michael@0: if (aKeyState == AVRC_KEY_PRESS_STATE) { michael@0: name.AssignLiteral("media-fast-forward-button-press"); michael@0: } else { michael@0: name.AssignLiteral("media-fast-forward-button-release"); michael@0: } michael@0: break; michael@0: case AVRC_ID_REWIND: michael@0: if (aKeyState == AVRC_KEY_PRESS_STATE) { michael@0: name.AssignLiteral("media-rewind-button-press"); michael@0: } else { michael@0: name.AssignLiteral("media-rewind-button-release"); michael@0: } michael@0: break; michael@0: default: michael@0: BT_WARNING("Unable to handle the unknown PassThrough command %d", aId); michael@0: break; michael@0: } michael@0: if (!name.IsEmpty()) { michael@0: NS_DispatchToMainThread(new UpdatePassthroughCmdTask(name)); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: static btav_callbacks_t sBtA2dpCallbacks = { michael@0: sizeof(sBtA2dpCallbacks), michael@0: A2dpConnectionStateCallback, michael@0: A2dpAudioStateCallback michael@0: }; michael@0: michael@0: #if ANDROID_VERSION > 17 michael@0: static btrc_callbacks_t sBtAvrcpCallbacks = { michael@0: sizeof(sBtAvrcpCallbacks), michael@0: #if ANDROID_VERSION > 18 michael@0: AvrcpRemoteFeaturesCallback, michael@0: #endif michael@0: AvrcpGetPlayStatusCallback, michael@0: AvrcpListPlayerAppAttributeCallback, michael@0: AvrcpListPlayerAppValuesCallback, michael@0: AvrcpGetPlayerAppValueCallback, michael@0: AvrcpGetPlayerAppAttrsTextCallback, michael@0: AvrcpGetPlayerAppValuesTextCallback, michael@0: AvrcpSetPlayerAppValueCallback, michael@0: AvrcpGetElementAttrCallback, michael@0: AvrcpRegisterNotificationCallback, michael@0: #if ANDROID_VERSION > 18 michael@0: AvrcpRemoteVolumeChangedCallback, michael@0: AvrcpPassThroughCallback michael@0: #endif michael@0: }; michael@0: #endif michael@0: michael@0: /* michael@0: * This function will be only called when Bluetooth is turning on. michael@0: * It is important to register a2dp callbacks before enable() gets called. michael@0: * It is required to register a2dp callbacks before a2dp media task michael@0: * starts up. michael@0: */ michael@0: bool michael@0: BluetoothA2dpManager::Init() michael@0: { michael@0: const bt_interface_t* btInf = GetBluetoothInterface(); michael@0: NS_ENSURE_TRUE(btInf, false); michael@0: michael@0: sBtA2dpInterface = (btav_interface_t *)btInf-> michael@0: get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID); michael@0: NS_ENSURE_TRUE(sBtA2dpInterface, false); michael@0: michael@0: int ret = sBtA2dpInterface->init(&sBtA2dpCallbacks); michael@0: if (ret != BT_STATUS_SUCCESS) { michael@0: BT_LOGR("Warning: failed to init a2dp module"); michael@0: return false; michael@0: } michael@0: michael@0: #if ANDROID_VERSION > 17 michael@0: sBtAvrcpInterface = (btrc_interface_t *)btInf-> michael@0: get_profile_interface(BT_PROFILE_AV_RC_ID); michael@0: NS_ENSURE_TRUE(sBtAvrcpInterface, false); michael@0: michael@0: ret = sBtAvrcpInterface->init(&sBtAvrcpCallbacks); michael@0: if (ret != BT_STATUS_SUCCESS) { michael@0: BT_LOGR("Warning: failed to init avrcp module"); michael@0: return false; michael@0: } michael@0: #endif 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: /* michael@0: * Static functions 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 (!sBtA2dpInterface) { michael@0: BT_LOGR("sBluetoothA2dpInterface is null"); michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: michael@0: bt_bdaddr_t remoteAddress; michael@0: StringToBdAddressType(aDeviceAddress, &remoteAddress); michael@0: michael@0: bt_status_t result = sBtA2dpInterface->connect(&remoteAddress); michael@0: if (BT_STATUS_SUCCESS != result) { michael@0: BT_LOGR("Failed to connect: %x", result); michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: BluetoothA2dpManager::Disconnect(BluetoothProfileController* aController) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!mController); 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: michael@0: mController = aController; michael@0: michael@0: if (!sBtA2dpInterface) { michael@0: BT_LOGR("sBluetoothA2dpInterface is null"); michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); michael@0: return; michael@0: } michael@0: michael@0: bt_bdaddr_t remoteAddress; michael@0: StringToBdAddressType(mDeviceAddress, &remoteAddress); michael@0: michael@0: bt_status_t result = sBtA2dpInterface->disconnect(&remoteAddress); michael@0: if (BT_STATUS_SUCCESS != result) { michael@0: BT_LOGR("Failed to disconnect: %x", result); michael@0: aController->NotifyCompletion(NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED)); 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() == michael@0: 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: 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: prevState == SinkState::SINK_CONNECTED) { michael@0: break; michael@0: } michael@0: michael@0: // case 3: Successfully connected 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: /* michael@0: * In bluedroid stack case, there is no interface to know exactly michael@0: * avrcp connection status. All connection are managed by bluedroid stack. 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: /* michael@0: * This function only updates meta data in BluetoothA2dpManager michael@0: * Send "Get Element Attributes response" in AvrcpGetElementAttrCallback 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: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: #if ANDROID_VERSION > 17 michael@0: NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); michael@0: michael@0: // Send track changed and position changed if track num is not the same. michael@0: // See also AVRCP 1.3 Spec 5.4.2 michael@0: if (mMediaNumber != aMediaNumber && michael@0: mTrackChangedNotifyType == BTRC_NOTIFICATION_TYPE_INTERIM) { michael@0: btrc_register_notification_t param; michael@0: // convert to network big endian format michael@0: // since track stores as uint8[8] michael@0: // 56 = 8 * (BTRC_UID_SIZE -1) michael@0: for (int i = 0; i < BTRC_UID_SIZE; ++i) { michael@0: param.track[i] = (aMediaNumber >> (56 - 8 * i)); michael@0: } michael@0: mTrackChangedNotifyType = BTRC_NOTIFICATION_TYPE_CHANGED; michael@0: sBtAvrcpInterface->register_notification_rsp(BTRC_EVT_TRACK_CHANGE, michael@0: BTRC_NOTIFICATION_TYPE_CHANGED, michael@0: ¶m); michael@0: if (mPlayPosChangedNotifyType == BTRC_NOTIFICATION_TYPE_INTERIM) { michael@0: param.song_pos = mPosition; michael@0: // EVENT_PLAYBACK_POS_CHANGED shall be notified if changed current track michael@0: mPlayPosChangedNotifyType = BTRC_NOTIFICATION_TYPE_CHANGED; michael@0: sBtAvrcpInterface->register_notification_rsp( michael@0: BTRC_EVT_PLAY_POS_CHANGED, michael@0: BTRC_NOTIFICATION_TYPE_CHANGED, michael@0: ¶m); michael@0: } michael@0: } 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: #endif michael@0: } michael@0: michael@0: /* michael@0: * This function is to reply AvrcpGetPlayStatusCallback (play-status-request) michael@0: * from media player application (Gaia side) 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: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: #if ANDROID_VERSION > 17 michael@0: NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); michael@0: // always update playstatus first michael@0: sBtAvrcpInterface->get_play_status_rsp((btrc_play_status_t)aPlayStatus, michael@0: aDuration, aPosition); michael@0: // when play status changed, send both play status and position michael@0: if (mPlayStatus != aPlayStatus && michael@0: mPlayStatusChangedNotifyType == BTRC_NOTIFICATION_TYPE_INTERIM) { michael@0: btrc_register_notification_t param; michael@0: param.play_status = (btrc_play_status_t)aPlayStatus; michael@0: mPlayStatusChangedNotifyType = BTRC_NOTIFICATION_TYPE_CHANGED; michael@0: sBtAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_STATUS_CHANGED, michael@0: BTRC_NOTIFICATION_TYPE_CHANGED, michael@0: ¶m); michael@0: } michael@0: michael@0: if (mPosition != aPosition && michael@0: mPlayPosChangedNotifyType == BTRC_NOTIFICATION_TYPE_INTERIM) { michael@0: btrc_register_notification_t param; michael@0: param.song_pos = aPosition; michael@0: mPlayPosChangedNotifyType = BTRC_NOTIFICATION_TYPE_CHANGED; michael@0: sBtAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_POS_CHANGED, michael@0: BTRC_NOTIFICATION_TYPE_CHANGED, michael@0: ¶m); michael@0: } michael@0: michael@0: mDuration = aDuration; michael@0: mPosition = aPosition; michael@0: mPlayStatus = aPlayStatus; michael@0: #endif michael@0: } michael@0: michael@0: /* michael@0: * This function handles RegisterNotification request from michael@0: * AvrcpRegisterNotificationCallback, which updates current michael@0: * track/status/position status in the INTERRIM response. michael@0: * michael@0: * aParam is only valid when position changed michael@0: */ michael@0: void michael@0: BluetoothA2dpManager::UpdateRegisterNotification(int aEventId, int aParam) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: #if ANDROID_VERSION > 17 michael@0: NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); michael@0: michael@0: btrc_register_notification_t param; michael@0: michael@0: switch (aEventId) { michael@0: case BTRC_EVT_PLAY_STATUS_CHANGED: michael@0: mPlayStatusChangedNotifyType = BTRC_NOTIFICATION_TYPE_INTERIM; michael@0: param.play_status = (btrc_play_status_t)mPlayStatus; michael@0: break; michael@0: case BTRC_EVT_TRACK_CHANGE: michael@0: // In AVRCP 1.3 and 1.4, the identifier parameter of EVENT_TRACK_CHANGED michael@0: // is different. michael@0: // AVRCP 1.4: If no track is selected, we shall return 0xFFFFFFFFFFFFFFFF, michael@0: // otherwise return 0x0 in the INTERRIM response. The expanded text in michael@0: // version 1.4 is to allow for new UID feature. As for AVRCP 1.3, we shall michael@0: // return 0xFFFFFFFF. Since PTS enforces to check this part to comply with michael@0: // the most updated spec. michael@0: mTrackChangedNotifyType = BTRC_NOTIFICATION_TYPE_INTERIM; michael@0: // needs to convert to network big endian format since track stores michael@0: // as uint8[8]. 56 = 8 * (BTRC_UID_SIZE -1). michael@0: for (int index = 0; index < BTRC_UID_SIZE; ++index) { michael@0: // We cannot easily check if a track is selected, so whenever A2DP is michael@0: // streaming, we assume a track is selected. michael@0: if (mSinkState == BluetoothA2dpManager::SinkState::SINK_PLAYING) { michael@0: param.track[index] = 0x0; michael@0: } else { michael@0: param.track[index] = 0xFF; michael@0: } michael@0: } michael@0: break; michael@0: case BTRC_EVT_PLAY_POS_CHANGED: michael@0: // If no track is selected, return 0xFFFFFFFF in the INTERIM response michael@0: mPlayPosChangedNotifyType = BTRC_NOTIFICATION_TYPE_INTERIM; michael@0: if (mSinkState == BluetoothA2dpManager::SinkState::SINK_PLAYING) { michael@0: param.song_pos = mPosition; michael@0: } else { michael@0: param.song_pos = 0xFFFFFFFF; michael@0: } michael@0: mPlaybackInterval = aParam; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: sBtAvrcpInterface->register_notification_rsp((btrc_event_id_t)aEventId, michael@0: BTRC_NOTIFICATION_TYPE_INTERIM, michael@0: ¶m); michael@0: #endif 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: uint64_t michael@0: BluetoothA2dpManager::GetTotalMediaNumber() michael@0: { michael@0: return mTotalMediaCount; 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: void michael@0: BluetoothA2dpManager::GetArtist(nsAString& aArtist) michael@0: { michael@0: aArtist.Assign(mArtist); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(BluetoothA2dpManager, nsIObserver) michael@0: