blob: d0f73577c9fc05e847f781681bfcb313778389ac [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{format_err, Context as _, Error};
use async_helpers::maybe_stream::MaybeStream;
use battery_client::{BatteryClient, BatteryInfo, BatteryLevel};
use fidl::endpoints;
use fidl_fuchsia_bluetooth_avrcp as avrcp;
use fidl_fuchsia_media as media;
use fidl_fuchsia_media_sessions2 as sessions2;
use fidl_table_validation::ValidFidlTable;
use fuchsia_async as fasync;
use fuchsia_bluetooth::types::PeerId;
use fuchsia_inspect::{self as inspect, Property};
use fuchsia_inspect_contrib::{inspect_log, nodes::BoundedListNode};
use fuchsia_inspect_derive::{AttachError, Inspect};
use fuchsia_zircon as zx;
use futures::{select, StreamExt};
use std::fmt::Debug;
use tracing::{debug, info, trace};
#[derive(Debug, Clone, ValidFidlTable, PartialEq)]
#[fidl_table_src(sessions2::PlayerStatus)]
pub struct ValidPlayerStatus {
#[fidl_field_type(optional)]
pub duration: Option<i64>,
pub player_state: sessions2::PlayerState,
#[fidl_field_type(optional)]
pub timeline_function: Option<media::TimelineFunction>,
pub repeat_mode: sessions2::RepeatMode,
pub shuffle_on: bool,
pub content_type: sessions2::ContentType,
#[fidl_field_type(optional)]
pub error: Option<sessions2::Error>,
}
/// Interval to poll Get Play Status on the remote AVRCP peer.
const AVRCP_GET_PLAY_STATUS_POLL_INTERVAL: zx::Duration = zx::Duration::from_seconds(2);
impl ValidPlayerStatus {
/// Sets the `player_state` given a state from AVRCP.
fn set_state_from_avrcp(&mut self, avrcp_status: avrcp::PlaybackStatus) {
self.player_state = match avrcp_status {
avrcp::PlaybackStatus::Stopped => sessions2::PlayerState::Idle,
avrcp::PlaybackStatus::Playing => sessions2::PlayerState::Playing,
avrcp::PlaybackStatus::Paused => sessions2::PlayerState::Paused,
avrcp::PlaybackStatus::FwdSeek => sessions2::PlayerState::Playing,
avrcp::PlaybackStatus::RevSeek => sessions2::PlayerState::Playing,
avrcp::PlaybackStatus::Error => sessions2::PlayerState::Idle,
};
}
/// Sets the TimelineFunction relating the current state and position given.
/// Sets the function to None if there is no correlation.
fn set_position(&mut self, position_millis: i64) {
let subject_delta = match self.player_state {
sessions2::PlayerState::Playing => 1,
sessions2::PlayerState::Paused => 0,
_ => {
self.timeline_function = None;
return;
}
};
self.timeline_function = Some(media::TimelineFunction {
subject_time: zx::Duration::from_millis(position_millis).into_nanos(),
reference_time: fasync::Time::now().into_nanos(),
subject_delta,
reference_delta: 1,
});
}
}
pub(crate) struct AvrcpRelay {
/// Whether the battery monitoring service is active.
battery_watcher_active: inspect::BoolProperty,
/// The last 5 Media Player requests that have been received.
recent_player_requests: BoundedListNode,
/// The Inspect node associated with the current player state.
player_status_node: inspect::Node,
/// Inspect node
inspect_node: inspect::Node,
}
impl Inspect for &mut AvrcpRelay {
fn iattach(self, parent: &inspect::Node, name: impl AsRef<str>) -> Result<(), AttachError> {
self.inspect_node = parent.create_child(name.as_ref());
self.battery_watcher_active =
self.inspect_node.create_bool("battery_watcher_active", false);
self.recent_player_requests =
BoundedListNode::new(self.inspect_node.create_child("recent_player_requests"), 5);
self.player_status_node = self.inspect_node.create_child("player_status");
Ok(())
}
}
impl Default for AvrcpRelay {
fn default() -> Self {
Self {
battery_watcher_active: Default::default(),
recent_player_requests: BoundedListNode::new(inspect::Node::default(), 5),
player_status_node: Default::default(),
inspect_node: Default::default(),
}
}
}
impl AvrcpRelay {
fn update_player_status_inspect(&mut self, latest: &ValidPlayerStatus) {
self.player_status_node.clear_recorded();
self.player_status_node
.record_string("player_state", &format!("{:?}", latest.player_state));
self.player_status_node.record_string("repeat_mode", &format!("{:?}", latest.repeat_mode));
self.player_status_node.record_bool("shuffle_on", latest.shuffle_on);
self.player_status_node
.record_string("content_type", &format!("{:?}", latest.content_type));
self.player_status_node.record_int("duration", latest.duration.unwrap_or(0));
let Some(timeline_fn) = latest.timeline_function else { return };
self.player_status_node.record_child("timeline_function", |node| {
node.record_int("subject_time", timeline_fn.subject_time);
node.record_int("reference_time", timeline_fn.reference_time);
node.record_uint("subject_delta", timeline_fn.subject_delta.into());
node.record_uint("reference_delta", timeline_fn.reference_delta.into());
});
}
fn record_recent_player_request(&mut self, request: &sessions2::PlayerRequest) {
// We don't record the WatchInfoChange player request since it is a noisy hanging-get
// request.
if !matches!(request, sessions2::PlayerRequest::WatchInfoChange { .. }) {
inspect_log!(self.recent_player_requests, request: request.method_name());
}
}
/// Start a relay between AVRCP and MediaSession.
/// A MediaSession is published with the information from the AVRCP target.
/// This starts the relay. The relay can be stopped by dropping it.
pub(crate) fn start(
self,
peer_id: PeerId,
player_request_stream: sessions2::PlayerRequestStream,
) -> Result<fasync::Task<()>, Error> {
let avrcp_svc =
fuchsia_component::client::connect_to_protocol::<avrcp::PeerManagerMarker>()
.context("Failed to connect to Bluetooth AVRCP interface")?;
let battery_client = BatteryClient::create().ok().into();
let session_fut =
self.session_relay(avrcp_svc, peer_id, player_request_stream, battery_client);
Ok(fasync::Task::spawn(async move {
if let Err(e) = session_fut.await {
info!(?e, "session completed");
}
}))
}
async fn session_relay(
mut self,
mut avrcp: avrcp::PeerManagerProxy,
peer_id: PeerId,
mut player_request_stream: sessions2::PlayerRequestStream,
mut battery_client: MaybeStream<BatteryClient>,
) -> Result<(), Error> {
let (controller, browse_controller) =
connect_avrcp(&mut avrcp, peer_id).await.context("getting controller from AVRCP")?;
let mut staged_info = Some(sessions2::PlayerInfoDelta {
local: Some(true),
player_capabilities: Some(sessions2::PlayerCapabilities {
flags: Some(
sessions2::PlayerCapabilityFlags::PLAY
| sessions2::PlayerCapabilityFlags::PAUSE
| sessions2::PlayerCapabilityFlags::CHANGE_TO_NEXT_ITEM
| sessions2::PlayerCapabilityFlags::CHANGE_TO_PREV_ITEM,
),
..Default::default()
}),
..Default::default()
});
let mut hanging_watcher = None;
let mut last_player_status = ValidPlayerStatus {
player_state: sessions2::PlayerState::Idle,
repeat_mode: sessions2::RepeatMode::Single,
shuffle_on: false,
content_type: sessions2::ContentType::Audio,
duration: None,
timeline_function: None,
error: None,
};
// Get the initial Media Attributes and status
let mut building = staged_info.as_mut().unwrap();
// TODO(https://fxbug.dev/42119160): sometimes these initially fail with "Protocol Error". Avoid exiting the
// relay for now.
let _ = update_attributes(&controller, &mut building, &mut last_player_status).await;
let _ = update_status(&controller, &mut last_player_status).await;
building.player_status = Some(last_player_status.clone().into());
self.update_player_status_inspect(&last_player_status);
self.battery_watcher_active.set(battery_client.is_some());
let mut avrcp_notify_stream = controller.take_event_stream();
let mut status_update_interval =
fasync::Interval::new(AVRCP_GET_PLAY_STATUS_POLL_INTERVAL).fuse();
loop {
let mut player_request_fut = player_request_stream.next();
let mut avrcp_notify_fut = avrcp_notify_stream.next();
let mut update_status_fut = status_update_interval.next();
let mut battery_client_fut = battery_client.next();
select! {
request = player_request_fut => {
let Some(request) = request else {
trace!("Player request stream is closed, quitting AVRCP.");
break;
};
let request = request.context("request from player")?;
self.record_recent_player_request(&request);
match request {
sessions2::PlayerRequest::WatchInfoChange { responder } => {
if let Some(_) = hanging_watcher.take() {
return Err(format_err!("Concurrent watches issued: not allowed"));
}
hanging_watcher = Some(responder);
}
sessions2::PlayerRequest::Pause { .. } => {
let _ = controller.send_command(avrcp::AvcPanelCommand::Pause).await;
},
sessions2::PlayerRequest::Play { .. } => {
let _ = controller.send_command(avrcp::AvcPanelCommand::Play).await;
},
sessions2::PlayerRequest::Stop { .. } => {
let _ = controller.send_command(avrcp::AvcPanelCommand::Stop).await;
},
sessions2::PlayerRequest::NextItem { .. } => {
let _ = controller.send_command(avrcp::AvcPanelCommand::Forward).await;
},
sessions2::PlayerRequest::PrevItem { .. } => {
let _ = controller.send_command(avrcp::AvcPanelCommand::Backward).await;
},
sessions2::PlayerRequest::BindVolumeControl { .. } => {
// Drop incoming channel, we don't support this interface.
},
x => info!(%peer_id, "unhandled player request {x:?}"),
}
}
event = avrcp_notify_fut => {
if event.is_none() {
info!(%peer_id, "AVRCP relay stop: notification stream end");
break;
}
let avrcp::ControllerEvent::OnNotification { timestamp: _, notification } = event.unwrap()?;
trace!(%peer_id, ?notification, "Notification from AVRCP");
let mut player_status_updated = false;
if let Some(millis) = notification.pos {
last_player_status.set_position(millis as i64);
player_status_updated = true;
}
if let Some(true) = notification.available_players_changed {
let res = browse_controller
.get_media_player_items(0, avrcp::MAX_MEDIA_PLAYER_ITEMS.into())
.await;
match res {
Ok(Ok(players)) => {
debug!(%peer_id, "Media players: {players:?}");
let valid_players: Vec<&avrcp::MediaPlayerItem> = players.iter().filter_map(|p| {
// Return player if and only if it's in active playback status.
use avrcp::PlaybackStatus::*;
match p.playback_status {
Some(Stopped) | Some(Error) | None => None,
_ => Some(p),
}
}).collect();
if valid_players.len() == 0 {
last_player_status.player_state = sessions2::PlayerState::Idle;
player_status_updated = true;
}
}
// Sometimes, browse connection is not yet established when we make the call.
Ok(Err(avrcp::BrowseControllerError::RemoteNotConnected)) => debug!(%peer_id, "Couldn't get media player items because browse connection isn't established"),
e => info!(%peer_id, ?e, "Error checking available player"),
}
}
if notification.status.is_some() ||
notification.track_id.is_some() ||
notification.addressed_player.is_some() {
let mut building = staged_info.get_or_insert(sessions2::PlayerInfoDelta::default());
if let Err(e) = update_attributes(&controller, &mut building, &mut last_player_status).await {
info!(%peer_id, ?e, "Couldn't update AVRCP attributes");
}
player_status_updated = true;
}
if notification.status.is_some() {
if let Err(e) = update_status(&controller, &mut last_player_status).await {
info!(%peer_id, ?e, "Error updating AVRCP status (notification)");
}
player_status_updated = true;
}
if player_status_updated {
let building = staged_info.get_or_insert(sessions2::PlayerInfoDelta::default());
building.player_status = Some(last_player_status.clone().into());
debug!(%peer_id, ?building, "Updated player status");
self.update_player_status_inspect(&last_player_status);
}
// Notify that the notification is handled so we can receive another one.
let _ = controller.notify_notification_handled().context("acknowledging notification")?;
}
_event = update_status_fut => {
if let Err(e) = update_status(&controller, &mut last_player_status).await {
info!(%peer_id, ?e, "Error updating AVRCP status (interval)");
}
let building = staged_info.get_or_insert(sessions2::PlayerInfoDelta::default());
building.player_status = Some(last_player_status.clone().into());
self.update_player_status_inspect(&last_player_status);
}
update = battery_client_fut => {
match update {
None => {
debug!(%peer_id, "BatteryClient finished");
self.battery_watcher_active.set(false);
}
Some(Err(e)) => info!(%peer_id, ?e, "BatteryClient stream error"),
Some(Ok(info)) => {
if let Err(e) = update_battery_status(&controller, info).await {
info!(%peer_id, ?e, "Error updating AVRCP battery status");
}
}
}
}
complete => unreachable!(),
}
if staged_info.is_some() && hanging_watcher.is_some() {
trace!("Sending watcher info: {:?}", staged_info.as_ref().unwrap());
hanging_watcher.take().unwrap().send(&staged_info.take().unwrap())?;
}
}
Ok(())
}
}
async fn update_attributes(
controller: &avrcp::ControllerProxy,
info_delta: &mut sessions2::PlayerInfoDelta,
status: &mut ValidPlayerStatus,
) -> Result<(), Error> {
let attributes = controller
.get_media_attributes()
.await?
.or_else(|e| Err(format_err!("AVRCP error: {e:?}")))?;
info_delta.metadata = Some(attributes_to_metadata(&attributes));
if let Some(playing_time) = attributes.playing_time {
if let Ok(millis) = playing_time.parse::<i64>() {
status.duration = Some(zx::Duration::from_millis(millis).into_nanos());
}
}
Ok(())
}
async fn update_status(
controller: &avrcp::ControllerProxy,
status: &mut ValidPlayerStatus,
) -> Result<(), Error> {
let avrcp_status =
controller.get_play_status().await?.or_else(|e| Err(format_err!("AVRCP error: {e:?}")))?;
let playback_status =
avrcp_status.playback_status.ok_or(format_err!("PlayStatus must have playback status"))?;
status.set_state_from_avrcp(playback_status);
status.duration =
avrcp_status.song_length.map(|m| zx::Duration::from_millis(m as i64).into_nanos());
let _ = avrcp_status.song_position.map(|m| status.set_position(m as i64));
Ok(())
}
async fn update_battery_status(
controller: &avrcp::ControllerProxy,
status: BatteryInfo,
) -> Result<(), Error> {
let avrcp_status = match status {
BatteryInfo::External => avrcp::BatteryStatus::External,
BatteryInfo::Battery(BatteryLevel::Normal(_)) => avrcp::BatteryStatus::Normal,
BatteryInfo::Battery(BatteryLevel::Warning(_)) => avrcp::BatteryStatus::Warning,
BatteryInfo::Battery(BatteryLevel::Critical(_)) => avrcp::BatteryStatus::Critical,
BatteryInfo::Battery(BatteryLevel::FullCharge) => avrcp::BatteryStatus::FullCharge,
BatteryInfo::NotAvailable => return Ok(()),
};
controller
.inform_battery_status(avrcp_status)
.await?
.or_else(|e| Err(format_err!("AVRCP error: {e:?}")))
}
macro_rules! nonempty_to_property {
( $source:expr, $prop_str:expr, $target:ident ) => {
if let Some(value) = $source {
$target.push(media::Property { label: $prop_str.to_string(), value: value.clone() });
}
};
}
fn attributes_to_metadata(attributes: &avrcp::MediaAttributes) -> media::Metadata {
let mut properties = Vec::new();
nonempty_to_property!(&attributes.title, media::METADATA_LABEL_TITLE, properties);
nonempty_to_property!(&attributes.artist_name, media::METADATA_LABEL_ARTIST, properties);
nonempty_to_property!(&attributes.album_name, media::METADATA_LABEL_ALBUM, properties);
nonempty_to_property!(&attributes.track_number, media::METADATA_LABEL_TRACK_NUMBER, properties);
nonempty_to_property!(&attributes.total_number_of_tracks, "total_number_of_tracks", properties);
nonempty_to_property!(&attributes.genre, media::METADATA_LABEL_GENRE, properties);
nonempty_to_property!(
&Some(String::from("Bluetooth")),
media::METADATA_SOURCE_TITLE,
properties
);
media::Metadata { properties }
}
async fn connect_avrcp(
avrcp: &mut avrcp::PeerManagerProxy,
peer_id: PeerId,
) -> Result<(avrcp::ControllerProxy, avrcp::BrowseControllerProxy), Error> {
let (controller, server) = endpoints::create_proxy()?;
let (browse_controller, browse_server) = endpoints::create_proxy()?;
let _ = avrcp.get_controller_for_target(&peer_id.into(), server).await?;
let _ = avrcp.get_browse_controller_for_target(&peer_id.into(), browse_server).await?;
controller.set_notification_filter(
avrcp::Notifications::PLAYBACK_STATUS
| avrcp::Notifications::TRACK
| avrcp::Notifications::CONNECTION
| avrcp::Notifications::AVAILABLE_PLAYERS
| avrcp::Notifications::ADDRESSED_PLAYER,
0,
)?;
Ok((controller, browse_controller))
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use async_test_helpers::run_while;
use async_utils::PollExt;
use diagnostics_assertions::{assert_data_tree, AnyProperty};
use fidl::endpoints::RequestStream;
use fidl_fuchsia_power_battery as fpower;
use fuchsia_inspect_derive::WithInspect;
use futures::{task::Poll, Future};
use std::pin::{pin, Pin};
use test_battery_manager::TestBatteryManager;
fn setup_media_relay() -> (sessions2::PlayerProxy, avrcp::PeerManagerRequestStream, impl Future)
{
let (player_proxy, player_requests) =
endpoints::create_proxy_and_stream::<sessions2::PlayerMarker>().unwrap();
let (avrcp_proxy, avrcp_requests) =
endpoints::create_proxy_and_stream::<avrcp::PeerManagerMarker>().unwrap();
let peer_id = PeerId(0);
let relay = AvrcpRelay::default();
let relay_fut = relay.session_relay(avrcp_proxy, peer_id, player_requests, None.into());
(player_proxy, avrcp_requests, relay_fut)
}
fn setup_media_relay_with_battery_manager(
exec: &mut fasync::TestExecutor,
) -> (sessions2::PlayerProxy, avrcp::PeerManagerRequestStream, impl Future, TestBatteryManager)
{
let (player_proxy, player_requests) =
endpoints::create_proxy_and_stream::<sessions2::PlayerMarker>().unwrap();
let (avrcp_proxy, avrcp_requests) =
endpoints::create_proxy_and_stream::<avrcp::PeerManagerMarker>().unwrap();
let peer_id = PeerId(1);
let mut setup_fut = pin!(TestBatteryManager::make_battery_client_with_test_manager());
let (battery_client, test_battery_manager) = exec.run_singlethreaded(&mut setup_fut);
let relay = AvrcpRelay::default();
let relay_fut =
relay.session_relay(avrcp_proxy, peer_id, player_requests, Some(battery_client).into());
(player_proxy, avrcp_requests, relay_fut, test_battery_manager)
}
#[track_caller]
fn expect_media_attributes_request(
exec: &mut fasync::TestExecutor,
controller_requests: &mut avrcp::ControllerRequestStream,
) {
// Should ask for the current media info and the status to return the correct results.
match exec.run_until_stalled(&mut controller_requests.next()).expect("should be ready") {
Some(Ok(avrcp::ControllerRequest::GetMediaAttributes { responder })) => {
responder
.send(Ok(&avrcp::MediaAttributes {
title: Some("Might Be Right".to_string()),
artist_name: Some("White Reaper".to_string()),
album_name: Some("You Deserve Love".to_string()),
track_number: Some("7".to_string()),
total_number_of_tracks: Some("10".to_string()),
genre: Some("Alternative".to_string()),
playing_time: Some("237000".to_string()),
..Default::default()
}))
.expect("should have succeeded");
}
x => panic!("Expected a GetMediaAttributes request, got {:?}", x),
}
}
#[track_caller]
fn expect_play_status_request(
exec: &mut fasync::TestExecutor,
controller_requests: &mut avrcp::ControllerRequestStream,
) {
match exec.run_until_stalled(&mut controller_requests.next()).expect("should be ready") {
Some(Ok(avrcp::ControllerRequest::GetPlayStatus { responder })) => {
responder
.send(Ok(&avrcp::PlayStatus {
song_length: Some(237000),
song_position: Some(1000),
playback_status: Some(avrcp::PlaybackStatus::Playing),
..Default::default()
}))
.expect("should have succeeded");
}
x => panic!("Expected a GetPlayStatus request, got {:?}", x),
}
}
#[track_caller]
fn finish_relay_setup(
mut relay_fut: &mut Pin<&mut impl Future>,
mut exec: &mut fasync::TestExecutor,
avrcp_request_stream: avrcp::PeerManagerRequestStream,
) -> (avrcp::ControllerRequestStream, avrcp::BrowseControllerRequestStream) {
let mut avrcp_request_stream = pin!(avrcp_request_stream);
// Connects to AVRCP.
let mut controller_request_stream = match exec
.run_until_stalled(&mut avrcp_request_stream.select_next_some())
{
Poll::Ready(Ok(avrcp::PeerManagerRequest::GetControllerForTarget {
client,
responder,
..
})) => {
responder.send(Ok(())).and_then(|_| client.into_stream()).expect("should have sent")
}
x => panic!("Expected a GetController request, got {:?}", x),
};
// Finish serving GetControllerForTarget.
exec.run_until_stalled(&mut relay_fut).expect_pending("should be pending");
let browse_controller_request_stream = match exec
.run_until_stalled(&mut avrcp_request_stream.select_next_some())
{
Poll::Ready(Ok(avrcp::PeerManagerRequest::GetBrowseControllerForTarget {
client,
responder,
..
})) => {
responder.send(Ok(())).and_then(|_| client.into_stream()).expect("should have sent")
}
x => panic!("Expected a GetBrowseController request, got {:?}", x),
};
// Finish serving GetBrowseControllerForTarget.
let res = exec.run_until_stalled(&mut relay_fut);
assert!(res.is_pending());
let complete = exec.run_until_stalled(&mut controller_request_stream.select_next_some());
match complete {
Poll::Ready(Ok(avrcp::ControllerRequest::SetNotificationFilter { .. })) => {}
x => panic!("Expected notifications to be set, got {:?}", x),
};
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
expect_media_attributes_request(&mut exec, &mut controller_request_stream);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
expect_play_status_request(&mut exec, &mut controller_request_stream);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// Expect play status timer interval request.
let _ = exec.wake_next_timer();
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
expect_play_status_request(&mut exec, &mut controller_request_stream);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// At this point, the relay is set up and should be waiting for requests from
// player_client, and sending commands / getting notifications from avrcp.
(controller_request_stream, browse_controller_request_stream)
}
fn run_to_stalled(exec: &mut fasync::TestExecutor) {
let _ = exec.run_until_stalled(&mut futures::future::pending::<()>());
}
/// Test that the relay sets up the connection to AVRCP and Sessions and stops on the stop
/// signal.
#[fuchsia::test]
fn test_relay_setup() {
let mut exec = fasync::TestExecutor::new();
let (player_client, avrcp_requests, relay_fut) = setup_media_relay();
let request_streams;
{
let mut relay_fut = pin!(relay_fut);
let res = exec.run_until_stalled(&mut relay_fut);
assert!(res.is_pending());
request_streams = finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
// Dropping the relay future drops all the connections.
}
run_to_stalled(&mut exec);
let mut controller_requests = request_streams.0;
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(None) => {}
x => panic!("Expected controller to be dropped, but got {:?}", x),
};
let mut watch_info_fut = player_client.watch_info_change();
match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Err(_e)) => {}
x => panic!("Expected player to be disconnected, but got {:?} from watch_info", x),
};
}
/// Relay will stop when AVRCP closes the notification channel.
#[fuchsia::test]
fn test_relay_avrcp_ends() {
let mut exec = fasync::TestExecutor::new();
let (player_client, avrcp_requests, relay_fut) = setup_media_relay();
let mut relay_fut = pin!(relay_fut);
let res = exec.run_until_stalled(&mut relay_fut);
assert!(res.is_pending());
let (controller_requests, _browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
// Closing the AVRCP controller should end the relay.
drop(controller_requests);
let res = exec.run_until_stalled(&mut relay_fut);
assert!(res.is_ready());
// The MediaSession should also drop based on this.
let mut watch_info_fut = player_client.watch_info_change();
match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Err(_e)) => {}
x => panic!("Expected player to be disconnected, but got {:?} from watch_info", x),
};
}
/// Relay will stop when Player stops asking for updates.
#[fuchsia::test]
fn test_relay_player_ends() {
let mut exec = fasync::TestExecutor::new();
let (player_client, avrcp_requests, relay_fut) = setup_media_relay();
let mut relay_fut = pin!(relay_fut);
let res = exec.run_until_stalled(&mut relay_fut);
assert!(res.is_pending());
let (mut controller_requests, _browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
// Closing of the MediaSession should end the relay.
drop(player_client);
let res = exec.run_until_stalled(&mut relay_fut);
assert!(res.is_ready());
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(None) => {}
x => panic!("Expected controller to be dropped, but got {:?}", x),
};
}
/// When mediasession initially asks for media info, a query of the remote AVRCP is made and
/// the data is translated.
#[fuchsia::test]
fn test_relay_sends_correct_media_info() {
let mut exec = fasync::TestExecutor::new_with_fake_time();
exec.set_fake_time(fasync::Time::from_nanos(7000));
let (player_client, avrcp_requests, relay_fut) = setup_media_relay();
let mut relay_fut = pin!(relay_fut);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
let _request_streams = finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
let mut watch_info_fut = player_client.watch_info_change();
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// Should return to the player the initial data.
let info_delta = match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Ok(delta)) => delta,
x => panic!("Expected WatchInfoChange to complete, instead: {:?}", x),
};
assert_eq!(
ValidPlayerStatus {
duration: Some(237000_000_000),
player_state: sessions2::PlayerState::Playing,
timeline_function: Some(media::TimelineFunction {
subject_time: 1000_000_000,
reference_time: 7000,
subject_delta: 1,
reference_delta: 1,
}),
repeat_mode: sessions2::RepeatMode::Single,
shuffle_on: false,
content_type: sessions2::ContentType::Audio,
error: None
},
info_delta.player_status.unwrap().try_into().expect("valid player status")
);
}
/// When playback status changes the new track info is sent to the Player client.
#[fuchsia::test]
fn test_relay_new_avrcp_track_info() {
let mut exec = fasync::TestExecutor::new_with_fake_time();
exec.set_fake_time(fasync::Time::from_nanos(7000));
let (player_client, avrcp_requests, relay_fut) = setup_media_relay();
let mut relay_fut = pin!(relay_fut);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
let (mut controller_requests, _browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
let mut watch_info_fut = player_client.watch_info_change();
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// Should return to the player the initial data.
let _info_delta = match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Ok(delta)) => delta,
x => panic!("Expected WatchInfoChange to complete, instead: {:?}", x),
};
// Queueing up another one with no change should just hang.
let mut watch_info_fut = player_client.watch_info_change();
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
assert!(exec.run_until_stalled(&mut watch_info_fut).is_pending());
// When a play status change notification happens, we get new requests.
controller_requests
.control_handle()
.send_on_notification(
7000,
&avrcp::Notification {
status: Some(avrcp::PlaybackStatus::Paused),
..Default::default()
},
)
.expect("should have sent");
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// Should ask for the current media info and the status to return the correct results.
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::ControllerRequest::GetMediaAttributes { responder }))) => {
responder
.send(Ok(&avrcp::MediaAttributes {
title: Some("Moneygrabber".to_string()),
artist_name: Some("Fitz and the Tantrums".to_string()),
album_name: Some("Pickin' Up the Pieces".to_string()),
track_number: Some("4".to_string()),
total_number_of_tracks: Some("11".to_string()),
genre: Some("Alternative".to_string()),
playing_time: Some("189000".to_string()),
..Default::default()
}))
.expect("should have sent");
}
x => panic!("Expected a GetMediaAttributes request, got {:?}", x),
}
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::ControllerRequest::GetPlayStatus { responder }))) => {
responder
.send(Ok(&avrcp::PlayStatus {
song_length: Some(189000),
song_position: Some(1000),
playback_status: Some(avrcp::PlaybackStatus::Paused),
..Default::default()
}))
.expect("should have sent");
}
x => panic!("Expected a GetPlayStatus request, got {:?}", x),
};
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// After the AVRCP requests, the info should have the delta.
let info_delta = match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Ok(delta)) => delta,
x => panic!("Expected WatchInfoChange to complete, instead: {:?}", x),
};
// After the notification is handled we should get an ack.
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::ControllerRequest::NotifyNotificationHandled {
..
}))) => {}
x => panic!("Expected ack of notification, but got {:?}", x),
};
assert_eq!(
ValidPlayerStatus {
duration: Some(189000_000_000),
player_state: sessions2::PlayerState::Paused,
timeline_function: Some(media::TimelineFunction {
subject_time: 1000_000_000,
reference_time: 7000,
subject_delta: 0,
reference_delta: 1,
}),
repeat_mode: sessions2::RepeatMode::Single,
shuffle_on: false,
content_type: sessions2::ContentType::Audio,
error: None
},
info_delta.player_status.unwrap().try_into().expect("valid player status")
);
// When addressed player change notification happens, we also get
// attributes.
controller_requests
.control_handle()
.send_on_notification(
7000,
&avrcp::Notification { addressed_player: Some(2), ..Default::default() },
)
.expect("should have sent");
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// Should ask for the current media info and the status to return the correct results.
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::ControllerRequest::GetMediaAttributes { responder }))) => {
responder
.send(Ok(&avrcp::MediaAttributes {
title: Some("some track".to_string()),
..Default::default()
}))
.expect("should have sent");
}
x => panic!("Expected a GetMediaAttributes request, got {:?}", x),
}
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
}
/// When the position update happens, the new position is updated for the Player.
#[fuchsia::test]
fn test_relay_updates_position() {
let mut exec = fasync::TestExecutor::new_with_fake_time();
exec.set_fake_time(fasync::Time::from_nanos(7000));
let (player_client, avrcp_requests, relay_fut) = setup_media_relay();
let mut relay_fut = pin!(relay_fut);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
let (mut controller_requests, _browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
let mut watch_info_fut = player_client.watch_info_change();
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// Should return to the player the initial data.
let _info_delta = match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Ok(delta)) => delta,
x => panic!("Expected WatchInfoChange to complete, instead: {:?}", x),
};
// Queueing up another one with no change should just hang.
let mut watch_info_fut = player_client.watch_info_change();
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
assert!(exec.run_until_stalled(&mut watch_info_fut).is_pending());
exec.set_fake_time(fasync::Time::from_nanos(9000));
// When a play status change notification happens, we get new requests.
controller_requests
.control_handle()
.send_on_notification(
9000,
&avrcp::Notification { pos: Some(3051), ..Default::default() },
)
.expect("should have sent");
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// After the notification is handled we should get an ack.
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::ControllerRequest::NotifyNotificationHandled {
..
}))) => {}
x => panic!("Expected ack of notification, but got {:?}", x),
};
// The info should have the delta.
let info_delta = match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Ok(delta)) => delta,
x => panic!("Expected WatchInfoChange to complete, instead: {:?}", x),
};
assert_eq!(
ValidPlayerStatus {
duration: Some(237000_000_000),
player_state: sessions2::PlayerState::Playing,
timeline_function: Some(media::TimelineFunction {
subject_time: 3051_000_000,
reference_time: 9000,
subject_delta: 1,
reference_delta: 1,
}),
repeat_mode: sessions2::RepeatMode::Single,
shuffle_on: false,
content_type: sessions2::ContentType::Audio,
error: None
},
info_delta.player_status.unwrap().try_into().expect("valid player status")
);
}
fn expect_panel_command(
exec: &mut fasync::TestExecutor,
controller_requests: &mut avrcp::ControllerRequestStream,
expected_command: avrcp::AvcPanelCommand,
) {
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::ControllerRequest::SendCommand { command, responder }))) => {
assert_eq!(expected_command, command);
responder.send(Ok(())).expect("should have sent");
}
x => panic!("Expected a SendCommand({:?}) request, got {:?}", expected_command, x),
}
}
/// When commands come from the Player, they are relayed to the AVRCP commands.
#[fuchsia::test]
fn test_relay_sends_commands() {
let mut exec = fasync::TestExecutor::new();
let (player_client, avrcp_requests, relay_fut) = setup_media_relay();
let mut relay_fut = pin!(relay_fut);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
let (mut controller_requests, _browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
player_client.pause().expect("should have been done");
player_client.play().expect("should have been done");
player_client.stop().expect("should have been done");
player_client.next_item().expect("should have been done");
player_client.prev_item().expect("should have been done");
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
expect_panel_command(&mut exec, &mut controller_requests, avrcp::AvcPanelCommand::Pause);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
expect_panel_command(&mut exec, &mut controller_requests, avrcp::AvcPanelCommand::Play);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
expect_panel_command(&mut exec, &mut controller_requests, avrcp::AvcPanelCommand::Stop);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
expect_panel_command(&mut exec, &mut controller_requests, avrcp::AvcPanelCommand::Forward);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
expect_panel_command(&mut exec, &mut controller_requests, avrcp::AvcPanelCommand::Backward);
}
fn expect_inform_battery_status_command(
exec: &mut fasync::TestExecutor,
controller_requests: &mut avrcp::ControllerRequestStream,
expected_battery_status: avrcp::BatteryStatus,
) {
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::ControllerRequest::InformBatteryStatus {
battery_status,
responder,
}))) => {
assert_eq!(battery_status, expected_battery_status);
responder.send(Ok(())).expect("can respond to client");
}
x => panic!("Expected a InformBatteryStatus request, got {:?}", x),
}
}
#[fuchsia::test]
fn relay_sends_battery_update_to_avrcp() {
let mut exec = fasync::TestExecutor::new();
let (_player_client, avrcp_requests, relay_fut, test_battery_manager) =
setup_media_relay_with_battery_manager(&mut exec);
let mut relay_fut = pin!(relay_fut);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
let (mut controller_requests, _browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
// Simulate a battery update via the TestBatteryManager.
let update = fpower::BatteryInfo {
status: Some(fpower::BatteryStatus::Ok),
level_status: Some(fpower::LevelStatus::Low),
level_percent: Some(33f32),
..Default::default()
};
let update_fut = pin!(test_battery_manager.send_update(update));
let (res, mut relay_fut) = run_while(&mut exec, relay_fut, update_fut);
assert_matches!(res, Ok(_));
expect_inform_battery_status_command(
&mut exec,
&mut controller_requests,
avrcp::BatteryStatus::Normal,
);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
}
#[fuchsia::test]
fn not_available_battery_update_is_not_relayed_to_avrcp() {
let mut exec = fasync::TestExecutor::new();
let (_player_client, avrcp_requests, relay_fut, test_battery_manager) =
setup_media_relay_with_battery_manager(&mut exec);
let mut relay_fut = pin!(relay_fut);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
let (mut controller_requests, _browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
// Simulate a battery update via the TestBatteryManager.
let update = fpower::BatteryInfo {
status: Some(fpower::BatteryStatus::Unknown),
..Default::default()
};
let update_fut = pin!(test_battery_manager.send_update(update));
let (res, mut relay_fut) = run_while(&mut exec, relay_fut, update_fut);
assert_matches!(res, Ok(_));
exec.run_until_stalled(&mut controller_requests.next()).expect_pending("No avrcp update");
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
}
#[fuchsia::test]
fn relay_still_active_when_battery_client_terminates() {
let mut exec = fasync::TestExecutor::new();
let (_player_client, avrcp_requests, relay_fut, test_battery_manager) =
setup_media_relay_with_battery_manager(&mut exec);
let mut relay_fut = pin!(relay_fut);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
let _request_streams = finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
// Battery Manager server disappears. The Battery Client stream should finish and the relay
// should be resilient.
drop(test_battery_manager);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay fut still running");
}
/// When available players changes we fetch for the list of available
/// players and change the media session player state based on the
/// available players status.
#[fuchsia::test]
fn test_relay_avrcp_available_players_changed() {
let mut exec = fasync::TestExecutor::new_with_fake_time();
exec.set_fake_time(fasync::Time::from_nanos(7000));
let (player_client, avrcp_requests, relay_fut) = setup_media_relay();
let mut relay_fut = pin!(relay_fut);
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
let (mut controller_requests, mut browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
let mut watch_info_fut = player_client.watch_info_change();
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// Should return to the player the initial data.
let _info_delta = match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Ok(delta)) => delta,
x => panic!("Expected WatchInfoChange to complete, instead: {:?}", x),
};
// Queueing up another one with no change should just hang.
let mut watch_info_fut = player_client.watch_info_change();
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
assert!(exec.run_until_stalled(&mut watch_info_fut).is_pending());
// When a play status change notification happens, we get new requests.
controller_requests
.control_handle()
.send_on_notification(
7000,
&avrcp::Notification {
available_players_changed: Some(true),
..Default::default()
},
)
.expect("should have sent");
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// Should ask for the list of media players.
match exec.run_until_stalled(&mut browse_controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::BrowseControllerRequest::GetMediaPlayerItems {
responder,
..
}))) => {
// Only return 1 inactive player.
responder
.send(Ok(&[avrcp::MediaPlayerItem {
player_id: Some(1),
playback_status: Some(avrcp::PlaybackStatus::Stopped),
..Default::default()
}]))
.expect("should have sent");
}
x => panic!("Expected a GetMediaPlayerItems request, got {:?}", x),
}
assert!(exec.run_until_stalled(&mut relay_fut).is_pending());
// After the AVRCP requests, the info should have the delta.
let info_delta = match exec.run_until_stalled(&mut watch_info_fut) {
Poll::Ready(Ok(delta)) => delta,
x => panic!("Expected WatchInfoChange to complete, instead: {:?}", x),
};
// After the notification is handled we should get an ack.
match exec.run_until_stalled(&mut controller_requests.next()) {
Poll::Ready(Some(Ok(avrcp::ControllerRequest::NotifyNotificationHandled {
..
}))) => {}
x => panic!("Expected ack of notification, but got {:?}", x),
};
// Player should have switched to idle state.
assert_eq!(
info_delta.player_status.unwrap().player_state.unwrap(),
sessions2::PlayerState::Idle
);
}
#[fuchsia::test]
fn avrcp_relay_inspect() {
let mut exec = fasync::TestExecutor::new_with_fake_time();
exec.set_fake_time(fasync::Time::from_nanos(7000));
let inspector = fuchsia_inspect::Inspector::default();
let (player_client, player_requests) =
endpoints::create_proxy_and_stream::<sessions2::PlayerMarker>().unwrap();
let (avrcp_proxy, avrcp_requests) =
endpoints::create_proxy_and_stream::<avrcp::PeerManagerMarker>().unwrap();
let peer_id = PeerId(0);
let relay = AvrcpRelay::default()
.with_inspect(inspector.root(), "avrcp_relay")
.expect("can attach");
let relay_fut = relay.session_relay(avrcp_proxy, peer_id, player_requests, None.into());
let mut relay_fut = pin!(relay_fut);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay active");
// The default Inspect tree doesn't have any player data.
assert_data_tree!(inspector, root: {
avrcp_relay: {
battery_watcher_active: false,
recent_player_requests: {},
player_status: {},
}
});
let (mut controller_requests, _browse_controller_requests) =
finish_relay_setup(&mut relay_fut, &mut exec, avrcp_requests);
// After finishing the initial relay set up, the inspect data should be updated. This data
// is populated by `expect_media_attributes_request` and `expect_play_status_request`.
assert_data_tree!(inspector, root: {
avrcp_relay: {
battery_watcher_active: false,
player_status: {
content_type: "Audio",
duration: 237000000000i64,
repeat_mode: "Single",
shuffle_on: false,
player_state: "Playing",
timeline_function: {
subject_time: 1000000000i64,
reference_time: 7000i64,
subject_delta: 1u64,
reference_delta: 1u64,
},
},
recent_player_requests: {},
}
});
exec.run_until_stalled(&mut relay_fut).expect_pending("relay active");
// Receiving player commands should also update the Inspect tree.
player_client.pause().expect("should have been done");
player_client.play().expect("should have been done");
// WatchInfoChange should not be recorded to the Inspect tree.
let watch_fut = player_client.watch_info_change();
exec.run_until_stalled(&mut relay_fut).expect_pending("relay active");
expect_panel_command(&mut exec, &mut controller_requests, avrcp::AvcPanelCommand::Pause);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay active");
expect_panel_command(&mut exec, &mut controller_requests, avrcp::AvcPanelCommand::Play);
exec.run_until_stalled(&mut relay_fut).expect_pending("relay active");
let (_delta, _relay_fut) = run_while(&mut exec, relay_fut, watch_fut);
assert_data_tree!(inspector, root: {
avrcp_relay: contains {
recent_player_requests: {
"0": { "@time": AnyProperty, request: "pause" },
"1": { "@time": AnyProperty, request: "play" },
},
}
});
}
}