mirror of
https://github.com/classilla/tenfourfox.git
synced 2025-01-30 03:33:33 +00:00
941 lines
25 KiB
C++
941 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 "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()
|
|
{
|
|
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, 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<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());
|
|
|
|
nsAutoArrayPtr<BluetoothAvrcpElementAttribute> attrs(
|
|
new 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, 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)
|
|
|