1 //! Suspend/Resume API. 2 3 use crate::bluetooth::{ 4 Bluetooth, BluetoothDevice, BtifBluetoothCallbacks, DelayedActions, IBluetooth, 5 }; 6 use crate::bluetooth_media::BluetoothMedia; 7 use crate::callbacks::Callbacks; 8 use crate::{BluetoothGatt, Message, RPCProxy}; 9 use bt_topshim::btif::{BluetoothInterface, BtDiscMode}; 10 use log::warn; 11 use num_derive::{FromPrimitive, ToPrimitive}; 12 use std::sync::{Arc, Mutex}; 13 use tokio::sync::mpsc::Sender; 14 15 /// Defines the Suspend/Resume API. 16 /// 17 /// This API is exposed by `btadapterd` and independent of the suspend/resume detection mechanism 18 /// which depends on the actual operating system the daemon runs on. Possible clients of this API 19 /// include `btmanagerd` with Chrome OS `powerd` integration, `btmanagerd` with systemd Inhibitor 20 /// interface, or any script hooked to suspend/resume events. 21 pub trait ISuspend { 22 /// Adds an observer to suspend events. 23 /// 24 /// Returns true if the callback can be registered. register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool25 fn register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool; 26 27 /// Removes an observer to suspend events. 28 /// 29 /// Returns true if the callback can be removed, false if `callback_id` is not recognized. unregister_callback(&mut self, callback_id: u32) -> bool30 fn unregister_callback(&mut self, callback_id: u32) -> bool; 31 32 /// Prepares the stack for suspend, identified by `suspend_id`. 33 /// 34 /// Returns a positive number identifying the suspend if it can be started. If there is already 35 /// a suspend, that active suspend id is returned. suspend(&mut self, suspend_type: SuspendType, suspend_id: i32)36 fn suspend(&mut self, suspend_type: SuspendType, suspend_id: i32); 37 38 /// Undoes previous suspend preparation identified by `suspend_id`. 39 /// 40 /// Returns true if suspend can be resumed, and false if there is no suspend to resume. resume(&mut self) -> bool41 fn resume(&mut self) -> bool; 42 } 43 44 /// Suspend events. 45 pub trait ISuspendCallback: RPCProxy { 46 /// Triggered when a callback is registered and given an identifier `callback_id`. on_callback_registered(&mut self, callback_id: u32)47 fn on_callback_registered(&mut self, callback_id: u32); 48 49 /// Triggered when the stack is ready for suspend and tell the observer the id of the suspend. on_suspend_ready(&mut self, suspend_id: i32)50 fn on_suspend_ready(&mut self, suspend_id: i32); 51 52 /// Triggered when the stack has resumed the previous suspend. on_resumed(&mut self, suspend_id: i32)53 fn on_resumed(&mut self, suspend_id: i32); 54 } 55 56 /// Events that are disabled when we go into suspend. This prevents spurious wakes from 57 /// events we know can happen but are not useful. 58 /// Bit 4 = Disconnect Complete. 59 /// Bit 19 = Mode Change. 60 const MASKED_EVENTS_FOR_SUSPEND: u64 = (1u64 << 4) | (1u64 << 19); 61 62 /// When we resume, we will want to reconnect audio devices that were previously connected. 63 /// However, we will need to delay a few seconds to avoid co-ex issues with Wi-Fi reconnection. 64 const RECONNECT_AUDIO_ON_RESUME_DELAY_MS: u64 = 3000; 65 66 #[derive(FromPrimitive, ToPrimitive)] 67 #[repr(u32)] 68 pub enum SuspendType { 69 NoWakesAllowed, 70 AllowWakeFromHid, 71 Other, 72 } 73 74 struct SuspendState { 75 le_rand_expected: bool, 76 suspend_expected: bool, 77 resume_expected: bool, 78 suspend_id: Option<i32>, 79 } 80 81 impl SuspendState { new() -> SuspendState82 pub fn new() -> SuspendState { 83 Self { 84 le_rand_expected: false, 85 suspend_expected: false, 86 resume_expected: false, 87 suspend_id: None, 88 } 89 } 90 } 91 92 /// Implementation of the suspend API. 93 pub struct Suspend { 94 bt: Arc<Mutex<Box<Bluetooth>>>, 95 intf: Arc<Mutex<BluetoothInterface>>, 96 gatt: Arc<Mutex<Box<BluetoothGatt>>>, 97 media: Arc<Mutex<Box<BluetoothMedia>>>, 98 tx: Sender<Message>, 99 callbacks: Callbacks<dyn ISuspendCallback + Send>, 100 101 /// This list keeps track of audio devices that had an audio profile before 102 /// suspend so that we can attempt to connect after suspend. 103 audio_reconnect_list: Vec<BluetoothDevice>, 104 105 /// Active reconnection attempt after resume. 106 audio_reconnect_joinhandle: Option<tokio::task::JoinHandle<()>>, 107 108 suspend_timeout_joinhandle: Option<tokio::task::JoinHandle<()>>, 109 suspend_state: Arc<Mutex<SuspendState>>, 110 111 /// Bluetooth adapter connectable state before suspending. 112 connectable_to_restore: bool, 113 /// Bluetooth adapter discoverable mode before suspending. 114 discoverable_mode_to_restore: BtDiscMode, 115 } 116 117 impl Suspend { new( bt: Arc<Mutex<Box<Bluetooth>>>, intf: Arc<Mutex<BluetoothInterface>>, gatt: Arc<Mutex<Box<BluetoothGatt>>>, media: Arc<Mutex<Box<BluetoothMedia>>>, tx: Sender<Message>, ) -> Suspend118 pub fn new( 119 bt: Arc<Mutex<Box<Bluetooth>>>, 120 intf: Arc<Mutex<BluetoothInterface>>, 121 gatt: Arc<Mutex<Box<BluetoothGatt>>>, 122 media: Arc<Mutex<Box<BluetoothMedia>>>, 123 tx: Sender<Message>, 124 ) -> Suspend { 125 Self { 126 bt, 127 intf, 128 gatt, 129 media, 130 tx: tx.clone(), 131 callbacks: Callbacks::new(tx.clone(), Message::SuspendCallbackDisconnected), 132 audio_reconnect_list: Vec::new(), 133 audio_reconnect_joinhandle: None, 134 suspend_timeout_joinhandle: None, 135 suspend_state: Arc::new(Mutex::new(SuspendState::new())), 136 connectable_to_restore: false, 137 discoverable_mode_to_restore: BtDiscMode::NonDiscoverable, 138 } 139 } 140 callback_registered(&mut self, id: u32)141 pub(crate) fn callback_registered(&mut self, id: u32) { 142 match self.callbacks.get_by_id_mut(id) { 143 Some(callback) => callback.on_callback_registered(id), 144 None => warn!("Suspend callback {} does not exist", id), 145 } 146 } 147 remove_callback(&mut self, id: u32) -> bool148 pub(crate) fn remove_callback(&mut self, id: u32) -> bool { 149 self.callbacks.remove_callback(id) 150 } 151 suspend_ready(&mut self, suspend_id: i32)152 pub(crate) fn suspend_ready(&mut self, suspend_id: i32) { 153 self.callbacks.for_all_callbacks(|callback| { 154 callback.on_suspend_ready(suspend_id); 155 }); 156 } 157 resume_ready(&mut self, suspend_id: i32)158 pub(crate) fn resume_ready(&mut self, suspend_id: i32) { 159 self.callbacks.for_all_callbacks(|callback| { 160 callback.on_resumed(suspend_id); 161 }); 162 } 163 164 /// On resume, we attempt to reconnect to any audio devices connected during suspend. 165 /// This marks this attempt as completed and we should clear the pending reconnects here. audio_reconnect_complete(&mut self)166 pub(crate) fn audio_reconnect_complete(&mut self) { 167 self.audio_reconnect_list.clear(); 168 self.audio_reconnect_joinhandle = None; 169 } 170 get_connected_audio_devices(&self) -> Vec<BluetoothDevice>171 pub(crate) fn get_connected_audio_devices(&self) -> Vec<BluetoothDevice> { 172 let bonded_connected = self.bt.lock().unwrap().get_bonded_and_connected_devices(); 173 self.media.lock().unwrap().filter_to_connected_audio_devices_from(&bonded_connected) 174 } 175 } 176 177 impl ISuspend for Suspend { register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool178 fn register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool { 179 let id = self.callbacks.add_callback(callback); 180 181 let tx = self.tx.clone(); 182 tokio::spawn(async move { 183 let _result = tx.send(Message::SuspendCallbackRegistered(id)).await; 184 }); 185 186 true 187 } 188 unregister_callback(&mut self, callback_id: u32) -> bool189 fn unregister_callback(&mut self, callback_id: u32) -> bool { 190 self.remove_callback(callback_id) 191 } 192 suspend(&mut self, suspend_type: SuspendType, suspend_id: i32)193 fn suspend(&mut self, suspend_type: SuspendType, suspend_id: i32) { 194 // Set suspend event mask 195 self.intf.lock().unwrap().set_default_event_mask_except(MASKED_EVENTS_FOR_SUSPEND, 0u64); 196 197 self.connectable_to_restore = self.bt.lock().unwrap().get_connectable_internal(); 198 self.discoverable_mode_to_restore = 199 self.bt.lock().unwrap().get_discoverable_mode_internal(); 200 self.bt.lock().unwrap().set_connectable_internal(false); 201 self.intf.lock().unwrap().clear_event_filter(); 202 self.intf.lock().unwrap().clear_filter_accept_list(); 203 204 self.bt.lock().unwrap().discovery_enter_suspend(); 205 self.gatt.lock().unwrap().advertising_enter_suspend(); 206 self.gatt.lock().unwrap().scan_enter_suspend(); 207 208 // Track connected audio devices and queue them for reconnect on resume. 209 // If we still have the previous reconnect list left-over, do not try 210 // to collect a new list here. 211 if self.audio_reconnect_list.is_empty() { 212 self.audio_reconnect_list = self.get_connected_audio_devices(); 213 } 214 215 // Cancel any active reconnect task. 216 if let Some(joinhandle) = &self.audio_reconnect_joinhandle { 217 joinhandle.abort(); 218 self.audio_reconnect_joinhandle = None; 219 } 220 221 self.intf.lock().unwrap().disconnect_all_acls(); 222 223 // Handle wakeful cases (Connected/Other) 224 // Treat Other the same as Connected 225 match suspend_type { 226 SuspendType::AllowWakeFromHid | SuspendType::Other => { 227 self.intf.lock().unwrap().allow_wake_by_hid(); 228 } 229 _ => {} 230 } 231 self.suspend_state.lock().unwrap().le_rand_expected = true; 232 self.suspend_state.lock().unwrap().suspend_expected = true; 233 self.suspend_state.lock().unwrap().suspend_id = Some(suspend_id); 234 self.bt.lock().unwrap().le_rand(); 235 236 if let Some(join_handle) = &self.suspend_timeout_joinhandle { 237 join_handle.abort(); 238 self.suspend_timeout_joinhandle = None; 239 } 240 241 let tx = self.tx.clone(); 242 let suspend_state = self.suspend_state.clone(); 243 self.suspend_timeout_joinhandle = Some(tokio::spawn(async move { 244 tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; 245 log::error!("Suspend did not complete in 2 seconds, continuing anyway."); 246 247 suspend_state.lock().unwrap().le_rand_expected = false; 248 suspend_state.lock().unwrap().suspend_expected = false; 249 suspend_state.lock().unwrap().suspend_id = None; 250 tokio::spawn(async move { 251 let _result = tx.send(Message::SuspendReady(suspend_id)).await; 252 }); 253 })); 254 } 255 resume(&mut self) -> bool256 fn resume(&mut self) -> bool { 257 self.intf.lock().unwrap().set_default_event_mask_except(0u64, 0u64); 258 259 // Restore event filter and accept list to normal. 260 self.intf.lock().unwrap().clear_event_filter(); 261 self.intf.lock().unwrap().clear_filter_accept_list(); 262 self.intf.lock().unwrap().restore_filter_accept_list(); 263 self.bt.lock().unwrap().set_connectable_internal(self.connectable_to_restore); 264 if self.discoverable_mode_to_restore != BtDiscMode::NonDiscoverable { 265 self.bt.lock().unwrap().set_discoverable(self.discoverable_mode_to_restore.clone(), 0); 266 } 267 268 if !self.audio_reconnect_list.is_empty() { 269 let reconnect_list = self.audio_reconnect_list.clone(); 270 let txl = self.tx.clone(); 271 272 // Cancel any existing reconnect attempt. 273 if let Some(joinhandle) = &self.audio_reconnect_joinhandle { 274 joinhandle.abort(); 275 self.audio_reconnect_joinhandle = None; 276 } 277 278 self.audio_reconnect_joinhandle = Some(tokio::spawn(async move { 279 // Wait a few seconds to avoid co-ex issues with wi-fi. 280 tokio::time::sleep(tokio::time::Duration::from_millis( 281 RECONNECT_AUDIO_ON_RESUME_DELAY_MS, 282 )) 283 .await; 284 285 // Queue up connections. 286 for device in reconnect_list { 287 let _unused: Option<()> = txl 288 .send(Message::DelayedAdapterActions(DelayedActions::ConnectAllProfiles( 289 device, 290 ))) 291 .await 292 .ok(); 293 } 294 295 // Mark that we're done. 296 let _unused: Option<()> = 297 txl.send(Message::AudioReconnectOnResumeComplete).await.ok(); 298 })); 299 } 300 301 self.bt.lock().unwrap().discovery_exit_suspend(); 302 self.gatt.lock().unwrap().advertising_exit_suspend(); 303 self.gatt.lock().unwrap().scan_exit_suspend(); 304 305 self.suspend_state.lock().unwrap().le_rand_expected = true; 306 self.suspend_state.lock().unwrap().resume_expected = true; 307 self.bt.lock().unwrap().le_rand(); 308 309 let tx = self.tx.clone(); 310 let suspend_state = self.suspend_state.clone(); 311 let suspend_id = self.suspend_state.lock().unwrap().suspend_id.unwrap(); 312 self.suspend_timeout_joinhandle = Some(tokio::spawn(async move { 313 tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; 314 log::error!("Resume did not complete in 2 seconds, continuing anyway."); 315 316 suspend_state.lock().unwrap().le_rand_expected = false; 317 suspend_state.lock().unwrap().resume_expected = false; 318 tokio::spawn(async move { 319 let _result = tx.send(Message::ResumeReady(suspend_id)).await; 320 }); 321 })); 322 323 true 324 } 325 } 326 327 impl BtifBluetoothCallbacks for Suspend { le_rand_cb(&mut self, _random: u64)328 fn le_rand_cb(&mut self, _random: u64) { 329 // TODO(b/232547719): Suspend readiness may not depend only on LeRand, make a generic state 330 // machine to support waiting for other conditions. 331 if !self.suspend_state.lock().unwrap().le_rand_expected { 332 log::warn!("Unexpected LE Rand callback, ignoring."); 333 return; 334 } 335 self.suspend_state.lock().unwrap().le_rand_expected = false; 336 337 if let Some(join_handle) = &self.suspend_timeout_joinhandle { 338 join_handle.abort(); 339 self.suspend_timeout_joinhandle = None; 340 } 341 342 let suspend_id = self.suspend_state.lock().unwrap().suspend_id.unwrap(); 343 344 if self.suspend_state.lock().unwrap().suspend_expected { 345 self.suspend_state.lock().unwrap().suspend_expected = false; 346 let tx = self.tx.clone(); 347 tokio::spawn(async move { 348 let _result = tx.send(Message::SuspendReady(suspend_id)).await; 349 }); 350 } 351 352 self.suspend_state.lock().unwrap().suspend_id = Some(suspend_id); 353 if self.suspend_state.lock().unwrap().resume_expected { 354 self.suspend_state.lock().unwrap().resume_expected = false; 355 let tx = self.tx.clone(); 356 tokio::spawn(async move { 357 let _result = tx.send(Message::ResumeReady(suspend_id)).await; 358 }); 359 } 360 } 361 } 362