Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c8e59c3 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "floss: Reconnect audio devices on resume"

parents 5a1ce48b 982ebd98
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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))));
+9 −0
Original line number Diff line number Diff line
@@ -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) {
+33 −9
Original line number Diff line number Diff line
@@ -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;
@@ -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;
        }
@@ -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;
        }
@@ -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()
@@ -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()
@@ -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 {
+5 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ pub enum Message {
    SuspendCallbackDisconnected(u32),
    SuspendReady(i32),
    ResumeReady(i32),
    AudioReconnectOnResumeComplete,

    // Scanner related
    ScannerCallbackDisconnected(u32),
@@ -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);
                }
+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;
@@ -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 {
@@ -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>>,
}
@@ -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())),
        }
@@ -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 {
@@ -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)
@@ -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
            }
            _ => {}
        }
@@ -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();