| //! Suspend/Resume API. |
| |
| use crate::bluetooth::{Bluetooth, BtifBluetoothCallbacks}; |
| use crate::callbacks::Callbacks; |
| use crate::{bluetooth_gatt::IBluetoothGatt, BluetoothGatt, Message, RPCProxy}; |
| use bt_topshim::btif::BluetoothInterface; |
| use log::warn; |
| use std::sync::{Arc, Mutex}; |
| use tokio::sync::mpsc::Sender; |
| |
| /// Defines the Suspend/Resume API. |
| /// |
| /// This API is exposed by `btadapterd` and independent of the suspend/resume detection mechanism |
| /// which depends on the actual operating system the daemon runs on. Possible clients of this API |
| /// include `btmanagerd` with Chrome OS `powerd` integration, `btmanagerd` with systemd Inhibitor |
| /// interface, or any script hooked to suspend/resume events. |
| pub trait ISuspend { |
| /// Adds an observer to suspend events. |
| /// |
| /// Returns true if the callback can be registered. |
| fn register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool; |
| |
| /// Removes an observer to suspend events. |
| /// |
| /// Returns true if the callback can be removed, false if `callback_id` is not recognized. |
| fn unregister_callback(&mut self, callback_id: u32) -> bool; |
| |
| /// Prepares the stack for suspend, identified by `suspend_id`. |
| /// |
| /// Returns a positive number identifying the suspend if it can be started. If there is already |
| /// a suspend, that active suspend id is returned. |
| fn suspend(&mut self, suspend_type: SuspendType, suspend_id: i32); |
| |
| /// Undoes previous suspend preparation identified by `suspend_id`. |
| /// |
| /// Returns true if suspend can be resumed, and false if there is no suspend to resume. |
| fn resume(&mut self) -> bool; |
| } |
| |
| /// Suspend events. |
| pub trait ISuspendCallback: RPCProxy { |
| /// Triggered when a callback is registered and given an identifier `callback_id`. |
| fn on_callback_registered(&self, callback_id: u32); |
| |
| /// Triggered when the stack is ready for suspend and tell the observer the id of the suspend. |
| fn on_suspend_ready(&self, suspend_id: i32); |
| |
| /// Triggered when the stack has resumed the previous suspend. |
| fn on_resumed(&self, suspend_id: i32); |
| } |
| |
| /// Events that are disabled when we go into suspend. This prevents spurious wakes from |
| /// events we know can happen but are not useful. |
| /// Bit 4 = Disconnect Complete. |
| /// Bit 19 = Mode Change. |
| const MASKED_EVENTS_FOR_SUSPEND: u64 = (1u64 << 4) | (1u64 << 19); |
| |
| #[derive(FromPrimitive, ToPrimitive)] |
| #[repr(u32)] |
| pub enum SuspendType { |
| NoWakesAllowed, |
| AllowWakeFromHid, |
| Other, |
| } |
| |
| struct SuspendState { |
| le_rand_expected: bool, |
| suspend_expected: bool, |
| resume_expected: bool, |
| suspend_id: Option<i32>, |
| } |
| |
| impl SuspendState { |
| pub fn new() -> SuspendState { |
| Self { |
| le_rand_expected: false, |
| suspend_expected: false, |
| resume_expected: false, |
| suspend_id: None, |
| } |
| } |
| } |
| |
| /// Implementation of the suspend API. |
| pub struct Suspend { |
| bt: Arc<Mutex<Box<Bluetooth>>>, |
| intf: Arc<Mutex<BluetoothInterface>>, |
| gatt: Arc<Mutex<Box<BluetoothGatt>>>, |
| tx: Sender<Message>, |
| callbacks: Callbacks<dyn ISuspendCallback + Send>, |
| is_connected_suspend: bool, |
| was_a2dp_connected: bool, |
| suspend_timeout_joinhandle: Option<tokio::task::JoinHandle<()>>, |
| suspend_state: Arc<Mutex<SuspendState>>, |
| } |
| |
| impl Suspend { |
| pub fn new( |
| bt: Arc<Mutex<Box<Bluetooth>>>, |
| intf: Arc<Mutex<BluetoothInterface>>, |
| gatt: Arc<Mutex<Box<BluetoothGatt>>>, |
| tx: Sender<Message>, |
| ) -> Suspend { |
| Self { |
| bt, |
| intf, |
| gatt, |
| tx: tx.clone(), |
| callbacks: Callbacks::new(tx.clone(), Message::SuspendCallbackDisconnected), |
| is_connected_suspend: false, |
| was_a2dp_connected: false, |
| suspend_timeout_joinhandle: None, |
| suspend_state: Arc::new(Mutex::new(SuspendState::new())), |
| } |
| } |
| |
| pub(crate) fn callback_registered(&mut self, id: u32) { |
| match self.callbacks.get_by_id(id) { |
| Some(callback) => callback.on_callback_registered(id), |
| None => warn!("Suspend callback {} does not exist", id), |
| } |
| } |
| |
| pub(crate) fn remove_callback(&mut self, id: u32) -> bool { |
| self.callbacks.remove_callback(id) |
| } |
| |
| pub(crate) fn suspend_ready(&self, suspend_id: i32) { |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_suspend_ready(suspend_id); |
| }); |
| } |
| |
| pub(crate) fn resume_ready(&self, suspend_id: i32) { |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_resumed(suspend_id); |
| }); |
| } |
| } |
| |
| impl ISuspend for Suspend { |
| fn register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool { |
| let id = self.callbacks.add_callback(callback); |
| |
| let tx = self.tx.clone(); |
| tokio::spawn(async move { |
| let _result = tx.send(Message::SuspendCallbackRegistered(id)).await; |
| }); |
| |
| true |
| } |
| |
| fn unregister_callback(&mut self, callback_id: u32) -> bool { |
| self.remove_callback(callback_id) |
| } |
| |
| fn suspend(&mut self, suspend_type: SuspendType, suspend_id: i32) { |
| // Set suspend event mask |
| self.intf.lock().unwrap().set_default_event_mask_except(MASKED_EVENTS_FOR_SUSPEND, 0u64); |
| |
| self.intf.lock().unwrap().clear_event_filter(); |
| self.intf.lock().unwrap().clear_filter_accept_list(); |
| |
| // TODO(224602924): How do we get the advertising ids? |
| self.gatt.lock().unwrap().stop_advertising_set(0); |
| // TODO(224602924): How do we get the scanning ids? |
| self.gatt.lock().unwrap().stop_scan(0); |
| |
| self.intf.lock().unwrap().disconnect_all_acls(); |
| |
| // Handle wakeful cases (Connected/Other) |
| // Treat Other the same as Connected |
| 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 |
| } |
| _ => {} |
| } |
| self.suspend_state.lock().unwrap().le_rand_expected = true; |
| self.suspend_state.lock().unwrap().suspend_expected = true; |
| self.suspend_state.lock().unwrap().suspend_id = Some(suspend_id); |
| self.bt.lock().unwrap().le_rand(); |
| |
| if let Some(join_handle) = &self.suspend_timeout_joinhandle { |
| join_handle.abort(); |
| self.suspend_timeout_joinhandle = None; |
| } |
| |
| let tx = self.tx.clone(); |
| let suspend_state = self.suspend_state.clone(); |
| self.suspend_timeout_joinhandle = Some(tokio::spawn(async move { |
| tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; |
| log::error!("Suspend did not complete in 2 seconds, continuing anyway."); |
| |
| suspend_state.lock().unwrap().le_rand_expected = false; |
| suspend_state.lock().unwrap().suspend_expected = false; |
| suspend_state.lock().unwrap().suspend_id = None; |
| tokio::spawn(async move { |
| let _result = tx.send(Message::SuspendReady(suspend_id)).await; |
| }); |
| })); |
| } |
| |
| fn resume(&mut self) -> bool { |
| self.intf.lock().unwrap().set_default_event_mask_except(0u64, 0u64); |
| self.intf.lock().unwrap().set_event_filter_inquiry_result_all_devices(); |
| self.intf.lock().unwrap().set_event_filter_connection_setup_all_devices(); |
| if self.is_connected_suspend { |
| if self.was_a2dp_connected { |
| // TODO(230604670): reconnect to a2dp device |
| } |
| // TODO(224603198): start all advertising again |
| } |
| |
| self.suspend_state.lock().unwrap().le_rand_expected = true; |
| self.suspend_state.lock().unwrap().resume_expected = true; |
| self.bt.lock().unwrap().le_rand(); |
| |
| let tx = self.tx.clone(); |
| let suspend_state = self.suspend_state.clone(); |
| let suspend_id = self.suspend_state.lock().unwrap().suspend_id.unwrap(); |
| self.suspend_timeout_joinhandle = Some(tokio::spawn(async move { |
| tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; |
| log::error!("Resume did not complete in 2 seconds, continuing anyway."); |
| |
| suspend_state.lock().unwrap().le_rand_expected = false; |
| suspend_state.lock().unwrap().resume_expected = false; |
| tokio::spawn(async move { |
| let _result = tx.send(Message::ResumeReady(suspend_id)).await; |
| }); |
| })); |
| |
| true |
| } |
| } |
| |
| impl BtifBluetoothCallbacks for Suspend { |
| fn le_rand_cb(&mut self, _random: u64) { |
| // TODO(b/232547719): Suspend readiness may not depend only on LeRand, make a generic state |
| // machine to support waiting for other conditions. |
| if !self.suspend_state.lock().unwrap().le_rand_expected { |
| log::warn!("Unexpected LE Rand callback, ignoring."); |
| return; |
| } |
| self.suspend_state.lock().unwrap().le_rand_expected = false; |
| |
| if let Some(join_handle) = &self.suspend_timeout_joinhandle { |
| join_handle.abort(); |
| self.suspend_timeout_joinhandle = None; |
| } |
| |
| let suspend_id = self.suspend_state.lock().unwrap().suspend_id.unwrap(); |
| |
| if self.suspend_state.lock().unwrap().suspend_expected { |
| self.suspend_state.lock().unwrap().suspend_expected = false; |
| let tx = self.tx.clone(); |
| tokio::spawn(async move { |
| let _result = tx.send(Message::SuspendReady(suspend_id)).await; |
| }); |
| } |
| |
| self.suspend_state.lock().unwrap().suspend_id = Some(suspend_id); |
| if self.suspend_state.lock().unwrap().resume_expected { |
| self.suspend_state.lock().unwrap().resume_expected = false; |
| let tx = self.tx.clone(); |
| tokio::spawn(async move { |
| let _result = tx.send(Message::ResumeReady(suspend_id)).await; |
| }); |
| } |
| } |
| } |