• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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