You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1512 lines
53 KiB
1512 lines
53 KiB
/*
|
|
* Copyright 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include "device.h"
|
|
|
|
#include "abstract_message_loop.h"
|
|
#include "connection_handler.h"
|
|
#include "packet/avrcp/avrcp_reject_packet.h"
|
|
#include "packet/avrcp/general_reject_packet.h"
|
|
#include "packet/avrcp/get_play_status_packet.h"
|
|
#include "packet/avrcp/pass_through_packet.h"
|
|
#include "packet/avrcp/set_absolute_volume.h"
|
|
#include "packet/avrcp/set_addressed_player.h"
|
|
#include "stack_config.h"
|
|
|
|
namespace bluetooth {
|
|
namespace avrcp {
|
|
|
|
#define DEVICE_LOG(LEVEL) LOG(LEVEL) << address_.ToString() << " : "
|
|
#define DEVICE_VLOG(LEVEL) VLOG(LEVEL) << address_.ToString() << " : "
|
|
|
|
#define VOL_NOT_SUPPORTED -1
|
|
#define VOL_REGISTRATION_FAILED -2
|
|
|
|
Device::Device(
|
|
const RawAddress& bdaddr, bool avrcp13_compatibility,
|
|
base::Callback<void(uint8_t label, bool browse,
|
|
std::unique_ptr<::bluetooth::PacketBuilder> message)>
|
|
send_msg_cb,
|
|
uint16_t ctrl_mtu, uint16_t browse_mtu)
|
|
: weak_ptr_factory_(this),
|
|
address_(bdaddr),
|
|
avrcp13_compatibility_(avrcp13_compatibility),
|
|
send_message_cb_(send_msg_cb),
|
|
ctrl_mtu_(ctrl_mtu),
|
|
browse_mtu_(browse_mtu),
|
|
has_bip_client_(false) {}
|
|
|
|
void Device::RegisterInterfaces(MediaInterface* media_interface,
|
|
A2dpInterface* a2dp_interface,
|
|
VolumeInterface* volume_interface) {
|
|
CHECK(media_interface);
|
|
CHECK(a2dp_interface);
|
|
a2dp_interface_ = a2dp_interface;
|
|
media_interface_ = media_interface;
|
|
volume_interface_ = volume_interface;
|
|
}
|
|
|
|
base::WeakPtr<Device> Device::Get() {
|
|
return weak_ptr_factory_.GetWeakPtr();
|
|
}
|
|
|
|
void Device::SetBrowseMtu(uint16_t browse_mtu) {
|
|
DEVICE_LOG(INFO) << __PRETTY_FUNCTION__ << ": browse_mtu = " << browse_mtu;
|
|
browse_mtu_ = browse_mtu;
|
|
}
|
|
|
|
void Device::SetBipClientStatus(bool connected) {
|
|
DEVICE_LOG(INFO) << __PRETTY_FUNCTION__ << ": connected = " << connected;
|
|
has_bip_client_ = connected;
|
|
}
|
|
|
|
bool Device::HasBipClient() const {
|
|
return has_bip_client_;
|
|
}
|
|
|
|
void filter_cover_art(SongInfo& s) {
|
|
for (auto it = s.attributes.begin(); it != s.attributes.end(); it++) {
|
|
if (it->attribute() == Attribute::DEFAULT_COVER_ART) {
|
|
s.attributes.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Device::IsActive() const {
|
|
return address_ == a2dp_interface_->active_peer();
|
|
}
|
|
|
|
bool Device::IsInSilenceMode() const {
|
|
return a2dp_interface_->is_peer_in_silence_mode(address_);
|
|
}
|
|
|
|
void Device::VendorPacketHandler(uint8_t label,
|
|
std::shared_ptr<VendorPacket> pkt) {
|
|
CHECK(media_interface_);
|
|
DEVICE_VLOG(3) << __func__ << ": pdu=" << pkt->GetCommandPdu();
|
|
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = RejectBuilder::MakeBuilder(static_cast<CommandPdu>(0), Status::INVALID_COMMAND);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
// All CTypes at and above NOT_IMPLEMENTED are all response types.
|
|
if (pkt->GetCType() == CType::NOT_IMPLEMENTED) {
|
|
return;
|
|
}
|
|
|
|
if (pkt->GetCType() >= CType::ACCEPTED) {
|
|
switch (pkt->GetCommandPdu()) {
|
|
// VOLUME_CHANGED is the only notification we register for while target.
|
|
case CommandPdu::REGISTER_NOTIFICATION: {
|
|
auto register_notification =
|
|
Packet::Specialize<RegisterNotificationResponse>(pkt);
|
|
|
|
if (!register_notification->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response =
|
|
RejectBuilder::MakeBuilder(pkt->GetCommandPdu(),
|
|
Status::INVALID_PARAMETER);
|
|
send_message(label, false, std::move(response));
|
|
active_labels_.erase(label);
|
|
volume_interface_ = nullptr;
|
|
volume_ = VOL_REGISTRATION_FAILED;
|
|
return;
|
|
}
|
|
|
|
if (register_notification->GetEvent() != Event::VOLUME_CHANGED) {
|
|
DEVICE_LOG(WARNING)
|
|
<< __func__ << ": Unhandled register notification received: "
|
|
<< register_notification->GetEvent();
|
|
return;
|
|
}
|
|
HandleVolumeChanged(label, register_notification);
|
|
break;
|
|
}
|
|
case CommandPdu::SET_ABSOLUTE_VOLUME:
|
|
// TODO (apanicke): Add a retry mechanism if the response has a
|
|
// different volume than the one we set. For now, we don't care
|
|
// about the response to this message.
|
|
break;
|
|
default:
|
|
DEVICE_LOG(WARNING)
|
|
<< __func__ << ": Unhandled Response: pdu=" << pkt->GetCommandPdu();
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (pkt->GetCommandPdu()) {
|
|
case CommandPdu::GET_CAPABILITIES: {
|
|
HandleGetCapabilities(label,
|
|
Packet::Specialize<GetCapabilitiesRequest>(pkt));
|
|
} break;
|
|
|
|
case CommandPdu::REGISTER_NOTIFICATION: {
|
|
HandleNotification(label,
|
|
Packet::Specialize<RegisterNotificationRequest>(pkt));
|
|
} break;
|
|
|
|
case CommandPdu::GET_ELEMENT_ATTRIBUTES: {
|
|
auto get_element_attributes_request_pkt = Packet::Specialize<GetElementAttributesRequest>(pkt);
|
|
|
|
if (!get_element_attributes_request_pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
media_interface_->GetSongInfo(base::Bind(&Device::GetElementAttributesResponse, weak_ptr_factory_.GetWeakPtr(),
|
|
label, get_element_attributes_request_pkt));
|
|
} break;
|
|
|
|
case CommandPdu::GET_PLAY_STATUS: {
|
|
media_interface_->GetPlayStatus(base::Bind(&Device::GetPlayStatusResponse,
|
|
weak_ptr_factory_.GetWeakPtr(),
|
|
label));
|
|
} break;
|
|
|
|
case CommandPdu::PLAY_ITEM: {
|
|
HandlePlayItem(label, Packet::Specialize<PlayItemRequest>(pkt));
|
|
} break;
|
|
|
|
case CommandPdu::SET_ADDRESSED_PLAYER: {
|
|
// TODO (apanicke): Implement set addressed player. We don't need
|
|
// this currently since the current implementation only has one
|
|
// player and the player will never change, but we need it for a
|
|
// more complete implementation.
|
|
auto set_addressed_player_request = Packet::Specialize<SetAddressedPlayerRequest>(pkt);
|
|
|
|
if (!set_addressed_player_request->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
media_interface_->GetMediaPlayerList(base::Bind(&Device::HandleSetAddressedPlayer, weak_ptr_factory_.GetWeakPtr(),
|
|
label, set_addressed_player_request));
|
|
} break;
|
|
|
|
default: {
|
|
DEVICE_LOG(ERROR) << "Unhandled Vendor Packet: " << pkt->ToString();
|
|
auto response = RejectBuilder::MakeBuilder(
|
|
(CommandPdu)pkt->GetCommandPdu(), Status::INVALID_COMMAND);
|
|
send_message(label, false, std::move(response));
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Device::HandleGetCapabilities(
|
|
uint8_t label, const std::shared_ptr<GetCapabilitiesRequest>& pkt) {
|
|
DEVICE_VLOG(4) << __func__
|
|
<< ": capability=" << pkt->GetCapabilityRequested();
|
|
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
switch (pkt->GetCapabilityRequested()) {
|
|
case Capability::COMPANY_ID: {
|
|
auto response =
|
|
GetCapabilitiesResponseBuilder::MakeCompanyIdBuilder(0x001958);
|
|
response->AddCompanyId(0x002345);
|
|
send_message_cb_.Run(label, false, std::move(response));
|
|
} break;
|
|
|
|
case Capability::EVENTS_SUPPORTED: {
|
|
auto response =
|
|
GetCapabilitiesResponseBuilder::MakeEventsSupportedBuilder(
|
|
Event::PLAYBACK_STATUS_CHANGED);
|
|
response->AddEvent(Event::TRACK_CHANGED);
|
|
response->AddEvent(Event::PLAYBACK_POS_CHANGED);
|
|
|
|
if (!avrcp13_compatibility_) {
|
|
response->AddEvent(Event::AVAILABLE_PLAYERS_CHANGED);
|
|
response->AddEvent(Event::ADDRESSED_PLAYER_CHANGED);
|
|
response->AddEvent(Event::UIDS_CHANGED);
|
|
response->AddEvent(Event::NOW_PLAYING_CONTENT_CHANGED);
|
|
}
|
|
|
|
send_message(label, false, std::move(response));
|
|
} break;
|
|
|
|
default: {
|
|
DEVICE_LOG(WARNING) << "Unhandled Capability: "
|
|
<< pkt->GetCapabilityRequested();
|
|
auto response = RejectBuilder::MakeBuilder(CommandPdu::GET_CAPABILITIES,
|
|
Status::INVALID_PARAMETER);
|
|
send_message(label, false, std::move(response));
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Device::HandleNotification(
|
|
uint8_t label, const std::shared_ptr<RegisterNotificationRequest>& pkt) {
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(),
|
|
Status::INVALID_PARAMETER);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(4) << __func__ << ": event=" << pkt->GetEventRegistered();
|
|
|
|
switch (pkt->GetEventRegistered()) {
|
|
case Event::TRACK_CHANGED: {
|
|
media_interface_->GetNowPlayingList(
|
|
base::Bind(&Device::TrackChangedNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, true));
|
|
} break;
|
|
|
|
case Event::PLAYBACK_STATUS_CHANGED: {
|
|
media_interface_->GetPlayStatus(
|
|
base::Bind(&Device::PlaybackStatusNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, true));
|
|
} break;
|
|
|
|
case Event::PLAYBACK_POS_CHANGED: {
|
|
play_pos_interval_ = pkt->GetInterval();
|
|
media_interface_->GetPlayStatus(
|
|
base::Bind(&Device::PlaybackPosNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, true));
|
|
} break;
|
|
|
|
case Event::NOW_PLAYING_CONTENT_CHANGED: {
|
|
media_interface_->GetNowPlayingList(
|
|
base::Bind(&Device::HandleNowPlayingNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, true));
|
|
} break;
|
|
|
|
case Event::AVAILABLE_PLAYERS_CHANGED: {
|
|
// TODO (apanicke): If we make a separate handler function for this, make
|
|
// sure to register the notification in the interim response.
|
|
|
|
// Respond immediately since this notification doesn't require any info
|
|
avail_players_changed_ = Notification(true, label);
|
|
auto response =
|
|
RegisterNotificationResponseBuilder::MakeAvailablePlayersBuilder(
|
|
true);
|
|
send_message(label, false, std::move(response));
|
|
} break;
|
|
|
|
case Event::ADDRESSED_PLAYER_CHANGED: {
|
|
media_interface_->GetMediaPlayerList(
|
|
base::Bind(&Device::AddressedPlayerNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, true));
|
|
} break;
|
|
|
|
case Event::UIDS_CHANGED: {
|
|
// TODO (apanicke): If we make a separate handler function for this, make
|
|
// sure to register the notification in the interim response.
|
|
|
|
// Respond immediately since this notification doesn't require any info
|
|
uids_changed_ = Notification(true, label);
|
|
auto response =
|
|
RegisterNotificationResponseBuilder::MakeUidsChangedBuilder(true, 0);
|
|
send_message(label, false, std::move(response));
|
|
} break;
|
|
|
|
default: {
|
|
DEVICE_LOG(ERROR) << __func__ << " : Unknown event registered. Event ID="
|
|
<< pkt->GetEventRegistered();
|
|
auto response = RejectBuilder::MakeBuilder(
|
|
(CommandPdu)pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
|
|
send_message(label, false, std::move(response));
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Device::RegisterVolumeChanged() {
|
|
DEVICE_VLOG(2) << __func__;
|
|
if (volume_interface_ == nullptr) return;
|
|
|
|
auto request =
|
|
RegisterNotificationRequestBuilder::MakeBuilder(Event::VOLUME_CHANGED, 0);
|
|
|
|
// Find an open transaction label to prevent conflicts with other commands
|
|
// that are in flight. We can not use the reserved label while the
|
|
// notification hasn't been completed.
|
|
uint8_t label = MAX_TRANSACTION_LABEL;
|
|
for (uint8_t i = 0; i < MAX_TRANSACTION_LABEL; i++) {
|
|
if (active_labels_.find(i) == active_labels_.end()) {
|
|
active_labels_.insert(i);
|
|
label = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (label == MAX_TRANSACTION_LABEL) {
|
|
DEVICE_LOG(FATAL)
|
|
<< __func__
|
|
<< ": Abandon all hope, something went catastrophically wrong";
|
|
}
|
|
|
|
send_message_cb_.Run(label, false, std::move(request));
|
|
}
|
|
|
|
void Device::HandleVolumeChanged(
|
|
uint8_t label, const std::shared_ptr<RegisterNotificationResponse>& pkt) {
|
|
DEVICE_VLOG(1) << __func__ << ": interim=" << pkt->IsInterim();
|
|
|
|
if (volume_interface_ == nullptr) return;
|
|
|
|
if (pkt->GetCType() == CType::REJECTED) {
|
|
// Disable Absolute Volume
|
|
active_labels_.erase(label);
|
|
volume_interface_ = nullptr;
|
|
volume_ = VOL_REGISTRATION_FAILED;
|
|
return;
|
|
}
|
|
|
|
// We only update on interim and just re-register on changes.
|
|
if (!pkt->IsInterim()) {
|
|
active_labels_.erase(label);
|
|
RegisterVolumeChanged();
|
|
return;
|
|
}
|
|
|
|
// Handle the first volume update.
|
|
if (volume_ == VOL_NOT_SUPPORTED) {
|
|
volume_ = pkt->GetVolume();
|
|
volume_interface_->DeviceConnected(
|
|
GetAddress(),
|
|
base::Bind(&Device::SetVolume, weak_ptr_factory_.GetWeakPtr()));
|
|
|
|
// Ignore the returned volume in favor of the volume returned
|
|
// by the volume interface.
|
|
return;
|
|
}
|
|
|
|
if (!IsActive()) {
|
|
DEVICE_VLOG(3) << __func__
|
|
<< ": Ignoring volume changes from non active device";
|
|
return;
|
|
}
|
|
|
|
volume_ = pkt->GetVolume();
|
|
DEVICE_VLOG(1) << __func__ << ": Volume has changed to " << (uint32_t)volume_;
|
|
volume_interface_->SetVolume(volume_);
|
|
}
|
|
|
|
void Device::SetVolume(int8_t volume) {
|
|
// TODO (apanicke): Implement logic for Multi-AVRCP
|
|
DEVICE_VLOG(1) << __func__ << ": volume=" << (int)volume;
|
|
auto request = SetAbsoluteVolumeRequestBuilder::MakeBuilder(volume);
|
|
|
|
uint8_t label = MAX_TRANSACTION_LABEL;
|
|
for (uint8_t i = 0; i < MAX_TRANSACTION_LABEL; i++) {
|
|
if (active_labels_.find(i) == active_labels_.end()) {
|
|
active_labels_.insert(i);
|
|
label = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
volume_ = volume;
|
|
send_message_cb_.Run(label, false, std::move(request));
|
|
}
|
|
|
|
void Device::TrackChangedNotificationResponse(uint8_t label, bool interim,
|
|
std::string curr_song_id,
|
|
std::vector<SongInfo> song_list) {
|
|
DEVICE_VLOG(1) << __func__;
|
|
uint64_t uid = 0;
|
|
|
|
if (interim) {
|
|
track_changed_ = Notification(true, label);
|
|
} else if (!track_changed_.first) {
|
|
DEVICE_VLOG(0) << __func__ << ": Device not registered for update";
|
|
return;
|
|
}
|
|
|
|
// Anytime we use the now playing list, update our map so that its always
|
|
// current
|
|
now_playing_ids_.clear();
|
|
for (const SongInfo& song : song_list) {
|
|
now_playing_ids_.insert(song.media_id);
|
|
if (curr_song_id == song.media_id) {
|
|
DEVICE_VLOG(3) << __func__ << ": Found media ID match for "
|
|
<< song.media_id;
|
|
uid = now_playing_ids_.get_uid(curr_song_id);
|
|
}
|
|
}
|
|
|
|
if (curr_song_id == "") {
|
|
DEVICE_LOG(WARNING) << "Empty media ID";
|
|
uid = 0;
|
|
if (stack_config_get_interface()->get_pts_avrcp_test()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": pts test mode";
|
|
uid = 0xffffffffffffffff;
|
|
}
|
|
}
|
|
|
|
auto response = RegisterNotificationResponseBuilder::MakeTrackChangedBuilder(
|
|
interim, uid);
|
|
send_message_cb_.Run(label, false, std::move(response));
|
|
if (!interim) {
|
|
active_labels_.erase(label);
|
|
track_changed_ = Notification(false, 0);
|
|
}
|
|
}
|
|
|
|
void Device::PlaybackStatusNotificationResponse(uint8_t label, bool interim,
|
|
PlayStatus status) {
|
|
DEVICE_VLOG(1) << __func__;
|
|
if (status.state == PlayState::PAUSED) play_pos_update_cb_.Cancel();
|
|
|
|
if (interim) {
|
|
play_status_changed_ = Notification(true, label);
|
|
} else if (!play_status_changed_.first) {
|
|
DEVICE_VLOG(0) << __func__ << ": Device not registered for update";
|
|
return;
|
|
}
|
|
|
|
auto state_to_send = status.state;
|
|
if (!IsActive()) state_to_send = PlayState::PAUSED;
|
|
if (!interim && state_to_send == last_play_status_.state) {
|
|
DEVICE_VLOG(0) << __func__
|
|
<< ": Not sending notification due to no state update "
|
|
<< address_.ToString();
|
|
return;
|
|
}
|
|
|
|
last_play_status_.state = state_to_send;
|
|
|
|
auto response =
|
|
RegisterNotificationResponseBuilder::MakePlaybackStatusBuilder(
|
|
interim, IsActive() ? status.state : PlayState::PAUSED);
|
|
send_message_cb_.Run(label, false, std::move(response));
|
|
|
|
if (!interim) {
|
|
active_labels_.erase(label);
|
|
play_status_changed_ = Notification(false, 0);
|
|
}
|
|
}
|
|
|
|
void Device::PlaybackPosNotificationResponse(uint8_t label, bool interim,
|
|
PlayStatus status) {
|
|
DEVICE_VLOG(4) << __func__;
|
|
|
|
if (interim) {
|
|
play_pos_changed_ = Notification(true, label);
|
|
} else if (!play_pos_changed_.first) {
|
|
DEVICE_VLOG(3) << __func__ << ": Device not registered for update";
|
|
return;
|
|
}
|
|
|
|
if (!interim && last_play_status_.position == status.position) {
|
|
DEVICE_LOG(WARNING) << address_.ToString()
|
|
<< ": No update to play position";
|
|
return;
|
|
}
|
|
|
|
auto response =
|
|
RegisterNotificationResponseBuilder::MakePlaybackPositionBuilder(
|
|
interim, status.position);
|
|
send_message_cb_.Run(label, false, std::move(response));
|
|
|
|
last_play_status_.position = status.position;
|
|
|
|
if (!interim) {
|
|
active_labels_.erase(label);
|
|
play_pos_changed_ = Notification(false, 0);
|
|
}
|
|
|
|
// We still try to send updates while music is playing to the non active
|
|
// device even though the device thinks the music is paused. This makes
|
|
// the status bar on the remote device move.
|
|
if (status.state == PlayState::PLAYING && !IsInSilenceMode()) {
|
|
DEVICE_VLOG(0) << __func__ << ": Queue next play position update";
|
|
play_pos_update_cb_.Reset(base::Bind(&Device::HandlePlayPosUpdate,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
btbase::AbstractMessageLoop::current_task_runner()->PostDelayedTask(
|
|
FROM_HERE, play_pos_update_cb_.callback(),
|
|
base::TimeDelta::FromSeconds(play_pos_interval_));
|
|
}
|
|
}
|
|
|
|
// TODO (apanicke): Finish implementing when we add support for more than one
|
|
// player
|
|
void Device::AddressedPlayerNotificationResponse(
|
|
uint8_t label, bool interim, uint16_t curr_player,
|
|
std::vector<MediaPlayerInfo> /* unused */) {
|
|
DEVICE_VLOG(1) << __func__
|
|
<< ": curr_player_id=" << (unsigned int)curr_player;
|
|
|
|
if (interim) {
|
|
addr_player_changed_ = Notification(true, label);
|
|
} else if (!addr_player_changed_.first) {
|
|
DEVICE_VLOG(3) << __func__ << ": Device not registered for update";
|
|
return;
|
|
}
|
|
|
|
// If there is no set browsed player, use the current addressed player as the
|
|
// default NOTE: Using any browsing commands before the browsed player is set
|
|
// is a violation of the AVRCP Spec but there are some carkits that try too
|
|
// anyways
|
|
if (curr_browsed_player_id_ == -1) curr_browsed_player_id_ = curr_player;
|
|
|
|
auto response =
|
|
RegisterNotificationResponseBuilder::MakeAddressedPlayerBuilder(
|
|
interim, curr_player, 0x0000);
|
|
send_message_cb_.Run(label, false, std::move(response));
|
|
|
|
if (!interim) {
|
|
active_labels_.erase(label);
|
|
addr_player_changed_ = Notification(false, 0);
|
|
RejectNotification();
|
|
}
|
|
}
|
|
|
|
void Device::RejectNotification() {
|
|
DEVICE_VLOG(1) << __func__;
|
|
Notification* rejectNotification[] = {&play_status_changed_, &track_changed_,
|
|
&play_pos_changed_,
|
|
&now_playing_changed_};
|
|
for (int i = 0; i < 4; i++) {
|
|
uint8_t label = rejectNotification[i]->second;
|
|
auto response = RejectBuilder::MakeBuilder(
|
|
CommandPdu::REGISTER_NOTIFICATION, Status::ADDRESSED_PLAYER_CHANGED);
|
|
send_message_cb_.Run(label, false, std::move(response));
|
|
active_labels_.erase(label);
|
|
rejectNotification[i] = new Notification(false, 0);
|
|
}
|
|
}
|
|
|
|
void Device::GetPlayStatusResponse(uint8_t label, PlayStatus status) {
|
|
DEVICE_VLOG(2) << __func__ << ": position=" << status.position
|
|
<< " duration=" << status.duration
|
|
<< " state=" << status.state;
|
|
auto response = GetPlayStatusResponseBuilder::MakeBuilder(
|
|
status.duration, status.position,
|
|
IsActive() ? status.state : PlayState::PAUSED);
|
|
send_message(label, false, std::move(response));
|
|
}
|
|
|
|
void Device::GetElementAttributesResponse(
|
|
uint8_t label, std::shared_ptr<GetElementAttributesRequest> pkt,
|
|
SongInfo info) {
|
|
auto get_element_attributes_pkt = pkt;
|
|
auto attributes_requested =
|
|
get_element_attributes_pkt->GetAttributesRequested();
|
|
|
|
auto response = GetElementAttributesResponseBuilder::MakeBuilder(ctrl_mtu_);
|
|
|
|
// Filter out DEFAULT_COVER_ART handle if this device has no client
|
|
if (!HasBipClient()) {
|
|
filter_cover_art(info);
|
|
}
|
|
|
|
last_song_info_ = info;
|
|
|
|
if (attributes_requested.size() != 0) {
|
|
for (const auto& attribute : attributes_requested) {
|
|
if (info.attributes.find(attribute) != info.attributes.end()) {
|
|
response->AddAttributeEntry(*info.attributes.find(attribute));
|
|
}
|
|
}
|
|
} else { // zero attributes requested which means all attributes requested
|
|
for (const auto& attribute : info.attributes) {
|
|
response->AddAttributeEntry(attribute);
|
|
}
|
|
}
|
|
|
|
send_message(label, false, std::move(response));
|
|
}
|
|
|
|
void Device::MessageReceived(uint8_t label, std::shared_ptr<Packet> pkt) {
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = RejectBuilder::MakeBuilder(static_cast<CommandPdu>(0), Status::INVALID_COMMAND);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(4) << __func__ << ": opcode=" << pkt->GetOpcode();
|
|
active_labels_.insert(label);
|
|
switch (pkt->GetOpcode()) {
|
|
// TODO (apanicke): Remove handling of UNIT_INFO and SUBUNIT_INFO from
|
|
// the AVRC_API and instead handle it here to reduce fragmentation.
|
|
case Opcode::UNIT_INFO: {
|
|
} break;
|
|
case Opcode::SUBUNIT_INFO: {
|
|
} break;
|
|
case Opcode::PASS_THROUGH: {
|
|
auto pass_through_packet = Packet::Specialize<PassThroughPacket>(pkt);
|
|
|
|
if (!pass_through_packet->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = RejectBuilder::MakeBuilder(static_cast<CommandPdu>(0), Status::INVALID_COMMAND);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
auto response = PassThroughPacketBuilder::MakeBuilder(
|
|
true, pass_through_packet->GetKeyState() == KeyState::PUSHED,
|
|
pass_through_packet->GetOperationId());
|
|
send_message(label, false, std::move(response));
|
|
|
|
// TODO (apanicke): Use an enum for media key ID's
|
|
if (pass_through_packet->GetOperationId() == 0x44 &&
|
|
pass_through_packet->GetKeyState() == KeyState::PUSHED) {
|
|
// We need to get the play status since we need to know
|
|
// what the actual playstate is without being modified
|
|
// by whether the device is active.
|
|
media_interface_->GetPlayStatus(base::Bind(
|
|
[](base::WeakPtr<Device> d, PlayStatus s) {
|
|
if (!d) return;
|
|
|
|
if (!d->IsActive()) {
|
|
LOG(INFO) << "Setting " << d->address_.ToString()
|
|
<< " to be the active device";
|
|
d->media_interface_->SetActiveDevice(d->address_);
|
|
|
|
if (s.state == PlayState::PLAYING) {
|
|
LOG(INFO)
|
|
<< "Skipping sendKeyEvent since music is already playing";
|
|
return;
|
|
}
|
|
}
|
|
|
|
d->media_interface_->SendKeyEvent(0x44, KeyState::PUSHED);
|
|
},
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
return;
|
|
}
|
|
|
|
if (IsActive()) {
|
|
media_interface_->SendKeyEvent(pass_through_packet->GetOperationId(),
|
|
pass_through_packet->GetKeyState());
|
|
}
|
|
} break;
|
|
case Opcode::VENDOR: {
|
|
auto vendor_pkt = Packet::Specialize<VendorPacket>(pkt);
|
|
VendorPacketHandler(label, vendor_pkt);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Device::HandlePlayItem(uint8_t label,
|
|
std::shared_ptr<PlayItemRequest> pkt) {
|
|
DEVICE_VLOG(2) << __func__ << ": scope=" << pkt->GetScope()
|
|
<< " uid=" << pkt->GetUid();
|
|
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
std::string media_id = "";
|
|
switch (pkt->GetScope()) {
|
|
case Scope::NOW_PLAYING:
|
|
media_id = now_playing_ids_.get_media_id(pkt->GetUid());
|
|
break;
|
|
case Scope::VFS:
|
|
media_id = vfs_ids_.get_media_id(pkt->GetUid());
|
|
break;
|
|
default:
|
|
DEVICE_LOG(WARNING) << __func__ << ": Unknown scope for play item";
|
|
}
|
|
|
|
if (media_id == "") {
|
|
DEVICE_VLOG(2) << "Could not find item";
|
|
auto response = RejectBuilder::MakeBuilder(CommandPdu::PLAY_ITEM,
|
|
Status::DOES_NOT_EXIST);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
media_interface_->PlayItem(curr_browsed_player_id_,
|
|
pkt->GetScope() == Scope::NOW_PLAYING, media_id);
|
|
|
|
auto response = PlayItemResponseBuilder::MakeBuilder(Status::NO_ERROR);
|
|
send_message(label, false, std::move(response));
|
|
}
|
|
|
|
void Device::HandleSetAddressedPlayer(
|
|
uint8_t label, std::shared_ptr<SetAddressedPlayerRequest> pkt,
|
|
uint16_t curr_player, std::vector<MediaPlayerInfo> players) {
|
|
DEVICE_VLOG(2) << __func__ << ": PlayerId=" << pkt->GetPlayerId();
|
|
|
|
if (curr_player != pkt->GetPlayerId()) {
|
|
DEVICE_VLOG(2) << "Reject invalid addressed player ID";
|
|
auto response = RejectBuilder::MakeBuilder(CommandPdu::SET_ADDRESSED_PLAYER,
|
|
Status::INVALID_PLAYER_ID);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
auto response =
|
|
SetAddressedPlayerResponseBuilder::MakeBuilder(Status::NO_ERROR);
|
|
send_message(label, false, std::move(response));
|
|
}
|
|
|
|
void Device::BrowseMessageReceived(uint8_t label,
|
|
std::shared_ptr<BrowsePacket> pkt) {
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = GeneralRejectBuilder::MakeBuilder(Status::INVALID_COMMAND);
|
|
send_message(label, false, std::move(response));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(1) << __func__ << ": pdu=" << pkt->GetPdu();
|
|
|
|
switch (pkt->GetPdu()) {
|
|
case BrowsePdu::SET_BROWSED_PLAYER:
|
|
HandleSetBrowsedPlayer(label,
|
|
Packet::Specialize<SetBrowsedPlayerRequest>(pkt));
|
|
break;
|
|
case BrowsePdu::GET_FOLDER_ITEMS:
|
|
HandleGetFolderItems(label,
|
|
Packet::Specialize<GetFolderItemsRequest>(pkt));
|
|
break;
|
|
case BrowsePdu::CHANGE_PATH:
|
|
HandleChangePath(label, Packet::Specialize<ChangePathRequest>(pkt));
|
|
break;
|
|
case BrowsePdu::GET_ITEM_ATTRIBUTES:
|
|
HandleGetItemAttributes(
|
|
label, Packet::Specialize<GetItemAttributesRequest>(pkt));
|
|
break;
|
|
case BrowsePdu::GET_TOTAL_NUMBER_OF_ITEMS:
|
|
HandleGetTotalNumberOfItems(
|
|
label, Packet::Specialize<GetTotalNumberOfItemsRequest>(pkt));
|
|
break;
|
|
default:
|
|
DEVICE_LOG(WARNING) << __func__ << ": " << pkt->GetPdu();
|
|
auto response = GeneralRejectBuilder::MakeBuilder(Status::INVALID_COMMAND);
|
|
send_message(label, true, std::move(response));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Device::HandleGetFolderItems(uint8_t label,
|
|
std::shared_ptr<GetFolderItemsRequest> pkt) {
|
|
if (!pkt->IsValid()) {
|
|
// The specific get folder items builder is unimportant on failure.
|
|
DEVICE_LOG(WARNING) << __func__ << ": Get folder items request packet is not valid";
|
|
auto response =
|
|
GetFolderItemsResponseBuilder::MakePlayerListBuilder(Status::INVALID_PARAMETER, 0x0000, browse_mtu_);
|
|
send_message(label, true, std::move(response));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(2) << __func__ << ": scope=" << pkt->GetScope();
|
|
|
|
switch (pkt->GetScope()) {
|
|
case Scope::MEDIA_PLAYER_LIST:
|
|
media_interface_->GetMediaPlayerList(
|
|
base::Bind(&Device::GetMediaPlayerListResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, pkt));
|
|
break;
|
|
case Scope::VFS:
|
|
media_interface_->GetFolderItems(
|
|
curr_browsed_player_id_, CurrentFolder(),
|
|
base::Bind(&Device::GetVFSListResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, pkt));
|
|
break;
|
|
case Scope::NOW_PLAYING:
|
|
media_interface_->GetNowPlayingList(
|
|
base::Bind(&Device::GetNowPlayingListResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, pkt));
|
|
break;
|
|
default:
|
|
DEVICE_LOG(ERROR) << __func__ << ": " << pkt->GetScope();
|
|
auto response = GetFolderItemsResponseBuilder::MakePlayerListBuilder(Status::INVALID_PARAMETER, 0, browse_mtu_);
|
|
send_message(label, true, std::move(response));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Device::HandleGetTotalNumberOfItems(
|
|
uint8_t label, std::shared_ptr<GetTotalNumberOfItemsRequest> pkt) {
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = GetTotalNumberOfItemsResponseBuilder::MakeBuilder(Status::INVALID_PARAMETER, 0x0000, 0);
|
|
send_message(label, true, std::move(response));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(2) << __func__ << ": scope=" << pkt->GetScope();
|
|
|
|
switch (pkt->GetScope()) {
|
|
case Scope::MEDIA_PLAYER_LIST: {
|
|
media_interface_->GetMediaPlayerList(
|
|
base::Bind(&Device::GetTotalNumberOfItemsMediaPlayersResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label));
|
|
break;
|
|
}
|
|
case Scope::VFS:
|
|
media_interface_->GetFolderItems(
|
|
curr_browsed_player_id_, CurrentFolder(),
|
|
base::Bind(&Device::GetTotalNumberOfItemsVFSResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label));
|
|
break;
|
|
case Scope::NOW_PLAYING:
|
|
media_interface_->GetNowPlayingList(
|
|
base::Bind(&Device::GetTotalNumberOfItemsNowPlayingResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label));
|
|
break;
|
|
default:
|
|
DEVICE_LOG(ERROR) << __func__ << ": " << pkt->GetScope();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Device::GetTotalNumberOfItemsMediaPlayersResponse(
|
|
uint8_t label, uint16_t curr_player, std::vector<MediaPlayerInfo> list) {
|
|
DEVICE_VLOG(2) << __func__ << ": num_items=" << list.size();
|
|
|
|
auto builder = GetTotalNumberOfItemsResponseBuilder::MakeBuilder(
|
|
Status::NO_ERROR, 0x0000, list.size());
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
void Device::GetTotalNumberOfItemsVFSResponse(uint8_t label,
|
|
std::vector<ListItem> list) {
|
|
DEVICE_VLOG(2) << __func__ << ": num_items=" << list.size();
|
|
|
|
auto builder = GetTotalNumberOfItemsResponseBuilder::MakeBuilder(
|
|
Status::NO_ERROR, 0x0000, list.size());
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
void Device::GetTotalNumberOfItemsNowPlayingResponse(
|
|
uint8_t label, std::string curr_song_id, std::vector<SongInfo> list) {
|
|
DEVICE_VLOG(2) << __func__ << ": num_items=" << list.size();
|
|
|
|
auto builder = GetTotalNumberOfItemsResponseBuilder::MakeBuilder(
|
|
Status::NO_ERROR, 0x0000, list.size());
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
void Device::HandleChangePath(uint8_t label,
|
|
std::shared_ptr<ChangePathRequest> pkt) {
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = ChangePathResponseBuilder::MakeBuilder(Status::INVALID_PARAMETER, 0);
|
|
send_message(label, true, std::move(response));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(2) << __func__ << ": direction=" << pkt->GetDirection()
|
|
<< " uid=" << loghex(pkt->GetUid());
|
|
|
|
if (pkt->GetDirection() == Direction::DOWN &&
|
|
vfs_ids_.get_media_id(pkt->GetUid()) == "") {
|
|
DEVICE_LOG(ERROR) << __func__
|
|
<< ": No item found for UID=" << pkt->GetUid();
|
|
auto builder =
|
|
ChangePathResponseBuilder::MakeBuilder(Status::DOES_NOT_EXIST, 0);
|
|
send_message(label, true, std::move(builder));
|
|
return;
|
|
}
|
|
|
|
if (pkt->GetDirection() == Direction::DOWN) {
|
|
current_path_.push(vfs_ids_.get_media_id(pkt->GetUid()));
|
|
DEVICE_VLOG(2) << "Pushing Path to stack: \"" << CurrentFolder() << "\"";
|
|
} else {
|
|
// Don't pop the root id off the stack
|
|
if (current_path_.size() > 1) {
|
|
current_path_.pop();
|
|
} else {
|
|
DEVICE_LOG(ERROR) << "Trying to change directory up past root.";
|
|
auto builder =
|
|
ChangePathResponseBuilder::MakeBuilder(Status::DOES_NOT_EXIST, 0);
|
|
send_message(label, true, std::move(builder));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(2) << "Popping Path from stack: new path=\"" << CurrentFolder()
|
|
<< "\"";
|
|
}
|
|
|
|
media_interface_->GetFolderItems(
|
|
curr_browsed_player_id_, CurrentFolder(),
|
|
base::Bind(&Device::ChangePathResponse, weak_ptr_factory_.GetWeakPtr(),
|
|
label, pkt));
|
|
}
|
|
|
|
void Device::ChangePathResponse(uint8_t label,
|
|
std::shared_ptr<ChangePathRequest> pkt,
|
|
std::vector<ListItem> list) {
|
|
// TODO (apanicke): Reconstruct the VFS ID's here. Right now it gets
|
|
// reconstructed in GetFolderItemsVFS
|
|
auto builder =
|
|
ChangePathResponseBuilder::MakeBuilder(Status::NO_ERROR, list.size());
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
void Device::HandleGetItemAttributes(
|
|
uint8_t label, std::shared_ptr<GetItemAttributesRequest> pkt) {
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto builder = GetItemAttributesResponseBuilder::MakeBuilder(Status::INVALID_PARAMETER, browse_mtu_);
|
|
send_message(label, true, std::move(builder));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(2) << __func__ << ": scope=" << pkt->GetScope()
|
|
<< " uid=" << loghex(pkt->GetUid())
|
|
<< " uid counter=" << loghex(pkt->GetUidCounter());
|
|
if (pkt->GetUidCounter() != 0x0000) { // For database unaware player, use 0
|
|
DEVICE_LOG(WARNING) << "UidCounter is invalid";
|
|
auto builder = GetItemAttributesResponseBuilder::MakeBuilder(
|
|
Status::UIDS_CHANGED, browse_mtu_);
|
|
send_message(label, true, std::move(builder));
|
|
return;
|
|
}
|
|
|
|
switch (pkt->GetScope()) {
|
|
case Scope::NOW_PLAYING: {
|
|
media_interface_->GetNowPlayingList(
|
|
base::Bind(&Device::GetItemAttributesNowPlayingResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, pkt));
|
|
} break;
|
|
case Scope::VFS:
|
|
// TODO (apanicke): Check the vfs_ids_ here. If the item doesn't exist
|
|
// then we can auto send the error without calling up. We do this check
|
|
// later right now though in order to prevent race conditions with updates
|
|
// on the media layer.
|
|
media_interface_->GetFolderItems(
|
|
curr_browsed_player_id_, CurrentFolder(),
|
|
base::Bind(&Device::GetItemAttributesVFSResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, pkt));
|
|
break;
|
|
default:
|
|
DEVICE_LOG(ERROR) << "UNKNOWN SCOPE FOR HANDLE GET ITEM ATTRIBUTES";
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Device::GetItemAttributesNowPlayingResponse(
|
|
uint8_t label, std::shared_ptr<GetItemAttributesRequest> pkt,
|
|
std::string curr_media_id, std::vector<SongInfo> song_list) {
|
|
DEVICE_VLOG(2) << __func__ << ": uid=" << loghex(pkt->GetUid());
|
|
auto builder = GetItemAttributesResponseBuilder::MakeBuilder(Status::NO_ERROR,
|
|
browse_mtu_);
|
|
|
|
auto media_id = now_playing_ids_.get_media_id(pkt->GetUid());
|
|
if (media_id == "") {
|
|
media_id = curr_media_id;
|
|
}
|
|
|
|
DEVICE_VLOG(2) << __func__ << ": media_id=\"" << media_id << "\"";
|
|
|
|
SongInfo info;
|
|
if (song_list.size() == 1) {
|
|
DEVICE_VLOG(2)
|
|
<< __func__
|
|
<< " Send out the only song in the queue as now playing song.";
|
|
info = song_list.front();
|
|
} else {
|
|
for (const auto& temp : song_list) {
|
|
if (temp.media_id == media_id) {
|
|
info = temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter out DEFAULT_COVER_ART handle if this device has no client
|
|
if (!HasBipClient()) {
|
|
filter_cover_art(info);
|
|
}
|
|
|
|
auto attributes_requested = pkt->GetAttributesRequested();
|
|
if (attributes_requested.size() != 0) {
|
|
for (const auto& attribute : attributes_requested) {
|
|
if (info.attributes.find(attribute) != info.attributes.end()) {
|
|
builder->AddAttributeEntry(*info.attributes.find(attribute));
|
|
}
|
|
}
|
|
} else {
|
|
// If zero attributes were requested, that means all attributes were
|
|
// requested
|
|
for (const auto& attribute : info.attributes) {
|
|
builder->AddAttributeEntry(attribute);
|
|
}
|
|
}
|
|
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
void Device::GetItemAttributesVFSResponse(
|
|
uint8_t label, std::shared_ptr<GetItemAttributesRequest> pkt,
|
|
std::vector<ListItem> item_list) {
|
|
DEVICE_VLOG(2) << __func__ << ": uid=" << loghex(pkt->GetUid());
|
|
|
|
auto media_id = vfs_ids_.get_media_id(pkt->GetUid());
|
|
if (media_id == "") {
|
|
LOG(WARNING) << __func__ << ": Item not found";
|
|
auto builder = GetItemAttributesResponseBuilder::MakeBuilder(
|
|
Status::DOES_NOT_EXIST, browse_mtu_);
|
|
send_message(label, true, std::move(builder));
|
|
return;
|
|
}
|
|
|
|
auto builder = GetItemAttributesResponseBuilder::MakeBuilder(Status::NO_ERROR,
|
|
browse_mtu_);
|
|
|
|
ListItem item_requested;
|
|
for (const auto& temp : item_list) {
|
|
if ((temp.type == ListItem::FOLDER && temp.folder.media_id == media_id) ||
|
|
(temp.type == ListItem::SONG && temp.song.media_id == media_id)) {
|
|
item_requested = temp;
|
|
}
|
|
}
|
|
|
|
// Filter out DEFAULT_COVER_ART handle if this device has no client
|
|
if (item_requested.type == ListItem::SONG && !HasBipClient()) {
|
|
filter_cover_art(item_requested.song);
|
|
}
|
|
|
|
// TODO (apanicke): Add a helper function or allow adding a map
|
|
// of attributes to GetItemAttributesResponseBuilder
|
|
auto attributes_requested = pkt->GetAttributesRequested();
|
|
if (item_requested.type == ListItem::FOLDER) {
|
|
if (attributes_requested.size() == 0) {
|
|
builder->AddAttributeEntry(Attribute::TITLE, item_requested.folder.name);
|
|
} else {
|
|
for (auto& attr : attributes_requested) {
|
|
if (attr == Attribute::TITLE) {
|
|
builder->AddAttributeEntry(Attribute::TITLE,
|
|
item_requested.folder.name);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (attributes_requested.size() != 0) {
|
|
for (const auto& attribute : attributes_requested) {
|
|
if (item_requested.song.attributes.find(attribute) !=
|
|
item_requested.song.attributes.end()) {
|
|
builder->AddAttributeEntry(
|
|
*item_requested.song.attributes.find(attribute));
|
|
}
|
|
}
|
|
} else {
|
|
// If zero attributes were requested, that means all attributes were
|
|
// requested
|
|
for (const auto& attribute : item_requested.song.attributes) {
|
|
builder->AddAttributeEntry(attribute);
|
|
}
|
|
}
|
|
}
|
|
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
void Device::GetMediaPlayerListResponse(
|
|
uint8_t label, std::shared_ptr<GetFolderItemsRequest> pkt,
|
|
uint16_t curr_player, std::vector<MediaPlayerInfo> players) {
|
|
DEVICE_VLOG(2) << __func__;
|
|
|
|
if (players.size() == 0) {
|
|
auto no_items_rsp = GetFolderItemsResponseBuilder::MakePlayerListBuilder(
|
|
Status::RANGE_OUT_OF_BOUNDS, 0x0000, browse_mtu_);
|
|
send_message(label, true, std::move(no_items_rsp));
|
|
}
|
|
|
|
auto builder = GetFolderItemsResponseBuilder::MakePlayerListBuilder(
|
|
Status::NO_ERROR, 0x0000, browse_mtu_);
|
|
|
|
// Move the current player to the first slot due to some carkits always
|
|
// connecting to the first listed player rather than using the ID
|
|
// returned by Addressed Player Changed
|
|
for (auto it = players.begin(); it != players.end(); it++) {
|
|
if (it->id == curr_player) {
|
|
DEVICE_VLOG(1) << " Adding player to first spot: " << it->name;
|
|
auto temp_player = *it;
|
|
players.erase(it);
|
|
players.insert(players.begin(), temp_player);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (size_t i = pkt->GetStartItem();
|
|
i <= pkt->GetEndItem() && i < players.size(); i++) {
|
|
MediaPlayerItem item(players[i].id, players[i].name,
|
|
players[i].browsing_supported);
|
|
builder->AddMediaPlayer(item);
|
|
}
|
|
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
std::set<AttributeEntry> filter_attributes_requested(
|
|
const SongInfo& song, const std::vector<Attribute>& attrs) {
|
|
std::set<AttributeEntry> result;
|
|
for (const auto& attr : attrs) {
|
|
if (song.attributes.find(attr) != song.attributes.end()) {
|
|
result.insert(*song.attributes.find(attr));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Device::GetVFSListResponse(uint8_t label,
|
|
std::shared_ptr<GetFolderItemsRequest> pkt,
|
|
std::vector<ListItem> items) {
|
|
DEVICE_VLOG(2) << __func__ << ": start_item=" << pkt->GetStartItem()
|
|
<< " end_item=" << pkt->GetEndItem();
|
|
|
|
// The builder will automatically correct the status if there are zero items
|
|
auto builder = GetFolderItemsResponseBuilder::MakeVFSBuilder(
|
|
Status::NO_ERROR, 0x0000, browse_mtu_);
|
|
|
|
// TODO (apanicke): Add test that checks if vfs_ids_ is the correct size after
|
|
// an operation.
|
|
for (const auto& item : items) {
|
|
if (item.type == ListItem::FOLDER) {
|
|
vfs_ids_.insert(item.folder.media_id);
|
|
} else if (item.type == ListItem::SONG) {
|
|
vfs_ids_.insert(item.song.media_id);
|
|
}
|
|
}
|
|
|
|
// Add the elements retrieved in the last get folder items request and map
|
|
// them to UIDs The maps will be cleared every time a directory change
|
|
// happens. These items do not need to correspond with the now playing list as
|
|
// the UID's only need to be unique in the context of the current scope and
|
|
// the current folder
|
|
for (auto i = pkt->GetStartItem(); i <= pkt->GetEndItem() && i < items.size();
|
|
i++) {
|
|
if (items[i].type == ListItem::FOLDER) {
|
|
auto folder = items[i].folder;
|
|
// right now we always use folders of mixed type
|
|
FolderItem folder_item(vfs_ids_.get_uid(folder.media_id), 0x00,
|
|
folder.is_playable, folder.name);
|
|
if (!builder->AddFolder(folder_item)) break;
|
|
} else if (items[i].type == ListItem::SONG) {
|
|
auto song = items[i].song;
|
|
|
|
// Filter out DEFAULT_COVER_ART handle if this device has no client
|
|
if (!HasBipClient()) {
|
|
filter_cover_art(song);
|
|
}
|
|
|
|
auto title =
|
|
song.attributes.find(Attribute::TITLE) != song.attributes.end()
|
|
? song.attributes.find(Attribute::TITLE)->value()
|
|
: "No Song Info";
|
|
MediaElementItem song_item(vfs_ids_.get_uid(song.media_id), title,
|
|
std::set<AttributeEntry>());
|
|
|
|
if (pkt->GetNumAttributes() == 0x00) { // All attributes requested
|
|
song_item.attributes_ = std::move(song.attributes);
|
|
} else {
|
|
song_item.attributes_ =
|
|
filter_attributes_requested(song, pkt->GetAttributesRequested());
|
|
}
|
|
|
|
// If we fail to add a song, don't accidentally add one later that might
|
|
// fit.
|
|
if (!builder->AddSong(song_item)) break;
|
|
}
|
|
}
|
|
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
void Device::GetNowPlayingListResponse(
|
|
uint8_t label, std::shared_ptr<GetFolderItemsRequest> pkt,
|
|
std::string /* unused curr_song_id */, std::vector<SongInfo> song_list) {
|
|
DEVICE_VLOG(2) << __func__;
|
|
auto builder = GetFolderItemsResponseBuilder::MakeNowPlayingBuilder(
|
|
Status::NO_ERROR, 0x0000, browse_mtu_);
|
|
|
|
now_playing_ids_.clear();
|
|
for (const SongInfo& song : song_list) {
|
|
now_playing_ids_.insert(song.media_id);
|
|
}
|
|
|
|
for (size_t i = pkt->GetStartItem();
|
|
i <= pkt->GetEndItem() && i < song_list.size(); i++) {
|
|
auto song = song_list[i];
|
|
|
|
// Filter out DEFAULT_COVER_ART handle if this device has no client
|
|
if (!HasBipClient()) {
|
|
filter_cover_art(song);
|
|
}
|
|
|
|
auto title = song.attributes.find(Attribute::TITLE) != song.attributes.end()
|
|
? song.attributes.find(Attribute::TITLE)->value()
|
|
: "No Song Info";
|
|
|
|
MediaElementItem item(i + 1, title, std::set<AttributeEntry>());
|
|
if (pkt->GetNumAttributes() == 0x00) {
|
|
item.attributes_ = std::move(song.attributes);
|
|
} else {
|
|
item.attributes_ =
|
|
filter_attributes_requested(song, pkt->GetAttributesRequested());
|
|
}
|
|
|
|
// If we fail to add a song, don't accidentally add one later that might
|
|
// fit.
|
|
if (!builder->AddSong(item)) break;
|
|
}
|
|
|
|
send_message(label, true, std::move(builder));
|
|
}
|
|
|
|
void Device::HandleSetBrowsedPlayer(
|
|
uint8_t label, std::shared_ptr<SetBrowsedPlayerRequest> pkt) {
|
|
if (!pkt->IsValid()) {
|
|
DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
|
|
auto response = SetBrowsedPlayerResponseBuilder::MakeBuilder(Status::INVALID_PARAMETER, 0x0000, 0, 0, "");
|
|
send_message(label, true, std::move(response));
|
|
return;
|
|
}
|
|
|
|
DEVICE_VLOG(2) << __func__ << ": player_id=" << pkt->GetPlayerId();
|
|
media_interface_->SetBrowsedPlayer(
|
|
pkt->GetPlayerId(),
|
|
base::Bind(&Device::SetBrowsedPlayerResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), label, pkt));
|
|
}
|
|
|
|
void Device::SetBrowsedPlayerResponse(
|
|
uint8_t label, std::shared_ptr<SetBrowsedPlayerRequest> pkt, bool success,
|
|
std::string root_id, uint32_t num_items) {
|
|
DEVICE_VLOG(2) << __func__ << ": success=" << success << " root_id=\""
|
|
<< root_id << "\" num_items=" << num_items;
|
|
|
|
if (!success) {
|
|
auto response = SetBrowsedPlayerResponseBuilder::MakeBuilder(
|
|
Status::INVALID_PLAYER_ID, 0x0000, num_items, 0, "");
|
|
send_message(label, true, std::move(response));
|
|
return;
|
|
}
|
|
|
|
if (pkt->GetPlayerId() == 0 && num_items == 0) {
|
|
// Response fail if no browsable player in Bluetooth Player
|
|
auto response = SetBrowsedPlayerResponseBuilder::MakeBuilder(
|
|
Status::PLAYER_NOT_BROWSABLE, 0x0000, num_items, 0, "");
|
|
send_message(label, true, std::move(response));
|
|
return;
|
|
}
|
|
|
|
curr_browsed_player_id_ = pkt->GetPlayerId();
|
|
|
|
// Clear the path and push the new root.
|
|
current_path_ = std::stack<std::string>();
|
|
current_path_.push(root_id);
|
|
|
|
auto response = SetBrowsedPlayerResponseBuilder::MakeBuilder(
|
|
Status::NO_ERROR, 0x0000, num_items, 0, "");
|
|
send_message(label, true, std::move(response));
|
|
}
|
|
|
|
void Device::SendMediaUpdate(bool metadata, bool play_status, bool queue) {
|
|
bool is_silence = IsInSilenceMode();
|
|
|
|
CHECK(media_interface_);
|
|
DEVICE_VLOG(4) << __func__ << ": Metadata=" << metadata
|
|
<< " : play_status= " << play_status << " : queue=" << queue
|
|
<< " ; is_silence=" << is_silence;
|
|
|
|
if (queue) {
|
|
HandleNowPlayingUpdate();
|
|
}
|
|
|
|
if (play_status) {
|
|
HandlePlayStatusUpdate();
|
|
if (!is_silence) {
|
|
HandlePlayPosUpdate();
|
|
}
|
|
}
|
|
|
|
if (metadata) HandleTrackUpdate();
|
|
}
|
|
|
|
void Device::SendFolderUpdate(bool available_players, bool addressed_player,
|
|
bool uids) {
|
|
CHECK(media_interface_);
|
|
DEVICE_VLOG(4) << __func__;
|
|
|
|
if (available_players) {
|
|
HandleAvailablePlayerUpdate();
|
|
}
|
|
|
|
if (addressed_player) {
|
|
HandleAddressedPlayerUpdate();
|
|
}
|
|
}
|
|
|
|
void Device::HandleTrackUpdate() {
|
|
DEVICE_VLOG(2) << __func__;
|
|
if (!track_changed_.first) {
|
|
LOG(WARNING) << "Device is not registered for track changed updates";
|
|
return;
|
|
}
|
|
|
|
media_interface_->GetNowPlayingList(
|
|
base::Bind(&Device::TrackChangedNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), track_changed_.second, false));
|
|
}
|
|
|
|
void Device::HandlePlayStatusUpdate() {
|
|
DEVICE_VLOG(2) << __func__;
|
|
if (!play_status_changed_.first) {
|
|
LOG(WARNING) << "Device is not registered for play status updates";
|
|
return;
|
|
}
|
|
|
|
media_interface_->GetPlayStatus(base::Bind(
|
|
&Device::PlaybackStatusNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), play_status_changed_.second, false));
|
|
}
|
|
|
|
void Device::HandleNowPlayingUpdate() {
|
|
DEVICE_VLOG(2) << __func__;
|
|
|
|
if (!now_playing_changed_.first) {
|
|
LOG(WARNING) << "Device is not registered for now playing updates";
|
|
return;
|
|
}
|
|
|
|
media_interface_->GetNowPlayingList(base::Bind(
|
|
&Device::HandleNowPlayingNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), now_playing_changed_.second, false));
|
|
}
|
|
|
|
void Device::HandleNowPlayingNotificationResponse(
|
|
uint8_t label, bool interim, std::string curr_song_id,
|
|
std::vector<SongInfo> song_list) {
|
|
if (interim) {
|
|
now_playing_changed_ = Notification(true, label);
|
|
} else if (!now_playing_changed_.first) {
|
|
LOG(WARNING) << "Device is not registered for now playing updates";
|
|
return;
|
|
}
|
|
|
|
now_playing_ids_.clear();
|
|
for (const SongInfo& song : song_list) {
|
|
now_playing_ids_.insert(song.media_id);
|
|
}
|
|
|
|
auto response =
|
|
RegisterNotificationResponseBuilder::MakeNowPlayingBuilder(interim);
|
|
send_message(now_playing_changed_.second, false, std::move(response));
|
|
|
|
if (!interim) {
|
|
active_labels_.erase(label);
|
|
now_playing_changed_ = Notification(false, 0);
|
|
}
|
|
}
|
|
|
|
void Device::HandlePlayPosUpdate() {
|
|
DEVICE_VLOG(0) << __func__;
|
|
if (!play_pos_changed_.first) {
|
|
LOG(WARNING) << "Device is not registered for play position updates";
|
|
return;
|
|
}
|
|
|
|
media_interface_->GetPlayStatus(base::Bind(
|
|
&Device::PlaybackPosNotificationResponse, weak_ptr_factory_.GetWeakPtr(),
|
|
play_pos_changed_.second, false));
|
|
}
|
|
|
|
void Device::HandleAvailablePlayerUpdate() {
|
|
DEVICE_VLOG(1) << __func__;
|
|
|
|
if (!avail_players_changed_.first) {
|
|
LOG(WARNING) << "Device is not registered for available player updates";
|
|
return;
|
|
}
|
|
|
|
auto response =
|
|
RegisterNotificationResponseBuilder::MakeAvailablePlayersBuilder(false);
|
|
send_message_cb_.Run(avail_players_changed_.second, false,
|
|
std::move(response));
|
|
|
|
if (!avail_players_changed_.first) {
|
|
active_labels_.erase(avail_players_changed_.second);
|
|
avail_players_changed_ = Notification(false, 0);
|
|
}
|
|
}
|
|
|
|
void Device::HandleAddressedPlayerUpdate() {
|
|
DEVICE_VLOG(1) << __func__;
|
|
if (!addr_player_changed_.first) {
|
|
DEVICE_LOG(WARNING)
|
|
<< "Device is not registered for addressed player updates";
|
|
return;
|
|
}
|
|
media_interface_->GetMediaPlayerList(base::Bind(
|
|
&Device::AddressedPlayerNotificationResponse,
|
|
weak_ptr_factory_.GetWeakPtr(), addr_player_changed_.second, false));
|
|
}
|
|
|
|
void Device::DeviceDisconnected() {
|
|
DEVICE_LOG(INFO) << "Device was disconnected";
|
|
play_pos_update_cb_.Cancel();
|
|
|
|
// TODO (apanicke): Once the interfaces are set in the Device construction,
|
|
// remove these conditionals.
|
|
if (volume_interface_ != nullptr)
|
|
volume_interface_->DeviceDisconnected(GetAddress());
|
|
}
|
|
|
|
static std::string volumeToStr(int8_t volume) {
|
|
if (volume == VOL_NOT_SUPPORTED) return "Absolute Volume not supported";
|
|
if (volume == VOL_REGISTRATION_FAILED)
|
|
return "Volume changed notification was rejected";
|
|
return std::to_string(volume);
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& out, const Device& d) {
|
|
out << d.address_.ToString();
|
|
if (d.IsActive()) out << " <Active>";
|
|
out << std::endl;
|
|
|
|
ScopedIndent indent(out);
|
|
out << "Current Volume: " << volumeToStr(d.volume_) << std::endl;
|
|
out << "Current Browsed Player ID: " << d.curr_browsed_player_id_
|
|
<< std::endl;
|
|
out << "Registered Notifications:\n";
|
|
{
|
|
ScopedIndent indent(out);
|
|
if (d.track_changed_.first) out << "Track Changed\n";
|
|
if (d.play_status_changed_.first) out << "Play Status\n";
|
|
if (d.play_pos_changed_.first) out << "Play Position\n";
|
|
if (d.now_playing_changed_.first) out << "Now Playing\n";
|
|
if (d.addr_player_changed_.first) out << "Addressed Player\n";
|
|
if (d.avail_players_changed_.first) out << "Available Players\n";
|
|
if (d.uids_changed_.first) out << "UIDs Changed\n";
|
|
}
|
|
out << "Last Play State: " << d.last_play_status_.state << std::endl;
|
|
out << "Last Song Sent ID: \"" << d.last_song_info_.media_id << "\"\n";
|
|
out << "Current Folder: \"" << d.CurrentFolder() << "\"\n";
|
|
out << "MTU Sizes: CTRL=" << d.ctrl_mtu_ << " BROWSE=" << d.browse_mtu_
|
|
<< std::endl;
|
|
// TODO (apanicke): Add supported features as well as media keys
|
|
return out;
|
|
}
|
|
|
|
} // namespace avrcp
|
|
} // namespace bluetooth
|