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

Commit 52b2a225 authored by Abhishek Pandit-Subedi's avatar Abhishek Pandit-Subedi
Browse files

floss: Track freshness of found devices

After devices are found during discovery, they need to be tracked to
make sure they are fresh. Add a recurring 30s timer after discovery ends
to check device freshness and clear stale devices.

Bug: 228629015
Tag: #floss
Test: Manual test
Change-Id: Iab8d56807b9c015d396c4fe6f3a7028a496f9569
parent 76e55632
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -125,12 +125,16 @@ impl IBluetoothCallback for BtCallback {
        print_info!("Found device: {:?}", remote_device);
    }

    fn on_device_cleared(&self, remote_device: BluetoothDevice) {
        match self.context.lock().unwrap().found_devices.remove(&remote_device.address) {
            Some(_) => print_info!("Removed device: {:?}", remote_device),
            None => (),
        };
    }

    fn on_discovering_changed(&self, discovering: bool) {
        self.context.lock().unwrap().discovering_state = discovering;

        if discovering {
            self.context.lock().unwrap().found_devices.clear();
        }
        print_info!("Discovering: {}", discovering);
    }

+3 −0
Original line number Diff line number Diff line
@@ -183,6 +183,9 @@ impl IBluetoothCallback for IBluetoothCallbackDBus {
    #[dbus_method("OnDeviceFound")]
    fn on_device_found(&self, remote_device: BluetoothDevice) {}

    #[dbus_method("OnDeviceCleared")]
    fn on_device_cleared(&self, remote_device: BluetoothDevice) {}

    #[dbus_method("OnDiscoveringChanged")]
    fn on_discovering_changed(&self, discovering: bool) {}

+4 −0
Original line number Diff line number Diff line
@@ -51,6 +51,10 @@ impl IBluetoothCallback for BluetoothCallbackDBus {
    fn on_device_found(&self, remote_device: BluetoothDevice) {
        dbus_generated!()
    }
    #[dbus_method("OnDeviceCleared")]
    fn on_device_cleared(&self, remote_device: BluetoothDevice) {
        dbus_generated!()
    }
    #[dbus_method("OnDiscoveringChanged")]
    fn on_discovering_changed(&self, discovering: bool) {
        dbus_generated!()
+157 −72
Original line number Diff line number Diff line
@@ -18,8 +18,11 @@ use num_traits::cast::ToPrimitive;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use std::time::Instant;
use tokio::sync::mpsc::Sender;
use tokio::task::JoinHandle;
use tokio::time;

use crate::bluetooth_media::{BluetoothMedia, IBluetoothMedia, MediaActions};
use crate::uuid::{Profile, UuidHelper};
@@ -28,6 +31,11 @@ use crate::{BluetoothCallbackType, Message, RPCProxy};
const DEFAULT_DISCOVERY_TIMEOUT_MS: u64 = 12800;
const MIN_ADV_INSTANCES_FOR_MULTI_ADV: u8 = 5;

/// Devices that were last seen longer than this duration are considered stale
/// if they haven't already bonded or connected. Once this duration expires, the
/// clear event should be sent to clients.
const FOUND_DEVICE_FRESHNESS: Duration = Duration::from_secs(30);

/// Defines the adapter API.
pub trait IBluetooth {
    /// Adds a callback from a client who wishes to observe adapter events.
@@ -196,6 +204,7 @@ struct BluetoothDeviceContext {
    pub bond_state: BtBondState,
    pub acl_state: BtAclState,
    pub info: BluetoothDevice,
    pub last_seen: Instant,
    pub properties: HashMap<BtPropertyType, BluetoothProperty>,
}

@@ -204,10 +213,16 @@ impl BluetoothDeviceContext {
        bond_state: BtBondState,
        acl_state: BtAclState,
        info: BluetoothDevice,
        last_seen: Instant,
        properties: Vec<BluetoothProperty>,
    ) -> BluetoothDeviceContext {
        let mut device =
            BluetoothDeviceContext { bond_state, acl_state, info, properties: HashMap::new() };
        let mut device = BluetoothDeviceContext {
            bond_state,
            acl_state,
            info,
            last_seen,
            properties: HashMap::new(),
        };
        device.update_properties(properties);
        device
    }
@@ -227,6 +242,11 @@ impl BluetoothDeviceContext {
            self.properties.insert(prop.get_type(), prop);
        }
    }

    /// Mark this device as seen.
    pub(crate) fn seen(&mut self) {
        self.last_seen = Instant::now();
    }
}

/// The interface for adapter callbacks registered through `IBluetooth::register_callback`.
@@ -243,6 +263,9 @@ pub trait IBluetoothCallback: RPCProxy {
    /// When a device is found via discovery.
    fn on_device_found(&self, remote_device: BluetoothDevice);

    /// When a device is cleared from discovered devices cache.
    fn on_device_cleared(&self, remote_device: BluetoothDevice);

    /// When the discovery state is changed.
    fn on_discovering_changed(&self, discovering: bool);

@@ -277,16 +300,17 @@ pub struct Bluetooth {
    connection_callbacks: HashMap<u32, Box<dyn IBluetoothConnectionCallback + Send>>,
    discovering_started: Instant,
    hh: Option<HidHost>,
    is_connectable: bool,
    is_discovering: bool,
    local_address: Option<RawAddress>,
    properties: HashMap<BtPropertyType, BluetoothProperty>,
    profiles_ready: bool,
    found_devices: HashMap<String, BluetoothDeviceContext>,
    freshness_check: Option<JoinHandle<()>>,
    sdp: Option<Sdp>,
    state: BtState,
    tx: Sender<Message>,
    uuid_helper: UuidHelper,
    is_connectable: bool,
}

impl Bluetooth {
@@ -304,16 +328,17 @@ impl Bluetooth {
            bluetooth_media,
            discovering_started: Instant::now(),
            intf,
            is_connectable: false,
            is_discovering: false,
            local_address: None,
            properties: HashMap::new(),
            profiles_ready: false,
            found_devices: HashMap::new(),
            freshness_check: None,
            sdp: None,
            state: BtState::Off,
            tx,
            uuid_helper: UuidHelper::new(),
            is_connectable: false,
        }
    }

@@ -401,19 +426,16 @@ impl Bluetooth {
        };
    }

    fn get_remote_device_if_found(
        &self,
        device: &BluetoothDevice,
    ) -> Option<&BluetoothDeviceContext> {
        self.bonded_devices.get(&device.address).or_else(|| self.found_devices.get(&device.address))
    fn get_remote_device_if_found(&self, address: &str) -> Option<&BluetoothDeviceContext> {
        self.bonded_devices.get(address).or_else(|| self.found_devices.get(address))
    }

    fn get_remote_device_if_found_mut(
        &mut self,
        device: &BluetoothDevice,
        address: &str,
    ) -> Option<&mut BluetoothDeviceContext> {
        match self.bonded_devices.get_mut(&device.address) {
            None => self.found_devices.get_mut(&device.address),
        match self.bonded_devices.get_mut(address) {
            None => self.found_devices.get_mut(address),
            some => some,
        }
    }
@@ -423,7 +445,7 @@ impl Bluetooth {
        device: &BluetoothDevice,
        property_type: &BtPropertyType,
    ) -> Option<BluetoothProperty> {
        self.get_remote_device_if_found(&device)
        self.get_remote_device_if_found(&device.address)
            .and_then(|d| d.properties.get(property_type).and_then(|p| Some(p.clone())))
    }

@@ -433,7 +455,7 @@ impl Bluetooth {
        property_type: BtPropertyType,
        property: BluetoothProperty,
    ) -> Result<(), ()> {
        let mut remote_device = match self.get_remote_device_if_found_mut(&device) {
        let remote_device = match self.get_remote_device_if_found_mut(&device.address) {
            Some(d) => d,
            None => {
                return Err(());
@@ -451,6 +473,48 @@ impl Bluetooth {
        self.intf.lock().unwrap().set_remote_device_property(addr, property);
        Ok(())
    }

    /// Check whether found devices are still fresh. If they're outside the
    /// freshness window, send a notification to clear the device from clients.
    pub(crate) fn trigger_freshness_check(&mut self) {
        // Drop previous joinhandle
        self.freshness_check = None;

        // A found device is considered fresh if:
        // * It was last seen less than |FOUND_DEVICE_FRESHNESS| ago.
        // * It is currently connected.
        fn is_fresh(d: &BluetoothDeviceContext, now: &Instant) -> bool {
            let fresh_at = d.last_seen + FOUND_DEVICE_FRESHNESS;
            now < &fresh_at || d.acl_state == BtAclState::Connected
        }

        let now = Instant::now();
        let stale_devices: Vec<BluetoothDevice> = self
            .found_devices
            .iter()
            .filter(|(_, d)| !is_fresh(d, &now))
            .map(|(_, d)| d.info.clone())
            .collect();

        // Retain only devices that are fresh.
        self.found_devices.retain(|_, d| is_fresh(d, &now));

        for d in stale_devices {
            self.for_all_callbacks(|callback| {
                callback.on_device_cleared(d.clone());
            });
        }

        // If we have any fresh devices remaining, re-queue a freshness check.
        if self.found_devices.len() > 0 {
            let txl = self.tx.clone();

            self.freshness_check = Some(tokio::spawn(async move {
                time::sleep(FOUND_DEVICE_FRESHNESS).await;
                let _ = txl.send(Message::DeviceFreshnessCheck).await;
            }));
        }
    }
}

#[btif_callbacks_dispatcher(Bluetooth, dispatch_base_callbacks, BaseCallbacks)]
@@ -593,6 +657,7 @@ impl BtifBluetoothCallbacks for Bluetooth {
                                BtBondState::Bonded,
                                BtAclState::Disconnected,
                                BluetoothDevice::new(address.clone(), "".to_string()),
                                Instant::now(),
                                vec![],
                            ));
                    }
@@ -621,11 +686,13 @@ impl BtifBluetoothCallbacks for Bluetooth {

        if let Some(existing) = self.found_devices.get_mut(&address) {
            existing.update_properties(properties);
            existing.seen();
        } else {
            let device_with_props = BluetoothDeviceContext::new(
                BtBondState::NotBonded,
                BtAclState::Disconnected,
                device,
                Instant::now(),
                properties,
            );
            self.found_devices.insert(address.clone(), device_with_props);
@@ -639,9 +706,11 @@ impl BtifBluetoothCallbacks for Bluetooth {
    }

    fn discovery_state(&mut self, state: BtDiscoveryState) {
        // Clear found devices when discovery session starts
        if !self.is_discovering && &state == &BtDiscoveryState::Started {
            self.found_devices.clear();
        let is_discovering = &state == &BtDiscoveryState::Started;

        // No-op if we're updating the state to the same value again.
        if &is_discovering == &self.is_discovering {
            return;
        }

        // Cache discovering state
@@ -653,6 +722,13 @@ impl BtifBluetoothCallbacks for Bluetooth {
        self.for_all_callbacks(|callback| {
            callback.on_discovering_changed(state == BtDiscoveryState::Started);
        });

        // Stopped discovering and no freshness check is active. Immediately do
        // freshness check which will schedule a recurring future until all
        // entries are cleared.
        if !is_discovering && self.freshness_check.is_none() {
            self.trigger_freshness_check();
        }
    }

    fn ssp_request(
@@ -706,6 +782,7 @@ impl BtifBluetoothCallbacks for Bluetooth {
                    bond_state.clone(),
                    BtAclState::Disconnected,
                    BluetoothDevice::new(address.clone(), "".to_string()),
                    Instant::now(),
                    vec![],
                ),
            };
@@ -736,27 +813,31 @@ impl BtifBluetoothCallbacks for Bluetooth {
        properties: Vec<BluetoothProperty>,
    ) {
        let address = addr.to_string();
        // Device should be in either found devices or bonded devices
        // If it isn't in either, create it and put it found devices.
        let device = if self.bonded_devices.contains_key(&address) {
            self.bonded_devices.get_mut(&address)
        } else if self.found_devices.contains_key(&address) {
            self.found_devices.get_mut(&address)
        } else {
        let device = match self.get_remote_device_if_found_mut(&address) {
            None => {
                self.found_devices.insert(
                    address.clone(),
                    BluetoothDeviceContext::new(
                        BtBondState::NotBonded,
                        BtAclState::Disconnected,
                        BluetoothDevice::new(address.clone(), String::from("")),
                        Instant::now(),
                        vec![],
                    ),
                );

                self.found_devices.get_mut(&address)
            }
            some => some,
        };

        device.unwrap().update_properties(properties);
        match device {
            Some(d) => {
                d.update_properties(properties);
                d.seen();
            }
            None => (),
        };
    }

    fn acl_state(
@@ -773,30 +854,31 @@ impl BtifBluetoothCallbacks for Bluetooth {
        }

        let address = addr.to_string();

        let found = if self.bonded_devices.contains_key(&address) {
            self.bonded_devices.get_mut(&address)
        } else if self.found_devices.contains_key(&address) {
            self.found_devices.get_mut(&address)
        } else {
        let device = match self.get_remote_device_if_found_mut(&address) {
            None => {
                self.found_devices.insert(
                    address.clone(),
                    BluetoothDeviceContext::new(
                        BtBondState::NotBonded,
                        BtAclState::Disconnected,
                        BluetoothDevice::new(address.clone(), String::from("")),
                        Instant::now(),
                        vec![],
                    ),
                );

                self.found_devices.get_mut(&address)
            }
            some => some,
        };

        match device {
            Some(found) => {
                // Only notify if there's been a change in state
        let prev_state = &found.as_ref().unwrap().acl_state;
                let prev_state = &found.acl_state;
                if prev_state != &state {
            let device = found.as_ref().unwrap().info.clone();
            found.unwrap().acl_state = state.clone();
                    let device = found.info.clone();
                    found.acl_state = state.clone();

                    match state {
                        BtAclState::Connected => {
@@ -812,6 +894,9 @@ impl BtifBluetoothCallbacks for Bluetooth {
                    };
                }
            }
            None => (),
        };
    }
}

// TODO: Add unit tests for this implementation
@@ -1219,7 +1304,7 @@ impl IBluetooth for Bluetooth {
    }

    fn fetch_remote_uuids(&self, device: BluetoothDevice) -> bool {
        if self.get_remote_device_if_found(&device).is_none() {
        if self.get_remote_device_if_found(&device.address).is_none() {
            warn!("Won't fetch UUIDs on unknown device {}", device.address);
            return false;
        }
+7 −0
Original line number Diff line number Diff line
@@ -53,6 +53,9 @@ pub enum Message {
    // Client callback disconnections
    BluetoothCallbackDisconnected(u32, BluetoothCallbackType),

    // Update list of found devices and remove old instances.
    DeviceFreshnessCheck,

    // Suspend related
    SuspendCallbackRegistered(u32),
    SuspendCallbackDisconnected(u32),
@@ -126,6 +129,10 @@ impl Stack {
                    bluetooth.lock().unwrap().callback_disconnected(id, cb_type);
                }

                Message::DeviceFreshnessCheck => {
                    bluetooth.lock().unwrap().trigger_freshness_check();
                }

                Message::SuspendCallbackRegistered(id) => {
                    suspend.lock().unwrap().callback_registered(id);
                }