blob: 4ca624b817ee327dddeade38c818dda22ef96c78 [file] [log] [blame]
Ajay Panicker2c822c92018-02-21 21:04:24 -08001/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Jakub Pawlowski67f5f372018-07-23 10:00:25 -070016#include "device.h"
Ajay Panicker2c822c92018-02-21 21:04:24 -080017
18#include <base/message_loop/message_loop.h>
19
Ajay Panickere6552092018-03-31 14:38:53 -070020#include "connection_handler.h"
Jakub Pawlowski9e814612018-05-25 09:31:26 -070021#include "packet/avrcp/avrcp_reject_packet.h"
22#include "packet/avrcp/general_reject_packet.h"
23#include "packet/avrcp/get_play_status_packet.h"
24#include "packet/avrcp/pass_through_packet.h"
25#include "packet/avrcp/set_absolute_volume.h"
26#include "packet/avrcp/set_addressed_player.h"
Jakub Pawlowski67f5f372018-07-23 10:00:25 -070027#include "stack_config.h"
Jakub Pawlowski9e814612018-05-25 09:31:26 -070028
Ajay Panicker2c822c92018-02-21 21:04:24 -080029namespace bluetooth {
30namespace avrcp {
31
32#define DEVICE_LOG(LEVEL) LOG(LEVEL) << address_.ToString() << " : "
33#define DEVICE_VLOG(LEVEL) VLOG(LEVEL) << address_.ToString() << " : "
34
Ajay Panicker341424a2018-04-03 22:28:06 -070035#define VOL_NOT_SUPPORTED -1
36#define VOL_REGISTRATION_FAILED -2
37
Ajay Panicker2c822c92018-02-21 21:04:24 -080038Device::Device(
39 const RawAddress& bdaddr, bool avrcp13_compatibility,
Ajay Panicker8e521032018-02-27 20:51:47 -080040 base::Callback<void(uint8_t label, bool browse,
Ajay Panicker2c822c92018-02-21 21:04:24 -080041 std::unique_ptr<::bluetooth::PacketBuilder> message)>
Ajay Panickere6552092018-03-31 14:38:53 -070042 send_msg_cb,
43 uint16_t ctrl_mtu, uint16_t browse_mtu)
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -070044 : weak_ptr_factory_(this),
45 address_(bdaddr),
Ajay Panicker2c822c92018-02-21 21:04:24 -080046 avrcp13_compatibility_(avrcp13_compatibility),
Ajay Panickere6552092018-03-31 14:38:53 -070047 send_message_cb_(send_msg_cb),
48 ctrl_mtu_(ctrl_mtu),
49 browse_mtu_(browse_mtu) {}
Ajay Panicker2c822c92018-02-21 21:04:24 -080050
51void Device::RegisterInterfaces(MediaInterface* media_interface,
52 A2dpInterface* a2dp_interface,
53 VolumeInterface* volume_interface) {
54 CHECK(media_interface);
Ajay Panicker84a28272018-03-12 16:41:21 -070055 CHECK(a2dp_interface);
Ajay Panicker2c822c92018-02-21 21:04:24 -080056 a2dp_interface_ = a2dp_interface;
Ajay Panicker2c822c92018-02-21 21:04:24 -080057 media_interface_ = media_interface;
58 volume_interface_ = volume_interface;
59}
60
Ajay Panicker4e4a69f2018-12-14 14:55:02 -080061base::WeakPtr<Device> Device::Get() {
62 return weak_ptr_factory_.GetWeakPtr();
63}
64
Ajay Panicker950d9cc2018-08-24 10:32:07 -070065void Device::SetBrowseMtu(uint16_t browse_mtu) {
66 DEVICE_LOG(INFO) << __PRETTY_FUNCTION__ << ": browse_mtu = " << browse_mtu;
67 browse_mtu_ = browse_mtu;
68}
69
Ajay Panicker2c822c92018-02-21 21:04:24 -080070bool Device::IsActive() const {
71 return address_ == a2dp_interface_->active_peer();
72}
73
Ugo Yu76180b62019-01-13 03:12:30 +080074bool Device::IsInSilenceMode() const {
75 return a2dp_interface_->is_peer_in_silence_mode(address_);
76}
77
Ajay Panicker2c822c92018-02-21 21:04:24 -080078void Device::VendorPacketHandler(uint8_t label,
79 std::shared_ptr<VendorPacket> pkt) {
80 CHECK(media_interface_);
81 DEVICE_VLOG(3) << __func__ << ": pdu=" << pkt->GetCommandPdu();
82
Ajay Panicker24680882019-03-05 13:07:09 -080083 if (!pkt->IsValid()) {
84 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
85 auto response = RejectBuilder::MakeBuilder(static_cast<CommandPdu>(0), Status::INVALID_COMMAND);
86 send_message(label, false, std::move(response));
87 return;
88 }
89
Myles Watsonf74cecf2018-05-18 11:31:33 -070090 // All CTypes at and above NOT_IMPLEMENTED are all response types.
91 if (pkt->GetCType() == CType::NOT_IMPLEMENTED) {
92 return;
93 }
94
Ajay Panicker2c822c92018-02-21 21:04:24 -080095 if (pkt->GetCType() >= CType::ACCEPTED) {
96 switch (pkt->GetCommandPdu()) {
97 // VOLUME_CHANGED is the only notification we register for while target.
98 case CommandPdu::REGISTER_NOTIFICATION: {
99 auto register_notification =
100 Packet::Specialize<RegisterNotificationResponse>(pkt);
101 if (register_notification->GetEvent() != Event::VOLUME_CHANGED) {
102 DEVICE_LOG(WARNING)
103 << __func__ << ": Unhandled register notification received: "
104 << register_notification->GetEvent();
105 return;
106 }
107 HandleVolumeChanged(label, register_notification);
108 break;
109 }
110 case CommandPdu::SET_ABSOLUTE_VOLUME:
111 // TODO (apanicke): Add a retry mechanism if the response has a
112 // different volume than the one we set. For now, we don't care
113 // about the response to this message.
114 break;
115 default:
116 DEVICE_LOG(WARNING)
117 << __func__ << ": Unhandled Response: pdu=" << pkt->GetCommandPdu();
118 break;
119 }
120 return;
121 }
122
123 switch (pkt->GetCommandPdu()) {
124 case CommandPdu::GET_CAPABILITIES: {
tedwangbf514e32018-05-09 14:28:04 +0800125 HandleGetCapabilities(label,
126 Packet::Specialize<GetCapabilitiesRequest>(pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800127 } break;
128
129 case CommandPdu::REGISTER_NOTIFICATION: {
130 HandleNotification(label,
131 Packet::Specialize<RegisterNotificationRequest>(pkt));
132 } break;
133
134 case CommandPdu::GET_ELEMENT_ATTRIBUTES: {
Ajay Panicker24680882019-03-05 13:07:09 -0800135 auto get_element_attributes_request_pkt = Packet::Specialize<GetElementAttributesRequest>(pkt);
136
137 if (!get_element_attributes_request_pkt->IsValid()) {
138 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
139 auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
140 send_message(label, false, std::move(response));
141 }
142 media_interface_->GetSongInfo(base::Bind(&Device::GetElementAttributesResponse, weak_ptr_factory_.GetWeakPtr(),
143 label, get_element_attributes_request_pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800144 } break;
145
146 case CommandPdu::GET_PLAY_STATUS: {
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700147 media_interface_->GetPlayStatus(base::Bind(&Device::GetPlayStatusResponse,
148 weak_ptr_factory_.GetWeakPtr(),
149 label));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800150 } break;
151
152 case CommandPdu::PLAY_ITEM: {
153 HandlePlayItem(label, Packet::Specialize<PlayItemRequest>(pkt));
154 } break;
155
156 case CommandPdu::SET_ADDRESSED_PLAYER: {
157 // TODO (apanicke): Implement set addressed player. We don't need
158 // this currently since the current implementation only has one
159 // player and the player will never change, but we need it for a
160 // more complete implementation.
Ajay Panicker24680882019-03-05 13:07:09 -0800161 auto set_addressed_player_request = Packet::Specialize<SetAddressedPlayerRequest>(pkt);
162
163 if (!set_addressed_player_request->IsValid()) {
164 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
165 auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
166 send_message(label, false, std::move(response));
167 return;
168 }
169
170 media_interface_->GetMediaPlayerList(base::Bind(&Device::HandleSetAddressedPlayer, weak_ptr_factory_.GetWeakPtr(),
171 label, set_addressed_player_request));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800172 } break;
173
174 default: {
175 DEVICE_LOG(ERROR) << "Unhandled Vendor Packet: " << pkt->ToString();
176 auto response = RejectBuilder::MakeBuilder(
177 (CommandPdu)pkt->GetCommandPdu(), Status::INVALID_COMMAND);
178 send_message(label, false, std::move(response));
179 } break;
180 }
181}
182
tedwangbf514e32018-05-09 14:28:04 +0800183void Device::HandleGetCapabilities(
184 uint8_t label, const std::shared_ptr<GetCapabilitiesRequest>& pkt) {
185 DEVICE_VLOG(4) << __func__
186 << ": capability=" << pkt->GetCapabilityRequested();
187
Ajay Panicker24680882019-03-05 13:07:09 -0800188 if (!pkt->IsValid()) {
189 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
190 auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
191 send_message(label, false, std::move(response));
192 return;
193 }
194
tedwangbf514e32018-05-09 14:28:04 +0800195 switch (pkt->GetCapabilityRequested()) {
196 case Capability::COMPANY_ID: {
197 auto response =
198 GetCapabilitiesResponseBuilder::MakeCompanyIdBuilder(0x001958);
199 response->AddCompanyId(0x002345);
200 send_message_cb_.Run(label, false, std::move(response));
201 } break;
202
203 case Capability::EVENTS_SUPPORTED: {
204 auto response =
205 GetCapabilitiesResponseBuilder::MakeEventsSupportedBuilder(
206 Event::PLAYBACK_STATUS_CHANGED);
207 response->AddEvent(Event::TRACK_CHANGED);
208 response->AddEvent(Event::PLAYBACK_POS_CHANGED);
209
210 if (!avrcp13_compatibility_) {
211 response->AddEvent(Event::AVAILABLE_PLAYERS_CHANGED);
212 response->AddEvent(Event::ADDRESSED_PLAYER_CHANGED);
213 response->AddEvent(Event::UIDS_CHANGED);
214 response->AddEvent(Event::NOW_PLAYING_CONTENT_CHANGED);
215 }
216
217 send_message(label, false, std::move(response));
218 } break;
219
220 default: {
221 DEVICE_LOG(WARNING) << "Unhandled Capability: "
222 << pkt->GetCapabilityRequested();
223 auto response = RejectBuilder::MakeBuilder(CommandPdu::GET_CAPABILITIES,
224 Status::INVALID_PARAMETER);
225 send_message(label, false, std::move(response));
226 } break;
227 }
228}
229
Ajay Panicker2c822c92018-02-21 21:04:24 -0800230void Device::HandleNotification(
231 uint8_t label, const std::shared_ptr<RegisterNotificationRequest>& pkt) {
Hansong Zhangdb61e462018-07-10 12:37:58 -0700232 if (!pkt->IsValid()) {
Ajay Panicker24680882019-03-05 13:07:09 -0800233 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
Hansong Zhangdb61e462018-07-10 12:37:58 -0700234 auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(),
235 Status::INVALID_PARAMETER);
236 send_message(label, false, std::move(response));
237 return;
238 }
239
Ajay Panicker2c822c92018-02-21 21:04:24 -0800240 DEVICE_VLOG(4) << __func__ << ": event=" << pkt->GetEventRegistered();
241
242 switch (pkt->GetEventRegistered()) {
243 case Event::TRACK_CHANGED: {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800244 media_interface_->GetNowPlayingList(
245 base::Bind(&Device::TrackChangedNotificationResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700246 weak_ptr_factory_.GetWeakPtr(), label, true));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800247 } break;
248
249 case Event::PLAYBACK_STATUS_CHANGED: {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800250 media_interface_->GetPlayStatus(
251 base::Bind(&Device::PlaybackStatusNotificationResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700252 weak_ptr_factory_.GetWeakPtr(), label, true));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800253 } break;
254
255 case Event::PLAYBACK_POS_CHANGED: {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800256 play_pos_interval_ = pkt->GetInterval();
257 media_interface_->GetPlayStatus(
258 base::Bind(&Device::PlaybackPosNotificationResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700259 weak_ptr_factory_.GetWeakPtr(), label, true));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800260 } break;
261
262 case Event::NOW_PLAYING_CONTENT_CHANGED: {
Ajay Panicker817e6bb2018-06-26 14:52:56 -0700263 media_interface_->GetNowPlayingList(
264 base::Bind(&Device::HandleNowPlayingNotificationResponse,
265 weak_ptr_factory_.GetWeakPtr(), label, true));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800266 } break;
267
268 case Event::AVAILABLE_PLAYERS_CHANGED: {
Ajay Panicker817e6bb2018-06-26 14:52:56 -0700269 // TODO (apanicke): If we make a separate handler function for this, make
270 // sure to register the notification in the interim response.
271
Ajay Panicker2c822c92018-02-21 21:04:24 -0800272 // Respond immediately since this notification doesn't require any info
273 avail_players_changed_ = Notification(true, label);
274 auto response =
275 RegisterNotificationResponseBuilder::MakeAvailablePlayersBuilder(
276 true);
277 send_message(label, false, std::move(response));
278 } break;
279
280 case Event::ADDRESSED_PLAYER_CHANGED: {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800281 media_interface_->GetMediaPlayerList(
282 base::Bind(&Device::AddressedPlayerNotificationResponse,
tedwang7130af12018-05-18 18:16:40 +0800283 weak_ptr_factory_.GetWeakPtr(), label, true));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800284 } break;
285
286 case Event::UIDS_CHANGED: {
Ajay Panicker817e6bb2018-06-26 14:52:56 -0700287 // TODO (apanicke): If we make a separate handler function for this, make
288 // sure to register the notification in the interim response.
289
Ajay Panicker2c822c92018-02-21 21:04:24 -0800290 // Respond immediately since this notification doesn't require any info
291 uids_changed_ = Notification(true, label);
292 auto response =
293 RegisterNotificationResponseBuilder::MakeUidsChangedBuilder(true, 0);
294 send_message(label, false, std::move(response));
295 } break;
296
297 default: {
298 DEVICE_LOG(ERROR) << __func__ << " : Unknown event registered. Event ID="
299 << pkt->GetEventRegistered();
300 auto response = RejectBuilder::MakeBuilder(
tedwang178c7b82018-05-15 15:25:11 +0800301 (CommandPdu)pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
Ajay Panicker2c822c92018-02-21 21:04:24 -0800302 send_message(label, false, std::move(response));
303 } break;
304 }
305}
306
307void Device::RegisterVolumeChanged() {
308 DEVICE_VLOG(2) << __func__;
309 if (volume_interface_ == nullptr) return;
310
311 auto request =
312 RegisterNotificationRequestBuilder::MakeBuilder(Event::VOLUME_CHANGED, 0);
313
314 // Find an open transaction label to prevent conflicts with other commands
315 // that are in flight. We can not use the reserved label while the
316 // notification hasn't been completed.
317 uint8_t label = MAX_TRANSACTION_LABEL;
318 for (uint8_t i = 0; i < MAX_TRANSACTION_LABEL; i++) {
319 if (active_labels_.find(i) == active_labels_.end()) {
320 active_labels_.insert(i);
321 label = i;
322 break;
323 }
324 }
325
326 if (label == MAX_TRANSACTION_LABEL) {
327 DEVICE_LOG(FATAL)
328 << __func__
329 << ": Abandon all hope, something went catastrophically wrong";
330 }
331
332 send_message_cb_.Run(label, false, std::move(request));
333}
334
335void Device::HandleVolumeChanged(
336 uint8_t label, const std::shared_ptr<RegisterNotificationResponse>& pkt) {
337 DEVICE_VLOG(1) << __func__ << ": interim=" << pkt->IsInterim();
Ajay Panicker24680882019-03-05 13:07:09 -0800338
339 if (!pkt->IsValid()) {
340 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
341 auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
342 send_message(label, false, std::move(response));
343 active_labels_.erase(label);
344 volume_interface_ = nullptr;
345 volume_ = VOL_REGISTRATION_FAILED;
346 return;
347 }
348
Ajay Panicker2c822c92018-02-21 21:04:24 -0800349 if (volume_interface_ == nullptr) return;
350
Ajay Panicker341424a2018-04-03 22:28:06 -0700351 if (pkt->GetCType() == CType::REJECTED) {
352 // Disable Absolute Volume
353 active_labels_.erase(label);
354 volume_interface_ = nullptr;
355 volume_ = VOL_REGISTRATION_FAILED;
356 return;
357 }
358
Ajay Panicker2c822c92018-02-21 21:04:24 -0800359 // We only update on interim and just re-register on changes.
360 if (!pkt->IsInterim()) {
361 active_labels_.erase(label);
362 RegisterVolumeChanged();
363 return;
364 }
365
366 // Handle the first volume update.
Ajay Panicker341424a2018-04-03 22:28:06 -0700367 if (volume_ == VOL_NOT_SUPPORTED) {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800368 volume_ = pkt->GetVolume();
369 volume_interface_->DeviceConnected(
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700370 GetAddress(),
371 base::Bind(&Device::SetVolume, weak_ptr_factory_.GetWeakPtr()));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800372
373 // Ignore the returned volume in favor of the volume returned
374 // by the volume interface.
375 return;
376 }
377
Ajay Panicker071caca2018-05-02 10:05:24 -0700378 if (!IsActive()) {
379 DEVICE_VLOG(3) << __func__
380 << ": Ignoring volume changes from non active device";
381 return;
382 }
383
Ajay Panicker2c822c92018-02-21 21:04:24 -0800384 volume_ = pkt->GetVolume();
385 DEVICE_VLOG(1) << __func__ << ": Volume has changed to " << (uint32_t)volume_;
386 volume_interface_->SetVolume(volume_);
387}
388
389void Device::SetVolume(int8_t volume) {
390 // TODO (apanicke): Implement logic for Multi-AVRCP
391 DEVICE_VLOG(1) << __func__ << ": volume=" << (int)volume;
392 auto request = SetAbsoluteVolumeRequestBuilder::MakeBuilder(volume);
393
394 uint8_t label = MAX_TRANSACTION_LABEL;
395 for (uint8_t i = 0; i < MAX_TRANSACTION_LABEL; i++) {
396 if (active_labels_.find(i) == active_labels_.end()) {
397 active_labels_.insert(i);
398 label = i;
399 break;
400 }
401 }
402
Ajay Panicker341424a2018-04-03 22:28:06 -0700403 volume_ = volume;
Ajay Panicker2c822c92018-02-21 21:04:24 -0800404 send_message_cb_.Run(label, false, std::move(request));
405}
406
407void Device::TrackChangedNotificationResponse(uint8_t label, bool interim,
408 std::string curr_song_id,
409 std::vector<SongInfo> song_list) {
410 DEVICE_VLOG(1) << __func__;
411 uint64_t uid = 0;
412
Ajay Panicker817e6bb2018-06-26 14:52:56 -0700413 if (interim) {
414 track_changed_ = Notification(true, label);
415 } else if (!track_changed_.first) {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800416 DEVICE_VLOG(0) << __func__ << ": Device not registered for update";
417 return;
418 }
419
420 // Anytime we use the now playing list, update our map so that its always
421 // current
422 now_playing_ids_.clear();
423 for (const SongInfo& song : song_list) {
424 now_playing_ids_.insert(song.media_id);
425 if (curr_song_id == song.media_id) {
426 DEVICE_VLOG(3) << __func__ << ": Found media ID match for "
427 << song.media_id;
428 uid = now_playing_ids_.get_uid(curr_song_id);
429 }
430 }
431
Ajay Panicker8b7b7372018-05-08 16:34:04 -0700432 if (curr_song_id == "") {
433 DEVICE_LOG(WARNING) << "Empty media ID";
434 uid = 0;
tedwang494dcc52018-05-16 19:51:26 +0800435 if (stack_config_get_interface()->get_pts_avrcp_test()) {
436 DEVICE_LOG(WARNING) << __func__ << ": pts test mode";
437 uid = 0xffffffffffffffff;
438 }
Ajay Panicker8b7b7372018-05-08 16:34:04 -0700439 }
440
Ajay Panicker2c822c92018-02-21 21:04:24 -0800441 auto response = RegisterNotificationResponseBuilder::MakeTrackChangedBuilder(
442 interim, uid);
443 send_message_cb_.Run(label, false, std::move(response));
444 if (!interim) {
445 active_labels_.erase(label);
446 track_changed_ = Notification(false, 0);
447 }
448}
449
450void Device::PlaybackStatusNotificationResponse(uint8_t label, bool interim,
451 PlayStatus status) {
452 DEVICE_VLOG(1) << __func__;
453 if (status.state == PlayState::PAUSED) play_pos_update_cb_.Cancel();
454
Ajay Panicker817e6bb2018-06-26 14:52:56 -0700455 if (interim) {
456 play_status_changed_ = Notification(true, label);
457 } else if (!play_status_changed_.first) {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800458 DEVICE_VLOG(0) << __func__ << ": Device not registered for update";
459 return;
460 }
461
462 auto state_to_send = status.state;
463 if (!IsActive()) state_to_send = PlayState::PAUSED;
464 if (!interim && state_to_send == last_play_status_.state) {
465 DEVICE_VLOG(0) << __func__
466 << ": Not sending notification due to no state update "
467 << address_.ToString();
468 return;
469 }
470
471 last_play_status_.state = state_to_send;
472
473 auto response =
474 RegisterNotificationResponseBuilder::MakePlaybackStatusBuilder(
475 interim, IsActive() ? status.state : PlayState::PAUSED);
476 send_message_cb_.Run(label, false, std::move(response));
477
478 if (!interim) {
479 active_labels_.erase(label);
480 play_status_changed_ = Notification(false, 0);
481 }
482}
483
484void Device::PlaybackPosNotificationResponse(uint8_t label, bool interim,
485 PlayStatus status) {
486 DEVICE_VLOG(4) << __func__;
487
Ajay Panicker817e6bb2018-06-26 14:52:56 -0700488 if (interim) {
489 play_pos_changed_ = Notification(true, label);
490 } else if (!play_pos_changed_.first) {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800491 DEVICE_VLOG(3) << __func__ << ": Device not registered for update";
492 return;
493 }
494
495 if (!interim && last_play_status_.position == status.position) {
496 DEVICE_LOG(WARNING) << address_.ToString()
497 << ": No update to play position";
498 return;
499 }
500
501 auto response =
502 RegisterNotificationResponseBuilder::MakePlaybackPositionBuilder(
503 interim, status.position);
504 send_message_cb_.Run(label, false, std::move(response));
505
506 last_play_status_.position = status.position;
507
508 if (!interim) {
509 active_labels_.erase(label);
510 play_pos_changed_ = Notification(false, 0);
511 }
512
513 // We still try to send updates while music is playing to the non active
514 // device even though the device thinks the music is paused. This makes
515 // the status bar on the remote device move.
Ugo Yu76180b62019-01-13 03:12:30 +0800516 if (status.state == PlayState::PLAYING && !IsInSilenceMode()) {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800517 DEVICE_VLOG(0) << __func__ << ": Queue next play position update";
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700518 play_pos_update_cb_.Reset(base::Bind(&Device::HandlePlayPosUpdate,
519 weak_ptr_factory_.GetWeakPtr()));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800520 base::MessageLoop::current()->task_runner()->PostDelayedTask(
521 FROM_HERE, play_pos_update_cb_.callback(),
522 base::TimeDelta::FromSeconds(play_pos_interval_));
523 }
524}
525
526// TODO (apanicke): Finish implementing when we add support for more than one
527// player
528void Device::AddressedPlayerNotificationResponse(
529 uint8_t label, bool interim, uint16_t curr_player,
530 std::vector<MediaPlayerInfo> /* unused */) {
531 DEVICE_VLOG(1) << __func__
532 << ": curr_player_id=" << (unsigned int)curr_player;
Ajay Panicker817e6bb2018-06-26 14:52:56 -0700533
534 if (interim) {
535 addr_player_changed_ = Notification(true, label);
536 } else if (!addr_player_changed_.first) {
537 DEVICE_VLOG(3) << __func__ << ": Device not registered for update";
538 return;
539 }
540
Ajay Panicker2c822c92018-02-21 21:04:24 -0800541 // If there is no set browsed player, use the current addressed player as the
542 // default NOTE: Using any browsing commands before the browsed player is set
543 // is a violation of the AVRCP Spec but there are some carkits that try too
544 // anyways
545 if (curr_browsed_player_id_ == -1) curr_browsed_player_id_ = curr_player;
546
547 auto response =
548 RegisterNotificationResponseBuilder::MakeAddressedPlayerBuilder(
tedwang7130af12018-05-18 18:16:40 +0800549 interim, curr_player, 0x0000);
Ajay Panicker2c822c92018-02-21 21:04:24 -0800550 send_message_cb_.Run(label, false, std::move(response));
551
552 if (!interim) {
553 active_labels_.erase(label);
554 addr_player_changed_ = Notification(false, 0);
tedwang7130af12018-05-18 18:16:40 +0800555 RejectNotification();
556 }
557}
558
559void Device::RejectNotification() {
560 DEVICE_VLOG(1) << __func__;
561 Notification* rejectNotification[] = {&play_status_changed_, &track_changed_,
562 &play_pos_changed_,
563 &now_playing_changed_};
564 for (int i = 0; i < 4; i++) {
565 uint8_t label = rejectNotification[i]->second;
566 auto response = RejectBuilder::MakeBuilder(
567 CommandPdu::REGISTER_NOTIFICATION, Status::ADDRESSED_PLAYER_CHANGED);
568 send_message_cb_.Run(label, false, std::move(response));
569 active_labels_.erase(label);
570 rejectNotification[i] = new Notification(false, 0);
Ajay Panicker2c822c92018-02-21 21:04:24 -0800571 }
572}
573
574void Device::GetPlayStatusResponse(uint8_t label, PlayStatus status) {
575 DEVICE_VLOG(2) << __func__ << ": position=" << status.position
576 << " duration=" << status.duration
577 << " state=" << status.state;
578 auto response = GetPlayStatusResponseBuilder::MakeBuilder(
579 status.duration, status.position,
580 IsActive() ? status.state : PlayState::PAUSED);
581 send_message(label, false, std::move(response));
582}
583
584void Device::GetElementAttributesResponse(
585 uint8_t label, std::shared_ptr<GetElementAttributesRequest> pkt,
586 SongInfo info) {
587 DEVICE_VLOG(2) << __func__;
Ajay Panicker24680882019-03-05 13:07:09 -0800588
Ajay Panicker2c822c92018-02-21 21:04:24 -0800589 auto get_element_attributes_pkt = pkt;
590 auto attributes_requested =
591 get_element_attributes_pkt->GetAttributesRequested();
592
Ajay Panickere6552092018-03-31 14:38:53 -0700593 auto response = GetElementAttributesResponseBuilder::MakeBuilder(ctrl_mtu_);
Ajay Panicker2c822c92018-02-21 21:04:24 -0800594
595 last_song_info_ = info;
596
597 if (attributes_requested.size() != 0) {
598 for (const auto& attribute : attributes_requested) {
599 if (info.attributes.find(attribute) != info.attributes.end()) {
600 response->AddAttributeEntry(*info.attributes.find(attribute));
601 }
602 }
603 } else { // zero attributes requested which means all attributes requested
604 for (const auto& attribute : info.attributes) {
605 response->AddAttributeEntry(attribute);
606 }
607 }
608
609 send_message(label, false, std::move(response));
610}
611
612void Device::MessageReceived(uint8_t label, std::shared_ptr<Packet> pkt) {
Ajay Panicker24680882019-03-05 13:07:09 -0800613 if (!pkt->IsValid()) {
614 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
615 auto response = RejectBuilder::MakeBuilder(static_cast<CommandPdu>(0), Status::INVALID_COMMAND);
616 send_message(label, false, std::move(response));
617 return;
618 }
619
Ajay Panicker2c822c92018-02-21 21:04:24 -0800620 DEVICE_VLOG(4) << __func__ << ": opcode=" << pkt->GetOpcode();
Ajay Panicker2c822c92018-02-21 21:04:24 -0800621 active_labels_.insert(label);
Ajay Panicker2c822c92018-02-21 21:04:24 -0800622 switch (pkt->GetOpcode()) {
623 // TODO (apanicke): Remove handling of UNIT_INFO and SUBUNIT_INFO from
624 // the AVRC_API and instead handle it here to reduce fragmentation.
625 case Opcode::UNIT_INFO: {
626 } break;
627 case Opcode::SUBUNIT_INFO: {
628 } break;
629 case Opcode::PASS_THROUGH: {
630 auto pass_through_packet = Packet::Specialize<PassThroughPacket>(pkt);
Ajay Panicker24680882019-03-05 13:07:09 -0800631
632 if (!pass_through_packet->IsValid()) {
633 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
634 auto response = RejectBuilder::MakeBuilder(static_cast<CommandPdu>(0), Status::INVALID_COMMAND);
635 send_message(label, false, std::move(response));
636 return;
637 }
638
Ajay Panicker2c822c92018-02-21 21:04:24 -0800639 auto response = PassThroughPacketBuilder::MakeBuilder(
Ajay Panickerc4546f52018-05-04 14:30:45 -0700640 true, pass_through_packet->GetKeyState() == KeyState::PUSHED,
Ajay Panicker2c822c92018-02-21 21:04:24 -0800641 pass_through_packet->GetOperationId());
642 send_message(label, false, std::move(response));
643
Ajay Panickerc4546f52018-05-04 14:30:45 -0700644 // TODO (apanicke): Use an enum for media key ID's
Ajay Panicker2c822c92018-02-21 21:04:24 -0800645 if (pass_through_packet->GetOperationId() == 0x44 &&
Ajay Panickerc4546f52018-05-04 14:30:45 -0700646 pass_through_packet->GetKeyState() == KeyState::PUSHED) {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800647 // We need to get the play status since we need to know
648 // what the actual playstate is without being modified
649 // by whether the device is active.
650 media_interface_->GetPlayStatus(base::Bind(
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700651 [](base::WeakPtr<Device> d, PlayStatus s) {
652 if (!d) return;
653
Ajay Panicker2c822c92018-02-21 21:04:24 -0800654 if (!d->IsActive()) {
655 LOG(INFO) << "Setting " << d->address_.ToString()
656 << " to be the active device";
657 d->media_interface_->SetActiveDevice(d->address_);
658
659 if (s.state == PlayState::PLAYING) {
660 LOG(INFO)
661 << "Skipping sendKeyEvent since music is already playing";
662 return;
663 }
664 }
665
Ajay Panickerc4546f52018-05-04 14:30:45 -0700666 d->media_interface_->SendKeyEvent(0x44, KeyState::PUSHED);
Ajay Panicker2c822c92018-02-21 21:04:24 -0800667 },
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700668 weak_ptr_factory_.GetWeakPtr()));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800669 return;
670 }
671
Ajay Panickerc4546f52018-05-04 14:30:45 -0700672 if (IsActive()) {
673 media_interface_->SendKeyEvent(pass_through_packet->GetOperationId(),
674 pass_through_packet->GetKeyState());
675 }
Ajay Panicker2c822c92018-02-21 21:04:24 -0800676 } break;
677 case Opcode::VENDOR: {
678 auto vendor_pkt = Packet::Specialize<VendorPacket>(pkt);
679 VendorPacketHandler(label, vendor_pkt);
680 } break;
681 }
682}
683
684void Device::HandlePlayItem(uint8_t label,
685 std::shared_ptr<PlayItemRequest> pkt) {
686 DEVICE_VLOG(2) << __func__ << ": scope=" << pkt->GetScope()
687 << " uid=" << pkt->GetUid();
688
Ajay Panicker24680882019-03-05 13:07:09 -0800689 if (!pkt->IsValid()) {
690 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
691 auto response = RejectBuilder::MakeBuilder(pkt->GetCommandPdu(), Status::INVALID_PARAMETER);
692 send_message(label, false, std::move(response));
693 return;
694 }
695
Ajay Panicker2c822c92018-02-21 21:04:24 -0800696 std::string media_id = "";
697 switch (pkt->GetScope()) {
698 case Scope::NOW_PLAYING:
699 media_id = now_playing_ids_.get_media_id(pkt->GetUid());
700 break;
701 case Scope::VFS:
702 media_id = vfs_ids_.get_media_id(pkt->GetUid());
703 break;
704 default:
705 DEVICE_LOG(WARNING) << __func__ << ": Unknown scope for play item";
706 }
707
708 if (media_id == "") {
709 DEVICE_VLOG(2) << "Could not find item";
Hansong Zhangc2f06bd2018-05-30 14:16:23 -0700710 auto response = RejectBuilder::MakeBuilder(CommandPdu::PLAY_ITEM,
711 Status::DOES_NOT_EXIST);
Ajay Panicker2c822c92018-02-21 21:04:24 -0800712 send_message(label, false, std::move(response));
713 return;
714 }
715
716 media_interface_->PlayItem(curr_browsed_player_id_,
717 pkt->GetScope() == Scope::NOW_PLAYING, media_id);
718
719 auto response = PlayItemResponseBuilder::MakeBuilder(Status::NO_ERROR);
720 send_message(label, false, std::move(response));
721}
722
Benson Li97c8fb62018-06-25 14:11:20 +0800723void Device::HandleSetAddressedPlayer(
724 uint8_t label, std::shared_ptr<SetAddressedPlayerRequest> pkt,
725 uint16_t curr_player, std::vector<MediaPlayerInfo> players) {
726 DEVICE_VLOG(2) << __func__ << ": PlayerId=" << pkt->GetPlayerId();
727
728 if (curr_player != pkt->GetPlayerId()) {
729 DEVICE_VLOG(2) << "Reject invalid addressed player ID";
730 auto response = RejectBuilder::MakeBuilder(CommandPdu::SET_ADDRESSED_PLAYER,
731 Status::INVALID_PLAYER_ID);
732 send_message(label, false, std::move(response));
733 return;
734 }
735
736 auto response =
737 SetAddressedPlayerResponseBuilder::MakeBuilder(Status::NO_ERROR);
738 send_message(label, false, std::move(response));
739}
740
Ajay Panicker2c822c92018-02-21 21:04:24 -0800741void Device::BrowseMessageReceived(uint8_t label,
742 std::shared_ptr<BrowsePacket> pkt) {
Ajay Panicker24680882019-03-05 13:07:09 -0800743 if (!pkt->IsValid()) {
744 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
745 auto response = GeneralRejectBuilder::MakeBuilder(Status::INVALID_COMMAND);
746 send_message(label, false, std::move(response));
747 return;
748 }
749
Ajay Panicker2c822c92018-02-21 21:04:24 -0800750 DEVICE_VLOG(1) << __func__ << ": pdu=" << pkt->GetPdu();
751
752 switch (pkt->GetPdu()) {
753 case BrowsePdu::SET_BROWSED_PLAYER:
754 HandleSetBrowsedPlayer(label,
755 Packet::Specialize<SetBrowsedPlayerRequest>(pkt));
756 break;
757 case BrowsePdu::GET_FOLDER_ITEMS:
758 HandleGetFolderItems(label,
759 Packet::Specialize<GetFolderItemsRequest>(pkt));
760 break;
761 case BrowsePdu::CHANGE_PATH:
762 HandleChangePath(label, Packet::Specialize<ChangePathRequest>(pkt));
763 break;
764 case BrowsePdu::GET_ITEM_ATTRIBUTES:
765 HandleGetItemAttributes(
766 label, Packet::Specialize<GetItemAttributesRequest>(pkt));
767 break;
Ajay Panicker0128c372018-04-27 06:59:22 -0700768 case BrowsePdu::GET_TOTAL_NUMBER_OF_ITEMS:
769 HandleGetTotalNumberOfItems(
770 label, Packet::Specialize<GetTotalNumberOfItemsRequest>(pkt));
771 break;
Ajay Panicker2c822c92018-02-21 21:04:24 -0800772 default:
tedwang75701752018-05-15 22:21:39 +0800773 DEVICE_LOG(WARNING) << __func__ << ": " << pkt->GetPdu();
Ajay Panicker24680882019-03-05 13:07:09 -0800774 auto response = GeneralRejectBuilder::MakeBuilder(Status::INVALID_COMMAND);
tedwang75701752018-05-15 22:21:39 +0800775 send_message(label, true, std::move(response));
776
Ajay Panicker2c822c92018-02-21 21:04:24 -0800777 break;
778 }
779}
780
781void Device::HandleGetFolderItems(uint8_t label,
782 std::shared_ptr<GetFolderItemsRequest> pkt) {
Ajay Panicker24680882019-03-05 13:07:09 -0800783 if (!pkt->IsValid()) {
784 // The specific get folder items builder is unimportant on failure.
785 DEVICE_LOG(WARNING) << __func__ << ": Get folder items request packet is not valid";
786 auto response =
787 GetFolderItemsResponseBuilder::MakePlayerListBuilder(Status::INVALID_PARAMETER, 0x0000, browse_mtu_);
788 send_message(label, true, std::move(response));
789 return;
790 }
791
Ajay Panicker2c822c92018-02-21 21:04:24 -0800792 DEVICE_VLOG(2) << __func__ << ": scope=" << pkt->GetScope();
793
794 switch (pkt->GetScope()) {
795 case Scope::MEDIA_PLAYER_LIST:
796 media_interface_->GetMediaPlayerList(
797 base::Bind(&Device::GetMediaPlayerListResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700798 weak_ptr_factory_.GetWeakPtr(), label, pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800799 break;
800 case Scope::VFS:
801 media_interface_->GetFolderItems(
802 curr_browsed_player_id_, CurrentFolder(),
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700803 base::Bind(&Device::GetVFSListResponse,
804 weak_ptr_factory_.GetWeakPtr(), label, pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800805 break;
806 case Scope::NOW_PLAYING:
807 media_interface_->GetNowPlayingList(
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700808 base::Bind(&Device::GetNowPlayingListResponse,
809 weak_ptr_factory_.GetWeakPtr(), label, pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800810 break;
811 default:
812 DEVICE_LOG(ERROR) << __func__ << ": " << pkt->GetScope();
Ajay Panicker24680882019-03-05 13:07:09 -0800813 auto response = GetFolderItemsResponseBuilder::MakePlayerListBuilder(Status::INVALID_PARAMETER, 0, browse_mtu_);
814 send_message(label, true, std::move(response));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800815 break;
816 }
817}
818
Ajay Panicker0128c372018-04-27 06:59:22 -0700819void Device::HandleGetTotalNumberOfItems(
820 uint8_t label, std::shared_ptr<GetTotalNumberOfItemsRequest> pkt) {
Ajay Panicker24680882019-03-05 13:07:09 -0800821 if (!pkt->IsValid()) {
822 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
823 auto response = GetTotalNumberOfItemsResponseBuilder::MakeBuilder(Status::INVALID_PARAMETER, 0x0000, 0);
824 send_message(label, true, std::move(response));
825 return;
826 }
827
Ajay Panicker0128c372018-04-27 06:59:22 -0700828 DEVICE_VLOG(2) << __func__ << ": scope=" << pkt->GetScope();
829
830 switch (pkt->GetScope()) {
831 case Scope::MEDIA_PLAYER_LIST: {
832 media_interface_->GetMediaPlayerList(
833 base::Bind(&Device::GetTotalNumberOfItemsMediaPlayersResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700834 weak_ptr_factory_.GetWeakPtr(), label));
Ajay Panicker0128c372018-04-27 06:59:22 -0700835 break;
836 }
837 case Scope::VFS:
838 media_interface_->GetFolderItems(
839 curr_browsed_player_id_, CurrentFolder(),
840 base::Bind(&Device::GetTotalNumberOfItemsVFSResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700841 weak_ptr_factory_.GetWeakPtr(), label));
Ajay Panicker0128c372018-04-27 06:59:22 -0700842 break;
843 case Scope::NOW_PLAYING:
844 media_interface_->GetNowPlayingList(
845 base::Bind(&Device::GetTotalNumberOfItemsNowPlayingResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700846 weak_ptr_factory_.GetWeakPtr(), label));
Ajay Panicker0128c372018-04-27 06:59:22 -0700847 break;
848 default:
849 DEVICE_LOG(ERROR) << __func__ << ": " << pkt->GetScope();
850 break;
851 }
852}
853
854void Device::GetTotalNumberOfItemsMediaPlayersResponse(
855 uint8_t label, uint16_t curr_player, std::vector<MediaPlayerInfo> list) {
856 DEVICE_VLOG(2) << __func__ << ": num_items=" << list.size();
857
858 auto builder = GetTotalNumberOfItemsResponseBuilder::MakeBuilder(
859 Status::NO_ERROR, 0x0000, list.size());
860 send_message(label, true, std::move(builder));
861}
862
863void Device::GetTotalNumberOfItemsVFSResponse(uint8_t label,
864 std::vector<ListItem> list) {
865 DEVICE_VLOG(2) << __func__ << ": num_items=" << list.size();
866
867 auto builder = GetTotalNumberOfItemsResponseBuilder::MakeBuilder(
868 Status::NO_ERROR, 0x0000, list.size());
869 send_message(label, true, std::move(builder));
870}
871
872void Device::GetTotalNumberOfItemsNowPlayingResponse(
873 uint8_t label, std::string curr_song_id, std::vector<SongInfo> list) {
874 DEVICE_VLOG(2) << __func__ << ": num_items=" << list.size();
875
876 auto builder = GetTotalNumberOfItemsResponseBuilder::MakeBuilder(
877 Status::NO_ERROR, 0x0000, list.size());
878 send_message(label, true, std::move(builder));
879}
880
Ajay Panicker2c822c92018-02-21 21:04:24 -0800881void Device::HandleChangePath(uint8_t label,
882 std::shared_ptr<ChangePathRequest> pkt) {
Ajay Panicker24680882019-03-05 13:07:09 -0800883 if (!pkt->IsValid()) {
884 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
885 auto response = ChangePathResponseBuilder::MakeBuilder(Status::INVALID_PARAMETER, 0);
886 send_message(label, true, std::move(response));
887 return;
888 }
889
Ajay Panicker2c822c92018-02-21 21:04:24 -0800890 DEVICE_VLOG(2) << __func__ << ": direction=" << pkt->GetDirection()
891 << " uid=" << loghex(pkt->GetUid());
892
893 if (pkt->GetDirection() == Direction::DOWN &&
894 vfs_ids_.get_media_id(pkt->GetUid()) == "") {
Ajay Panicker73c975a2018-03-28 17:04:39 -0700895 DEVICE_LOG(ERROR) << __func__
896 << ": No item found for UID=" << pkt->GetUid();
Ajay Panicker2c822c92018-02-21 21:04:24 -0800897 auto builder =
898 ChangePathResponseBuilder::MakeBuilder(Status::DOES_NOT_EXIST, 0);
899 send_message(label, true, std::move(builder));
900 return;
901 }
902
903 if (pkt->GetDirection() == Direction::DOWN) {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800904 current_path_.push(vfs_ids_.get_media_id(pkt->GetUid()));
Ajay Panicker73c975a2018-03-28 17:04:39 -0700905 DEVICE_VLOG(2) << "Pushing Path to stack: \"" << CurrentFolder() << "\"";
Ajay Panicker2c822c92018-02-21 21:04:24 -0800906 } else {
907 // Don't pop the root id off the stack
Ajay Panicker73c975a2018-03-28 17:04:39 -0700908 if (current_path_.size() > 1) {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800909 current_path_.pop();
Ajay Panicker73c975a2018-03-28 17:04:39 -0700910 } else {
Ajay Panicker2c822c92018-02-21 21:04:24 -0800911 DEVICE_LOG(ERROR) << "Trying to change directory up past root.";
912 auto builder =
913 ChangePathResponseBuilder::MakeBuilder(Status::DOES_NOT_EXIST, 0);
914 send_message(label, true, std::move(builder));
915 return;
916 }
917
918 DEVICE_VLOG(2) << "Popping Path from stack: new path=\"" << CurrentFolder()
919 << "\"";
Ajay Panicker2c822c92018-02-21 21:04:24 -0800920 }
921
922 media_interface_->GetFolderItems(
Ajay Panicker73c975a2018-03-28 17:04:39 -0700923 curr_browsed_player_id_, CurrentFolder(),
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700924 base::Bind(&Device::ChangePathResponse, weak_ptr_factory_.GetWeakPtr(),
925 label, pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800926}
927
928void Device::ChangePathResponse(uint8_t label,
929 std::shared_ptr<ChangePathRequest> pkt,
930 std::vector<ListItem> list) {
931 // TODO (apanicke): Reconstruct the VFS ID's here. Right now it gets
932 // reconstructed in GetFolderItemsVFS
933 auto builder =
934 ChangePathResponseBuilder::MakeBuilder(Status::NO_ERROR, list.size());
935 send_message(label, true, std::move(builder));
936}
937
938void Device::HandleGetItemAttributes(
939 uint8_t label, std::shared_ptr<GetItemAttributesRequest> pkt) {
Ajay Panicker24680882019-03-05 13:07:09 -0800940 if (!pkt->IsValid()) {
941 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
942 auto builder = GetItemAttributesResponseBuilder::MakeBuilder(Status::INVALID_PARAMETER, browse_mtu_);
943 send_message(label, true, std::move(builder));
944 return;
945 }
946
Ajay Panicker2c822c92018-02-21 21:04:24 -0800947 DEVICE_VLOG(2) << __func__ << ": scope=" << pkt->GetScope()
Hansong Zhang335d6e62018-05-14 11:29:20 -0700948 << " uid=" << loghex(pkt->GetUid())
949 << " uid counter=" << loghex(pkt->GetUidCounter());
950 if (pkt->GetUidCounter() != 0x0000) { // For database unaware player, use 0
951 DEVICE_LOG(WARNING) << "UidCounter is invalid";
952 auto builder = GetItemAttributesResponseBuilder::MakeBuilder(
953 Status::UIDS_CHANGED, browse_mtu_);
954 send_message(label, true, std::move(builder));
955 return;
956 }
Ajay Panicker24680882019-03-05 13:07:09 -0800957
Ajay Panicker2c822c92018-02-21 21:04:24 -0800958 switch (pkt->GetScope()) {
959 case Scope::NOW_PLAYING: {
960 media_interface_->GetNowPlayingList(
961 base::Bind(&Device::GetItemAttributesNowPlayingResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700962 weak_ptr_factory_.GetWeakPtr(), label, pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800963 } break;
964 case Scope::VFS:
965 // TODO (apanicke): Check the vfs_ids_ here. If the item doesn't exist
966 // then we can auto send the error without calling up. We do this check
967 // later right now though in order to prevent race conditions with updates
968 // on the media layer.
969 media_interface_->GetFolderItems(
970 curr_browsed_player_id_, CurrentFolder(),
971 base::Bind(&Device::GetItemAttributesVFSResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -0700972 weak_ptr_factory_.GetWeakPtr(), label, pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -0800973 break;
974 default:
975 DEVICE_LOG(ERROR) << "UNKNOWN SCOPE FOR HANDLE GET ITEM ATTRIBUTES";
976 break;
977 }
978}
979
980void Device::GetItemAttributesNowPlayingResponse(
981 uint8_t label, std::shared_ptr<GetItemAttributesRequest> pkt,
982 std::string curr_media_id, std::vector<SongInfo> song_list) {
983 DEVICE_VLOG(2) << __func__ << ": uid=" << loghex(pkt->GetUid());
Ajay Panickere6552092018-03-31 14:38:53 -0700984 auto builder = GetItemAttributesResponseBuilder::MakeBuilder(Status::NO_ERROR,
985 browse_mtu_);
Ajay Panicker2c822c92018-02-21 21:04:24 -0800986
987 auto media_id = now_playing_ids_.get_media_id(pkt->GetUid());
988 if (media_id == "") {
989 media_id = curr_media_id;
990 }
991
992 DEVICE_VLOG(2) << __func__ << ": media_id=\"" << media_id << "\"";
993
994 SongInfo info;
995 for (const auto& temp : song_list) {
996 if (temp.media_id == media_id) {
997 info = temp;
998 }
999 }
1000
1001 auto attributes_requested = pkt->GetAttributesRequested();
1002 if (attributes_requested.size() != 0) {
1003 for (const auto& attribute : attributes_requested) {
1004 if (info.attributes.find(attribute) != info.attributes.end()) {
1005 builder->AddAttributeEntry(*info.attributes.find(attribute));
1006 }
1007 }
1008 } else {
1009 // If zero attributes were requested, that means all attributes were
1010 // requested
1011 for (const auto& attribute : info.attributes) {
1012 builder->AddAttributeEntry(attribute);
1013 }
1014 }
1015
1016 send_message(label, true, std::move(builder));
1017}
1018
1019void Device::GetItemAttributesVFSResponse(
1020 uint8_t label, std::shared_ptr<GetItemAttributesRequest> pkt,
1021 std::vector<ListItem> item_list) {
1022 DEVICE_VLOG(2) << __func__ << ": uid=" << loghex(pkt->GetUid());
1023
1024 auto media_id = vfs_ids_.get_media_id(pkt->GetUid());
1025 if (media_id == "") {
1026 LOG(WARNING) << __func__ << ": Item not found";
Ajay Panickere6552092018-03-31 14:38:53 -07001027 auto builder = GetItemAttributesResponseBuilder::MakeBuilder(
1028 Status::DOES_NOT_EXIST, browse_mtu_);
Ajay Panicker2c822c92018-02-21 21:04:24 -08001029 send_message(label, true, std::move(builder));
1030 return;
1031 }
1032
Ajay Panickere6552092018-03-31 14:38:53 -07001033 auto builder = GetItemAttributesResponseBuilder::MakeBuilder(Status::NO_ERROR,
1034 browse_mtu_);
Ajay Panicker2c822c92018-02-21 21:04:24 -08001035
1036 ListItem item_requested;
1037 for (const auto& temp : item_list) {
1038 if ((temp.type == ListItem::FOLDER && temp.folder.media_id == media_id) ||
1039 (temp.type == ListItem::SONG && temp.song.media_id == media_id)) {
1040 item_requested = temp;
1041 }
1042 }
1043
1044 // TODO (apanicke): Add a helper function or allow adding a map
1045 // of attributes to GetItemAttributesResponseBuilder
1046 auto attributes_requested = pkt->GetAttributesRequested();
1047 if (item_requested.type == ListItem::FOLDER) {
1048 if (attributes_requested.size() == 0) {
1049 builder->AddAttributeEntry(Attribute::TITLE, item_requested.folder.name);
1050 } else {
1051 for (auto& attr : attributes_requested) {
1052 if (attr == Attribute::TITLE) {
1053 builder->AddAttributeEntry(Attribute::TITLE,
1054 item_requested.folder.name);
1055 }
1056 }
1057 }
1058 } else {
1059 if (attributes_requested.size() != 0) {
1060 for (const auto& attribute : attributes_requested) {
1061 if (item_requested.song.attributes.find(attribute) !=
1062 item_requested.song.attributes.end()) {
1063 builder->AddAttributeEntry(
1064 *item_requested.song.attributes.find(attribute));
1065 }
1066 }
1067 } else {
1068 // If zero attributes were requested, that means all attributes were
1069 // requested
1070 for (const auto& attribute : item_requested.song.attributes) {
1071 builder->AddAttributeEntry(attribute);
1072 }
1073 }
1074 }
1075
1076 send_message(label, true, std::move(builder));
1077}
1078
1079void Device::GetMediaPlayerListResponse(
1080 uint8_t label, std::shared_ptr<GetFolderItemsRequest> pkt,
1081 uint16_t curr_player, std::vector<MediaPlayerInfo> players) {
1082 DEVICE_VLOG(2) << __func__;
1083
1084 if (players.size() == 0) {
1085 auto no_items_rsp = GetFolderItemsResponseBuilder::MakePlayerListBuilder(
Ajay Panickere6552092018-03-31 14:38:53 -07001086 Status::RANGE_OUT_OF_BOUNDS, 0x0000, browse_mtu_);
Ajay Panicker2c822c92018-02-21 21:04:24 -08001087 send_message(label, true, std::move(no_items_rsp));
1088 }
1089
1090 auto builder = GetFolderItemsResponseBuilder::MakePlayerListBuilder(
Ajay Panickere6552092018-03-31 14:38:53 -07001091 Status::NO_ERROR, 0x0000, browse_mtu_);
Ajay Panicker2c822c92018-02-21 21:04:24 -08001092
1093 // Move the current player to the first slot due to some carkits always
1094 // connecting to the first listed player rather than using the ID
1095 // returned by Addressed Player Changed
1096 for (auto it = players.begin(); it != players.end(); it++) {
1097 if (it->id == curr_player) {
1098 DEVICE_VLOG(1) << " Adding player to first spot: " << it->name;
1099 auto temp_player = *it;
1100 players.erase(it);
1101 players.insert(players.begin(), temp_player);
1102 break;
1103 }
1104 }
1105
1106 for (size_t i = pkt->GetStartItem();
1107 i <= pkt->GetEndItem() && i < players.size(); i++) {
1108 MediaPlayerItem item(players[i].id, players[i].name,
1109 players[i].browsing_supported);
1110 builder->AddMediaPlayer(item);
1111 }
1112
1113 send_message(label, true, std::move(builder));
1114}
1115
Ajay Panickere6552092018-03-31 14:38:53 -07001116std::set<AttributeEntry> filter_attributes_requested(
Ajay Panicker2c822c92018-02-21 21:04:24 -08001117 const SongInfo& song, const std::vector<Attribute>& attrs) {
Ajay Panickere6552092018-03-31 14:38:53 -07001118 std::set<AttributeEntry> result;
Ajay Panicker2c822c92018-02-21 21:04:24 -08001119 for (const auto& attr : attrs) {
1120 if (song.attributes.find(attr) != song.attributes.end()) {
1121 result.insert(*song.attributes.find(attr));
1122 }
1123 }
1124
1125 return result;
1126}
1127
1128void Device::GetVFSListResponse(uint8_t label,
1129 std::shared_ptr<GetFolderItemsRequest> pkt,
1130 std::vector<ListItem> items) {
1131 DEVICE_VLOG(2) << __func__ << ": start_item=" << pkt->GetStartItem()
1132 << " end_item=" << pkt->GetEndItem();
1133
1134 // The builder will automatically correct the status if there are zero items
Ajay Panickere6552092018-03-31 14:38:53 -07001135 auto builder = GetFolderItemsResponseBuilder::MakeVFSBuilder(
1136 Status::NO_ERROR, 0x0000, browse_mtu_);
Ajay Panicker2c822c92018-02-21 21:04:24 -08001137
1138 // TODO (apanicke): Add test that checks if vfs_ids_ is the correct size after
1139 // an operation.
Ajay Panicker2c822c92018-02-21 21:04:24 -08001140 for (const auto& item : items) {
1141 if (item.type == ListItem::FOLDER) {
1142 vfs_ids_.insert(item.folder.media_id);
1143 } else if (item.type == ListItem::SONG) {
1144 vfs_ids_.insert(item.song.media_id);
1145 }
1146 }
1147
1148 // Add the elements retrieved in the last get folder items request and map
1149 // them to UIDs The maps will be cleared every time a directory change
1150 // happens. These items do not need to correspond with the now playing list as
1151 // the UID's only need to be unique in the context of the current scope and
1152 // the current folder
1153 for (auto i = pkt->GetStartItem(); i <= pkt->GetEndItem() && i < items.size();
1154 i++) {
1155 if (items[i].type == ListItem::FOLDER) {
1156 auto folder = items[i].folder;
1157 // right now we always use folders of mixed type
1158 FolderItem folder_item(vfs_ids_.get_uid(folder.media_id), 0x00,
1159 folder.is_playable, folder.name);
Ajay Panicker7f29cc52018-08-22 16:24:03 -07001160 if (!builder->AddFolder(folder_item)) break;
Ajay Panicker2c822c92018-02-21 21:04:24 -08001161 } else if (items[i].type == ListItem::SONG) {
1162 auto song = items[i].song;
1163 auto title =
1164 song.attributes.find(Attribute::TITLE) != song.attributes.end()
Ajay Panickere6552092018-03-31 14:38:53 -07001165 ? song.attributes.find(Attribute::TITLE)->value()
Ajay Panicker2c822c92018-02-21 21:04:24 -08001166 : "No Song Info";
1167 MediaElementItem song_item(vfs_ids_.get_uid(song.media_id), title,
Ajay Panickere6552092018-03-31 14:38:53 -07001168 std::set<AttributeEntry>());
Ajay Panicker2c822c92018-02-21 21:04:24 -08001169
1170 if (pkt->GetNumAttributes() == 0x00) { // All attributes requested
1171 song_item.attributes_ = std::move(song.attributes);
1172 } else {
1173 song_item.attributes_ =
1174 filter_attributes_requested(song, pkt->GetAttributesRequested());
1175 }
1176
Ajay Panicker7f29cc52018-08-22 16:24:03 -07001177 // If we fail to add a song, don't accidentally add one later that might
1178 // fit.
1179 if (!builder->AddSong(song_item)) break;
Ajay Panicker2c822c92018-02-21 21:04:24 -08001180 }
1181 }
1182
1183 send_message(label, true, std::move(builder));
1184}
1185
1186void Device::GetNowPlayingListResponse(
1187 uint8_t label, std::shared_ptr<GetFolderItemsRequest> pkt,
1188 std::string /* unused curr_song_id */, std::vector<SongInfo> song_list) {
1189 DEVICE_VLOG(2) << __func__;
1190 auto builder = GetFolderItemsResponseBuilder::MakeNowPlayingBuilder(
Ajay Panickere6552092018-03-31 14:38:53 -07001191 Status::NO_ERROR, 0x0000, browse_mtu_);
Ajay Panicker2c822c92018-02-21 21:04:24 -08001192
1193 now_playing_ids_.clear();
1194 for (const SongInfo& song : song_list) {
1195 now_playing_ids_.insert(song.media_id);
1196 }
1197
1198 for (size_t i = pkt->GetStartItem();
1199 i <= pkt->GetEndItem() && i < song_list.size(); i++) {
1200 auto song = song_list[i];
1201 auto title = song.attributes.find(Attribute::TITLE) != song.attributes.end()
Ajay Panickere6552092018-03-31 14:38:53 -07001202 ? song.attributes.find(Attribute::TITLE)->value()
Ajay Panicker2c822c92018-02-21 21:04:24 -08001203 : "No Song Info";
1204
Ajay Panickere6552092018-03-31 14:38:53 -07001205 MediaElementItem item(i + 1, title, std::set<AttributeEntry>());
Ajay Panicker2c822c92018-02-21 21:04:24 -08001206 if (pkt->GetNumAttributes() == 0x00) {
1207 item.attributes_ = std::move(song.attributes);
1208 } else {
1209 item.attributes_ =
1210 filter_attributes_requested(song, pkt->GetAttributesRequested());
1211 }
Ajay Panicker7f29cc52018-08-22 16:24:03 -07001212
1213 // If we fail to add a song, don't accidentally add one later that might
1214 // fit.
1215 if (!builder->AddSong(item)) break;
Ajay Panicker2c822c92018-02-21 21:04:24 -08001216 }
1217
1218 send_message(label, true, std::move(builder));
1219}
1220
1221void Device::HandleSetBrowsedPlayer(
1222 uint8_t label, std::shared_ptr<SetBrowsedPlayerRequest> pkt) {
Ajay Panicker24680882019-03-05 13:07:09 -08001223 if (!pkt->IsValid()) {
1224 DEVICE_LOG(WARNING) << __func__ << ": Request packet is not valid";
1225 auto response = SetBrowsedPlayerResponseBuilder::MakeBuilder(Status::INVALID_PARAMETER, 0x0000, 0, 0, "");
1226 send_message(label, true, std::move(response));
1227 return;
1228 }
1229
Ajay Panicker2c822c92018-02-21 21:04:24 -08001230 DEVICE_VLOG(2) << __func__ << ": player_id=" << pkt->GetPlayerId();
1231 media_interface_->SetBrowsedPlayer(
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -07001232 pkt->GetPlayerId(),
1233 base::Bind(&Device::SetBrowsedPlayerResponse,
1234 weak_ptr_factory_.GetWeakPtr(), label, pkt));
Ajay Panicker2c822c92018-02-21 21:04:24 -08001235}
1236
1237void Device::SetBrowsedPlayerResponse(
1238 uint8_t label, std::shared_ptr<SetBrowsedPlayerRequest> pkt, bool success,
1239 std::string root_id, uint32_t num_items) {
1240 DEVICE_VLOG(2) << __func__ << ": success=" << success << " root_id=\""
1241 << root_id << "\" num_items=" << num_items;
1242
Hansong Zhangf54a72c2018-05-14 16:48:58 -07001243 if (!success) {
1244 auto response = SetBrowsedPlayerResponseBuilder::MakeBuilder(
1245 Status::INVALID_PLAYER_ID, 0x0000, num_items, 0, "");
1246 send_message(label, true, std::move(response));
1247 return;
1248 }
1249
Ajay Panicker2c822c92018-02-21 21:04:24 -08001250 curr_browsed_player_id_ = pkt->GetPlayerId();
1251
1252 // Clear the path and push the new root.
1253 current_path_ = std::stack<std::string>();
1254 current_path_.push(root_id);
1255
1256 auto response = SetBrowsedPlayerResponseBuilder::MakeBuilder(
1257 Status::NO_ERROR, 0x0000, num_items, 0, "");
1258 send_message(label, true, std::move(response));
1259}
1260
1261void Device::SendMediaUpdate(bool metadata, bool play_status, bool queue) {
Ugo Yu76180b62019-01-13 03:12:30 +08001262 bool is_silence = IsInSilenceMode();
1263
Ajay Panicker2c822c92018-02-21 21:04:24 -08001264 CHECK(media_interface_);
1265 DEVICE_VLOG(4) << __func__ << ": Metadata=" << metadata
Ugo Yu76180b62019-01-13 03:12:30 +08001266 << " : play_status= " << play_status << " : queue=" << queue
1267 << " ; is_silence=" << is_silence;
Ajay Panicker2c822c92018-02-21 21:04:24 -08001268
1269 if (queue) {
1270 HandleNowPlayingUpdate();
1271 }
1272
1273 if (play_status) {
1274 HandlePlayStatusUpdate();
Ugo Yu76180b62019-01-13 03:12:30 +08001275 if (!is_silence) {
1276 HandlePlayPosUpdate();
1277 }
Ajay Panicker2c822c92018-02-21 21:04:24 -08001278 }
1279
1280 if (metadata) HandleTrackUpdate();
1281}
1282
1283void Device::SendFolderUpdate(bool available_players, bool addressed_player,
1284 bool uids) {
1285 CHECK(media_interface_);
1286 DEVICE_VLOG(4) << __func__;
1287
1288 if (available_players) {
tedwang7130af12018-05-18 18:16:40 +08001289 HandleAvailablePlayerUpdate();
Ajay Panicker2c822c92018-02-21 21:04:24 -08001290 }
1291
1292 if (addressed_player) {
tedwang7130af12018-05-18 18:16:40 +08001293 HandleAddressedPlayerUpdate();
Ajay Panicker2c822c92018-02-21 21:04:24 -08001294 }
Ajay Panicker2c822c92018-02-21 21:04:24 -08001295}
1296
1297void Device::HandleTrackUpdate() {
1298 DEVICE_VLOG(2) << __func__;
1299 if (!track_changed_.first) {
1300 LOG(WARNING) << "Device is not registered for track changed updates";
1301 return;
1302 }
1303
1304 media_interface_->GetNowPlayingList(
1305 base::Bind(&Device::TrackChangedNotificationResponse,
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -07001306 weak_ptr_factory_.GetWeakPtr(), track_changed_.second, false));
Ajay Panicker2c822c92018-02-21 21:04:24 -08001307}
1308
1309void Device::HandlePlayStatusUpdate() {
1310 DEVICE_VLOG(2) << __func__;
1311 if (!play_status_changed_.first) {
1312 LOG(WARNING) << "Device is not registered for play status updates";
1313 return;
1314 }
1315
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -07001316 media_interface_->GetPlayStatus(base::Bind(
1317 &Device::PlaybackStatusNotificationResponse,
1318 weak_ptr_factory_.GetWeakPtr(), play_status_changed_.second, false));
Ajay Panicker2c822c92018-02-21 21:04:24 -08001319}
1320
1321void Device::HandleNowPlayingUpdate() {
1322 DEVICE_VLOG(2) << __func__;
1323
1324 if (!now_playing_changed_.first) {
1325 LOG(WARNING) << "Device is not registered for now playing updates";
1326 return;
1327 }
1328
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -07001329 media_interface_->GetNowPlayingList(base::Bind(
1330 &Device::HandleNowPlayingNotificationResponse,
1331 weak_ptr_factory_.GetWeakPtr(), now_playing_changed_.second, false));
Ajay Panickere016b532018-04-02 15:36:35 -07001332}
1333
1334void Device::HandleNowPlayingNotificationResponse(
1335 uint8_t label, bool interim, std::string curr_song_id,
1336 std::vector<SongInfo> song_list) {
Ajay Panicker817e6bb2018-06-26 14:52:56 -07001337 if (interim) {
1338 now_playing_changed_ = Notification(true, label);
1339 } else if (!now_playing_changed_.first) {
Ajay Panickere016b532018-04-02 15:36:35 -07001340 LOG(WARNING) << "Device is not registered for now playing updates";
1341 return;
1342 }
1343
1344 now_playing_ids_.clear();
1345 for (const SongInfo& song : song_list) {
1346 now_playing_ids_.insert(song.media_id);
1347 }
1348
Ajay Panicker2c822c92018-02-21 21:04:24 -08001349 auto response =
Ajay Panickere016b532018-04-02 15:36:35 -07001350 RegisterNotificationResponseBuilder::MakeNowPlayingBuilder(interim);
Ajay Panicker2c822c92018-02-21 21:04:24 -08001351 send_message(now_playing_changed_.second, false, std::move(response));
Ajay Panickere016b532018-04-02 15:36:35 -07001352
1353 if (!interim) {
1354 active_labels_.erase(label);
1355 now_playing_changed_ = Notification(false, 0);
1356 }
Ajay Panicker2c822c92018-02-21 21:04:24 -08001357}
1358
1359void Device::HandlePlayPosUpdate() {
1360 DEVICE_VLOG(0) << __func__;
1361 if (!play_pos_changed_.first) {
1362 LOG(WARNING) << "Device is not registered for play position updates";
1363 return;
1364 }
1365
Ajay Panickerfa9bc3b2018-05-10 12:39:32 -07001366 media_interface_->GetPlayStatus(base::Bind(
1367 &Device::PlaybackPosNotificationResponse, weak_ptr_factory_.GetWeakPtr(),
1368 play_pos_changed_.second, false));
Ajay Panicker2c822c92018-02-21 21:04:24 -08001369}
1370
tedwang7130af12018-05-18 18:16:40 +08001371void Device::HandleAvailablePlayerUpdate() {
1372 DEVICE_VLOG(1) << __func__;
1373
1374 if (!avail_players_changed_.first) {
1375 LOG(WARNING) << "Device is not registered for available player updates";
1376 return;
1377 }
1378
1379 auto response =
1380 RegisterNotificationResponseBuilder::MakeAvailablePlayersBuilder(false);
1381 send_message_cb_.Run(avail_players_changed_.second, false,
1382 std::move(response));
1383
1384 if (!avail_players_changed_.first) {
1385 active_labels_.erase(avail_players_changed_.second);
1386 avail_players_changed_ = Notification(false, 0);
1387 }
1388}
1389
1390void Device::HandleAddressedPlayerUpdate() {
1391 DEVICE_VLOG(1) << __func__;
1392 if (!addr_player_changed_.first) {
1393 DEVICE_LOG(WARNING)
1394 << "Device is not registered for addressed player updates";
1395 return;
1396 }
1397 media_interface_->GetMediaPlayerList(base::Bind(
1398 &Device::AddressedPlayerNotificationResponse,
1399 weak_ptr_factory_.GetWeakPtr(), addr_player_changed_.second, false));
1400}
1401
Ajay Panicker2c822c92018-02-21 21:04:24 -08001402void Device::DeviceDisconnected() {
1403 DEVICE_LOG(INFO) << "Device was disconnected";
1404 play_pos_update_cb_.Cancel();
Ajay Panicker8e521032018-02-27 20:51:47 -08001405
1406 // TODO (apanicke): Once the interfaces are set in the Device construction,
1407 // remove these conditionals.
1408 if (volume_interface_ != nullptr)
1409 volume_interface_->DeviceDisconnected(GetAddress());
Ajay Panicker2c822c92018-02-21 21:04:24 -08001410}
1411
Ajay Panicker341424a2018-04-03 22:28:06 -07001412static std::string volumeToStr(int8_t volume) {
1413 if (volume == VOL_NOT_SUPPORTED) return "Absolute Volume not supported";
1414 if (volume == VOL_REGISTRATION_FAILED)
1415 return "Volume changed notification was rejected";
1416 return std::to_string(volume);
1417}
1418
Ajay Panicker2c822c92018-02-21 21:04:24 -08001419std::ostream& operator<<(std::ostream& out, const Device& d) {
Ajay Panickerfe8edd02018-05-03 12:58:53 -07001420 out << d.address_.ToString();
Ajay Panicker774eb4c2018-05-02 16:23:13 -07001421 if (d.IsActive()) out << " <Active>";
Ajay Panicker2c822c92018-02-21 21:04:24 -08001422 out << std::endl;
Ajay Panicker774eb4c2018-05-02 16:23:13 -07001423
Ajay Panickerfe8edd02018-05-03 12:58:53 -07001424 ScopedIndent indent(out);
1425 out << "Current Volume: " << volumeToStr(d.volume_) << std::endl;
1426 out << "Current Browsed Player ID: " << d.curr_browsed_player_id_
Ajay Panicker774eb4c2018-05-02 16:23:13 -07001427 << std::endl;
Ajay Panickerfe8edd02018-05-03 12:58:53 -07001428 out << "Registered Notifications:\n";
1429 {
1430 ScopedIndent indent(out);
1431 if (d.track_changed_.first) out << "Track Changed\n";
1432 if (d.play_status_changed_.first) out << "Play Status\n";
1433 if (d.play_pos_changed_.first) out << "Play Position\n";
1434 if (d.now_playing_changed_.first) out << "Now Playing\n";
1435 if (d.addr_player_changed_.first) out << "Addressed Player\n";
1436 if (d.avail_players_changed_.first) out << "Available Players\n";
1437 if (d.uids_changed_.first) out << "UIDs Changed\n";
1438 }
1439 out << "Last Play State: " << d.last_play_status_.state << std::endl;
1440 out << "Last Song Sent ID: \"" << d.last_song_info_.media_id << "\"\n";
1441 out << "Current Folder: \"" << d.CurrentFolder() << "\"\n";
1442 out << "MTU Sizes: CTRL=" << d.ctrl_mtu_ << " BROWSE=" << d.browse_mtu_
Ajay Panicker774eb4c2018-05-02 16:23:13 -07001443 << std::endl;
1444 // TODO (apanicke): Add supported features as well as media keys
Ajay Panicker2c822c92018-02-21 21:04:24 -08001445 return out;
1446}
1447
1448} // namespace avrcp
Ajay Panicker341424a2018-04-03 22:28:06 -07001449} // namespace bluetooth