mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-18 06:45:33 +00:00
![Thomas Zimmermann](/assets/img/avatar_default.png)
The current Bluetooth profile managers remove themselves from a number of observer lists in their destructors. But |nsIObserverService| keeps it's own reference to the managers, so the destructors never run. Con- sequently the Bluetooth module nevers cleans up correctly. This patch adds an explicit uninit method to each profile manager. It removes the manager from the observer lists.
951 lines
25 KiB
C++
951 lines
25 KiB
C++
/* -*- 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 "mozilla/UniquePtr.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<BluetoothAvrcpManager> 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<BluetoothProfileResultHandler> 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<BluetoothProfileResultHandler> 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<nsRunnable> 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<nsRunnable> 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<nsRunnable> 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<nsRunnable> 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()
|
|
{ }
|
|
|
|
void
|
|
BluetoothAvrcpManager::Uninit()
|
|
{
|
|
nsCOMPtr<nsIObserverService> 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 and return
|
|
sBluetoothAvrcpManager = new BluetoothAvrcpManager();
|
|
|
|
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;
|
|
|
|
sBluetoothAvrcpManager->Uninit();
|
|
sBluetoothAvrcpManager = nullptr;
|
|
|
|
if (mRes) {
|
|
mRes->OnError(NS_ERROR_FAILURE);
|
|
}
|
|
}
|
|
|
|
void UnregisterModule() override
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
sBtAvrcpInterface->SetNotificationHandler(nullptr);
|
|
sBtAvrcpInterface = nullptr;
|
|
|
|
sBluetoothAvrcpManager->Uninit();
|
|
sBluetoothAvrcpManager = nullptr;
|
|
|
|
if (mRes) {
|
|
mRes->Deinit();
|
|
}
|
|
}
|
|
|
|
private:
|
|
RefPtr<BluetoothProfileResultHandler> 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<BluetoothProfileResultHandler> 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<nsRunnable> 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<nsRunnable> 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<nsRunnable> 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<BluetoothProfileController> 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<BluetoothProfileController> 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());
|
|
|
|
auto attrs = MakeUnique<BluetoothAvrcpElementAttribute[]>(aNumAttrs);
|
|
|
|
for (uint8_t i = 0; i < aNumAttrs; ++i) {
|
|
attrs[i].mId = aAttrs[i];
|
|
ConvertAttributeString(
|
|
static_cast<BluetoothAvrcpMediaAttribute>(attrs[i].mId),
|
|
attrs[i].mValue);
|
|
}
|
|
|
|
MOZ_ASSERT(sBtAvrcpInterface);
|
|
sBtAvrcpInterface->GetElementAttrRsp(aNumAttrs, attrs.get(), 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)
|
|
|