blob: 3d6d50ed85b06229fe281b4e671c2f09a0f40850 [file] [log] [blame]
//! Suspend/Resume API.
use crate::callbacks::Callbacks;
use crate::{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(&self, suspend_type: SuspendType) -> u32;
/// 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(&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: u32);
/// Triggered when the stack has resumed the previous suspend.
fn on_resumed(&self, suspend_id: u32);
}
#[derive(FromPrimitive, ToPrimitive)]
#[repr(u32)]
pub enum SuspendType {
Disconnected,
Connected,
Other,
}
/// Implementation of the suspend API.
pub struct Suspend {
intf: Arc<Mutex<BluetoothInterface>>,
tx: Sender<Message>,
callbacks: Callbacks<dyn ISuspendCallback + Send>,
is_connected_suspend: bool,
was_a2dp_connected: bool,
}
impl Suspend {
pub fn new(intf: Arc<Mutex<BluetoothInterface>>, tx: Sender<Message>) -> Suspend {
Self {
intf: intf,
tx: tx.clone(),
callbacks: Callbacks::new(tx.clone(), Message::SuspendCallbackDisconnected),
is_connected_suspend: false,
was_a2dp_connected: false,
}
}
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)
}
}
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(&self, suspend_type: SuspendType) -> u32 {
let suspend_id = 1;
match suspend_type {
SuspendType::Connected => {
// TODO(231345733): API For allowing classic HID only
// TODO(230604670): check if A2DP is connected
// TODO(224603198): save all advertiser information
}
SuspendType::Disconnected => {
self.intf.lock().unwrap().clear_event_filter();
self.intf.lock().unwrap().clear_event_mask();
}
SuspendType::Other => {
// TODO(231438120): Decide what to do about Other suspend type
// For now perform disconnected suspend flow
self.intf.lock().unwrap().clear_event_filter();
self.intf.lock().unwrap().clear_event_mask();
}
}
self.intf.lock().unwrap().clear_filter_accept_list();
// TODO(231435700): self.intf.lock().unwrap().disconnect_all_acls();
self.intf.lock().unwrap().le_rand();
self.callbacks.for_all_callbacks(|callback| {
callback.on_suspend_ready(suspend_id);
});
return 1;
}
fn resume(&self) -> bool {
let suspend_id = 1;
self.intf.lock().unwrap().set_event_filter_inquiry_result_all_devices();
self.intf.lock().unwrap().set_default_event_mask();
if self.is_connected_suspend {
if self.was_a2dp_connected {
// TODO(230604670): self.intf.lock().unwrap().restore_filter_accept_list();
// TODO(230604670): reconnect to a2dp device if connected before
}
// TODO(224603198): start all advertising again
}
self.intf.lock().unwrap().le_rand();
self.callbacks.for_all_callbacks(|callback| {
callback.on_resumed(suspend_id);
});
return true;
}
}