| // Copyright 2021 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 super::types::{ |
| CustomAvcPanelCommand, CustomBatteryStatus, CustomPlayStatus, CustomPlayerApplicationSettings, |
| CustomPlayerApplicationSettingsAttributeIds, |
| }; |
| use crate::common_utils::common::macros::{fx_err_and_bail, with_line}; |
| use anyhow::Error; |
| use fidl::endpoints::create_endpoints; |
| use fidl_fuchsia_bluetooth::PeerId; |
| use fidl_fuchsia_bluetooth_avrcp::{ |
| ControllerMarker, ControllerProxy, Notifications, PeerManagerMarker, PeerManagerProxy, |
| }; |
| use fuchsia_component::client; |
| use fuchsia_sync::RwLock; |
| use tracing::info; |
| /// AvrcpFacadeInner contains the proxies used by the AvrcpFacade. |
| #[derive(Debug)] |
| struct AvrcpFacadeInner { |
| /// Proxy for the PeerManager service. |
| avrcp_service_proxy: Option<PeerManagerProxy>, |
| /// Proxy for the Controller service. |
| controller_proxy: Option<ControllerProxy>, |
| } |
| |
| #[derive(Debug)] |
| pub struct AvrcpFacade { |
| inner: RwLock<AvrcpFacadeInner>, |
| } |
| |
| impl AvrcpFacade { |
| pub fn new() -> AvrcpFacade { |
| AvrcpFacade { |
| inner: RwLock::new(AvrcpFacadeInner { |
| avrcp_service_proxy: None, |
| controller_proxy: None, |
| }), |
| } |
| } |
| |
| /// Creates the proxy to the PeerManager service. |
| async fn create_avrcp_service_proxy(&self) -> Result<PeerManagerProxy, Error> { |
| let tag = "AvrcpFacade::create_avrcp_service_proxy"; |
| match self.inner.read().avrcp_service_proxy.clone() { |
| Some(avrcp_service_proxy) => { |
| info!( |
| tag = &with_line!(tag), |
| "Current AVRCP service proxy: {:?}", avrcp_service_proxy |
| ); |
| Ok(avrcp_service_proxy) |
| } |
| None => { |
| let avrcp_service_proxy = client::connect_to_protocol::<PeerManagerMarker>(); |
| if let Err(err) = avrcp_service_proxy { |
| fx_err_and_bail!( |
| &with_line!(tag), |
| format_err!("Failed to create AVRCP service proxy: {}", err) |
| ); |
| } |
| avrcp_service_proxy |
| } |
| } |
| } |
| |
| /// Initialize the AVRCP service and retrieve the controller for the provided Bluetooth target id. |
| /// |
| /// # Arguments |
| /// * `id` - A u64 representing the device ID. |
| pub async fn init_avrcp(&self, id: u64) -> Result<(), Error> { |
| let tag = "AvrcpFacade::init_avrcp"; |
| self.inner.write().avrcp_service_proxy = Some(self.create_avrcp_service_proxy().await?); |
| let avrcp_service_proxy = match &self.inner.read().avrcp_service_proxy { |
| Some(p) => p.clone(), |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy created"), |
| }; |
| let (cont_client, cont_server) = create_endpoints::<ControllerMarker>(); |
| let _status = avrcp_service_proxy |
| .get_controller_for_target(&PeerId { value: id }, cont_server) |
| .await?; |
| self.inner.write().controller_proxy = |
| Some(cont_client.into_proxy().expect("Error obtaining controller client proxy")); |
| Ok(()) |
| } |
| |
| /// Returns the media attributes from the controller. |
| pub async fn get_media_attributes(&self) -> Result<String, Error> { |
| let tag = "AvrcpFacade::get_media_attributes"; |
| match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => match proxy.get_media_attributes().await? { |
| Ok(media_attribs) => Ok(format!("Media attributes: {:#?}", media_attribs)), |
| Err(e) => fx_err_and_bail!( |
| &with_line!(tag), |
| format!("Error fetching media attributes: {:?}", e) |
| ), |
| }, |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| } |
| } |
| |
| /// Returns the play status from the controller. |
| pub async fn get_play_status(&self) -> Result<CustomPlayStatus, Error> { |
| let tag = "AvrcpFacade::get_play_status"; |
| match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => match proxy.get_play_status().await? { |
| Ok(play_status) => Ok(CustomPlayStatus::new(&play_status)), |
| Err(e) => fx_err_and_bail!( |
| &with_line!(tag), |
| format!("Error fetching play status: {:?}", e) |
| ), |
| }, |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| } |
| } |
| |
| /// Sends an AVCPanelCommand to the controller. |
| /// |
| /// # Arguments |
| /// * `command` - an enum representing the AVCPanelCommand. |
| pub async fn send_command(&self, command: CustomAvcPanelCommand) -> Result<(), Error> { |
| let tag = "AvrcpFacade::send_command"; |
| let result = match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => proxy.send_command(command.into()).await?, |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| }; |
| match result { |
| Ok(res) => Ok(res), |
| Err(err) => { |
| fx_err_and_bail!(&with_line!(tag), format!("Error sending command:{:?}", err)) |
| } |
| } |
| } |
| |
| /// Sends an AVCPanelCommand to the controller. |
| /// |
| /// # Arguments |
| /// * `absolute_volume` - the value to which the volume is set. |
| pub async fn set_absolute_volume(&self, absolute_volume: u8) -> Result<u8, Error> { |
| let tag = "AvrcpFacade::set_absolute_volume"; |
| let result = match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => proxy.set_absolute_volume(absolute_volume).await?, |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| }; |
| match result { |
| Ok(res) => Ok(res), |
| Err(err) => { |
| fx_err_and_bail!(&with_line!(tag), format!("Error setting volume:{:?}", err)) |
| } |
| } |
| } |
| |
| /// Returns the player application settings from the controller. |
| /// |
| /// # Arguments |
| /// * `attribute_ids` - the attribute ids for the application settings to return. If empty, returns all. |
| pub async fn get_player_application_settings( |
| &self, |
| attribute_ids: CustomPlayerApplicationSettingsAttributeIds, |
| ) -> Result<CustomPlayerApplicationSettings, Error> { |
| let tag = "AvrcpFacade::get_player_application_settings"; |
| match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => { |
| match proxy.get_player_application_settings(&attribute_ids.to_vec()).await? { |
| Ok(player_application_settings) => Ok(player_application_settings.into()), |
| Err(e) => fx_err_and_bail!( |
| &with_line!(tag), |
| format!("Error fetching player application settings: {:?}", e) |
| ), |
| } |
| } |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| } |
| } |
| |
| /// Sets the player application settings on the controller. |
| /// |
| /// # Arguments |
| /// * `settings` - the player application settings to set. |
| pub async fn set_player_application_settings( |
| &self, |
| settings: CustomPlayerApplicationSettings, |
| ) -> Result<CustomPlayerApplicationSettings, Error> { |
| let tag = "AvrcpFacade::set_player_application_settings"; |
| match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => match proxy.set_player_application_settings(&settings.into()).await? { |
| Ok(player_application_settings) => Ok(player_application_settings.into()), |
| Err(e) => fx_err_and_bail!( |
| &with_line!(tag), |
| format!("Error fetching player application settings: {:?}", e) |
| ), |
| }, |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| } |
| } |
| |
| /// Informs battery status on the controller. |
| /// |
| /// # Arguments |
| /// * `battery_status` - the battery status to inform. |
| pub async fn inform_battery_status( |
| &self, |
| battery_status: CustomBatteryStatus, |
| ) -> Result<(), Error> { |
| let tag = "AvrcpFacade::inform_battery_status"; |
| match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => match proxy.inform_battery_status(battery_status.into()).await? { |
| Ok(()) => Ok(()), |
| Err(e) => fx_err_and_bail!( |
| &with_line!(tag), |
| format!("Error informing battery status: {:?}", e) |
| ), |
| }, |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| } |
| } |
| |
| /// Sets addressed player on the controller. |
| /// |
| /// # Arguments |
| /// * `player_id` - the player id to set as the addressed player. |
| pub async fn set_addressed_player(&self, player_id: u16) -> Result<(), Error> { |
| let tag = "AvrcpFacade::set_addressed_player"; |
| match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => match proxy.set_addressed_player(player_id).await? { |
| Ok(()) => Ok(()), |
| Err(e) => fx_err_and_bail!( |
| &with_line!(tag), |
| format!("Error setting addressed player: {:?}", e) |
| ), |
| }, |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| } |
| } |
| |
| /// Sets notification filter on the controller. |
| /// |
| /// # Arguments |
| /// * `notifications_filter` - the notification ids as bit flags for which to filter. |
| /// * `position_change_interval` - the interval in seconds that the controller client would |
| /// like to be notified of `TRACK_POS_CHANGED` events. It is ignored if 'TRACK_POS' bit flag is not set. |
| pub async fn set_notification_filter( |
| &self, |
| notifications_filter: u32, |
| position_change_interval: u32, |
| ) -> Result<(), Error> { |
| let tag = "AvrcpFacade::set_notification_filter"; |
| let notifications = match Notifications::from_bits(notifications_filter) { |
| Some(notifications) => notifications, |
| _ => fx_err_and_bail!( |
| &with_line!(tag), |
| format!( |
| "Invalid bit flags value for notifications filter: {:?}", |
| notifications_filter |
| ) |
| ), |
| }; |
| match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => { |
| match proxy.set_notification_filter(notifications, position_change_interval) { |
| Ok(()) => Ok(()), |
| Err(e) => fx_err_and_bail!( |
| &with_line!(tag), |
| format!("Error setting notification filter: {:?}", e) |
| ), |
| } |
| } |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| } |
| } |
| |
| /// Notifies that the OnNotification event was handled. |
| pub async fn notify_notification_handled(&self) -> Result<(), Error> { |
| let tag = "AvrcpFacade::notify_notification_handled"; |
| match self.inner.read().controller_proxy.clone() { |
| Some(proxy) => match proxy.notify_notification_handled() { |
| Ok(()) => Ok(()), |
| Err(e) => fx_err_and_bail!( |
| &with_line!(tag), |
| format!("Error setting notification filter: {:?}", e) |
| ), |
| }, |
| None => fx_err_and_bail!(&with_line!(tag), "No AVRCP service proxy available"), |
| } |
| } |
| |
| /// A function to remove the profile service proxy and clear connected devices. |
| fn clear(&self) { |
| self.inner.write().avrcp_service_proxy = None; |
| self.inner.write().controller_proxy = None; |
| } |
| |
| /// Cleanup any Profile Server related objects. |
| pub async fn cleanup(&self) -> Result<(), Error> { |
| self.clear(); |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| |
| use super::super::types::{ |
| CustomCustomAttributeValue, CustomCustomPlayerApplicationSetting, CustomEqualizer, |
| CustomRepeatStatusMode, CustomScanMode, |
| }; |
| use super::*; |
| use assert_matches::assert_matches; |
| use fidl::endpoints::create_proxy_and_stream; |
| use fidl_fuchsia_bluetooth_avrcp::{BatteryStatus, ControllerRequest, PlayStatus}; |
| use fuchsia_async as fasync; |
| use futures::prelude::*; |
| use lazy_static::lazy_static; |
| |
| lazy_static! { |
| static ref PLAY_STATUS: CustomPlayStatus = CustomPlayStatus { |
| song_length: Some(120), |
| song_position: Some(10), |
| playback_status: Some(4), |
| }; |
| static ref PLAYER_APPLICATION_SETTINGS: CustomPlayerApplicationSettings = |
| CustomPlayerApplicationSettings { |
| equalizer: Some(CustomEqualizer::Off), |
| repeat_status_mode: Some(CustomRepeatStatusMode::AllTrackRepeat), |
| shuffle_mode: None, |
| scan_mode: Some(CustomScanMode::GroupScan), |
| custom_settings: Some(vec![CustomCustomPlayerApplicationSetting { |
| attribute_id: Some(1), |
| attribute_name: Some("attribute".to_string()), |
| possible_values: Some(vec![CustomCustomAttributeValue { |
| description: "description".to_string(), |
| value: 5 |
| }]), |
| current_value: Some(5), |
| }]) |
| }; |
| static ref PLAYER_APPLICATION_SETTINGS_INPUT: CustomPlayerApplicationSettings = |
| CustomPlayerApplicationSettings { |
| equalizer: Some(CustomEqualizer::Off), |
| repeat_status_mode: None, |
| shuffle_mode: None, |
| scan_mode: None, |
| custom_settings: Some(vec![CustomCustomPlayerApplicationSetting { |
| attribute_id: Some(1), |
| attribute_name: Some("attribute".to_string()), |
| possible_values: Some(vec![CustomCustomAttributeValue { |
| description: "description".to_string(), |
| value: 5 |
| }]), |
| current_value: Some(5), |
| }]) |
| }; |
| static ref PLAYER_APPLICATION_SETTINGS_ATTRIBUTE_IDS: CustomPlayerApplicationSettingsAttributeIds = |
| CustomPlayerApplicationSettingsAttributeIds { attribute_ids: Some(vec![1]) }; |
| } |
| struct MockAvrcpTester { |
| expected_state: Vec<Box<dyn FnOnce(ControllerRequest) + Send + 'static>>, |
| } |
| |
| impl MockAvrcpTester { |
| fn new() -> Self { |
| Self { expected_state: vec![] } |
| } |
| |
| fn push(mut self, request: impl FnOnce(ControllerRequest) + Send + 'static) -> Self { |
| self.expected_state.push(Box::new(request)); |
| self |
| } |
| |
| fn build_controller(self) -> (AvrcpFacade, impl Future<Output = ()>) { |
| let (proxy, mut stream) = create_proxy_and_stream::<ControllerMarker>().unwrap(); |
| let fut = async move { |
| for expected in self.expected_state { |
| expected(stream.next().await.unwrap().unwrap()); |
| } |
| assert_matches!(stream.next().await, None); |
| }; |
| ( |
| AvrcpFacade { |
| inner: RwLock::new(AvrcpFacadeInner { |
| controller_proxy: Some(proxy), |
| avrcp_service_proxy: None, |
| }), |
| }, |
| fut, |
| ) |
| } |
| |
| fn expect_get_play_status(self, result: CustomPlayStatus) -> Self { |
| self.push(move |req| match req { |
| ControllerRequest::GetPlayStatus { responder } => { |
| responder.send(Ok(&PlayStatus::from(result))).unwrap(); |
| } |
| _ => {} |
| }) |
| } |
| |
| fn expect_get_player_application_settings( |
| self, |
| result: CustomPlayerApplicationSettings, |
| input: &'static CustomPlayerApplicationSettingsAttributeIds, |
| ) -> Self { |
| self.push(move |req| match req { |
| ControllerRequest::GetPlayerApplicationSettings { attribute_ids, responder } => { |
| assert_eq!(attribute_ids, input.to_vec()); |
| responder.send(Ok(&result.into())).unwrap(); |
| } |
| _ => {} |
| }) |
| } |
| |
| fn expect_set_player_application_settings( |
| self, |
| result: CustomPlayerApplicationSettings, |
| input: &'static CustomPlayerApplicationSettings, |
| ) -> Self { |
| self.push(move |req| match req { |
| ControllerRequest::SetPlayerApplicationSettings { |
| requested_settings, |
| responder, |
| } => { |
| let player_application_settings: CustomPlayerApplicationSettings = |
| requested_settings.into(); |
| assert_eq!(player_application_settings, *input); |
| responder.send(Ok(&result.into())).unwrap(); |
| } |
| _ => {} |
| }) |
| } |
| |
| fn expect_inform_battery_status(self, input: CustomBatteryStatus) -> Self { |
| self.push(move |req| match req { |
| ControllerRequest::InformBatteryStatus { battery_status, responder } => { |
| let battery_status_expected: BatteryStatus = input.into(); |
| assert_eq!(battery_status_expected, battery_status); |
| responder.send(Ok(())).unwrap(); |
| } |
| _ => {} |
| }) |
| } |
| |
| fn expect_set_addressed_player(self, input: u16) -> Self { |
| self.push(move |req| match req { |
| ControllerRequest::SetAddressedPlayer { player_id, responder } => { |
| assert_eq!(input, player_id); |
| responder.send(Ok(())).unwrap(); |
| } |
| _ => {} |
| }) |
| } |
| |
| fn expect_set_notification_filter( |
| self, |
| input_notification_filter: u32, |
| input_position_change_interval: u32, |
| ) -> Self { |
| self.push(move |req| match req { |
| ControllerRequest::SetNotificationFilter { |
| notifications, |
| position_change_interval, |
| .. |
| } => { |
| assert_eq!( |
| Notifications::from_bits(input_notification_filter).unwrap(), |
| notifications |
| ); |
| assert_eq!(input_position_change_interval, position_change_interval); |
| } |
| _ => {} |
| }) |
| } |
| } |
| #[fasync::run_singlethreaded(test)] |
| async fn test_get_play_status() { |
| let (facade, play_status_fut) = |
| MockAvrcpTester::new().expect_get_play_status(*PLAY_STATUS).build_controller(); |
| let facade_fut = async move { |
| let play_status = facade.get_play_status().await.unwrap(); |
| assert_eq!(play_status, *PLAY_STATUS); |
| }; |
| future::join(facade_fut, play_status_fut).await; |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_get_player_application_settings() { |
| let (facade, application_settings_fut) = MockAvrcpTester::new() |
| .expect_get_player_application_settings( |
| PLAYER_APPLICATION_SETTINGS.clone(), |
| &PLAYER_APPLICATION_SETTINGS_ATTRIBUTE_IDS, |
| ) |
| .build_controller(); |
| let facade_fut = async move { |
| let application_settings = facade |
| .get_player_application_settings(PLAYER_APPLICATION_SETTINGS_ATTRIBUTE_IDS.clone()) |
| .await |
| .unwrap(); |
| assert_eq!(application_settings, *PLAYER_APPLICATION_SETTINGS); |
| }; |
| future::join(facade_fut, application_settings_fut).await; |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_set_player_application_settings() { |
| let (facade, application_settings_fut) = MockAvrcpTester::new() |
| .expect_set_player_application_settings( |
| PLAYER_APPLICATION_SETTINGS.clone(), |
| &PLAYER_APPLICATION_SETTINGS_INPUT, |
| ) |
| .build_controller(); |
| let facade_fut = async move { |
| let application_settings = facade |
| .set_player_application_settings(PLAYER_APPLICATION_SETTINGS_INPUT.clone()) |
| .await |
| .unwrap(); |
| assert_eq!(application_settings, *PLAYER_APPLICATION_SETTINGS); |
| }; |
| future::join(facade_fut, application_settings_fut).await; |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_inform_battery_status() { |
| let (facade, battery_status_fut) = MockAvrcpTester::new() |
| .expect_inform_battery_status(CustomBatteryStatus::Normal) |
| .build_controller(); |
| let facade_fut = async move { |
| facade.inform_battery_status(CustomBatteryStatus::Normal).await.unwrap(); |
| }; |
| future::join(facade_fut, battery_status_fut).await; |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_set_addressed_player() { |
| let addressed_player = 5; |
| let (facade, addressed_player_fut) = |
| MockAvrcpTester::new().expect_set_addressed_player(addressed_player).build_controller(); |
| let facade_fut = async move { |
| facade.set_addressed_player(addressed_player).await.unwrap(); |
| }; |
| future::join(facade_fut, addressed_player_fut).await; |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_set_notification_filter() { |
| let input_notification_filter = 3; |
| let input_position_change_interval = 1; |
| let (facade, notification_filter_fut) = MockAvrcpTester::new() |
| .expect_set_notification_filter( |
| input_notification_filter, |
| input_position_change_interval, |
| ) |
| .build_controller(); |
| let facade_fut = async move { |
| facade |
| .set_notification_filter(input_notification_filter, input_position_change_interval) |
| .await |
| .unwrap(); |
| }; |
| future::join(facade_fut, notification_filter_fut).await; |
| } |
| } |