Loading system/gd/rust/linux/service/src/main.rs +1 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,7 @@ fn main() -> Result<(), Box<dyn Error>> { bluetooth.clone(), intf.clone(), bluetooth_gatt.clone(), bluetooth_media.clone(), tx.clone(), )))); let logging = Arc::new(Mutex::new(Box::new(BluetoothLogging::new(is_debug, log_output)))); Loading system/gd/rust/linux/stack/src/bluetooth.rs +9 −0 Original line number Diff line number Diff line Loading @@ -619,6 +619,15 @@ impl Bluetooth { Ok(()) } /// Returns all bonded and connected devices. pub(crate) fn get_bonded_and_connected_devices(&mut self) -> Vec<BluetoothDevice> { self.bonded_devices .values() .filter(|v| v.acl_state == BtAclState::Connected && v.bond_state == BtBondState::Bonded) .map(|v| v.info.clone()) .collect() } /// Check whether found devices are still fresh. If they're outside the /// freshness window, send a notification to clear the device from clients. fn trigger_freshness_check(&mut self) { Loading system/gd/rust/linux/stack/src/bluetooth_media.rs +33 −9 Original line number Diff line number Diff line Loading @@ -49,6 +49,10 @@ const PROFILE_DISCOVERY_TIMEOUT_SEC: u64 = 10; // 6s is set to align with Android's default. See "btservice/PhonePolicy". const CONNECT_MISSING_PROFILES_TIMEOUT_SEC: u64 = 6; /// The list of profiles we consider as audio profiles for media. const MEDIA_AUDIO_PROFILES: &[uuid::Profile] = &[uuid::Profile::A2dpSink, uuid::Profile::Hfp, uuid::Profile::AvrcpController]; pub trait IBluetoothMedia { /// fn register_callback(&mut self, callback: Box<dyn IBluetoothMediaCallback + Send>) -> bool; Loading Loading @@ -242,15 +246,20 @@ impl BluetoothMedia { } } fn is_profile_connected(&self, addr: RawAddress, profile: uuid::Profile) -> bool { if let Some(connected_profiles) = self.connected_profiles.get(&addr) { return connected_profiles.contains(&profile); fn is_profile_connected(&self, addr: &RawAddress, profile: &uuid::Profile) -> bool { self.is_any_profile_connected(addr, &[profile.clone()]) } fn is_any_profile_connected(&self, addr: &RawAddress, profiles: &[uuid::Profile]) -> bool { if let Some(connected_profiles) = self.connected_profiles.get(addr) { return profiles.iter().any(|p| connected_profiles.contains(&p)); } return false; } fn add_connected_profile(&mut self, addr: RawAddress, profile: uuid::Profile) { if self.is_profile_connected(addr, profile) { if self.is_profile_connected(&addr, &profile) { warn!("[{}]: profile is already connected", addr.to_string()); return; } Loading @@ -266,7 +275,7 @@ impl BluetoothMedia { profile: uuid::Profile, is_profile_critical: bool, ) { if !self.is_profile_connected(addr, profile) { if !self.is_profile_connected(&addr, &profile) { warn!("[{}]: profile is already disconnected", addr.to_string()); return; } Loading Loading @@ -833,9 +842,6 @@ impl BluetoothMedia { fn adapter_get_audio_profiles(&self, addr: RawAddress) -> HashSet<uuid::Profile> { let device = BluetoothDevice::new(addr.to_string(), "".to_string()); if let Some(adapter) = &self.adapter { let audio_profiles = vec![uuid::Profile::A2dpSink, uuid::Profile::Hfp, uuid::Profile::AvrcpController]; adapter .lock() .unwrap() Loading @@ -844,7 +850,7 @@ impl BluetoothMedia { .map(|u| uuid::UuidHelper::is_known_profile(&u)) .filter(|u| u.is_some()) .map(|u| u.unwrap()) .filter(|u| audio_profiles.contains(&u)) .filter(|u| MEDIA_AUDIO_PROFILES.contains(&u)) .collect() } else { HashSet::new() Loading Loading @@ -917,6 +923,24 @@ impl BluetoothMedia { winning_state } } pub fn filter_to_connected_audio_devices_from( &self, devices: &Vec<BluetoothDevice>, ) -> Vec<BluetoothDevice> { devices .iter() .filter(|d| { let addr = match RawAddress::from_string(&d.address) { None => return false, Some(a) => a, }; self.is_any_profile_connected(&addr, &MEDIA_AUDIO_PROFILES) }) .cloned() .collect() } } fn get_a2dp_dispatcher(tx: Sender<Message>) -> A2dpCallbacksDispatcher { Loading system/gd/rust/linux/stack/src/lib.rs +5 −0 Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ pub enum Message { SuspendCallbackDisconnected(u32), SuspendReady(i32), ResumeReady(i32), AudioReconnectOnResumeComplete, // Scanner related ScannerCallbackDisconnected(u32), Loading Loading @@ -268,6 +269,10 @@ impl Stack { suspend.lock().unwrap().resume_ready(suspend_id); } Message::AudioReconnectOnResumeComplete => { suspend.lock().unwrap().audio_reconnect_complete(); } Message::ScannerCallbackDisconnected(id) => { bluetooth_gatt.lock().unwrap().remove_scanner_callback(id); } Loading system/gd/rust/linux/stack/src/suspend.rs +75 −10 Original line number Diff line number Diff line //! Suspend/Resume API. use crate::bluetooth::{Bluetooth, BtifBluetoothCallbacks}; use crate::bluetooth::{Bluetooth, BluetoothDevice, BtifBluetoothCallbacks, DelayedActions}; use crate::bluetooth_media::BluetoothMedia; use crate::callbacks::Callbacks; use crate::{BluetoothGatt, Message, RPCProxy}; use bt_topshim::btif::BluetoothInterface; Loading Loading @@ -56,6 +57,10 @@ pub trait ISuspendCallback: RPCProxy { /// Bit 19 = Mode Change. const MASKED_EVENTS_FOR_SUSPEND: u64 = (1u64 << 4) | (1u64 << 19); /// When we resume, we will want to reconnect audio devices that were previously connected. /// However, we will need to delay a few seconds to avoid co-ex issues with Wi-Fi reconnection. const RECONNECT_AUDIO_ON_RESUME_DELAY_MS: u64 = 3000; #[derive(FromPrimitive, ToPrimitive)] #[repr(u32)] pub enum SuspendType { Loading Loading @@ -87,10 +92,17 @@ pub struct Suspend { bt: Arc<Mutex<Box<Bluetooth>>>, intf: Arc<Mutex<BluetoothInterface>>, gatt: Arc<Mutex<Box<BluetoothGatt>>>, media: Arc<Mutex<Box<BluetoothMedia>>>, tx: Sender<Message>, callbacks: Callbacks<dyn ISuspendCallback + Send>, is_wakeful_suspend: bool, was_a2dp_connected: bool, /// This list keeps track of audio devices that had an audio profile before /// suspend so that we can attempt to connect after suspend. audio_reconnect_list: Vec<BluetoothDevice>, /// Active reconnection attempt after resume. audio_reconnect_joinhandle: Option<tokio::task::JoinHandle<()>>, suspend_timeout_joinhandle: Option<tokio::task::JoinHandle<()>>, suspend_state: Arc<Mutex<SuspendState>>, } Loading @@ -100,16 +112,18 @@ impl Suspend { bt: Arc<Mutex<Box<Bluetooth>>>, intf: Arc<Mutex<BluetoothInterface>>, gatt: Arc<Mutex<Box<BluetoothGatt>>>, media: Arc<Mutex<Box<BluetoothMedia>>>, tx: Sender<Message>, ) -> Suspend { Self { bt, intf, gatt, media, tx: tx.clone(), callbacks: Callbacks::new(tx.clone(), Message::SuspendCallbackDisconnected), is_wakeful_suspend: false, was_a2dp_connected: false, audio_reconnect_list: Vec::new(), audio_reconnect_joinhandle: None, suspend_timeout_joinhandle: None, suspend_state: Arc::new(Mutex::new(SuspendState::new())), } Loading Loading @@ -137,6 +151,18 @@ impl Suspend { callback.on_resumed(suspend_id); }); } /// On resume, we attempt to reconnect to any audio devices connected during suspend. /// This marks this attempt as completed and we should clear the pending reconnects here. pub(crate) fn audio_reconnect_complete(&mut self) { self.audio_reconnect_list.clear(); self.audio_reconnect_joinhandle = None; } pub(crate) fn get_connected_audio_devices(&self) -> Vec<BluetoothDevice> { let bonded_connected = self.bt.lock().unwrap().get_bonded_and_connected_devices(); self.media.lock().unwrap().filter_to_connected_audio_devices_from(&bonded_connected) } } impl ISuspend for Suspend { Loading Loading @@ -165,6 +191,19 @@ impl ISuspend for Suspend { self.gatt.lock().unwrap().advertising_enter_suspend(); self.gatt.lock().unwrap().scan_enter_suspend(); // Track connected audio devices and queue them for reconnect on resume. // If we still have the previous reconnect list left-over, do not try // to collect a new list here. if self.audio_reconnect_list.is_empty() { self.audio_reconnect_list = self.get_connected_audio_devices(); } // Cancel any active reconnect task. if let Some(joinhandle) = &self.audio_reconnect_joinhandle { joinhandle.abort(); self.audio_reconnect_joinhandle = None; } self.intf.lock().unwrap().disconnect_all_acls(); // Handle wakeful cases (Connected/Other) Loading @@ -172,8 +211,6 @@ impl ISuspend for Suspend { match suspend_type { SuspendType::AllowWakeFromHid | SuspendType::Other => { self.intf.lock().unwrap().allow_wake_by_hid(); // self.was_a2dp_connected = TODO(230604670): check if A2DP is connected // TODO(230604670): check if A2DP is connected } _ => {} } Loading Loading @@ -211,11 +248,39 @@ impl ISuspend for Suspend { self.intf.lock().unwrap().allow_wake_by_hid(); self.intf.lock().unwrap().clear_event_filter(); if self.is_wakeful_suspend { if self.was_a2dp_connected { // TODO(230604670): reconnect to a2dp device if !self.audio_reconnect_list.is_empty() { let reconnect_list = self.audio_reconnect_list.clone(); let txl = self.tx.clone(); // Cancel any existing reconnect attempt. if let Some(joinhandle) = &self.audio_reconnect_joinhandle { joinhandle.abort(); self.audio_reconnect_joinhandle = None; } self.audio_reconnect_joinhandle = Some(tokio::spawn(async move { // Wait a few seconds to avoid co-ex issues with wi-fi. tokio::time::sleep(tokio::time::Duration::from_millis( RECONNECT_AUDIO_ON_RESUME_DELAY_MS, )) .await; // Queue up connections. for device in reconnect_list { let _unused: Option<()> = txl .send(Message::DelayedAdapterActions(DelayedActions::ConnectAllProfiles( device, ))) .await .ok(); } // Mark that we're done. let _unused: Option<()> = txl.send(Message::AudioReconnectOnResumeComplete).await.ok(); })); } self.gatt.lock().unwrap().advertising_exit_suspend(); self.gatt.lock().unwrap().scan_exit_suspend(); Loading Loading
system/gd/rust/linux/service/src/main.rs +1 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,7 @@ fn main() -> Result<(), Box<dyn Error>> { bluetooth.clone(), intf.clone(), bluetooth_gatt.clone(), bluetooth_media.clone(), tx.clone(), )))); let logging = Arc::new(Mutex::new(Box::new(BluetoothLogging::new(is_debug, log_output)))); Loading
system/gd/rust/linux/stack/src/bluetooth.rs +9 −0 Original line number Diff line number Diff line Loading @@ -619,6 +619,15 @@ impl Bluetooth { Ok(()) } /// Returns all bonded and connected devices. pub(crate) fn get_bonded_and_connected_devices(&mut self) -> Vec<BluetoothDevice> { self.bonded_devices .values() .filter(|v| v.acl_state == BtAclState::Connected && v.bond_state == BtBondState::Bonded) .map(|v| v.info.clone()) .collect() } /// Check whether found devices are still fresh. If they're outside the /// freshness window, send a notification to clear the device from clients. fn trigger_freshness_check(&mut self) { Loading
system/gd/rust/linux/stack/src/bluetooth_media.rs +33 −9 Original line number Diff line number Diff line Loading @@ -49,6 +49,10 @@ const PROFILE_DISCOVERY_TIMEOUT_SEC: u64 = 10; // 6s is set to align with Android's default. See "btservice/PhonePolicy". const CONNECT_MISSING_PROFILES_TIMEOUT_SEC: u64 = 6; /// The list of profiles we consider as audio profiles for media. const MEDIA_AUDIO_PROFILES: &[uuid::Profile] = &[uuid::Profile::A2dpSink, uuid::Profile::Hfp, uuid::Profile::AvrcpController]; pub trait IBluetoothMedia { /// fn register_callback(&mut self, callback: Box<dyn IBluetoothMediaCallback + Send>) -> bool; Loading Loading @@ -242,15 +246,20 @@ impl BluetoothMedia { } } fn is_profile_connected(&self, addr: RawAddress, profile: uuid::Profile) -> bool { if let Some(connected_profiles) = self.connected_profiles.get(&addr) { return connected_profiles.contains(&profile); fn is_profile_connected(&self, addr: &RawAddress, profile: &uuid::Profile) -> bool { self.is_any_profile_connected(addr, &[profile.clone()]) } fn is_any_profile_connected(&self, addr: &RawAddress, profiles: &[uuid::Profile]) -> bool { if let Some(connected_profiles) = self.connected_profiles.get(addr) { return profiles.iter().any(|p| connected_profiles.contains(&p)); } return false; } fn add_connected_profile(&mut self, addr: RawAddress, profile: uuid::Profile) { if self.is_profile_connected(addr, profile) { if self.is_profile_connected(&addr, &profile) { warn!("[{}]: profile is already connected", addr.to_string()); return; } Loading @@ -266,7 +275,7 @@ impl BluetoothMedia { profile: uuid::Profile, is_profile_critical: bool, ) { if !self.is_profile_connected(addr, profile) { if !self.is_profile_connected(&addr, &profile) { warn!("[{}]: profile is already disconnected", addr.to_string()); return; } Loading Loading @@ -833,9 +842,6 @@ impl BluetoothMedia { fn adapter_get_audio_profiles(&self, addr: RawAddress) -> HashSet<uuid::Profile> { let device = BluetoothDevice::new(addr.to_string(), "".to_string()); if let Some(adapter) = &self.adapter { let audio_profiles = vec![uuid::Profile::A2dpSink, uuid::Profile::Hfp, uuid::Profile::AvrcpController]; adapter .lock() .unwrap() Loading @@ -844,7 +850,7 @@ impl BluetoothMedia { .map(|u| uuid::UuidHelper::is_known_profile(&u)) .filter(|u| u.is_some()) .map(|u| u.unwrap()) .filter(|u| audio_profiles.contains(&u)) .filter(|u| MEDIA_AUDIO_PROFILES.contains(&u)) .collect() } else { HashSet::new() Loading Loading @@ -917,6 +923,24 @@ impl BluetoothMedia { winning_state } } pub fn filter_to_connected_audio_devices_from( &self, devices: &Vec<BluetoothDevice>, ) -> Vec<BluetoothDevice> { devices .iter() .filter(|d| { let addr = match RawAddress::from_string(&d.address) { None => return false, Some(a) => a, }; self.is_any_profile_connected(&addr, &MEDIA_AUDIO_PROFILES) }) .cloned() .collect() } } fn get_a2dp_dispatcher(tx: Sender<Message>) -> A2dpCallbacksDispatcher { Loading
system/gd/rust/linux/stack/src/lib.rs +5 −0 Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ pub enum Message { SuspendCallbackDisconnected(u32), SuspendReady(i32), ResumeReady(i32), AudioReconnectOnResumeComplete, // Scanner related ScannerCallbackDisconnected(u32), Loading Loading @@ -268,6 +269,10 @@ impl Stack { suspend.lock().unwrap().resume_ready(suspend_id); } Message::AudioReconnectOnResumeComplete => { suspend.lock().unwrap().audio_reconnect_complete(); } Message::ScannerCallbackDisconnected(id) => { bluetooth_gatt.lock().unwrap().remove_scanner_callback(id); } Loading
system/gd/rust/linux/stack/src/suspend.rs +75 −10 Original line number Diff line number Diff line //! Suspend/Resume API. use crate::bluetooth::{Bluetooth, BtifBluetoothCallbacks}; use crate::bluetooth::{Bluetooth, BluetoothDevice, BtifBluetoothCallbacks, DelayedActions}; use crate::bluetooth_media::BluetoothMedia; use crate::callbacks::Callbacks; use crate::{BluetoothGatt, Message, RPCProxy}; use bt_topshim::btif::BluetoothInterface; Loading Loading @@ -56,6 +57,10 @@ pub trait ISuspendCallback: RPCProxy { /// Bit 19 = Mode Change. const MASKED_EVENTS_FOR_SUSPEND: u64 = (1u64 << 4) | (1u64 << 19); /// When we resume, we will want to reconnect audio devices that were previously connected. /// However, we will need to delay a few seconds to avoid co-ex issues with Wi-Fi reconnection. const RECONNECT_AUDIO_ON_RESUME_DELAY_MS: u64 = 3000; #[derive(FromPrimitive, ToPrimitive)] #[repr(u32)] pub enum SuspendType { Loading Loading @@ -87,10 +92,17 @@ pub struct Suspend { bt: Arc<Mutex<Box<Bluetooth>>>, intf: Arc<Mutex<BluetoothInterface>>, gatt: Arc<Mutex<Box<BluetoothGatt>>>, media: Arc<Mutex<Box<BluetoothMedia>>>, tx: Sender<Message>, callbacks: Callbacks<dyn ISuspendCallback + Send>, is_wakeful_suspend: bool, was_a2dp_connected: bool, /// This list keeps track of audio devices that had an audio profile before /// suspend so that we can attempt to connect after suspend. audio_reconnect_list: Vec<BluetoothDevice>, /// Active reconnection attempt after resume. audio_reconnect_joinhandle: Option<tokio::task::JoinHandle<()>>, suspend_timeout_joinhandle: Option<tokio::task::JoinHandle<()>>, suspend_state: Arc<Mutex<SuspendState>>, } Loading @@ -100,16 +112,18 @@ impl Suspend { bt: Arc<Mutex<Box<Bluetooth>>>, intf: Arc<Mutex<BluetoothInterface>>, gatt: Arc<Mutex<Box<BluetoothGatt>>>, media: Arc<Mutex<Box<BluetoothMedia>>>, tx: Sender<Message>, ) -> Suspend { Self { bt, intf, gatt, media, tx: tx.clone(), callbacks: Callbacks::new(tx.clone(), Message::SuspendCallbackDisconnected), is_wakeful_suspend: false, was_a2dp_connected: false, audio_reconnect_list: Vec::new(), audio_reconnect_joinhandle: None, suspend_timeout_joinhandle: None, suspend_state: Arc::new(Mutex::new(SuspendState::new())), } Loading Loading @@ -137,6 +151,18 @@ impl Suspend { callback.on_resumed(suspend_id); }); } /// On resume, we attempt to reconnect to any audio devices connected during suspend. /// This marks this attempt as completed and we should clear the pending reconnects here. pub(crate) fn audio_reconnect_complete(&mut self) { self.audio_reconnect_list.clear(); self.audio_reconnect_joinhandle = None; } pub(crate) fn get_connected_audio_devices(&self) -> Vec<BluetoothDevice> { let bonded_connected = self.bt.lock().unwrap().get_bonded_and_connected_devices(); self.media.lock().unwrap().filter_to_connected_audio_devices_from(&bonded_connected) } } impl ISuspend for Suspend { Loading Loading @@ -165,6 +191,19 @@ impl ISuspend for Suspend { self.gatt.lock().unwrap().advertising_enter_suspend(); self.gatt.lock().unwrap().scan_enter_suspend(); // Track connected audio devices and queue them for reconnect on resume. // If we still have the previous reconnect list left-over, do not try // to collect a new list here. if self.audio_reconnect_list.is_empty() { self.audio_reconnect_list = self.get_connected_audio_devices(); } // Cancel any active reconnect task. if let Some(joinhandle) = &self.audio_reconnect_joinhandle { joinhandle.abort(); self.audio_reconnect_joinhandle = None; } self.intf.lock().unwrap().disconnect_all_acls(); // Handle wakeful cases (Connected/Other) Loading @@ -172,8 +211,6 @@ impl ISuspend for Suspend { match suspend_type { SuspendType::AllowWakeFromHid | SuspendType::Other => { self.intf.lock().unwrap().allow_wake_by_hid(); // self.was_a2dp_connected = TODO(230604670): check if A2DP is connected // TODO(230604670): check if A2DP is connected } _ => {} } Loading Loading @@ -211,11 +248,39 @@ impl ISuspend for Suspend { self.intf.lock().unwrap().allow_wake_by_hid(); self.intf.lock().unwrap().clear_event_filter(); if self.is_wakeful_suspend { if self.was_a2dp_connected { // TODO(230604670): reconnect to a2dp device if !self.audio_reconnect_list.is_empty() { let reconnect_list = self.audio_reconnect_list.clone(); let txl = self.tx.clone(); // Cancel any existing reconnect attempt. if let Some(joinhandle) = &self.audio_reconnect_joinhandle { joinhandle.abort(); self.audio_reconnect_joinhandle = None; } self.audio_reconnect_joinhandle = Some(tokio::spawn(async move { // Wait a few seconds to avoid co-ex issues with wi-fi. tokio::time::sleep(tokio::time::Duration::from_millis( RECONNECT_AUDIO_ON_RESUME_DELAY_MS, )) .await; // Queue up connections. for device in reconnect_list { let _unused: Option<()> = txl .send(Message::DelayedAdapterActions(DelayedActions::ConnectAllProfiles( device, ))) .await .ok(); } // Mark that we're done. let _unused: Option<()> = txl.send(Message::AudioReconnectOnResumeComplete).await.ok(); })); } self.gatt.lock().unwrap().advertising_exit_suspend(); self.gatt.lock().unwrap().scan_exit_suspend(); Loading