| /* |
| * 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 <base/message_loop/message_loop.h> |
| |
| #include "connection_handler.h" |
| #include "device.h" |
| #include "stack_config.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" |
| |
| 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) {} |
| |
| 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; |
| } |
| |
| bool Device::IsActive() const { |
| return address_ == a2dp_interface_->active_peer(); |
| } |
| |
| void Device::VendorPacketHandler(uint8_t label, |
| std::shared_ptr<VendorPacket> pkt) { |
| CHECK(media_interface_); |
| DEVICE_VLOG(3) << __func__ << ": pdu=" << pkt->GetCommandPdu(); |
| |
| // 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->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: { |
| media_interface_->GetSongInfo(base::Bind( |
| &Device::GetElementAttributesResponse, weak_ptr_factory_.GetWeakPtr(), |
| label, Packet::Specialize<GetElementAttributesRequest>(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. |
| media_interface_->GetMediaPlayerList(base::Bind( |
| &Device::HandleSetAddressedPlayer, weak_ptr_factory_.GetWeakPtr(), |
| label, Packet::Specialize<SetAddressedPlayerRequest>(pkt))); |
| } 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(); |
| |
| 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) { |
| DEVICE_VLOG(4) << __func__ << ": event=" << pkt->GetEventRegistered(); |
| |
| switch (pkt->GetEventRegistered()) { |
| case Event::TRACK_CHANGED: { |
| track_changed_ = Notification(true, label); |
| media_interface_->GetNowPlayingList( |
| base::Bind(&Device::TrackChangedNotificationResponse, |
| weak_ptr_factory_.GetWeakPtr(), label, true)); |
| } break; |
| |
| case Event::PLAYBACK_STATUS_CHANGED: { |
| play_status_changed_ = Notification(true, label); |
| media_interface_->GetPlayStatus( |
| base::Bind(&Device::PlaybackStatusNotificationResponse, |
| weak_ptr_factory_.GetWeakPtr(), label, true)); |
| } break; |
| |
| case Event::PLAYBACK_POS_CHANGED: { |
| play_pos_changed_ = Notification(true, label); |
| 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: { |
| now_playing_changed_ = Notification(true, label); |
| media_interface_->GetNowPlayingList(base::Bind( |
| &Device::HandleNowPlayingNotificationResponse, |
| weak_ptr_factory_.GetWeakPtr(), now_playing_changed_.second, true)); |
| } break; |
| |
| case Event::AVAILABLE_PLAYERS_CHANGED: { |
| // 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: { |
| addr_player_changed_ = Notification(true, label); |
| media_interface_->GetMediaPlayerList( |
| base::Bind(&Device::AddressedPlayerNotificationResponse, |
| weak_ptr_factory_.GetWeakPtr(), label, true)); |
| } break; |
| |
| case Event::UIDS_CHANGED: { |
| // 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 (!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 (!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 (!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) { |
| DEVICE_VLOG(0) << __func__ << ": Queue next play position update"; |
| play_pos_update_cb_.Reset(base::Bind(&Device::HandlePlayPosUpdate, |
| weak_ptr_factory_.GetWeakPtr())); |
| base::MessageLoop::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 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) { |
| DEVICE_VLOG(2) << __func__; |
| auto get_element_attributes_pkt = pkt; |
| auto attributes_requested = |
| get_element_attributes_pkt->GetAttributesRequested(); |
| |
| auto response = GetElementAttributesResponseBuilder::MakeBuilder(ctrl_mtu_); |
| |
| 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) { |
| 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); |
| 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(); |
| |
| 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) { |
| 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( |
| BrowsePdu::GENERAL_REJECT, Status::INVALID_COMMAND); |
| send_message(label, true, std::move(response)); |
| |
| break; |
| } |
| } |
| |
| void Device::HandleGetFolderItems(uint8_t label, |
| std::shared_ptr<GetFolderItemsRequest> pkt) { |
| 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(); |
| break; |
| } |
| } |
| |
| void Device::HandleGetTotalNumberOfItems( |
| uint8_t label, std::shared_ptr<GetTotalNumberOfItemsRequest> pkt) { |
| 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) { |
| 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) { |
| 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; |
| for (const auto& temp : song_list) { |
| if (temp.media_id == media_id) { |
| info = temp; |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| // 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); |
| builder->AddFolder(folder_item); |
| } else if (items[i].type == ListItem::SONG) { |
| auto song = items[i].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()); |
| } |
| |
| builder->AddSong(song_item); |
| } |
| } |
| |
| 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]; |
| 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()); |
| } |
| builder->AddSong(item); |
| } |
| |
| send_message(label, true, std::move(builder)); |
| } |
| |
| void Device::HandleSetBrowsedPlayer( |
| uint8_t label, std::shared_ptr<SetBrowsedPlayerRequest> pkt) { |
| 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; |
| } |
| |
| 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) { |
| CHECK(media_interface_); |
| DEVICE_VLOG(4) << __func__ << ": Metadata=" << metadata |
| << " : play_status= " << play_status << " : queue=" << queue; |
| |
| if (queue) { |
| HandleNowPlayingUpdate(); |
| } |
| |
| if (play_status) { |
| HandlePlayStatusUpdate(); |
| 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 (!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 |