/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=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 "BluetoothAvrcpManager.h" #include "BluetoothCommon.h" #include "BluetoothService.h" #include "BluetoothSocket.h" #include "BluetoothUtils.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "MainThreadUtils.h" #include "nsIObserverService.h" #include "nsThreadUtils.h" using namespace mozilla; USING_BLUETOOTH_NAMESPACE // AVRC_ID op code follows bluedroid avrc_defs.h #define AVRC_ID_REWIND 0x48 #define AVRC_ID_FAST_FOR 0x49 #define AVRC_KEY_PRESS_STATE 1 #define AVRC_KEY_RELEASE_STATE 0 // bluedroid bt_rc.h #define AVRC_MAX_ATTR_STR_LEN 255 namespace { StaticRefPtr sBluetoothAvrcpManager; bool sInShutdown = false; static BluetoothAvrcpInterface* sBtAvrcpInterface; } // namespace const int BluetoothAvrcpManager::MAX_NUM_CLIENTS = 1; /* * This function maps attribute id and returns corresponding values */ static void ConvertAttributeString(BluetoothAvrcpMediaAttribute aAttrId, nsAString& aAttrStr) { BluetoothAvrcpManager* avrcp = BluetoothAvrcpManager::Get(); NS_ENSURE_TRUE_VOID(avrcp); switch (aAttrId) { case AVRCP_MEDIA_ATTRIBUTE_TITLE: avrcp->GetTitle(aAttrStr); /* * bluedroid can only send string length AVRC_MAX_ATTR_STR_LEN - 1 */ if (aAttrStr.Length() >= AVRC_MAX_ATTR_STR_LEN) { aAttrStr.Truncate(AVRC_MAX_ATTR_STR_LEN - 1); BT_WARNING("Truncate media item attribute title, length is over 255"); } break; case AVRCP_MEDIA_ATTRIBUTE_ARTIST: avrcp->GetArtist(aAttrStr); if (aAttrStr.Length() >= AVRC_MAX_ATTR_STR_LEN) { aAttrStr.Truncate(AVRC_MAX_ATTR_STR_LEN - 1); BT_WARNING("Truncate media item attribute artist, length is over 255"); } break; case AVRCP_MEDIA_ATTRIBUTE_ALBUM: avrcp->GetAlbum(aAttrStr); if (aAttrStr.Length() >= AVRC_MAX_ATTR_STR_LEN) { aAttrStr.Truncate(AVRC_MAX_ATTR_STR_LEN - 1); BT_WARNING("Truncate media item attribute album, length is over 255"); } break; case AVRCP_MEDIA_ATTRIBUTE_TRACK_NUM: aAttrStr.AppendInt(avrcp->GetMediaNumber()); break; case AVRCP_MEDIA_ATTRIBUTE_NUM_TRACKS: aAttrStr.AppendInt(avrcp->GetTotalMediaNumber()); break; case AVRCP_MEDIA_ATTRIBUTE_GENRE: // TODO: we currently don't support genre from music player aAttrStr.Truncate(); break; case AVRCP_MEDIA_ATTRIBUTE_PLAYING_TIME: aAttrStr.AppendInt(avrcp->GetDuration()); break; } } NS_IMETHODIMP BluetoothAvrcpManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(sBluetoothAvrcpManager); if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { HandleShutdown(); return NS_OK; } MOZ_ASSERT(false, "BluetoothAvrcpManager got unexpected topic!"); return NS_ERROR_UNEXPECTED; } BluetoothAvrcpManager::BluetoothAvrcpManager() { Reset(); } void BluetoothAvrcpManager::Reset() { mAvrcpConnected = false; mDuration = 0; mMediaNumber = 0; mTotalMediaCount = 0; mPosition = 0; mPlayStatus = ControlPlayStatus::PLAYSTATUS_STOPPED; } class BluetoothAvrcpManager::RegisterModuleResultHandler final : public BluetoothSetupResultHandler { public: RegisterModuleResultHandler(BluetoothAvrcpInterface* aInterface, BluetoothProfileResultHandler* aRes) : mInterface(aInterface) , mRes(aRes) { MOZ_ASSERT(mInterface); } void OnError(BluetoothStatus aStatus) override { MOZ_ASSERT(NS_IsMainThread()); BT_WARNING("BluetoothSetupInterface::RegisterModule failed for AVRCP: %d", (int)aStatus); mInterface->SetNotificationHandler(nullptr); if (mRes) { if (aStatus == STATUS_UNSUPPORTED) { /* Not all versions of Bluedroid support AVRCP. So if the * initialization fails with STATUS_UNSUPPORTED, we still * signal success. */ mRes->Init(); } else { mRes->OnError(NS_ERROR_FAILURE); } } } void RegisterModule() override { MOZ_ASSERT(NS_IsMainThread()); sBtAvrcpInterface = mInterface; if (mRes) { mRes->Init(); } } private: BluetoothAvrcpInterface* mInterface; RefPtr mRes; }; class BluetoothAvrcpManager::InitProfileResultHandlerRunnable final : public nsRunnable { public: InitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes, nsresult aRv) : mRes(aRes) , mRv(aRv) { MOZ_ASSERT(mRes); } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); if (NS_SUCCEEDED(mRv)) { mRes->Init(); } else { mRes->OnError(mRv); } return NS_OK; } private: RefPtr mRes; nsresult mRv; }; /* * This function will be only called when Bluetooth is turning on. */ // static void BluetoothAvrcpManager::InitAvrcpInterface(BluetoothProfileResultHandler* aRes) { MOZ_ASSERT(NS_IsMainThread()); if (sBtAvrcpInterface) { BT_LOGR("Bluetooth AVRCP interface is already initalized."); RefPtr r = new InitProfileResultHandlerRunnable(aRes, NS_OK); if (NS_FAILED(NS_DispatchToMainThread(r))) { BT_LOGR("Failed to dispatch AVRCP Init runnable"); } return; } auto btInf = BluetoothInterface::GetInstance(); if (NS_WARN_IF(!btInf)) { // If there's no Bluetooth interface, we dispatch a runnable // that calls the profile result handler. RefPtr r = new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE); if (NS_FAILED(NS_DispatchToMainThread(r))) { BT_LOGR("Failed to dispatch AVRCP OnError runnable"); } return; } auto setupInterface = btInf->GetBluetoothSetupInterface(); if (NS_WARN_IF(!setupInterface)) { // If there's no Setup interface, we dispatch a runnable // that calls the profile result handler. RefPtr r = new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE); if (NS_FAILED(NS_DispatchToMainThread(r))) { BT_LOGR("Failed to dispatch AVRCP OnError runnable"); } return; } auto avrcpInterface = btInf->GetBluetoothAvrcpInterface(); if (NS_WARN_IF(!avrcpInterface)) { // If there's no AVRCP interface, we dispatch a runnable // that calls the profile result handler. RefPtr r = new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE); if (NS_FAILED(NS_DispatchToMainThread(r))) { BT_LOGR("Failed to dispatch AVRCP OnError runnable"); } return; } // Set notification handler _before_ registering the module. It could // happen that we receive notifications, before the result handler runs. avrcpInterface->SetNotificationHandler(BluetoothAvrcpManager::Get()); setupInterface->RegisterModule( SETUP_SERVICE_ID_AVRCP, 0, MAX_NUM_CLIENTS, new RegisterModuleResultHandler(avrcpInterface, aRes)); } BluetoothAvrcpManager::~BluetoothAvrcpManager() { nsCOMPtr obs = services::GetObserverService(); NS_ENSURE_TRUE_VOID(obs); if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { BT_WARNING("Failed to remove shutdown observer!"); } } /* * Static functions */ //static BluetoothAvrcpManager* BluetoothAvrcpManager::Get() { MOZ_ASSERT(NS_IsMainThread()); // If sBluetoothAvrcpManager already exists, exit early if (sBluetoothAvrcpManager) { return sBluetoothAvrcpManager; } // If we're in shutdown, don't create a new instance NS_ENSURE_FALSE(sInShutdown, nullptr); // Create a new instance, register, and return BluetoothAvrcpManager* manager = new BluetoothAvrcpManager(); sBluetoothAvrcpManager = manager; return sBluetoothAvrcpManager; } class BluetoothAvrcpManager::UnregisterModuleResultHandler final : public BluetoothSetupResultHandler { public: UnregisterModuleResultHandler(BluetoothProfileResultHandler* aRes) : mRes(aRes) { } void OnError(BluetoothStatus aStatus) override { MOZ_ASSERT(NS_IsMainThread()); BT_WARNING("BluetoothSetupInterface::UnregisterModule failed for AVRCP: %d", (int)aStatus); sBtAvrcpInterface->SetNotificationHandler(nullptr); sBtAvrcpInterface = nullptr; if (mRes) { mRes->OnError(NS_ERROR_FAILURE); } } void UnregisterModule() override { MOZ_ASSERT(NS_IsMainThread()); sBtAvrcpInterface->SetNotificationHandler(nullptr); sBtAvrcpInterface = nullptr; if (mRes) { mRes->Deinit(); } } private: RefPtr mRes; }; class BluetoothAvrcpManager::DeinitProfileResultHandlerRunnable final : public nsRunnable { public: DeinitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes, nsresult aRv) : mRes(aRes) , mRv(aRv) { MOZ_ASSERT(mRes); } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); if (NS_SUCCEEDED(mRv)) { mRes->Deinit(); } else { mRes->OnError(mRv); } return NS_OK; } private: RefPtr mRes; nsresult mRv; }; // static void BluetoothAvrcpManager::DeinitAvrcpInterface(BluetoothProfileResultHandler* aRes) { MOZ_ASSERT(NS_IsMainThread()); if (!sBtAvrcpInterface) { BT_LOGR("Bluetooth AVRCP interface has not been initalized."); RefPtr r = new DeinitProfileResultHandlerRunnable(aRes, NS_OK); if (NS_FAILED(NS_DispatchToMainThread(r))) { BT_LOGR("Failed to dispatch AVRCP Deinit runnable"); } return; } auto btInf = BluetoothInterface::GetInstance(); if (NS_WARN_IF(!btInf)) { // If there's no backend interface, we dispatch a runnable // that calls the profile result handler. RefPtr r = new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE); if (NS_FAILED(NS_DispatchToMainThread(r))) { BT_LOGR("Failed to dispatch AVRCP OnError runnable"); } return; } auto setupInterface = btInf->GetBluetoothSetupInterface(); if (NS_WARN_IF(!setupInterface)) { // If there's no Setup interface, we dispatch a runnable // that calls the profile result handler. RefPtr r = new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE); if (NS_FAILED(NS_DispatchToMainThread(r))) { BT_LOGR("Failed to dispatch AVRCP OnError runnable"); } return; } setupInterface->UnregisterModule( SETUP_SERVICE_ID_AVRCP, new UnregisterModuleResultHandler(aRes)); } void BluetoothAvrcpManager::HandleShutdown() { MOZ_ASSERT(NS_IsMainThread()); sInShutdown = true; Disconnect(nullptr); sBluetoothAvrcpManager = nullptr; } class BluetoothAvrcpManager::ConnectRunnable final : public nsRunnable { public: ConnectRunnable(BluetoothAvrcpManager* aManager) : mManager(aManager) { MOZ_ASSERT(mManager); } NS_METHOD Run() override { mManager->OnConnect(EmptyString()); return NS_OK; } private: BluetoothAvrcpManager* mManager; }; void BluetoothAvrcpManager::Connect(const BluetoothAddress& aDeviceAddress, BluetoothProfileController* aController) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aDeviceAddress.IsCleared()); MOZ_ASSERT(aController); // AVRCP doesn't require connecting. We just set the remote address here. mDeviceAddress = aDeviceAddress; mController = aController; SetConnected(true); NS_DispatchToMainThread(new ConnectRunnable(this)); } class BluetoothAvrcpManager::DisconnectRunnable final : public nsRunnable { public: DisconnectRunnable(BluetoothAvrcpManager* aManager) : mManager(aManager) { MOZ_ASSERT(mManager); } NS_METHOD Run() override { mManager->OnDisconnect(EmptyString()); return NS_OK; } private: BluetoothAvrcpManager* mManager; }; void BluetoothAvrcpManager::Disconnect(BluetoothProfileController* aController) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mController); mDeviceAddress.Clear(); mController = aController; SetConnected(false); NS_DispatchToMainThread(new DisconnectRunnable(this)); } void BluetoothAvrcpManager::OnConnect(const nsAString& aErrorStr) { MOZ_ASSERT(NS_IsMainThread()); /** * On the one hand, notify the controller that we've done for outbound * connections. On the other hand, we do nothing for inbound connections. */ NS_ENSURE_TRUE_VOID(mController); RefPtr controller = mController.forget(); controller->NotifyCompletion(aErrorStr); } void BluetoothAvrcpManager::OnDisconnect(const nsAString& aErrorStr) { MOZ_ASSERT(NS_IsMainThread()); /** * On the one hand, notify the controller that we've done for outbound * connections. On the other hand, we do nothing for inbound connections. */ NS_ENSURE_TRUE_VOID(mController); RefPtr controller = mController.forget(); controller->NotifyCompletion(aErrorStr); Reset(); } void BluetoothAvrcpManager::OnGetServiceChannel( const BluetoothAddress& aDeviceAddress, const BluetoothUuid& aServiceUuid, int aChannel) { } void BluetoothAvrcpManager::OnUpdateSdpRecords( const BluetoothAddress& aDeviceAddress) { } void BluetoothAvrcpManager::GetAddress(BluetoothAddress& aDeviceAddress) { aDeviceAddress = mDeviceAddress; } bool BluetoothAvrcpManager::IsConnected() { return mAvrcpConnected; } /* * In bluedroid stack case, there is no interface to know exactly * avrcp connection status. All connection are managed by bluedroid stack. */ void BluetoothAvrcpManager::SetConnected(bool aConnected) { mAvrcpConnected = aConnected; if (!aConnected) { Reset(); } } /* * This function only updates meta data in BluetoothAvrcpManager. Send * "Get Element Attributes response" in AvrcpGetElementAttrCallback */ void BluetoothAvrcpManager::UpdateMetaData(const nsAString& aTitle, const nsAString& aArtist, const nsAString& aAlbum, uint64_t aMediaNumber, uint64_t aTotalMediaCount, uint32_t aDuration) { MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); // Send track changed and position changed if track num is not the same. // See also AVRCP 1.3 Spec 5.4.2 if (mMediaNumber != aMediaNumber && mTrackChangedNotifyType == AVRCP_NTF_INTERIM) { BluetoothAvrcpNotificationParam param; // convert to network big endian format // since track stores as uint8[8] // 56 = 8 * (AVRCP_UID_SIZE -1) for (int i = 0; i < AVRCP_UID_SIZE; ++i) { param.mTrack[i] = (aMediaNumber >> (56 - 8 * i)); } mTrackChangedNotifyType = AVRCP_NTF_CHANGED; sBtAvrcpInterface->RegisterNotificationRsp(AVRCP_EVENT_TRACK_CHANGE, AVRCP_NTF_CHANGED, param, nullptr); if (mPlayPosChangedNotifyType == AVRCP_NTF_INTERIM) { param.mSongPos = mPosition; // EVENT_PLAYBACK_POS_CHANGED shall be notified if changed current track mPlayPosChangedNotifyType = AVRCP_NTF_CHANGED; sBtAvrcpInterface->RegisterNotificationRsp(AVRCP_EVENT_PLAY_POS_CHANGED, AVRCP_NTF_CHANGED, param, nullptr); } } mTitle.Assign(aTitle); mArtist.Assign(aArtist); mAlbum.Assign(aAlbum); mMediaNumber = aMediaNumber; mTotalMediaCount = aTotalMediaCount; mDuration = aDuration; } /* * This function is to reply AvrcpGetPlayStatusCallback (play-status-request) * from media player application (Gaia side) */ void BluetoothAvrcpManager::UpdatePlayStatus(uint32_t aDuration, uint32_t aPosition, ControlPlayStatus aPlayStatus) { MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); // always update playstatus first sBtAvrcpInterface->GetPlayStatusRsp(aPlayStatus, aDuration, aPosition, nullptr); // when play status changed, send both play status and position if (mPlayStatus != aPlayStatus && mPlayStatusChangedNotifyType == AVRCP_NTF_INTERIM) { BluetoothAvrcpNotificationParam param; param.mPlayStatus = aPlayStatus; mPlayStatusChangedNotifyType = AVRCP_NTF_CHANGED; sBtAvrcpInterface->RegisterNotificationRsp(AVRCP_EVENT_PLAY_STATUS_CHANGED, AVRCP_NTF_CHANGED, param, nullptr); } if (mPosition != aPosition && mPlayPosChangedNotifyType == AVRCP_NTF_INTERIM) { BluetoothAvrcpNotificationParam param; param.mSongPos = aPosition; mPlayPosChangedNotifyType = AVRCP_NTF_CHANGED; sBtAvrcpInterface->RegisterNotificationRsp(AVRCP_EVENT_PLAY_POS_CHANGED, AVRCP_NTF_CHANGED, param, nullptr); } mDuration = aDuration; mPosition = aPosition; mPlayStatus = aPlayStatus; } /* * This function handles RegisterNotification request from * AvrcpRegisterNotificationCallback, which updates current * track/status/position status in the INTERRIM response. * * aParam is only valid when position changed */ void BluetoothAvrcpManager::UpdateRegisterNotification(BluetoothAvrcpEvent aEvent, uint32_t aParam) { MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_TRUE_VOID(sBtAvrcpInterface); BluetoothAvrcpNotificationParam param; switch (aEvent) { case AVRCP_EVENT_PLAY_STATUS_CHANGED: mPlayStatusChangedNotifyType = AVRCP_NTF_INTERIM; param.mPlayStatus = mPlayStatus; break; case AVRCP_EVENT_TRACK_CHANGE: // In AVRCP 1.3 and 1.4, the identifier parameter of EVENT_TRACK_CHANGED // is different. // AVRCP 1.4: If no track is selected, we shall return 0xFFFFFFFFFFFFFFFF, // otherwise return 0x0 in the INTERRIM response. The expanded text in // version 1.4 is to allow for new UID feature. As for AVRCP 1.3, we shall // return 0xFFFFFFFF. Since PTS enforces to check this part to comply with // the most updated spec. mTrackChangedNotifyType = AVRCP_NTF_INTERIM; // needs to convert to network big endian format since track stores // as uint8[8]. 56 = 8 * (BTRC_UID_SIZE -1). for (int index = 0; index < AVRCP_UID_SIZE; ++index) { // We cannot easily check if a track is selected, so whenever A2DP is // streaming, we assume a track is selected. if (mPlayStatus == ControlPlayStatus::PLAYSTATUS_PLAYING) { param.mTrack[index] = 0x0; } else { param.mTrack[index] = 0xFF; } } break; case AVRCP_EVENT_PLAY_POS_CHANGED: // If no track is selected, return 0xFFFFFFFF in the INTERIM response mPlayPosChangedNotifyType = AVRCP_NTF_INTERIM; if (mPlayStatus == ControlPlayStatus::PLAYSTATUS_PLAYING) { param.mSongPos = mPosition; } else { param.mSongPos = 0xFFFFFFFF; } mPlaybackInterval = aParam; break; case AVRCP_EVENT_APP_SETTINGS_CHANGED: mAppSettingsChangedNotifyType = AVRCP_NTF_INTERIM; param.mNumAttr = 2; param.mIds[0] = AVRCP_PLAYER_ATTRIBUTE_REPEAT; param.mValues[0] = AVRCP_PLAYER_VAL_OFF_REPEAT; param.mIds[1] = AVRCP_PLAYER_ATTRIBUTE_SHUFFLE; param.mValues[1] = AVRCP_PLAYER_VAL_OFF_SHUFFLE; break; default: break; } sBtAvrcpInterface->RegisterNotificationRsp(aEvent, AVRCP_NTF_INTERIM, param, nullptr); } void BluetoothAvrcpManager::GetAlbum(nsAString& aAlbum) { aAlbum.Assign(mAlbum); } uint32_t BluetoothAvrcpManager::GetDuration() { return mDuration; } ControlPlayStatus BluetoothAvrcpManager::GetPlayStatus() { return mPlayStatus; } uint32_t BluetoothAvrcpManager::GetPosition() { return mPosition; } uint64_t BluetoothAvrcpManager::GetMediaNumber() { return mMediaNumber; } uint64_t BluetoothAvrcpManager::GetTotalMediaNumber() { return mTotalMediaCount; } void BluetoothAvrcpManager::GetTitle(nsAString& aTitle) { aTitle.Assign(mTitle); } void BluetoothAvrcpManager::GetArtist(nsAString& aArtist) { aArtist.Assign(mArtist); } /* * Notifications */ void BluetoothAvrcpManager::GetPlayStatusNotification() { MOZ_ASSERT(NS_IsMainThread()); BluetoothService* bs = BluetoothService::Get(); if (!bs) { return; } bs->DistributeSignal(NS_LITERAL_STRING(REQUEST_MEDIA_PLAYSTATUS_ID), NS_LITERAL_STRING(KEY_ADAPTER)); } /* Player application settings is optional for AVRCP 1.3. B2G * currently does not support player-application-setting related * functionality. */ void BluetoothAvrcpManager::ListPlayerAppAttrNotification() { MOZ_ASSERT(NS_IsMainThread()); // TODO: Support AVRCP application-setting-related functions } void BluetoothAvrcpManager::ListPlayerAppValuesNotification( BluetoothAvrcpPlayerAttribute aAttrId) { MOZ_ASSERT(NS_IsMainThread()); // TODO: Support AVRCP application-setting-related functions } void BluetoothAvrcpManager::GetPlayerAppValueNotification( uint8_t aNumAttrs, const BluetoothAvrcpPlayerAttribute* aAttrs) { MOZ_ASSERT(NS_IsMainThread()); // TODO: Support AVRCP application-setting-related functions } void BluetoothAvrcpManager::GetPlayerAppAttrsTextNotification( uint8_t aNumAttrs, const BluetoothAvrcpPlayerAttribute* aAttrs) { MOZ_ASSERT(NS_IsMainThread()); // TODO: Support AVRCP application-setting-related functions } void BluetoothAvrcpManager::GetPlayerAppValuesTextNotification( uint8_t aAttrId, uint8_t aNumVals, const uint8_t* aValues) { MOZ_ASSERT(NS_IsMainThread()); // TODO: Support AVRCP application-setting-related functions } void BluetoothAvrcpManager::SetPlayerAppValueNotification( const BluetoothAvrcpPlayerSettings& aSettings) { MOZ_ASSERT(NS_IsMainThread()); // TODO: Support AVRCP application-setting-related functions } /* This method returns element attributes, which are requested from * CT. Unlike BlueZ it calls only UpdateMetaData. Bluedroid does not cache * meta-data information, but instead uses |GetElementAttrNotifications| * and |GetElementAttrRsp| request them. */ void BluetoothAvrcpManager::GetElementAttrNotification( uint8_t aNumAttrs, const BluetoothAvrcpMediaAttribute* aAttrs) { MOZ_ASSERT(NS_IsMainThread()); nsAutoArrayPtr attrs( new BluetoothAvrcpElementAttribute[aNumAttrs]); for (uint8_t i = 0; i < aNumAttrs; ++i) { attrs[i].mId = aAttrs[i]; ConvertAttributeString( static_cast(attrs[i].mId), attrs[i].mValue); } MOZ_ASSERT(sBtAvrcpInterface); sBtAvrcpInterface->GetElementAttrRsp(aNumAttrs, attrs, nullptr); } void BluetoothAvrcpManager::RegisterNotificationNotification( BluetoothAvrcpEvent aEvent, uint32_t aParam) { MOZ_ASSERT(NS_IsMainThread()); BluetoothAvrcpManager* avrcp = BluetoothAvrcpManager::Get(); if (!avrcp) { return; } avrcp->UpdateRegisterNotification(aEvent, aParam); } /* This method is used to get CT features from the Feature Bit Mask. If * Advanced Control Player bit is set, the CT supports volume sync (absolute * volume feature). If Browsing bit is set, AVRCP 1.4 Browse feature will be * supported. */ void BluetoothAvrcpManager::RemoteFeatureNotification( const BluetoothAddress& aBdAddr, unsigned long aFeatures) { MOZ_ASSERT(NS_IsMainThread()); // TODO: Support AVRCP 1.4 absolute volume/browse } /* This method is used to get notifications about volume changes on the * remote car kit (if it supports AVRCP 1.4), not notification from phone. */ void BluetoothAvrcpManager::VolumeChangeNotification(uint8_t aVolume, uint8_t aCType) { MOZ_ASSERT(NS_IsMainThread()); // TODO: Support AVRCP 1.4 absolute volume/browse } void BluetoothAvrcpManager::PassthroughCmdNotification(int aId, int aKeyState) { MOZ_ASSERT(NS_IsMainThread()); // Fast-forward and rewind key events won't be generated from bluedroid // stack after ANDROID_VERSION > 18, but via passthrough callback. nsAutoString name; NS_ENSURE_TRUE_VOID(aKeyState == AVRC_KEY_PRESS_STATE || aKeyState == AVRC_KEY_RELEASE_STATE); switch (aId) { case AVRC_ID_FAST_FOR: if (aKeyState == AVRC_KEY_PRESS_STATE) { name.AssignLiteral("media-fast-forward-button-press"); } else { name.AssignLiteral("media-fast-forward-button-release"); } break; case AVRC_ID_REWIND: if (aKeyState == AVRC_KEY_PRESS_STATE) { name.AssignLiteral("media-rewind-button-press"); } else { name.AssignLiteral("media-rewind-button-release"); } break; default: BT_WARNING("Unable to handle the unknown PassThrough command %d", aId); return; } NS_NAMED_LITERAL_STRING(type, "media-button"); BroadcastSystemMessage(type, BluetoothValue(name)); } NS_IMPL_ISUPPORTS(BluetoothAvrcpManager, nsIObserver)